Extension de mon blog avec des traductions par Amazon Nova

Ce fichier a été traduit automatiquement par IA, des erreurs peuvent survenir
Lisez-vous ce post en anglais ? Ou peut-être en espagnol ? Ou pourquoi pas en italien ?
Dans mon précédent post sur l'automatisation des tâches pour mon blog, j'ai ajouté la relecture automatisée utilisant Amazon Nova. Bien qu'écrire en anglais me permette de me connecter avec un large public technique, il existe une communauté mondiale encore plus grande qui pourrait bénéficier de mon contenu s'il était disponible dans leurs langues maternelles.
J'ai déjà étendu mon blog avec la génération de voix utilisant Amazon Polly. J'ai fait cela pour que les personnes ayant des difficultés de lecture, comme celles atteintes de dyslexie, puissent apprendre de mon contenu sans difficulté de lecture, mais en l'écoutant.
Cette fois, je brise les barrières linguistiques et rends mon contenu technique accessible aux développeurs et aux passionnés du cloud du monde entier grâce à la traduction automatisée utilisant Amazon Nova.
Vision
La communauté technique est incroyablement diverse, et bien que l'anglais soit principalement utilisé dans le monde de la technologie, loin d'être tout le monde est un locuteur natif de l'anglais. Je ne le suis pas, et loin d'être tout le monde est 100% à l'aise pour consommer du contenu en anglais. Ils préfèrent le lire dans leur langue maternelle. Les concepts techniques complexes peuvent être suffisamment difficiles sans la barrière supplémentaire de la compréhension linguistique.
Dans mon post Solution de statistiques serverless avec Lambda@Edge, j'explique comment j'ai construit un système d'analyse serverless pour mon blog, où je pouvais voir d'où venaient mes lecteurs, et plus encore.
En regardant mes analyses, je peux voir beaucoup de lecteurs d'Allemagne, d'Autriche, de France, d'Italie, d'Espagne (et d'autres pays hispanophones), et du Brésil et du Portugal. Je voulais donc créer une solution où les gens pourraient choisir s'ils souhaitent lire en anglais ou dans une langue différente.
Pour cette première implémentation d'un service de traduction, j'ai choisi de prendre en charge cinq langues qui représentent une grande partie de mes lecteurs : allemand, espagnol, français, italien et portugais. Je pourrais ajouter d'autres langues comme le japonais dans une seconde phase.
Ce qui était important, c'est que la solution :
Maintienne la précision technique : Les termes techniques et les noms de services AWS doivent être traduits correctement.
Preserve la structure markdown : Tout le formatage, les blocs de code, les liens et les images doivent rester intacts.
Garde mon style d'écriture : La traduction doit sembler naturelle tout en maintenant la profondeur technique.
S'échelonne efficacement : Prend en charge plusieurs langues sans multiplier mon travail.
S'intègre de manière transparente : S'intègre dans mon pipeline serverless et orienté événements existant.
Amazon Nova pour la traduction
Ayant utilisé avec succès Amazon Nova Pro pour la relecture, j'étais sûr qu'il serait également capable de gérer mes tâches de traduction. Les capacités multimodales de Nova Pro et sa compréhension du contexte le rendent bien adapté pour traduire du contenu technique. Vous pouvez en savoir plus sur Nova, les différents modèles et BedRock dans mon post relecture automatisée utilisant Amazon Nova.
Architecture de la solution
En me basant sur mon architecture orientée événements existante, j'ai étendu le pipeline pour inclure la traduction. Lorsque j'ai créé le pipeline, je l'ai construit de manière très modulaire, afin qu'il puisse être facilement étendu. Cette approche est vraiment payante maintenant car je peux intégrer des extensions de manière transparente dans le même flux avec très peu d'effort.
Les traductions s'exécutent en parallèle avec la génération de voix et de quiz en cas d'exécution d'une pull request. Cependant, j'ai décidé de le séparer du flux saga standard que j'utilise pour la voix et le quiz. La raison en est que je souhaite pouvoir exécuter les traductions non seulement sur les pull requests, mais aussi invoquer les traductions de posts plus anciens.
Donc, dans le cas du contexte de pull request, le flux commence lorsque la relecture est terminée, par un événement de GitHub Actions. Mais je peux aussi invoquer et démarrer le flux en envoyant un événement avec un nom de branche et le blog à traduire, au service d'information qui démarre alors tout le flux. Une addition dans ce cas est qu'en une dernière étape, j'ouvrirai également une nouvelle pull request avec les traductions pour l'ancien post.
Les traductions sont stockées dans des répertoires spécifiques à chaque langue (par exemple, /de/
, /es/
, /fr/
), ce qui me permet d'organiser tout pendant le processus de construction. J'utilise 11ty pour générer du HTML statique à partir de mes posts écrits en markdown.
Plongée technique en profondeur
La traduction est implémentée en tant que service propre et est basée sur Step Functions comme service principal et orchestrateur. La traduction commence soit par la création d'une nouvelle branche et le démarrage du flux pour un post plus ancien, soit elle est automatiquement démarrée lorsqu'une nouvelle pull request est ouverte, pour un nouveau post. Création d'un flux de traduction comme celui-ci.
Step Function de traduction
Le cœur du service de traduction est cette Step Function, qui orchestre tout le flux. Si vous me suivez depuis un moment, vous savez que j'essaie normalement d'utiliser les intégrations SDK de Step Functions autant que possible, mais pour le flux de traduction, je dois m'appuyer fortement sur mon deuxième service préféré, Lambda.
Le workflow consiste en plusieurs états clés :
- CollectInfo : Normalise les données d'entrée et prépare les traductions, lit les langues de l'événement ou utilise la valeur par défaut.
- CheckoutMarkdownFile : Télécharge le fichier markdown source depuis GitHub.
- Iterate Languages : Exécute les traductions pour les langues cibles, limité à 1 traduction parallèle pour le moment.
- CreateCommit : Committe tous les fichiers traduits dans le référentiel, ouvre une nouvelle pull request si nécessaire.
En regardant la définition de la Step Function, j'utilise le support pour le langage de requête JSONata, qui est l'une des meilleures ajouts à Step Functions avec les variables. L'état CollectInfo
stockera des informations dans des variables qui sont disponibles pour tous les états futurs. En itérant sur les langues, j'utilise un état Map
avec une limite fixée à 1.
Comment: Traduire le contenu du post en utilisant Bedrock.
QueryLanguage: JSONata
StartAt: CollectInfo
States:
CollectInfo:
Type: Pass
Assign:
Languages: "{% $exists($states.input.detail.Data.Translate.Languages) ? $states.input.detail.Data.Translate.Languages : ['de','es','fr','it','pt'] %}"
GitInfo:
CommitSha: "{% $exists($states.input.detail.Data.GitInfo.CommitSha) ? $states.input.detail.Data.GitInfo.CommitSha : $states.input.detail.Data.PullRequestInfo.PullRequestCommitSha %}"
Branch: "{% $exists($states.input.detail.Data.GitInfo.Branch) ? $states.input.detail.Data.GitInfo.Branch : $states.input.detail.Data.PullRequestInfo.PullRequestBranch %}"
MarkdownInfo:
FilePath: "{% $states.input.detail.Data.MarkdownFile.path %}"
FileSlug: "{% $states.input.detail.Data.MarkdownFile.fileSlug %}"
Next: CheckoutMarkdownFile
CheckoutMarkdownFile:
Type: Task
Resource: arn:aws:states:::lambda:invoke
Arguments:
FunctionName: ${CheckoutMarkdownFileFunctionArn}
Payload:
GitInfo: "{% $GitInfo %}"
MarkdownInfo: "{% $MarkdownInfo %}"
Next: Iterate Languages
Iterate Languages:
Type: Map
ItemProcessor:
ProcessorConfig:
Mode: INLINE
StartAt: TranslateMarkdownFile
States:
TranslateMarkdownFile:
Type: Task
Resource: arn:aws:states:::lambda:invoke
Arguments:
FunctionName: ${TranslateBedrockFunctionArn}
Payload:
Key: "{%$MarkdownInfo.FileSlug & '/' & $MarkdownInfo.FilePath %}"
S3Bucket: ${ETLBucket}
Language: "{% $states.input %}"
End: true
Next: CreateCommit
MaxConcurrency: 1
Items: "{% $Languages %}"
CreateCommit:
Type: Task
Resource: arn:aws:states:::lambda:invoke
Arguments:
FunctionName: ${CreateCommitFunctionArn}
Payload:
GitInfo: "{% $GitInfo %}"
MarkdownInfo: "{% $MarkdownInfo %}"
End: true
Fonctions Lambda
En regardant les fonctions Lambda, il y a trois fonctions principales en jeu dans la solution. La fonction qui utilisera Octokit pour extraire le code, et la fonction qui créera le nouveau commit. Ces deux fonctions ont été expliquées dans des posts précédents sur ce sujet, et sont omises cette fois.
Fonction de traduction
C'est le cœur et l'âme de toute la solution et interagira avec Bedrock pour traduire le post. Le post traduit sera stocké dans un répertoire spécifique à la langue.
import json
import os
import boto3
from botocore.exceptions import ClientError
def handler(event, context):
try:
bucket = event.get("S3Bucket")
key = event.get("Key")
lang = event.get("Language", "error")
if lang not in ["de", "es", "fr", "it", "pt"]:
raise ValueError(
f"Langue non supportée '{lang}'. Langues supportées : de, es, fr, it, pt."
)
s3_client = boto3.client("s3")
bedrock_client = boto3.client("bedrock-runtime", region_name="eu-west-1")
response = s3_client.get_object(Bucket=bucket, Key=key)
file_content = response["Body"].read().decode("utf-8")
prompt = f"""Vous êtes un éditeur technique professionnel et traducteur, expert en AWS et Cloud computing. Veuillez traduire soigneusement le post de blog markdown suivant en {lang}.
Instructions:
- Maintenir le formatage markdown original
- Garder le ton et le style cohérents
- Ne pas changer le sens ou la structure du contenu
- Retourner uniquement le texte markdown traduit sans aucun commentaire supplémentaire
- Ne pas traduire les blocs de code ou le code en ligne
- Assurer une grammaire, une orthographe et une ponctuation correctes
- Si le contenu est déjà en {lang}, ignorez-le et retournez le contenu tel quel
- Ne pas entourer la version traduite avec ```markdown
Voici le contenu markdown à traduire:
{file_content}"""
# Préparer la requête pour le modèle Nova Pro
request_body = {
"messages": [{"role": "user", "content": [{"text": prompt}]}],
"inferenceConfig": {
"temperature": 0.1, # Basse température pour des traductions cohérentes
"topP": 0.9,
"maxTokens": 10240, # Ajuster en fonction de la longueur de sortie attendue
},
}
bedrock_response = bedrock_client.invoke_model(
modelId="eu.amazon.nova-pro-v1:0",
body=json.dumps(request_body),
contentType="application/json",
)
response_body = json.loads(bedrock_response["body"].read())
translated_content = response_body["output"]["message"]["content"][0]["text"]
new_key = f"{lang}/{key}"
s3_client.put_object(
Bucket=bucket,
Key=new_key,
Body=translated_content.encode("utf-8"),
ContentType="text/markdown",
)
return {
"statusCode": 200,
"body": "Traduction terminée",
}
except Exception as e:
print(f"Erreur inattendue: {str(e)}")
return {
"statusCode": 500,
"body": json.dumps({"error": "Erreur interne du serveur", "message": str(e)}),
}
Qualité de la traduction et ingénierie des prompts
Pendant l'implémentation de la solution de relecture, j'ai appris à quel point la partie ingénierie des prompts était importante pour le résultat final. L'IA fait toujours ce que vous lui dites de faire, et si votre prompt n'est pas suffisamment spécifique, la qualité du résultat peut varier. En utilisant les leçons apprises de la relecture, mon prompt incluait des exigences spécifiques.
En fin de compte, le prompt suivant m'a donné d'excellents résultats :
Vous êtes un éditeur technique professionnel et traducteur, expert en AWS et Cloud computing.
Veuillez traduire soigneusement le post de blog markdown suivant en {lang}.
Instructions:
- Maintenir le formatage markdown original
- Garder le ton et le style cohérents
- Ne pas changer le sens ou la structure du contenu
- Retourner uniquement le texte markdown traduit sans aucun commentaire supplémentaire
- Ne pas traduire les blocs de code ou le code en ligne
- Assurer une grammaire, une orthographe et une ponctuation correctes
- Si le contenu est déjà en {lang}, ignorez-le et retournez le contenu tel quel
- Ne pas entourer la version traduite avec ```markdown
Voici le contenu markdown à traduire:
Changements 11ty
J'utilise 11ty comme générateur HTML statique, tous les blogs sont écrits en markdown et ensuite convertis en HTML. Pour pouvoir supporter différentes langues, j'ai dû apporter quelques changements à ma construction et à ma mise en page 11ty. Tout d'abord, j'ai ajouté un nouveau champ au front matter qui indique la langue du post. Ensuite, j'ai dû changer la façon dont ma collection de posts récupérait les posts du blog.
J'ai ajouté un filtre pour ne récupérer que les posts en anglais afin de ne pas obtenir tous les posts de la collection.
export default (coll) => {
const posts = coll
.getFilteredByGlob("src/posts/*.md")
.filter((p) => (p.data.lang || "en") === "en");
return posts.reverse();
};
Ensuite, j'ai dû localiser toutes les traductions pour un post. J'ai utilisé le slug du fichier comme clé pour créer cette collection.
config.addCollection("postsBySlug", (collection) => {
const posts = collection.getFilteredByGlob("src/posts/**/*.md");
const postsBySlug = {};
posts.forEach((post) => {
const pathParts = post.inputPath.split("/");
const filename = pathParts[pathParts.length - 1];
const baseSlug = filename.replace(".md", "");
if (!postsBySlug[baseSlug]) {
postsBySlug[baseSlug] = [];
}
postsBySlug[baseSlug].push(post);
});
return postsBySlug;
});
Avec cela en place, j'ai pu créer et inclure un indicateur de langue qui ajouterait des drapeaux indiquant les traductions à la grille des posts. Où chaque drapeau est cliquable et vous emmènerait directement au post traduit.
{% set item = post or talk %}
{% set pathParts = item.inputPath.split('/') %}
{% set filename = pathParts[pathParts.length - 1] %}
{% set baseSlug = filename.replace('.md', '') %}
{% set currentLang = item.data.lang or 'en' %}
{# Ne montrer l'indicateur de langue que pour les posts (pas les talks ou les liens) #}
{% if item.inputPath.includes('/posts/') %}
{% set availablePosts = collections.postsBySlug[baseSlug] %}
{% if availablePosts.length > 1 %}
<div class="mx-auto flex items-center flex-wrap">
<div class="justify-start items-center flex-grow w-full md:w-auto md:flex">
<p class="text-gray-700 mb-1 font-medium" title="Langues disponibles">
<i class="fas fa-globe text-teal-600 fill-current"></i>
{% for translatedPost in availablePosts %}
{% set postLang = translatedPost.data.lang or 'en' %}
<a href="{{ translatedPost.url }}" class="text-sm ml-1 hover:scale-110 transition-transform duration-200 inline-block" title="{{ postLang | getLanguageName }}">{{ postLang | getLanguageFlag }}</a>
{% endfor %}
</p>
</div>
</div>
{% endif %}
{% endif %}
Résultant en :
Enfin, j'ai ajouté un menu déroulant à la mise en page du post avec un avertissement que le post a été traduit par une IA.
Avec ces changements, vous pouvez sélectionner la langue dans laquelle vous souhaitez lire chaque post.
Conclusion
Ajouter une traduction automatisée à mon blog m'a semblé être la prochaine étape pour rendre mon contenu accessible à tous. En utilisant Amazon Nova Pro et mon architecture serverless existante, j'ai créé une solution de traduction évolutive sans ajouter de complexité opérationnelle. Que vous construisiez un blog technique, de la documentation ou toute plateforme de contenu, la traduction automatisée pourrait étendre votre portée.
Pourquoi n'ai-je pas traduit l'ensemble du site ? C'était une voie que j'ai d'abord explorée, mais j'ai estimé que le principal avantage serait d'avoir les posts traduits. Cela me donne également la liberté de décider si je souhaite traduire tous les anciens posts ou non, et je peux ajouter des traductions par la suite. Un jour, je pourrais tout traduire, mais pour le moment, l'accent était mis sur les posts individuellement.
Mots finaux
N'oubliez pas de me suivre sur LinkedIn et pour plus de contenu, lisez le reste de mes Blogs.
Comme dit Werner ! Allez construire maintenant !
Ce post a été initialement écrit en anglais et automatiquement traduit en plusieurs langues en utilisant le système même décrit ici. Le processus de traduction a pris moins de 5 minutes et n'a nécessité aucune intervention manuelle.