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
Короткіше переклади Pt. M SK1
Sunday, 25 August 2024
Введение
У попередніх статтях, ми обговорювали важливість перекладу в контексті веб-приложенийM SK1 Ми також дослідили використання бібліотеки EasyNMT для виконання перекладів у ASP.NET Корінній програміMSC3 В цьому пості я ' розповім про те, як я додав до додатки базову службу, щоб дозволити вам надіслати запрошення на перекладМSK5, які обробляються в фоні МSK6
Знову ж таки, ви можете побачити весь вихідний код для цього на моєму GitHub сторінка.
Поprzedні статті
Тут ми додаємо маленький інструмент, який надсилає зворотньі завдання до сервісу, який ми розповідали частково. 2. Це простий бланк, який дозволяє вам надіслати запит на переклад до сервису.
Це доповнює функцію, коли, вибравши документ 'newM SK1, ви можете його перекласти

Код перекладу
Переводчик
На нашій сторінці редактора Маркдау я додав трохи коду, який містить маленький падінь (in _LanguageDropDown.cshtml), що дозволяє вам вибрати мову, яку ви хочете перекласти на .
@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 Це чудовий спосіб зберегти чисті і легкі перегляди ваших записів
@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 файл.
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
Хоча це виглядає як багато коду
Ми надсилаємо POST запит на сервер з мовою та змістом відмітки. Це йде до кінцевого пункту, що називається
start-translationщо визначено в нашомуTranslationAPI. Це запускає задачу перекладу і додає цю задачу в Cache для цього користувачаСервер реагує з ідеєю завдання (, яку ми записуємо, але не використовуємо
Потім ми надсилаємо запит GET на сервер, щоб отримати tłumaczenia. Це робиться за допомогою HTMX, яка є бібліотекою, що дозволяє вам оновлювати частини сторінки без поновлення всієї сторінкиM SK1 Це дуже потужний інструмент і його використовують в багатьох місцях в цій програмі.
Кінець точки запуску перекладу
Це веб-апі-контролер, який бере запити, що містять позначку та мову коду. Потім надсилає запити на наш сервер фонового перекладу , відкладає завдання знову, видає UserId ( міститься в cookie M SK3 і повертає Id завдання клієнтові . ( Я відключив атрибут 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 і повертає переклади для теперішнього користувача. Це простий кінцевий пункт, який отримує переклади з кэшу та повертається до клієнта
[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, щоб показати час, потрібний для завершення перекладу в людському читабельному форматі.
В результаті отримуємо ось цей результат:

@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);
}
На завершення
Результатом всього цього є те, що ви можете тепер надіслати запит на переклад і побачити статус перекладу в редакторі після нього.
У мене є більше, що я хочу зробити тут, включаючи ре- підготування потоку маркування Render для перекладеного контенту і т.д. . Але це МSK2 це веселоте місце на цій сайті МСК3 не все повністю поліпшено, але це М СК4 це весь справжній код, з яким ви можете грати, як я будую його MСК5 Знову ж таки, ви можете побачити весь вихідний код для цього на моєму GitHub страница. Тож, будь ласка, погляньте на це, якщо ви хочете побачити більше