Hintergrundübersetzungen
Einleitung
In früheren Artikeln haben wir die Bedeutung der Übersetzung im Kontext von Web-Anwendungen diskutiert.Wir haben auch den Einsatz der EasyNMT-Bibliothek erforscht, um Übersetzungen in einer ASP-.]NET Core-Anwendung durchzuführen.In diesem Beitrag behandelt I'«ll, wie ich einen Hintergrunddienst zur Anwendung hinzugefügt habe, damit Sie Übersetzungsanfragen einreichen können, die im Hintergrund verarbeitet werden..
Wiederum können Sie alle den Quellcode für dieses auf meinem sehen GitHub Seite - .
Vorherige Artikel
Hier fügen wir ein kleines Werkzeug hinzu, das Backround-Jobs an den Service abgibt, den wir im Teil beschrieben haben.Dieses Tool ist eine einfache Form, mit der Sie einen Übersetzungsantrag an den Dienst übermitteln können.
Dies fügt Funktionalität hinzu, wo Sie bei der Auswahl eines Dokuments übersetzen können

Der Übersetzungscode
Übersetzungsübersetzer
Auf unserer Markdown-Editorseite habe ich einen Code hinzugefügt, der ein kleines Dropdown enthält. _LanguageDropDown.cshtmlSie können die Sprache auswählen, in die Sie übersetzen möchten.
@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>
}
_SpracheDropdown
Unsere _LanguageDropDown Teilansicht ist ein einfacher Dropdown, mit dem Sie die Sprache auswählen können, in die Sie übersetzen möchten. Languages Eigentum des Modells-.
Sie können sehen, dass es Alpine-.-js verwendet, um den Dropdown zu handhaben und die ausgewählte Sprache und Flagge festzulegen, um im Hauptauswahlteil anzuzeigen... Es legt auch den kurzen Code der Sprache fest, der beim Absenden der Übersetzungsanfrage verwendet wird.
Die Verwendung von Alping bedeutet, dass wir minimal halten - , referenziert lokal JavaScript in unseren Ansichten Dies ist eine gute Möglichkeit, um Ihre Ansichten sauber und einfach zu lesen
@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>
SendenÜbersetzung
Sie sehen, dass dieses einige Apline hat, ..js Code, der in unsere ruft window.mostlylucid.translations.submitTranslation Diese Funktion ist in unserem translations.js Datei, die in unserem _Layout.cshtml Datei„ ."
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);
});
Sequenzdiagramm
Dieser Code wird im folgenden Sequenzdiagramm beschrieben.
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
Obwohl dies sieht aus wie eine Menge von Code...
Wir senden eine POST-Anfrage an den Server mit der Sprache und dem Markdown-Content.
start-translationdie in unseremTranslationAPIDieses startet die Übersetzungsaufgabe und fügt diese Aufgabe dem Cache für diesen Benutzer hinzu.Der Server reagiert mit einer Aufgabe Id .( , die wir protokollieren aber nicht verwenden ?) .
Wir senden dann eine GET-Anfrage an den Server, um die Übersetzungen zu erhalten Dies wird mit HTMX durchgeführt, die eine Bibliothek ist, die es Ihnen ermöglicht, Teile der Seite ohne volle Seitenwiederholung zu aktualisieren... Dies ist ein sehr leistungsfähiges Tool und wird an vielen Stellen in dieser Anwendung verwendet.
Der Endpoint für die Übersetzung starten
Dies ist ein WebAPI-Controller, der Anfragen nimmt, die Markdown und einen Sprachcode enthalten.Es sendet dann die Anfrage an unseren Hintergrundübersetzungsserver.,.Caches die Aufgabe wieder tthe UserId. Ich habe das Attribut ValidateAntiForgeryToken für jetzt deaktiviert, da I'®m es nicht verwendet.
[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);
}
Der Endpunkt für Übersetzungen
Dies wird mit HTMX angefordert und gibt die Übersetzungen für den aktuellen Benutzer zurück.Dies ist ein einfacher Endpunkt, der die Übersetzungen aus dem Cache erhält und sie an den Client zurückgibt.
[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);
}
Die teilweise Ansicht von Übersetzungen abrufen
Dies ist eine einfache Ansicht, die HTMX verwendet, um den Server jede Sekunde zu überprüfen, um die Übersetzungen für den aktuellen Benutzer zu erhalten. Es zeigt eine Tabelle mit Übersetzungen mit einem Link zur Anzeige der Übersetzung an.
Es hat auch die Handhabung für, wenn alle Übersetzungen abgeschlossen sind, um das Polling zu stoppen, indem Sie den Trigger auf noneUnd um eine alternative Nachricht anzuzeigen, wenn es keine Übersetzungen gibt.
Ich benutze auch die Humanizer Bibliothek, um die Zeit zu zeigen, die genommen wird, um das Übersetzen in einem menschlichen lesbaren Format abzuschließen
Dies ergibt sich in dieser Ansicht

@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>
}
}
Die Ansicht Übersetzungsfunktion
Wie du in der obigen Ansicht siehst, rufen wir einen kleinen Alping-Klick auf, um die Übersetzung zu sehen... Dies ist eine einfache Funktion, die die Übersetzung vom Server erhält und sie in einem Modaldialog anzeigt.
<a href="#" x-on:click.prevent="window.mostlylucid.translations.viewTranslation('@item.TaskId')">View</a>
Was dazu aufruft, ist die bevölkerte Transaltion vom Server zu bekommen und sie in der Seite anzuzeigen.
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
}
Der Endpoint für die Übersetzung erhalten
Dies ist ähnlich wie die frühere Methode, um eine Liste der Übersetzungen zu erhalten, außer es erhält eine einzige Übersetzung mit dem OriginalMarkdown und TranslatedMarkdown Bevölkert
[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);
}
Schlussfolgerung
Das Ergebnis von all dem ist, dass Sie jetzt eine Übersetzungsanfrage einreichen können und den Status der Übersetzung im Editor nach ihm sehen.''s complete.
Ich habe mehr, die ich hier tun möchte, einschließlich der Re--hooking den Render Markdown Fluss für den übersetzten Inhalt etc. Aber dass -;'s der Spaß dieser Website - nicht alles ist komplett poliert, aber es - 's alle echten Code, mit dem Sie spielen können, wie ich es ausbaue Wiederum können Sie alle den Quellcode für dieses auf meinem sehen GitHub so gehen Sie bitte schauen, dass, wenn Sie mehr sehen möchten.