Hintergrundöversättningar Pt. 3 (Svenska (Swedish))

Hintergrundöversättningar Pt. 3

Sunday, 25 August 2024

//

10 minute read

Inleiding

I tidigare artiklarna, har vi diskuterat vikten av översättningar i kontexten av webbapplikationer . Vi har också utforskat användandet av EasyNMT biblioteket för att utföra översättningarna i en ASP. NET Core-applikationM SK3 I denna artikel ska jag beskriva hur jag lagt till en bakgrundsservice till applikationen för att tillåta dig att skicka en översättningsansökningMSC5 som processeras i bakgrundenMska6

Igen, kan ni se alla källkod för detta på min GitHub sida.

Previous Articles

Här lägger vi till ett litet verktyg som skickar backround-arbeten till tjänsten som vi förklarade i del 2. Det här verktyget är en enkel form som tillåter dig att skicka en översättningsansökan till tjänsterna . Den ' är sedan kastad och tillförd till en kö som ger dig information om översättningens status

Det här lägger till en funktion där du kan översätta ett 'nyt' dokument

Redakteur

Den översättningskoden

T översättning

På vår markerdown-editor-sida lade jag in lite kod som innehåller en liten drop down (in _LanguageDropDown.cshtml) som låter dig välja språket du vill översätta till

        @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

Vår _LanguageDropDown partial view är en enkel dropdown som låter dig välja språket du vill översätta till. Det här är fyllt av en lista av språk i Languages egenskapen av modellen.

Ni kan se att den använder Alpine.js för att hantera dropdownen och för att sätta in det valriktade språket och flaggan som ska visas i huvudselektionsdelen.

Att använda Alping innebär att vi sparar minimalt, lokalt referensrat JavaScript i våra synpunkter. Det är ett bra sätt att hålla dina synpunkter rena och enkla att läsa

@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>

Senda översättning

Du kommer att se att det här har någon Apline- -.- -js-kod som låter oss window.mostlylucid.translations.submitTranslation funktion. Denna funktion är definierad i vår translations.js file som ingår i vår _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);
        });

Sektionsdiagramm

Den här koden beskrivs i följande sekvensdiagrammen:

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

Även om det här ser ut som en massa kod.

  1. Vi skickar en POST-fråga till servern med språket och markerdown innehållet. Det går till en slutpunkt som kallas start-translation som definieras i vår TranslationAPI. Det här börjar översättningsuppgiften och lägger den till kasken för den här användaren

  2. Servern svarar med en uppgift ID ( som vi registrerar, men inte använder

  3. Sedan skickar vi en GET- begäran till servern för att få ut översättningarna. Detta görs med hjälp av HTMX som är en bibliotek som tillåter dig att uppdatera delar av sidan utan att ladda upp hela sidan. Det här är ett mycket kraftfullt verktyg och används på många ställen i den här applikationen

Börja översättningens slutpunkt

Det här är en WebAPI-kontroller som tar uppgifter som innehåller markering och ett språkkode. Den skickar sedan in begäran till vår bakgrundsöversättningsserver , kastar upp uppgifterna igen tthe UserId ( som är inbegripda i en cookie ) och återlämnar uppgifternas ID till klienten M SK4 ( Jag deaktiverade attityden ValidateAntiForgeryToken för tillfället eftersom jag inte använder den

    [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);
    }

Att få ut översättningar

Detta är begärt med hjälp av HTMX och återskapar översättningarna för den nuvarande användaren. Det här är en enkel slutpunkt som får översättningerna från cachet och återställer dem till klienten.

    [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);
    }
    

Att få en del av översättningar

Det här är en enkel bild som använder HTMX för att svara på servern varenda 5 sekund för att få ut översättningarna för den nuvarande användaren.

Det har också hantering när alla översättningar är färdiga för att stoppa röstsedeln ( genom att sätta ut aktiveringsknappen till none). Och att visa ett alternativ meddelande när det inte finns några översättningar

Jag använder också Humanizer biblioteket för att visa tiden som krävs för att komplettera översättningen i ett mänskligt läsbart format.

Detta resulterar i denna bild:

Töversättningar

@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>
    }
}

Den syn på översättningsfunktionen

Som ni ser i den här bilden kallar vi till en liten Alping-klick för att se översättningen.

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

Vilket ropar in i detta. Allt det gör är att få upp den befolkade transaltionen från servern och visa den på sidan.


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
}

Att få översättnings slutpunkt

Detta är liknande den tidigare metoden för att få en lista på översättningar förutom att det får en enda översättning med OriginalMarkdown och TranslatedMarkdown befolkad:

    [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);
    }

I slutsats

Resultatet av allt detta är att du nu kan skicka ett översättnings begäran och se översättningens status i redaktören efter det.

Jag har mer som jag vill göra här, inklusive att göra re . "-", Render Markdown flow for the translated content etc , och så vidare. Igen, kan ni se alla källkod för detta på min GitHub sida. Så snälla gå och titta på det om ni vill se mer

Finding related posts...
logo

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