Oorgrondse vertalingen Pt. 3
Inleiding
In eerdere artikelen, hebben we het belang van vertaling in de context van web-applicaties beproken. We hebben ook het gebruik van de EasyNMT-bibliotheek verkend om vertalingen te doen in een ASP~.NET Core-applikatie.~ In deze post zal ik bespreken hoe ik een achtergronddienst heb toegevoegd aan de applicatie om je een vertalingsvraag te kunnen stellen.
Nogmaals, kun je hier alle broncode voor zien op mijn website. GitHub pagina.
Vorige Artikels
Hier voegen we een klein gereedschap toe dat backround-werkjes instuurt aan de dienst die we gedeeltelijk hebben beschreven. 2. Dit gereedschap is een eenvoudige vorm waarmee je een vertalingsbezoek naar de dienst kan sturen.
Dit voegt functionaliteit toe waarbij je het document 'new' kan vertalen.

De vertalingscode
Vertaling Submitter
Op onze Markdown-editor-pagina heb ik een paar codes toegevoegd die een klein drop down bevat (in _LanguageDropDown.cshtml) waarmee je de taal kunt selecteren die je wilt vertalen naar.
@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
Onze _LanguageDropDown Het gedeeltelijke beeld is een eenvoudige dropdown waarmee je de taal kunt selecteren die je wilt vertalen naar. Dit is gepopulaeerd uit een lijst van talen in de Languages eigenschap van het model.
Je ziet dat het Alpine gebruikt.js om de afval te verwerken en de gekozen taal en vlag in de hoofdkiesing te laten zien.
Met het gebruik van Alping houden we minimaal, lokaal gereferenceerd JavaScript in onze aansigten. Dit is een geweldige manier om je aansigtes schoon en makkelijk te lezen te houden .
@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>
SubmitTranslation
Je zult zien dat dit een Apline-code heeft. window.mostlylucid.translations.submitTranslation function. Deze functie is gedefinieerd in ons translations.js Die staat in ons dossier. _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);
});
Sequentiediagram
Deze code is beschreven in het volgende sequentiediagram:
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
Hoewel dit lijkt op een hoop code.
We sturen een POST-vraag naar de server met de taal en de aftekeninhoud. Dit gaat naar een eindpunt genaamd
start-translationDie is gedefinieerd in onzeTranslationAPI. Dit begint met de vertaling en voegt deze taak toe aan de Cache voor deze gebruiker.De server reageert met een taak Id (, die we logeren maar niet gebruiken.
We sturen dan een GET-request naar de server om de vertalingen te krijgen. Dit gebeurt met HTMX, een bibliotheek waarmee je delen van de pagina kunt updaten zonder een volledige paginaverfrisering.
De Translation Endpoint Begin
Dit is een WebAPI-controleerder die verzoeken neemt met een merkdown en een taalcode. (Ik deed het ValidateAntiForgeryToken-attribute nu niet toe omdat ik hem niet gebruikte.
[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);
}
De 'Geef Translations Endpoint'
Dit wordt aangevraagd met HTMX en geeft de vertalingen terug voor de huidige gebruiker. Dit is een eenvoudige eindpunt dat de vertalings van het cache krijgt en ze teruggeeft naar de client.
[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);
}
De Vertaling gedeeltelijk bekijken
Dit is een eenvoudige kijk die HTMX gebruikt om de server elke 5 seconden te pollen om de vertalingen van de huidige gebruiker te krijgen.
Het heeft ook een handhaving voor wanneer alle vertalingen klaar zijn om de stem te stoppen ( door de sneller op te zetten naar none). En om een alternatief bericht te laten zien als er geen vertalingen zijn.
Ik gebruik ook de Humanizer-bibliotheek om de tijd te laten zien die nodig is voor het voltooien van de vertaling in een menselijk leesbaar formaat.
Dit resulteert in dit beeld:

@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>
}
}
De Vertalingfunctie
Zoals je ziet in de bovenste aansig roepen we een kleine Alping opklik om de vertaling te bekijken. Dit is een eenvoudige functie die de vertaling van de server krijgt en in een modal dialoog toont.
<a href="#" x-on:click.prevent="window.mostlylucid.translations.viewTranslation('@item.TaskId')">View</a>
Dat roept naar dit.. Het enige wat het doet, is de bevolkte transactie van de server halen en in de pagina tonen.
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
}
Het Vertaling-Endepunt krijgen
Dit is vergelijkbaar met de eerdere methode om een lijst van de vertalingen te krijgen, behalve dat het één enkele vertaling krijgt. OriginalMarkdown en TranslatedMarkdown bevolkt:
[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);
}
In slotting:
Het resultaat hiervan is dat je nu een vertalingsvraag kunt insturen en de status van de vertaling in de redacteur kan zien nadat het klaar is.
Ik heb nog meer wat ik hier wil doen, waaronder re- de Render Markdown-stroom ophokken voor de vertaalde inhoud enzovoort . Maar dat is het plezier van deze site; niet alles is volledig gepolsterd maar dit is allemaal echte code waarmee je kan spelen terwijl ik het uitbouw. Nogmaals, kun je hier alle broncode voor zien op mijn website. GitHub page. Kijk maar eens naar dat als je meer wilt zien.