Comment Traduire un Article de Blog en 13 Langues avec un Agent
Un seul agent TypeScript appelle l'API Claude en parallèle pour traduire un article EN en 12 langues en moins de 90 secondes. Préserver la voix nécessite un system prompt en deux parties : contraintes de style d'abord, puis notes spécifiques à chaque locale. Le coût est d'environ $0.004–$0.02 par article avec Haiku. Mon site a enregistré une hausse de trafic international de 34% en 60 jours.
Chaque mercredi. 28 400+ opérateurs. Zéro superflu.
✓ Check your inbox — click the confirmation link to complete sign-up.
✓ You're subscribed!
✓ You're already on the list.
Table des matières
Mis à jour mai 2026.
TL;DR: Un seul agent TypeScript appelle l’API Claude en parallèle pour traduire un article EN en 12 langues en moins de 90 secondes. Préserver la voix nécessite un system prompt en deux parties : contraintes de style d’abord, puis notes spécifiques à chaque locale. Le coût est d’environ $0.004–$0.02 par article avec Haiku. Mon site a enregistré une hausse de trafic international de 34% en 60 jours.
[Perspective d’opérateur] J’exécute cet agent à chaque fois que je publie un nouvel article. Il a traité 341 articles en 12 langues sans que je touche une seule traduction manuellement. Voici exactement comment ça fonctionne.
Pourquoi j’ai créé un agent de traduction plutôt que de faire appel à des traducteurs
Je passe l’argument en faveur du SEO multilingue — tu sais déjà que ça compte. Le problème que j’avais était le flux de travail. Engager des traducteurs par article coûte cher ($40–$120/article × 12 langues = $480–$1.440 par article), est lent (délai de 3 à 7 jours) et impossible à traiter en lot quand tu as 341 articles existants à rattraper.
L’autre option que les gens suggèrent c’est Google Translate ou DeepL. Les deux sont précis, mais ils détruisent la voix. Mon style d’écriture est direct, à la première personne et légèrement à contre-courant. La traduction automatique tend à tout rendre formel et passif. C’est un problème quand la cohérence de voix fait partie de ta marque.
J’ai donc construit un agent TypeScript propulsé par Claude. Il tourne en CI à chaque merge sur main, distribue les traductions en parallèle, réécrit les fichiers sur le disque et ignore les langues qui ont déjà un fichier. Le tout prend moins de 90 secondes pour un nouvel article.
La structure du projet
L’agent vit dans scripts/agent/translate-worker.ts. Il est appelé par un orchestrateur de haut niveau qui lit l’article EN, extrait le frontmatter et dispatche un job de traduction par langue.
scripts/
agent/
translate-worker.ts # logique de traduction par locale
translate-all.ts # orchestrateur : lit EN, distribue à 12 langues
lib/
frontmatter.ts # parser/sérialiser le frontmatter gray-matter
voice-prompt.ts # constructeur de system prompt partagéL’orchestrateur (translate-all.ts) utilise Promise.allSettled pour qu’une seule locale échouée ne bloque pas le reste.
L’engineering du system prompt
C’est là que la plupart des gens se trompent. Ils écrivent un one-liner comme « traduis ça en français, garde la voix de l’auteur. » Ça produit des résultats médiocres.
Mon system prompt a deux sections obligatoires :
Section 1 — Contraintes de style (universelles, ajoutées à chaque appel) :
// scripts/agent/lib/voice-prompt.ts
export function buildSystemPrompt(targetLocale: string): string {
const styleConstraints = `
You are a professional translator working on blog posts written by Alejandro Rioja.
STYLE RULES — apply to every locale:
- Short paragraphs (1–3 sentences max). Do not merge them.
- First-person, direct voice. Never passive if active is natural.
- No filler phrases: no "In today's world", no "It is worth noting that".
- Preserve all markdown: headings, bold, italics, code blocks, links.
- Translate heading text but keep the ## / ### prefix exactly.
- Code blocks: translate comments only. Keep all variable names, strings, and syntax in English.
- Preserve frontmatter keys exactly. Only translate the VALUES for: title, ogTitle, description, tldr, imageAlt.
- Keep these frontmatter values UNCHANGED: pubDate, updatedDate, translation_key, tags, image, author, draft, lang (set lang to: ${targetLocale}).
`.trim();Section 2 — Notes spécifiques à la locale (ajoutées par appel) :
const localeNotes: Record<string, string> = {
ar: "Arabic: use Modern Standard Arabic (MSA). RTL layout is handled by the CMS — do not add any RTL markup. Avoid overly formal Classical Arabic registers.",
de: "German: use informal 'du' not formal 'Sie'. Compound nouns are fine; don't over-hyphenate. Keep tech terms in English when that's the industry standard (e.g. 'Content Marketing', 'SEO').",
es: "Spanish: use neutral Latin American Spanish, not Castilian. Tuteo ('tú') over 'usted'. Keep anglicisms that are standard in tech (SEO, agente, prompt).",
fr: "French: use informal 'tu'. Avoid over-formalizing. Tech anglicisms are acceptable when widely used (SEO, agent, prompt).",
hi: "Hindi: use Devanagari script. Mix Hindi and English naturally for tech terms — this is standard in Indian tech writing. Don't force Hindi equivalents for words like 'agent', 'prompt', 'SEO'.",
it: "Italian: use 'tu' form. Keep English tech terms where they're standard in Italian digital marketing.",
ja: "Japanese: use です/ます (polite) style, not casual or keigo. Keep technical English terms in katakana where standard (e.g. エージェント, プロンプト, SEO).",
ko: "Korean: use 합쇼체 (formal polite). Tech terms in English or standard Korean loanwords. Keep SEO, agent, prompt as-is or standard loanwords.",
nl: "Dutch: use 'je/jij' (informal). Keep English tech terms standard in Dutch digital marketing.",
pt: "Portuguese: use Brazilian Portuguese (pt-BR). Informal 'você'. Keep tech anglicisms standard in Brazilian digital marketing.",
ru: "Russian: use modern, accessible Russian. Avoid overly bureaucratic phrasing. Tech terms can stay in English where that's the norm in Russian tech writing.",
zh: "Chinese: use Simplified Chinese (zh-CN). Modern, accessible tone. Tech terms can use standard Chinese equivalents or keep English where that's industry norm.",
};
return `${styleConstraints}\n\nLOCALE-SPECIFIC NOTES for ${targetLocale}:\n${localeNotes[targetLocale]}`;
}Le translate worker
Voici le worker complet. Il lit le fichier EN, appelle Claude et écrit le résultat sur le disque.
// scripts/agent/translate-worker.ts
import Anthropic from "@anthropic-ai/sdk";
import * as fs from "fs";
import * as path from "path";
import { buildSystemPrompt } from "./lib/voice-prompt";
const client = new Anthropic();
export interface TranslateJob {
enFilePath: string;
locale: string;
outputDir: string;
model?: "claude-haiku-4-5" | "claude-sonnet-4-5";
dryRun?: boolean;
}
export async function translatePost(job: TranslateJob): Promise<string> {
const { enFilePath, locale, outputDir, model = "claude-haiku-4-5", dryRun = false } = job;
// Idempotence : ignorer si la traduction existe déjà
const filename = path.basename(enFilePath);
const outPath = path.join(outputDir, locale, filename);
if (fs.existsSync(outPath)) {
console.log(`[${locale}] Déjà existant — ignoré : ${outPath}`);
return outPath;
}
const enContent = fs.readFileSync(enFilePath, "utf-8");
const systemPrompt = buildSystemPrompt(locale);
const message = await client.messages.create({
model,
max_tokens: 8192,
system: systemPrompt,
messages: [
{
role: "user",
content: `Translate the following blog post to ${locale}. Return ONLY the translated markdown file content — no explanation, no preamble, no code fences around the whole file.\n\n${enContent}`,
},
],
});
const translated = (message.content[0] as { type: string; text: string }).text;
if (!dryRun) {
fs.mkdirSync(path.join(outputDir, locale), { recursive: true });
fs.writeFileSync(outPath, translated, "utf-8");
console.log(`[${locale}] Écrit : ${outPath}`);
}
return outPath;
}L’orchestrateur
// scripts/agent/translate-all.ts
import * as path from "path";
import * as fs from "fs";
import { translatePost } from "./translate-worker";
const LOCALES = ["ar", "de", "es", "fr", "hi", "it", "ja", "ko", "nl", "pt", "ru", "zh"];
const POSTS_DIR = path.resolve("src/content/posts");
const MODEL = (process.env.TRANSLATE_MODEL as "claude-haiku-4-5" | "claude-sonnet-4-5") ?? "claude-haiku-4-5";
async function main() {
// Accepter un fichier spécifique ou traduire tous les articles EN
const targetFile = process.argv[2];
const enFiles = targetFile
? [path.resolve(targetFile)]
: fs.readdirSync(path.join(POSTS_DIR, "en")).map((f) => path.join(POSTS_DIR, "en", f));
console.log(`Traduction de ${enFiles.length} article(s) × ${LOCALES.length} langues. Modèle : ${MODEL}`);
for (const enFile of enFiles) {
const results = await Promise.allSettled(
LOCALES.map((locale) =>
translatePost({
enFilePath: enFile,
locale,
outputDir: POSTS_DIR,
model: MODEL,
})
)
);
results.forEach((r, i) => {
if (r.status === "rejected") {
console.error(`[${LOCALES[i]}] ÉCHOUÉ :`, r.reason);
}
});
}
console.log("Terminé.");
}
main();À exécuter avec :
# Traduire un nouvel article
npx ts-node scripts/agent/translate-all.ts src/content/posts/en/mon-nouvel-article.md
# Tout traduire (idempotent — ignore les existants)
npx ts-node scripts/agent/translate-all.tsComparatif de coûts : Haiku vs Sonnet
Voici ce que ça coûte vraiment par article, d’après mon utilisation :
| Modèle | Tokens d’entrée (moy.) | Tokens de sortie (moy.) | Coût par langue | Coût × 12 langues |
|---|---|---|---|---|
| claude-haiku-4-5 | ~2 400 | ~2 600 | ~$0.0004 | ~$0.005 |
| claude-sonnet-4-5 | ~2 400 | ~2 600 | ~$0.015 | ~$0.18 |
Pour 341 articles × 12 langues avec Haiku : environ $1.70 au total. C’est tout le backlog.
Sonnet produit un phrasé idiomatique marginalement meilleur, mais pour la plupart des articles la différence ne vaut pas 36 fois le prix. J’utilise Sonnet uniquement pour les articles où le ton persuasif nuancé compte — comme les pages de vente ou le contenu pilier à fort trafic.
Tu peux changer de modèle par exécution avec la variable d’environnement TRANSLATE_MODEL :
TRANSLATE_MODEL=claude-sonnet-4-5 npx ts-node scripts/agent/translate-all.ts src/content/posts/en/article-phare.mdRésultats réels : ce qui est arrivé à mon trafic
J’ai publié la traduction complète du backlog (341 articles) en décembre 2025. En 60 jours :
- +34% de sessions organiques sur l’ensemble du site (Google Search Console, jan–fév 2026 vs oct–nov 2025)
- Principale nouvelle langue par sessions : Portugais brésilien (pt) — 11% du nouveau trafic international
- Principale nouvelle langue par taux de conversion : Allemand (de) — 2,1% de taux de réservation de consultation vs 1,8% de moyenne mondiale
- Pire performance : Arabe (ar) — le trafic est arrivé mais zéro conversion. Je pense que le flux de réservation n’est pas localisé au-delà du contenu de l’article.
- Japonais (ja) et coréen (ko) : hausse de trafic significative (8% et 6% des sessions internationales respectivement) avec un engagement supérieur à la moyenne (temps sur la page +40% vs baseline EN)
Les résultats japonais et coréens m’ont surpris. Les deux langues ont des communautés IA de haute qualité et apparemment un bon appétit pour du contenu pratique d’opérateur.
La conclusion de l’opérateur
Un agent, une heure de configuration, $1.70 en coûts d’API. C’est ce qu’il a fallu pour rendre 341 articles découvrables dans 12 langues supplémentaires. La hausse SEO à elle seule a rentabilisé les coûts de calcul dès la première semaine. Si tu gères un site riche en contenu et que tu n’as pas encore construit ça, tu laisses du trafic international sur la table. Le code ci-dessus est l’implémentation complète — fork-le, remplace les notes de ton voice-prompt et lance-le sur ton backlog ce soir.
Chaque mercredi. 28 400+ opérateurs. Zéro superflu.
✓ Check your inbox — click the confirmation link to complete sign-up.
✓ You're subscribed!
✓ You're already on the list.
Recevez le guide IA dans votre boîte mail
Chaque mercredi. 28 400+ opérateurs. Zéro superflu.
Check your inbox.
We sent you a confirmation email — click the link inside to complete your subscription. Check spam if you don't see it within a minute.
You're subscribed.
Welcome — the next edition lands in your inbox soon.
You're already on the list — look for it every Wednesday.