Traduzione di fondo Pt. 3 (Italiano (Italian))

Traduzione di fondo Pt. 3

Sunday, 25 August 2024

//

11 minute read

Introduzione

Negli articoli precedenti, abbiamo discusso l'importanza della traduzione nel contesto delle applicazioni web. Abbiamo anche esplorato l'uso della biblioteca EasyNMT per eseguire le traduzioni in un'applicatione ASPM SK2NET Core . In questo post IMSC4 vi racconterò di come ho aggiunto un servizio di background all'applicazione per permettervi di inviare la richiesta di traduzioneMST5s che sono processate in backgroundM ST6

Di nuovo, potete vedere tutto il codice sorgente per questo sul mio computer. GitHub Page.

Previous Articles

Qui aggiungiamo un piccolo strumento che invia lavori di fondo al servizio che abbiamo spiegato in parte 2. Questo strumento è un semplice modulo che vi permette di inviare una richiesta di traduzione al servizio. E' poi archiviato e aggiunto a una fila che vi dà informazioni sullo stato della traduzioneM SK3

Questo aggiunge una funzionalità in cui quando si seleziona un documento 'new' lo si può tradurre.

Editore

Il codice di traduzione

Inviatore di traduzione

Sulla nostra pagina di editore Markdown ho aggiunto un codice che contiene una piccola boccia (in _LanguageDropDown.cshtml) che vi permette di scegliere la lingua che volete tradurre.

        @if (Model.IsNew)
                {
                    var translationHidden = Model.TranslationTasks.Any() ? "" : "hidden";
              
                    <p class="text-blue-dark dark:text-blue-light inline-flex  items-center justify-center space-x-2">
                        <partial name="_LanguageDropDown" for="Languages"/>
                        <button class="btn btn-outline btn-sm mt-1" x-on:click="window.mostlylucid.translations.submitTranslation"><i class='bx bx-send'></i>Translate</button>
                    </p>
                    <div id="translations" class="@translationHidden">
                        <partial name="_GetTranslations" model="Model.TranslationTasks" />
                    </div>
                    <div id="translatedcontent" class="hidden">
                        <textarea class="hidden" id="translatedcontentarea"></textarea>
                    </div>
                }

_LanguageDropDown

La nostra. _LanguageDropDown la vista parziale è un semplice spunto che vi permette di selezionare il linguaggio che volete tradurre in. Questo è popolato da una lista di lingue nella Languages proprietà del modello.

Potete vedere che usa Alpine.js per gestire il dropdown e per stabilire la lingua e la bandiera selezionate da mostrare nella parte principale della selezioneM SK1 Si stabilisce anche il breve codice della lingua che viene usata quando si presenta la richiesta di traduzione.

L'uso di Alping significa che manteniamo il minimo, JavaScript localmente riferito nelle nostre visualiM SK1 È un ottimo modo per mantenere le vostre visuali pulite e facili da leggere.

@using Mostlylucid.Helpers
@model List<string>

<div id="LanguageDropDown" x-data="{ 
    open: false, 
    selectedLanguage: 'Select Language', 
    selectedFlag: '' ,
    selectedShortCode:''
}" class="relative inline-block mt-3">
    <!-- Dropdown Button -->
    <button x-on:click="open = !open" class="btn btn-sm btn-outline flex items-center space-x-2">
        <!-- Dynamically Show the Flag Icon -->
        <template x-if="selectedFlag">
            <img :src="selectedFlag" class="h-4 w-4 rounded outline outline-1  outline-green-dark dark:outline-white" alt="Selected Language Flag">
        </template>
        <span x-text="selectedLanguage"></span>
        <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
        </svg>
    </button>

    <!-- Dropdown Menu -->
    <div x-show="open" x-on:click.away="open = false"
         class="absolute left-0 mt-2 w-64 rounded-md shadow-lg dark:bg-custom-dark-bg bg-white ring-1 ring-black ring-opacity-5 z-50">
        <ul class="p-2">
            @foreach (var language in Model)
            {
            <li>
                <a href="#"
                   x-on:click.prevent="selectedLanguage = '@(language.ConvertCodeToLanguage())'; selectedFlag = '/img/flags/@(language).svg'; selectedShortCode='@language'; open = false"
                   class="flex dark:text-white text-black items-center p-2 hover:bg-gray-100">
                    <img src="/img/flags/@(language).svg" asp-append-version="true" class="ml-2 h-4 w-4 mr-4 rounded outline outline-1  outline-green-dark dark:outline-white" alt="@language"> @language.ConvertCodeToLanguage()
                </a>
            </li>
            }
        </ul>
    </div>
</div>

Traduzione

Vedrete che questo ha qualche codice Apline.js che chiama al nostro computer. window.mostlylucid.translations.submitTranslation Funzione. Questa funzione è definita nel nostro translations.js Il file che è incluso nel nostro _Layout.cshtml file.

export function submitTranslation() {
    const languageDropDown = document.getElementById('LanguageDropDown');

    // Access Alpine.js data using Apline.$data (Alpine.js internal structure)
    const alpineData = Alpine.$data(languageDropDown);
const shortCode = alpineData.selectedShortCode;
const markdown = simplemde.value();
if (shortCode === '' || markdown === '') return;
    
    // Create the data object that matches your model
    const model = {
        Language: shortCode,
        OriginalMarkdown: markdown
    };

// Perform the fetch request to start the translation using POST
    fetch('/api/translate/start-translation', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'  // The content type should be JSON
        },
        body: JSON.stringify(model)  // Send the data object as JSON
    })
        .then(function(response) {
            if (response.ok) {
                // Process the returned task ID
                return response.json();  // Parse the JSON response (assuming the task ID is returned in JSON)
            } else {
                console.error('Failed to start the translation');
            }
        })
        .then(function(taskId) {
            if (taskId) {
                console.log("Task ID:", taskId);

                // Trigger an HTMX request to get the translations after saving
                htmx.ajax('get', "/editor/get-translations", {
                    target: '#translations',  // Update this element with the response
                    swap: 'innerHTML',        // Replace the content inside the target
                }).then(function () {
                    // Remove the hidden class after the content is updated
                    document.getElementById('translations').classList.remove('hidden');
                });
            }
        })
        .catch(function(error) {
            // Handle any errors that occur during the fetch
            console.error('An error occurred:', error);
        });

Diagramma di sequenza

Questo codice è descritto nel diagramma di sequenza seguente:

sequenceDiagram
    autonumber
    actor User
    participant JS as Client JS
    participant Alpine as Alpine.js
    participant FetchAPI as Fetch API
    participant HTMX as HTMX

    User->>JS: Trigger submitTranslation()
    JS->>Alpine: Access data from LanguageDropDown via Alpine.js
    Alpine-->>JS: Return selectedShortCode and markdown
    JS->>JS: Check if shortCode and markdown are not empty
    alt shortCode or markdown is empty
        JS->>User: Do nothing
    else
        JS->>FetchAPI: Send POST request with model {Language, OriginalMarkdown}
        FetchAPI-->>JS: Return taskId in JSON response
        alt response is ok
            JS->>HTMX: Send GET request to /editor/get-translations via HTMX
            HTMX-->>JS: Return updated translations
            JS->>DOM: Remove 'hidden' class from translations element
        else
            JS->>Console: Log "Failed to start the translation"
        end
    end
    JS->>Console: Handle any fetch errors

Anche se sembra un sacco di codice.

  1. Inviamo una richiesta POST al server con il contenuto linguistico e marcato. Va a un punto di fine chiamato start-translation che è definita nel nostro TranslationAPI. Questo inizia la task di traduzione e aggiunge questa task alla Cache per questo utente.

  2. Il server risponde con un ID di compito (che ci logiamo ma non lo usiamo'non usammo )

  3. Poi inviamo una richiesta GET al server per ottenere le traduzioni. Questo si fa usando HTMX che è una biblioteca che permette di aggiornare parti della pagina senza rifrescare la pagina completa. È uno strumento molto potente e viene usato in molti posti in questa applicazioneM SK2

Il punto finale della traduzione iniziale

Questo è un controllore WebAPI che prende le richieste che contengono una marcata e un codice linguistico. Dopodiché manda la richiesta al nostro server di traduzione di fondo, archivia l'attività di nuovo il UserId M SK2 contenuto in un cookie ) e restituisce l'Id dell'attività al clienteMSC4 (Ho disabilitato l'attributo ValidateAntiForgeryToken per ora perché non lo sto usando.

    [HttpPost("start-translation")]
   // [ValidateAntiForgeryToken]
    public async Task<Results<Ok<string>, BadRequest<string>>> StartTranslation([FromBody] MarkdownTranslationModel model)
    {
        if(ModelState.IsValid == false)
        {
            return TypedResults.BadRequest("Invalid model");
        }
        if(!backgroundTranslateService.TranslationServiceUp)
        {
            return TypedResults.BadRequest("Translation service is down");
        }
        // Create a unique identifier for this translation task
        var taskId = Guid.NewGuid().ToString("N");
        var userId = Request.GetUserId(Response);
       
        // Trigger translation and store the associated task
        var translationTask = await backgroundTranslateService.Translate(model);
    
        var translateTask = new TranslateTask(taskId, DateTime.Now,  model.Language, translationTask);
        translateCacheService.AddTask(userId, translateTask);

        // Return the task ID to the client
        return TypedResults.Ok(taskId);
    }

Il punto finale di ottenere le traduzione

Questo è richiesto usando HTMX e restituisce le traduzioni per l'utente attuale. È un semplice punto di fine che riceve le tradutture dal cache e le restituisce al cliente.

    [HttpGet]
    [Route("get-translations")]
    public IActionResult GetTranslations()
    {
        var userId = Request.GetUserId(Response);
        var tasks = translateCacheService.GetTasks(userId);
        var translations = tasks.Select(x=> new TranslateResultTask(x, false)).ToList();
        return PartialView("_GetTranslations", translations);
    }
    

La visione parziale delle traduzione

Questa è una semplice visuale che usa HTMX per sondare il server ogni 5 secondi per ottenere le traduzioni per l'utente attuale. Si mostra una tabella di tradutture con un link per vedere la traduzioneM SK2

Ha anche il modo di gestire quando tutte le traduzione sono complete per fermare la votazione (cambiando l'attivatore a none). E mostrare un messaggio alternativo quando non ci sono traduzioni.

Uso anche la biblioteca Humanizer per mostrare il tempo impiegato per completare la traduzione in un formato leggibile per l'uomo.

Questo risulta in questa visuale:

Traduzione

@using Humanizer
@using Mostlylucid.Helpers
@model List<Mostlylucid.MarkdownTranslator.Models.TranslateResultTask>

@{
    var allCompleted = Model.All(x => x.Completed);
    var noTranslations = Model.Count == 0;
    var trigger = allCompleted ? "none" : "every 5s";
    if (noTranslations)
    {
        <div class="alert alert-info" role="alert">
            No translations have been requested yet.
        </div>
    }
    else
    {
        <div class="translationpoller" hx-controller="Editor" hx-action="GetTranslations" hx-get hx-swap="outerHTML" hx-trigger="@trigger">
            <table class="table">
                <thead>
                <th>
                  
                    @Html.DisplayNameFor(model => model[0].TaskId)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model[0].Completed)
                </th>
                <th >
                    @Html.DisplayNameFor(model => model[0].Language)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model[0].TotalMilliseconds)
                </th>
                </thead>
                @foreach (var item in Model)
                {
                    <tr>
                        <td>  <a href="#" x-on:click.prevent="window.mostlylucid.translations.viewTranslation('@item.TaskId')">View</a></td>
                        <td>@if (item.Completed)
                            {
                                <i class='bx bx-check text-green'></i>
                            }
                            else
                            {
                                <i class='bx bx-loader-alt animate-spin dark:text-white text-black'></i>
                            }
                            </td>
                        <td>
                            <p class="flex items-center">
                                <img src="/img/flags/@(item.Language).svg" asp-append-version="true" class="ml-2 h-4 w-4 mr-4 rounded outline outline-1 outline-green-dark dark:outline-white" alt="@item.Language">
                                @item.Language.ConvertCodeToLanguage()
                            </p>

                        </td>
                        <td>@(TimeSpan.FromMilliseconds(item.TotalMilliseconds).Humanize())</td>
                    </tr>
                }
            </table>
        </div>
    }
}

La funzione di traduzione View

Come vedete nella visuale precedente chiamamo un piccolo Alping suclick per vedere la traduzione. Questa è una semplice funzione che riceve la trascrizione dal server e la mostra in un dialogo modale.

 <a href="#" x-on:click.prevent="window.mostlylucid.translations.viewTranslation('@item.TaskId')">View</a>

Che chiama in questo. Tutto quello che fa è prendere la transalzione popolata dal server e mostrarla sulla pagina.


export function viewTranslation(taskId) {
    // Construct the URL with the query parameters
    const url = `/api/translate/get-translation/${taskId}`;

    // Fetch call to the API endpoint
    fetch(url, {
        method: 'GET',
        headers: {
            'Accept': 'application/json'  // Indicate that we expect a JSON response
        }
    })
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! Status: ${response.status}`);
            }
            return response.json();
        })
        .then(data =>

        {
            let translatedContentArea = document.getElementById("translatedcontent")
            translatedContentArea.classList.remove("hidden");
            let textArea = document.getElementById('translatedcontentarea');
            textArea.classList.remove('hidden');
            textArea.value = data.originalMarkdown;
            simplemde.value(data.translatedMarkdown);
        })  // Log the successful response data
        .catch(error => console.error('Error:', error));  // Handle any errors
}

Il punto finale della traduzione

Questo è simile al metodo precedente per ottenere una lista delle traduziones eccetto che riceve una sola traduzione con la OriginalMarkdown e TranslatedMarkdown popolato:

    [HttpGet]
    [Route("get-translation/{taskId}")]
    public Results<JsonHttpResult<TranslateResultTask>, BadRequest<string>> GetTranslation(string taskId)
    {
        var userId = Request.GetUserId(Response);
        var tasks = translateCacheService.GetTasks(userId);
        var translationTask = tasks.FirstOrDefault(t => t.TaskId == taskId);
        if (translationTask == null) return TypedResults.BadRequest("Task not found");
        var result = new TranslateResultTask(translationTask, true);
        return TypedResults.Json(result);
    }

Conclusione

Il risultato di tutto questo è che ora si può sottoporre una richiesta di traduzione e vedere lo stato della traduzione nell'editore dopo averla completata.

Ho più di quello che voglio fare qui, tra cui ri-fornire il flusso del Render Markdown per il contenuto tradotto e così viaM SK1 Ma questo;'è l'aspetto divertente di questo sito ; non è tutto completamente pulito ma è tutto codice reale con cui potete giocare mentre lo costruiscoMSC5 Di nuovo, potete vedere tutto il codice sorgente per questo sul mio computer. GitHub page. Quindi, per favore, andate a guardarlo se volete vedere di più.

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.