# Μεταφράσεις Πτ. ΜSK1

<datetime class="hidden">2024-08-25T03:20</datetime>

<!--category-- EasyNMT, ASP.NET, WebAPI, Alpine.js, HTMX -->
# Εισαγωγή

Σε προηγούμενα άρθρα, έχουμε συζητήσει τη σημασία της μετάφρασης στο πλαίσιο των διαδικτυακών εφαρμογώνΜSK1 Έχουμε επίσης εξερευνήσει τη χρήση της βιβλιοθήκης EasyNMT για την εκτέλεση μεταφράσεων σε μια ASP.NET Κορυφορική εφαρμογήM SK3Σε αυτό το άρθρο θα με ενδιαφέρει' θα καλύψει πώς πρόσθεσα μια υπηρεσία υπόβαθρου στην εφαρμογή ώστε να σας επιτρέπεται να υποβάλετε αίτηση μετάφρασηMST5 που επεξεργάζονται στο υπόβαθοςMSM6

Ξανά, ίσως μπορείτε να δείτε όλο το κώδικα πηγής για αυτό στο μου [GitHub](https://github.com/scottgal/mostlylucidweb) Σελίδα.

## Προηγούμενα άρθρα

- [Μεταφράσεις Πτ. ΜSK1](/blog/backgroundtranslationspt1)
- [Μεταφράσεις Πτ. ΜSK1](/blog/backgroundtranslationspt2)

Εδώ προσθέτουμε ένα μικρό εργαλείο το οποίο υποβάλλει πίσω εργασίες στην υπηρεσία που περιγράφουμε λεπτομερώς 2.Αυτό το εργαλείου είναι μια απλή φόρμα που σας επιτρέπει να υποβάλετε αίτηση μετάφρασης στην Υπηρεσία.Αυτή η ' αναφέρει στη συνέχεια κρυμμένη και προστέθηκε σε μια κουβέντα που σας δίνει πληροφορίες σχετικά με την κατάσταση της μετάφρασήςM SK3

[TOC]

Αυτό προσθέτει λειτουργικότητα όπου, όταν επιλέγετε ένα 'ΜSK1 έγγραφο μπορείτε να το μεταφράσετε.

![Συντάκτης](neweditor.gif?a)

# Ο Κώδικας Μετάφρασης

## Μεταφραστής

Στη σελίδα επεξεργαστή Markdown μας, πρόσθεσα κάποιο κώδικα που περιέχει μια μικρή πτώση ( διατηρεί `_LanguageDropDown.cshtml`) που σας επιτρέπει να επιλέξετε τη γλώσσα που θέλετε να μεταφράσετε σεΜSK1

```razor
        @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 Αυτό είναι ένας πολύ καλός τρόπος για να κρατήσετε τις προβολείς σας καθαρές και εύκολα διαβάσιμες.

```razor
@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` Πληροφορίες .

```javascript
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);
        });
```

#### Διαγράμματα ακολουθίας

Ο κωδικός αυτός περιγράφεται στο ακόλουθο διάγραμμα σειράς:

```mermaid
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

1. Στείλουμε ένα αίτημα POST στον διακομιστή με τη γλώσσα και το περιεχόμενο σήμανσης. Αυτό πηγαίνει σε ένα τελικό σημείο που ονομάζεται `start-translation` που καθορίζονται στο δικό μας `TranslationAPI`. Αυτό απενεργοποιεί το έργο μετάφρασης και προσθέτει αυτό το καθήκον στο Cache για αυτόν τον χρήστη.

2. Ο διακομιστής ανταποκρίνεται με ένα αναγνωριστικό εργασιών (, το οποίο συνδεόμαστε αλλά δεν γνωρίζουμε' χρησιμοποιεί)

3. Στη συνέχεια, στέλνουμε ένα αίτημα GET στον διακομιστή για να λάβει τις μεταφράσεις. Αυτό γίνεται χρησιμοποιώντας HTMX η οποία είναι μια βιβλιοθήκη που σας επιτρέπει να ενημερώσετε τμήματα της σελίδας χωρίς πλήρη αναβάθμισηΜSK1 Αυτό είναι ένα πολύ ισχυρό εργαλείο και χρησιμοποιείται σε πολλά μέρη σε αυτή την εφαρμογή.

### Η αρχή της μετάφρασης Endpoint

Αυτό είναι ένας διαχειριστής WebAPI ο οποίος λαμβάνει αιτήματα που περιέχουν σήμανση και έναν κωδικό γλώσσας. Στη συνέχεια αποστέλλει το αίτημα στον μεταφραστικό server background μας, caches την εργασία tthe UserId M SK2 περιεχόμενο σε ένα cookieΜSK3 και επιστρέφει το αναγνωριστικό εργασίας στο πελάτηMK4
(Έχω απενεργοποιήσει το χαρακτηριστικό ValidateAntiForgeryToken για τώρα καθώς μου αρέσει' δεν το χρησιμοποιώ)

```csharp
    [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 και τους επιστρέψει στον πελάτη.

```csharp
    [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 για να δείξω το χρόνο που χρειάστηκε για να ολοκληρώσω την μετάφραση σε ανθρώπινη μορφή.

Αυτό το αποτέλεσμα σε αυτή την άποψη:

![Μεταφράσεις](translatedtable.png?width=800&format=webp&quality=40)

```razor
@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 για να δούμε την μετάφραση. Αυτή είναι μια απλή λειτουργία που λαμβάνει τη μετάφρασή του από τον διακομιστή και την εμφανίζει σε έναν μονόλογο διάλογο.

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

Ποιος καλεί σε αυτό.Όλα αυτά είναι να πάρει τη λαϊκή μετάφραση από τον διακομιστή και την εμφανίζει στη σελίδα.

```javascript

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` ΕΠΙΧΕΙΡΕΥΣΗ ΠΡΟΓΡΑΜΜΑΤΑ

```csharp
    [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](https://github.com/scottgal/mostlylucidweb) σελίδα. Επομένως, παρακαλώ κοιτάξτε το αν θέλετε να δείτε περισσότεραΜSK1