This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Μεταφράσεις Πτ. ΜSK1
Sunday, 25 August 2024
Εισαγωγή
Σε προηγούμενα άρθρα, έχουμε συζητήσει τη σημασία της μετάφρασης στο πλαίσιο των διαδικτυακών εφαρμογώνΜSK1 Έχουμε επίσης εξερευνήσει τη χρήση της βιβλιοθήκης EasyNMT για την εκτέλεση μεταφράσεων σε μια ASP.NET Κορυφορική εφαρμογήM SK3Σε αυτό το άρθρο θα με ενδιαφέρει' θα καλύψει πώς πρόσθεσα μια υπηρεσία υπόβαθρου στην εφαρμογή ώστε να σας επιτρέπεται να υποβάλετε αίτηση μετάφρασηMST5 που επεξεργάζονται στο υπόβαθοςMSM6
Ξανά, ίσως μπορείτε να δείτε όλο το κώδικα πηγής για αυτό στο μου GitHub Σελίδα.
Προηγούμενα άρθρα
Εδώ προσθέτουμε ένα μικρό εργαλείο το οποίο υποβάλλει πίσω εργασίες στην υπηρεσία που περιγράφουμε λεπτομερώς 2.Αυτό το εργαλείου είναι μια απλή φόρμα που σας επιτρέπει να υποβάλετε αίτηση μετάφρασης στην Υπηρεσία.Αυτή η ' αναφέρει στη συνέχεια κρυμμένη και προστέθηκε σε μια κουβέντα που σας δίνει πληροφορίες σχετικά με την κατάσταση της μετάφρασήςM SK3
Αυτό προσθέτει λειτουργικότητα όπου, όταν επιλέγετε ένα 'ΜSK1 έγγραφο μπορείτε να το μεταφράσετε.

Ο Κώδικας Μετάφρασης
Μεταφραστής
Στη σελίδα επεξεργαστή Markdown μας, πρόσθεσα κάποιο κώδικα που περιέχει μια μικρή πτώση ( διατηρεί _LanguageDropDown.cshtml) που σας επιτρέπει να επιλέξετε τη γλώσσα που θέλετε να μεταφράσετε σεΜSK1
@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>
}
_ΓλώσσαDropDown
Η δική μας _LanguageDropDown μερική προβολή είναι μια απλή λήψη που σας επιτρέπει να επιλέξετε τη γλώσσα που θέλετε να μεταφράσετε σε. Αυτό κατοικείται από έναν κατάλογο γλωσσών στο Languages Η ιδιοκτησία του μοντέλου.
Μπορείτε να δείτε ότι χρησιμοποιεί το Alpine.js για να χειριστεί την πτώση και να ρυθμίσει τη επιλεγμένη γλώσσα και σημαία για να εμφανιστεί στο κύριο τμήμα επιλογής. Επίσης καθορίζει τον σύντομο κώδικα της γλώσσας που χρησιμοποιείται κατά την υποβολή του αιτήματος μετάφρασηςM SK2
Χρησιμοποιώντας Alping σημαίνει ότι διατηρούμε ελάχιστα, τοπικά αναφερόμενη JavaScript στις προβολές μαςΜSK1 Αυτό είναι ένας πολύ καλός τρόπος για να κρατήσετε τις προβολείς σας καθαρές και εύκολα διαβάσιμες.
@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>
Υποδιατύπωση
You' αναμένεται να δείτε ότι αυτό έχει κάποιο κώδικα εφαρμογής για το . που καλεί στο window.mostlylucid.translations.submitTranslation λειτουργία.Αυτή η λειτουργία καθορίζεται στο translations.js Τα αρχεία που περιλαμβάνονται στο _Layout.cshtml Πληροφορίες .
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);
});
Διαγράμματα ακολουθίας
Ο κωδικός αυτός περιγράφεται στο ακόλουθο διάγραμμα σειράς:
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
Παρόλο που αυτό μοιάζει με πολύ κώδικα, το' είναι πραγματικά αρκετά απλόΜSK2
Στείλουμε ένα αίτημα POST στον διακομιστή με τη γλώσσα και το περιεχόμενο σήμανσης. Αυτό πηγαίνει σε ένα τελικό σημείο που ονομάζεται
start-translationπου καθορίζονται στο δικό μαςTranslationAPI. Αυτό απενεργοποιεί το έργο μετάφρασης και προσθέτει αυτό το καθήκον στο Cache για αυτόν τον χρήστη.Ο διακομιστής ανταποκρίνεται με ένα αναγνωριστικό εργασιών (, το οποίο συνδεόμαστε αλλά δεν γνωρίζουμε' χρησιμοποιεί)
Στη συνέχεια, στέλνουμε ένα αίτημα GET στον διακομιστή για να λάβει τις μεταφράσεις. Αυτό γίνεται χρησιμοποιώντας HTMX η οποία είναι μια βιβλιοθήκη που σας επιτρέπει να ενημερώσετε τμήματα της σελίδας χωρίς πλήρη αναβάθμισηΜSK1 Αυτό είναι ένα πολύ ισχυρό εργαλείο και χρησιμοποιείται σε πολλά μέρη σε αυτή την εφαρμογή.
Η αρχή της μετάφρασης Endpoint
Αυτό είναι ένας διαχειριστής WebAPI ο οποίος λαμβάνει αιτήματα που περιέχουν σήμανση και έναν κωδικό γλώσσας. Στη συνέχεια αποστέλλει το αίτημα στον μεταφραστικό server background μας, caches την εργασία tthe UserId M SK2 περιεχόμενο σε ένα cookieΜSK3 και επιστρέφει το αναγνωριστικό εργασίας στο πελάτηMK4 (Έχω απενεργοποιήσει το χαρακτηριστικό ValidateAntiForgeryToken για τώρα καθώς μου αρέσει' δεν το χρησιμοποιώ)
[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);
}
Η μετάφραση τελειώνει
Αυτό ζητείται χρησιμοποιώντας το HTMX και επιστρέφει τις μεταφράσεις για τον τρέχοντα χρήστη.Αυτό είναι ένα απλό τελικό σημείο που λαμβάνει τις μετάφραση από το cache και τους επιστρέψει στον πελάτη.
[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);
}
Η μετάφραση μερικής προβολής
Αυτή είναι μια απλή προβολή που χρησιμοποιεί το HTMX για να διερευνήσει τον διακομιστή κάθε 5 δευτερόλεπτα για να λάβει τις μεταφράσεις για τον τρέχοντα χρήστη. Εμφανίζει έναν πίνακα μετάφρασης με ένα σύνδεσμο για να δείτε την μετάφρασηM SK2
Έχει επίσης χειρισμό όταν όλες οι μεταφράσεις είναι ολοκληρωμένες για να σταματήσει η έρευνα ( καθιστώντας τον εκκίνηση none). Και για να εμφανιστεί ένα εναλλακτικό μήνυμα όταν δεν υπάρχουν μεταφράσειςΜSK1
Χρησιμοποιώ επίσης τη βιβλιοθήκη Humanizer για να δείξω το χρόνο που χρειάστηκε για να ολοκληρώσω την μετάφραση σε ανθρώπινη μορφή.
Αυτό το αποτέλεσμα σε αυτή την άποψη:

@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>
}
}
Η λειτουργία μετάφρασης προβολής
Όπως θα δείτε στην ανωτέρω προβολή, καλούμε σε ένα μικρό κλικ Alping για να δούμε την μετάφραση. Αυτή είναι μια απλή λειτουργία που λαμβάνει τη μετάφρασή του από τον διακομιστή και την εμφανίζει σε έναν μονόλογο διάλογο.
<a href="#" x-on:click.prevent="window.mostlylucid.translations.viewTranslation('@item.TaskId')">View</a>
Ποιος καλεί σε αυτό.Όλα αυτά είναι να πάρει τη λαϊκή μετάφραση από τον διακομιστή και την εμφανίζει στη σελίδα.
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
}
Το τέλος της μετάφρασης
Αυτό είναι παρόμοιο με την προηγούμενη μέθοδο για να πάρετε μια λίστα των μεταφράσεων εκτός από ότι παίρνει μία μόνη μετάφραση με τη OriginalMarkdown και TranslatedMarkdown ΕΠΙΧΕΙΡΕΥΣΗ ΠΡΟΓΡΑΜΜΑΤΑ
[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);
}
Στο συμπέρασμα
Το αποτέλεσμα όλων αυτών είναι ότι μπορείτε τώρα να υποβάλετε αίτηση μετάφρασης και να δείτε την κατάσταση της μετάφρασής σας στον εκδότη, αφού το ' ανέφερε ολοκληρωμένο. ανέφερε ότι η ' ανέφερε πως θα εξετάσει περαιτέρω λεπτομέρειες σχετικά με τον τρόπο λειτουργίας της υπηρεσίας μεταφράσεων στο επόμενο post.
Έχω περισσότερα που θέλω να κάνω εδώ συμπεριλαμβανομένης της επανεξέτασης- ανακάλυψης της ροής Render Markdown για το μεταφρασμένο περιεχόμενο κλπ.Αλλά ότι η ;' διασκέδαση αυτού του ιστότοπουΗ ; σίγουρα δεν κάθε τζιν είναι εντελώς πολυσύχναστη αλλά αυτό σημαίνει Η ' διασφαλίζει όλο τον πραγματικό κώδικα μπορείτε να παίξετε με την οποία μπορώ να το χτίσω Ξανά, ίσως μπορείτε να δείτε όλο το κώδικα πηγής για αυτό στο μου GitHub σελίδα. Επομένως, παρακαλώ κοιτάξτε το αν θέλετε να δείτε περισσότεραΜSK1