Back to "Автоматичний переклад файлів відмітки за допомогою EasyNMT"

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

EasyNMT Markdown

Автоматичний переклад файлів відмітки за допомогою EasyNMT

Saturday, 03 August 2024

Введение

EasyNMT - це локальна інсталяційна служба, яка надає простий інтерфейс багатьом машинам перекладу. В цьому вправі ми використаємо EasyN MT, щоб автоматично перекладати файл Markdown з англійської на кілька мов.

Ви можете знайти всі файли для цього уроку в Репозиторія GitHub для цього проекту.

Результатом цього було створення BUNCH нових файлів відмітки в цільових мовах. Це дуже простий спосіб отримати блоговий пост перекладений на різні мови.

Переведені листи

Потрібні умови

Установка EasyNMT необхідна для того, щоб слідувати за цим інструкцією. Я зазвичай запускаю її як докерну службу . Ви можете знайти інструкції щодо instalacji тут який описує, як запустити його як докер-сервіс.

docker run -d -p 24080:80 --env MAX_WORKERS_BACKEND=6 --env MAX_WORKERS_FRONTEND=6 easynmt/api:2.0-cpu

Або якщо ви маєте NVIDIA GPU

docker run -d -p 24080:80 --env MAX_WORKERS_BACKEND=6 --env MAX_WORKERS_FRONTEND=6 easynmt/api:2.0.2-cuda11.3

В середовищі MAX_WORKERSM SK1BACKEND і MAXМSK2WOSKERS _FRONTEND встановлюють кількість працівників, які використовуватиме EasyNMT.

Зверніть увагу, що EasyNMT не є SMOOTHEST-сервісом для запуску, але він найкращий, який я знайшов для цього.

І він також перетворив МСК0 , Конkluzія, "'" на дещо безглуздо про подачу wniosku до ЕС .

Наївний підхід до балансування

Easy NMT - це жахлива bestie, коли йдеться про ресурси, тож у моєму MarkdownTranslatorService я маю дуже простий випадковий IP відбірник, який просто обертається через список IP з списку машин, які я використовую для запуску EasyNMT.

Спочатку це відбувається на model_name метод на сайті EasyNMT, це простий спосіб перевірити, чи є служба підключена до мережі

    private string[] IPs = translateServiceConfig.IPs;
    public async ValueTask<bool> IsServiceUp(CancellationToken cancellationToken)
    {
        var workingIPs = new List<string>();

        try
        {
            foreach (var ip in IPs)
            {
                logger.LogInformation("Checking service status at {IP}", ip);
                var response = await client.GetAsync($"{ip}/model_name", cancellationToken);
                if (response.IsSuccessStatusCode)
                {
                    workingIPs.Add(ip);
                }
            }

            IPs = workingIPs.ToArray();
            if (!IPs.Any()) return false;
            return true;
        }
        catch (Exception e)
        {
            logger.LogError(e, "Error checking service status");
            return false;
        }
    }

Тоді всередині Post метод MarkdownTranslatorService ми обертаємось через робочі IP, щоб знайти наступний

          if(!IPs.Any())
            {
                logger.LogError("No IPs available for translation");
                throw new Exception("No IPs available for translation");
            }
            var ip = IPs[currentIPIndex];
            
            logger.LogInformation("Sending request to {IP}", ip);
        
            // Update the index for the next request
            currentIPIndex = (currentIPIndex + 1) % IPs.Length;

Це дуже простий спосіб завантаження баланса запитів на різних машинах. МСК0. Він не ідеальний. МSK2 не означає, що МСК3 буде надто зайнятою машиною для тестування. М СК4, але він достатньо добрий для моїх цілей

Шмік currentIPIndex = (currentIPIndex + 1) % IPs.Length; просто обертається по списку IP, починаючи з 0 і рухаючись до довжини спискуM SK1

Перекладання файла Markdown

Це код, який я маю в MarkdownTranslatorService.cs файлі . Це' проста служба, яка бере строки відмітки та мову-мішанку і повертає перекладений строк відміток

    public async Task<string> TranslateMarkdown(string markdown, string targetLang, CancellationToken cancellationToken)
    {
        var document = Markdig.Markdown.Parse(markdown);
        var textStrings = ExtractTextStrings(document);
        var batchSize = 10;
        var stringLength = textStrings.Count;
        List<string> translatedStrings = new();
        for (int i = 0; i < stringLength; i += batchSize)
        {
            var batch = textStrings.Skip(i).Take(batchSize).ToArray();
            translatedStrings.AddRange(await Post(batch, targetLang, cancellationToken));
        }


        ReinsertTranslatedStrings(document, translatedStrings.ToArray());
        return document.ToMarkdownString();
    }

Як бачите, він має декілька кроків:

  1. var document = Markdig.Markdown.Parse(markdown); - Це розшифровує символ відмітки в документі
  2. var textStrings = ExtractTextStrings(document); - Це виокремлює текстові стрічки з документу Він використовує метод
  private bool IsWord(string text)
    {
        var imageExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg" };
        if (imageExtensions.Any(text.Contains)) return false;
        return text.Any(char.IsLetter);
    } 

Це перевіряє, чи слово ' word ' дійсно є роботою.

  1. var batchSize = 10; - Це визначає величину пакету для послуг перекладу . EasyNMT має обмеження на кількість слів, які він може перекласти за один раз.
  2. csharp await Post(batch, targetLang, cancellationToken) Це вводить метод, який потім відправляє пакет на службу EasyNMT.
    private async Task<string[]> Post(string[] elements, string targetLang, CancellationToken cancellationToken)
    {
        try
        {
            var postObject = new PostRecord(targetLang, elements);
            var response = await client.PostAsJsonAsync("/translate", postObject, cancellationToken);

            var phrase = response.ReasonPhrase;
            response.EnsureSuccessStatusCode();
            var result = await response.Content.ReadFromJsonAsync<PostResponse>(cancellationToken: cancellationToken);

            return result.translated;
        }
        catch (Exception e)
        {
            logger.LogError(e, "Error translating markdown: {Message} for strings {Strings}", e.Message, string.Concat( elements, Environment.NewLine));
            throw;
        }
    }
  1. ReinsertTranslatedStrings(document, translatedStrings.ToArray()); - Це повертає переведені строки назад у документ. Використовуючи MarkDig

    private void ReinsertTranslatedStrings(MarkdownDocument document, string[] translatedStrings)
    {
        int index = 0;

        foreach (var node in document.Descendants())
        {
            if (node is LiteralInline literalInline && index < translatedStrings.Length)
            {
                var content = literalInline.Content.ToString();
         
                if (!IsWord(content)) continue;
                literalInline.Content = new Markdig.Helpers.StringSlice(translatedStrings[index]);
                index++;
            }
        }
    }

Обслуговлена послуга

Для всього цього я використовую IHostedLifetimeService, що запускається в програмі.cs file. Ця служба читає в файлі відміткиM SK2 перекладає його на певну кількість мов і записує перетворені файли на диск

  public async Task StartedAsync(CancellationToken cancellationToken)
    {
        if(!await blogService.IsServiceUp(cancellationToken))
        {
            logger.LogError("Translation service is not available");
            return;
        }
        ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = blogService.IPCount, CancellationToken = cancellationToken};
        var files = Directory.GetFiles(markdownConfig.MarkdownPath, "*.md");

        var outDir = markdownConfig.MarkdownTranslatedPath;

        var languages = translateServiceConfig.Languages;
        foreach(var language in languages)
        {
            await Parallel.ForEachAsync(files, parallelOptions, async (file,ct) =>
            {
                var fileChanged = await file.IsFileChanged(outDir);
                var outName = Path.GetFileNameWithoutExtension(file);

                var outFileName = $"{outDir}/{outName}.{language}.md";
                if (File.Exists(outFileName) && !fileChanged)
                {
                    return;
                }

                var text = await File.ReadAllTextAsync(file, cancellationToken);
                try
                {
                    logger.LogInformation("Translating {File} to {Language}", file, language);
                    var translatedMarkdown = await blogService.TranslateMarkdown(text, language, ct);
                    await File.WriteAllTextAsync(outFileName, translatedMarkdown, cancellationToken);
                }
                catch (Exception e)
                {
                    logger.LogError(e, "Error translating {File} to {Language}", file, language);
                }
            });
        }
       

Як бачите, він також перевіряє хеш файлу, щоб побачити, чи він змінився.

Це робиться, вираховуючи швидкий хаш оригінального файлу відмітки, а потім перевіряючи, чи цей файл змінився перед тим, як спробувати його перекласти.

    private static async Task<string> ComputeHash(string filePath)
    {
        await using var stream = File.OpenRead(filePath);
        stream.Position = 0;
        var bytes = new byte[stream.Length];
        await stream.ReadAsync(bytes);
        stream.Position = 0;
        var hash = XxHash64.Hash(bytes);
        var hashString = Convert.ToBase64String(hash);
        hashString = InvalidCharsRegex.Replace(hashString, "_");
        return hashString;
    }

Установка в програмі досить проста.


    builder.Services.AddHostedService<BackgroundTranslateService>();
services.AddHttpClient<MarkdownTranslatorService>(options =>
{
    options.Timeout = TimeSpan.FromMinutes(15);
});

Я створив хостельну службу (BackgroundTranslateService) і HttpClient для MarkdownTranslatorService . Хостинг Сервіс - це довгий-сервіс, що працює в фоні. Це ' хороше місце для того, щоб помістити послуги, які мусять постійно працювати у фоні або просто зайняти деякий час, щоб закінчитиM SK3 Нова інтерфейс IHostedLifetimeService є трохи більш гнучким ніж стараlний інтерфейс iHostedService і дозволяє нам виконувати завдання повністю в фону з легкістю, ніж стараls IHosedService

Тут ви бачите, як я встановлюю тайм-аут для HttpClient на 15 хвилини . Це тому, що EasyNMT може реагувати трохи повільно МSK3 особливо вперше, використовуючи мову-модель

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

Це досить простий спосіб перекладати файл відмітки на різні мови. MarkdownBlogService щоб витягти перекладені імена для кожного блогового посту.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.