# स्वचालित रूप से EasyNMT के साथ मार्कडाउन फ़ाइलों का अनुवाद

<datetime class="hidden">2024-08-03T13:30</datetime>

<!--category-- EasyNMT, Markdown -->
## परिचय

EasyNMT एक स्थानीय रूप से संस्थापित सेवा है जो मशीन अनुवाद सेवाओं की एक संख्या के लिए एक सरल अंतरफलक प्रदान करता है

आप इस ट्युटोरियल के लिए सभी फ़ाइलों को खोज सकते हैं [जिटहब रिपोजिटरी](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid/MarkdownTranslator) इस परियोजना के लिए

इसका आउटपुट लक्ष्य भाषाओं में नए मार्कडाउन फ़ाइलों का एक बन्च उत्पन्न करता है

[अनुवादित डाक](/translatedposts.png)

[TOC]

## पूर्व आवश्यकताएं

EasyNMT का इंस्टॉलेशन इस टूल्यूटर को अनुसरण करने के लिए जरूरी है. मैं सामान्यतः इसे डोकर सेवा के रूप में चलाता हूँ [यहाँ](https://github.com/UKPLab/EasyNMT/blob/main/docker/README.md) जो यह एक डकर सेवा के रूप में चलाने के लिए कैसे शामिल है.

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

या यदि आपके पास एनवीजीए GPU उपलब्ध है

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

MAX_WORKERS_BACKEND और MAX _WOSKERS_FRONTEND परिवेश चर EasyNMT को प्रयोग करने वाले कर्मियों की संख्या निर्धारित करते हैं

नोटः EasyNMT यह SMOOTHEST सेवा नहीं है कि चलाया जाए।

ओह और यह भी अनुवाद कर दिया है 'निर्धारणन'EU को प्रस्ताव प्रस्तुत करने के बारे में कुछ बेवकूफ

## लोड संतुलन के लिए सहज दृष्टिकोण

आसान एनएमटी संसाधनों के बारे में एक प्यास की जानवर है, तो मेरे MarkdownTranslatorService में मैं एक अति सरल अनियमित आईपी चयनक है जो सिर्फ मशीनों की सूची के आईपी की सूची को चलाने के लिए मैं प्रयोग करता हूँ EasyNMT

आरंभ में यह एक पर मिलता है `model_name` EasyNMT सेवा पर विधि

```csharp
    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` हम अगले एक ढूंढने के लिए कार्य IPs के माध्यम से घुमाएँ

```csharp
          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;
```

यह कई मशीनों के बीच अनुरोधों को संतुलित लोड करने का एक अति सरल तरीका है।

स्कमिक ` currentIPIndex = (currentIPIndex + 1) % IPs.Length;` सिर्फ आरंभ करने पर 0 और सूची की लंबाई में जा रहा IPs की सूची के माध्यम से घूमता है

## एक मार्कडाउन फ़ाइल का अनुवाद

MarkdownTranslatorService में मेरे पास यह कोड है

```csharp
    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);` यह दस्तावेज़ से पाठ स्ट्रिंग निकालता है
   यह विधि का उपयोग करता है

```csharp
  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' वास्तव में एक कार्य है।

3. `  var batchSize = 10;` यह अनुवाद सेवा के लिए बैच आकार सेट करता है
4. `csharp await Post(batch, targetLang, cancellationToken)`
   यह विधि में कॉल करता है जो बाद EasyNMT सेवा को बैच पोस्ट करता है

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

5. `  ReinsertTranslatedStrings(document, translatedStrings.ToArray());` - यह अनुवादित स्ट्रिंग को दस्तावेज़ में वापस जोड़ता है

```csharp

    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 का उपयोग करता हूँ जो कार्यक्रम में आरंभ होता है

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

जैसा कि आप देख सकते हैं यह भी देखने के लिए फ़ाइल की हैश की जाँच करता है कि क्या यह बदल गया है, जब इसे अनुवाद करने से पहले

यह मूल मार्क डाउन फ़ाइल की एक तीव्र हैश को संगणित करके किया जाता है फिर इसे अनुवाद करने के प्रयास से पहले यह देखने के लिए परीक्षण करता है कि क्या फ़ाइल बदली है

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

प्रोग्राम में सेटअप काफी सरल है

```csharp

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

मैं होस्ट्ड सर्विस (BackgroundTranslateService) और MarkdownTranslatorService के लिए HttpClient सेटअप किया है
एक होस्टेड सेवा एक लंबे समय तक चलाने वाली सेवा है जो पृष्ठभूमि में चलती है।

यहाँ आप देख सकते हैं I'm HttpClient के लिए समय समाप्ति को 15 मिनटों में सेट करने पर यह इसलिए है कि EasyNMT प्रतिक्रिया देने में थोड़ा धीमा हो सकता है।

## अंत में

यह एक Markdown फ़ाइल को कई भाषाओं में अनुवाद करने के लिए काफी सरल तरीका है। `MarkdownBlogService` प्रत्येक ब्लॉग पोस्ट के लिए अनुवादित नाम निकालना