# Короткіше переклади Pt. M SK1

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

<!--category-- EasyNMT, ASP.NET, WebAPI, Alpine.js, HTMX -->
# Введение

У попередніх статтях, ми обговорювали важливість перекладу в контексті веб-приложенийM SK1 Ми також дослідили використання бібліотеки EasyNMT для виконання перекладів у ASP.NET Корінній програміMSC3 В цьому пості я ' розповім про те, як я додав до додатки базову службу, щоб дозволити вам надіслати запрошення на перекладМSK5, які обробляються в фоні МSK6

Знову ж таки, ви можете побачити весь вихідний код для цього на моєму [GitHub](https://github.com/scottgal/mostlylucidweb) сторінка.

## Поprzedні статті

- [Короткіше переклади Pt. M SK1](/blog/backgroundtranslationspt1)
- [Короткіше переклади Pt. M SK1](/blog/backgroundtranslationspt2)

Тут ми додаємо маленький інструмент, який надсилає зворотньі завдання до сервісу, який ми розповідали частково. 2. Це простий бланк, який дозволяє вам надіслати запит на переклад до сервису.

[TOC]

Це доповнює функцію, коли, вибравши документ 'newM SK1, ви можете його перекласти

![Редактор](neweditor.gif?a)

# Код перекладу

## Переводчик

На нашій сторінці редактора Маркдау я додав трохи коду, який містить маленький падінь (in `_LanguageDropDown.cshtml`), що дозволяє вам вибрати мову, яку ви хочете перекласти на .

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

#### _LanguageDropDown

Наш `_LanguageDropDown` частково - це простий крап-даун, який дозволяє вам вибрати мову, яку ви хочете перекласти на. Це заповнюється з списку мов в `Languages` властивість моделі.

Ви бачите, що він використовує Alpine.js для obsługi вказівок і для набору обраного мови та прапорця, який має бути показаний у головній частині вибору.

Використовуючи алгоритм Alping, ми зберігаємо мінімальні дані, локально відібрані Java-скрипти в наших widokахM 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>
```

### Передача

Ви побачите, що це має певний Apline.js код, який підходить до нашого `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

```

Хоча це виглядає як багато коду

1. Ми надсилаємо POST запит на сервер з мовою та змістом відмітки. Це йде до кінцевого пункту, що називається `start-translation` що визначено в нашому `TranslationAPI`. Це запускає задачу перекладу і додає цю задачу в Cache для цього користувача

2. Сервер реагує з ідеєю завдання (, яку ми записуємо, але не використовуємо

3. Потім ми надсилаємо запит GET на сервер, щоб отримати tłumaczenia. Це робиться за допомогою HTMX, яка є бібліотекою, що дозволяє вам оновлювати частини сторінки без поновлення всієї сторінкиM SK1 Це дуже потужний інструмент і його використовують в багатьох місцях в цій програмі.

### Кінець точки запуску перекладу

Це веб-апі-контролер, який бере запити, що містять позначку та мову коду. Потім надсилає запити на наш сервер фонового перекладу , відкладає завдання знову, видає UserId ( міститься в cookie M SK3 і повертає Id завдання клієнтові .
( Я відключив атрибут 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 і повертає переклади для теперішнього користувача. Це простий кінцевий пункт, який отримує переклади з кэшу та повертається до клієнта

```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 SK1 Він показує таблицю перекладів з посиланням для перегляду перекладу.

Він також має справу з тим, коли всі переклади завершені, щоб зупинити голосування (, встановивши триггер на `none`). І відображати альтернативне повідомлення, коли немає перекладів

Я також використовую бібліотеку 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);
    }
```

## На завершення

Результатом всього цього є те, що ви можете тепер надіслати запит на переклад і побачити статус перекладу в редакторі після нього.

У мене є більше, що я хочу зробити тут, включаючи ре- підготування потоку маркування Render для перекладеного контенту і т.д. . Але це МSK2 це веселоте місце на цій сайті МСК3 не все повністю поліпшено, але це М СК4 це весь справжній код, з яким ви можете грати, як я будую його MСК5
Знову ж таки, ви можете побачити весь вихідний код для цього на моєму [GitHub](https://github.com/scottgal/mostlylucidweb) страница. Тож, будь ласка, погляньте на це, якщо ви хочете побачити більше