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
Αυτόματη μετάφραση αρχείων Markdown με EasyNMT
Saturday, 03 August 2024
Εισαγωγή
Το EasyNMT είναι μια τοπικά εγκατεστημένη υπηρεσία που παρέχει μια απλή διεπαφή σε διάφορες υπηρεσίες μηχανικής μετάφρασης. Σε αυτό το tutorial, θα χρησιμοποιήσουμε το EasyN MT για να μεταφράσει αυτόματα ένα αρχείο Markdown από Αγγλικά σε πολλές γλώσσες
Μπορείτε να βρείτε όλα τα αρχεία για αυτό το tutorial στο Αποθεματικό GitHub Για το συγκεκριμένο σχέδιο, οι επιχειρήσεις
Η παραγωγή αυτού δημιούργησε ένα BUNCH των νέων αρχείων μάρκινγκ στις γλώσσες-στόχους.Αυτό είναι ένας πολύ απλός τρόπος για να πάρετε μια ανάρτηση στο blog μεταφρασμένη σε πολλές γλώσσές.
Προϋποθέσεις
Μια εγκατάσταση του EasyNMT απαιτείται για να ακολουθήσετε αυτό το tutorial. Συνήθως την εκτείνω ως υπηρεσία Docker. Μπορείτε να βρείτε τις οδηγίες εγκατάστασης ΕΔΩ που καλύπτει πώς να το εκτελέσει ως υπηρεσία docker.
docker run -d -p 24080:80 --env MAX_WORKERS_BACKEND=6 --env MAX_WORKERS_FRONTEND=6 easynmt/api:2.0-cpu
Ή αν έχετε GPU NVIDIA διαθέσιμη:
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ΜSK2WorkerM SK3FRONTEND περιβαλλοντικές μεταβλητές καθορίζει τον αριθμό των εργαζομένων που η EasyNMT θα χρησιμοποιήσειMSKA4 Μπορείτε να τα προσαρμόσετε για να ταιριάζει με τη μηχανή σαςMKA5
ΣΗΜΕΙΩΣΗ:EasyNMT δεν' η SMOOTHEST υπηρεσία για να τρέξειM SK2 αλλά αυτόMK3 το καλύτερο που είδαμε' βρέθηκε γι’ αυτόν τον σκοπόMSKA5Είναι μια μικρή αμφιβολία σχετικά με τη γραμμή εισόδου πουMKA6 πέρασεMSKI7 έτσι μπορεί να χρειαστεί να κάνετε κάποια προMKI8 επεξεργασία του κειμένου εισόδου σας πριν την περάσει στο EasyNMTMSKE9
Ω και μεταφράστηκε επίσης 'ΣυμφωνίαΜSK1 αναφέρθηκε σε κάποια ανοησία σχετικά με την υποβολή της πρότασης στην ΕΕΗ ΜSK2 αναφέρει την προδοσία τουΗ ' αναφέρει εκπαιδευτικό σύνολοΟ ΜSK4 αναφέρει
Ναυτική προσέγγιση για την ισορροπία φορτίων
Το Easy NMT είναι ένα θλιβερό ζώο όταν πρόκειται για πόρους, έτσι στο MarkdownTranslatorService έχω έναν super απλό τυχαίο επιλεκτή IP που απλά περιστρέφεται μέσα από τη λίστα των IPs μιας λίστας μηχανών που χρησιμοποιώ για να εκτελέσω το EasyNMT
Αρχικά, αυτό γίνεται με την model_name μέθοδος για την υπηρεσία EasyNMT, αυτό είναι ένα γρήγορο, απλός τρόπος για να ελέγξετε αν η υπηρεσία βρίσκεται σε εξέλιξηM SK2 Εάν είναιΜSK3 προσθέτει το IP σε μια λίστα των λειτουργικών IPsMSKA4 Αν έχει' δεν έχειMSKA6 δεν κάνει' Προσθέτει στον κατάλογοMKA8
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 για να βρούμε το επόμενο.
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;
Αυτό είναι ένας πολύ απλός τρόπος για να εξισορροπήσετε τις απαιτήσεις σε μια σειρά από μηχανές.ΑυτόΜSK1 δεν είναι τέλεια (Δεν εγγυάται' λογοδοτεί για ένα super busy machine για exampelM SK4 αλλά αυτό ΜSK5 καλά αρκετά για τους σκοπούς μου
Το Schmick currentIPIndex = (currentIPIndex + 1) % IPs.Length; Απλά περιστρέφεται μέσα από τη λίστα των IPs που ξεκινούν με 0 και πηγαίνουν στο μήκος της λίστας.
Μετάφραση ενός αρχείου Markdown
Αυτός είναι ο κώδικας που έχω στο MarkdownTranslatorService. αναφέρει το αρχείο. αναφέρει ότι το ' διατηρεί μια απλή υπηρεσία η οποία λαμβάνει ένα σήμα και μια γλώσσα στόχου και επιστρέφει την μεταφρασμένη σήμα-αποκάλυψηM SK3 αναφέρει
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();
}
Όπως μπορείτε να δείτε έχει μια σειρά από βήματα:
var document = Markdig.Markdown.Parse(markdown);- Αυτό διαχωρίζει τη γραμμή μείωσης σε ένα έγγραφο.var textStrings = ExtractTextStrings(document);- Αυτό εξάγει τις γραμμές κειμένου από το έγγραφοΜSK1 Χρησιμοποιεί τη μέθοδο
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' είναι πραγματικά μια εργασίαΜSK2 τα ονόματα εικόνας μπορούν να αποσπάσουν τη λειτουργικότητα διαχωρισμού φράσης στο EasyNMTMK3
var batchSize = 10;- Αυτό καθορίζει το μέγεθος της συσκευής για την υπηρεσία μετάφρασης ΜSK1 Το EasyNMT έχει ένα όριο στον αριθμό των λέξεων που μπορεί να μεταφράσει σε ένα go ΜSK2around 500, έτσι Η γραμμή αναφοράς 10 είναι γενικά ένα καλό μέγεθός συσκευών εδώ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;
}
}
ReinsertTranslatedStrings(document, translatedStrings.ToArray());- Αυτό επαναπροσθέτει τις μεταφρασμένες γραμμές πίσω στο έγγραφοΜSK1 Χρησιμοποιώντας το MarkDig' διατηρεί την ικανότητα να περπατάει το εγγράφων και να αντικαθιστά τα γραμμάρια κειμένου ΜSK3
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 το οποίο ξεκινά στο πρόγραμμα.χρησιμοποιεί αρχείο.Αυτή η υπηρεσία διαβάζει σε αρχείο μάρκετινγκΜSK2 μεταφράζεται σε μια σειρά γλωσσών και γράφει τα μεταφρασμένα αρχεία έξω στον δίσκοM SK3
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);
}
});
}
Όπως μπορείτε να δείτε, ελέγχει επίσης το hash του αρχείου για να δει αν έχει αλλάξει' αναφέρει πριν μεταφραστεί. αναφέρει Αυτό είναι για να αποφευχθεί η μετάφραση αρχείων που έχουν αλλάξει ' αναφέρει
Αυτό γίνεται υπολογίζοντας ένα γρήγορο hash του αρχείου markdown και στη συνέχεια δοκιμάζουμε για να δούμε αν το αρχείο έχει αλλάξει πριν προσπαθήσουμε να μεταφράσει.
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;
}
Η ρύθμιση στο πρόγραμμα.cs είναι αρκετά απλήΜSK1
builder.Services.AddHostedService<BackgroundTranslateService>();
services.AddHttpClient<MarkdownTranslatorService>(options =>
{
options.Timeout = TimeSpan.FromMinutes(15);
});
Εγκαθιστώ το HostedService (BackgroundTranslateService) και τον HttpClient για την MarkdownTranslatorService<. Μια Υπηρεσία φιλοξενίας είναι μια μακρά υπηρεσία που λειτουργεί στο υπόβαθρο.ΑυτόΜSK2 διατηρεί ένα καλό μέρος για να τοποθετήσετε υπηρεσίες που πρέπει να εκτελούνται συνεχώς στο υπόβάθρο ή απλά πάρει λίγο χρόνο για να ολοκληρώσει.Η νέα διεπαφή IHostedLifetimeService είναι ελαφρώς πιο ευέλικτη από την παλιά διεπαφές IHostiedService και μας επιτρέπει να εκτέλεση εργασιών εντελώς στο υπόβασμα πιο εύκολα από το παλιό IHOSTEDServiceMST4
Εδώ μπορείτε να δείτε I'm ρύθμιση του χρονοδιάγραμμα για το HttpClient σε 15 λεπτάM SK2Αυτό είναι επειδή EasyNMT μπορεί να είναι λίγο αργά να απαντήσει ΜSK3ιδιαίτερα την πρώτη φορά που χρησιμοποιεί ένα μοντέλο γλώσσαςMSKA4IMKA5m επίσης θέσει τη διεύθυνση βάσης στη διεύθυνση IP της μηχανής που εκτελεί την υπηρεσία EasyN MT
Στο συμπέρασμα
Αυτό είναι ένας αρκετά απλός τρόπος για να μεταφράσετε ένα αρχείο μάρκετινγκ σε πολλές γλώσσες. Το ' δεν είναι τέλειο, αλλά το ' μια καλή αρχήM SK4 Γενικά τρέχω αυτό για κάθε νέα ανάρτηση στο blog και χρησιμοποιείται στην MarkdownBlogService να τραβήξετε τα μεταφρασμένα ονόματα για κάθε ανάρτηση blog.