Alejandro Rioja.
AI Agents SEO

Comment Traduire un Article de Blog en 13 Langues avec un Agent

Alejandro Rioja
Alejandro Rioja
7 min de lecture
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.

Newsletter gratuite

Chaque mercredi. 28 400+ opérateurs. Zéro superflu.

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.

code
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) :

typescript
// 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) :

typescript
  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.

typescript
// 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

typescript
// 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 :

sh
# 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.ts

Comparatif de coûts : Haiku vs Sonnet

Voici ce que ça coûte vraiment par article, d’après mon utilisation :

ModèleTokens d’entrée (moy.)Tokens de sortie (moy.)Coût par langueCoû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 :

sh
TRANSLATE_MODEL=claude-sonnet-4-5 npx ts-node scripts/agent/translate-all.ts src/content/posts/en/article-phare.md

Ré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 :

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.

Continuer à lire

Recevez le guide IA dans votre boîte mail

Chaque mercredi. 28 400+ opérateurs. Zéro superflu.

↵ pour voir tous les résultats esc esc pour fermer