Alejandro Rioja.
AI Agents SEO

Hoe Je Een Blogpost Vertaalt naar 13 Talen met Één Agent

Alejandro Rioja
Alejandro Rioja
7 min lezen
TL;DR

Één TypeScript-agent roept de Claude API parallel aan om een EN-post in minder dan 90 seconden naar 12 talen te vertalen. Stem bewaren vereist een system prompt in twee delen: eerst stijlregels, dan taalspecifieke notities. Kosten zijn circa $0.004–$0.02 per post met Haiku. Mijn site zag binnen 60 dagen een internationale traffic-stijging van 34%.

Gratis nieuwsbrief

Elke woensdag. 28.400+ operators. Geen opvulling.

Inhoudsopgave

Bijgewerkt mei 2026.

TL;DR: Één TypeScript-agent roept de Claude API parallel aan om een EN-post in minder dan 90 seconden naar 12 talen te vertalen. Stem bewaren vereist een system prompt in twee delen: eerst stijlregels, dan taalspecifieke notities. Kosten zijn circa $0.004–$0.02 per post met Haiku. Mijn site zag binnen 60 dagen een internationale traffic-stijging van 34%.

[Operator-perspectief] Ik draai deze agent elke keer als ik een nieuwe post publiceer. Hij heeft 341 posts in 12 talen verwerkt zonder dat ik één vertaling handmatig heb aangeraakt. Dit is precies hoe het werkt.

Waarom ik een vertalingsagent bouwde in plaats van vertalers in te huren

Ik sla het argument voor meertalige SEO over — je weet al dat het belangrijk is. Het probleem was de workflow. Vertalers per post inhuren is duur ($40–$120/post × 12 talen = $480–$1.440 per artikel), traag (3–7 dagen doorlooptijd) en onmogelijk in batches te verwerken als je 341 bestaande posts hebt die ingehaald moeten worden.

De andere optie die mensen aanraden is Google Translate of DeepL. Beide zijn nauwkeurig, maar ze vernietigen de stem. Mijn schrijfstijl is direct, in de eerste persoon en licht contrair. Machinevertaling maakt alles formeel en passief. Dat is een probleem als stemconsistentie deel uitmaakt van je merk.

Dus bouwde ik een Claude-ondersteunde TypeScript-agent. Hij draait in CI bij elke merge naar main, verspreidt vertalingen parallel, schrijft bestanden terug naar schijf en slaat elke taal over die al een bestand heeft. Het geheel duurt minder dan 90 seconden voor een nieuwe post.

De projectstructuur

De agent leeft in scripts/agent/translate-worker.ts. Hij wordt aangeroepen door een overkoepelende orchestrator die de EN-post leest, frontmatter extraheert en één vertaaltaak per taal verstuurt.

code
scripts/
  agent/
    translate-worker.ts   # vertalingslogica per locale
    translate-all.ts      # orchestrator: leest EN, verspreidt naar 12 talen
    lib/
      frontmatter.ts      # gray-matter frontmatter parsen/serialiseren
      voice-prompt.ts     # gedeelde system prompt builder

De orchestrator (translate-all.ts) gebruikt Promise.allSettled zodat één mislukte taal de rest niet blokkeert.

De system prompt engineering

Hier gaan de meeste mensen de mist in. Ze schrijven een one-liner als “vertaal dit naar het Frans, bewaar de stem van de auteur.” Dat levert middelmatig resultaat op.

Mijn system prompt heeft twee verplichte secties:

Sectie 1 — Stijlregels (universeel, toegevoegd aan elke aanroep):

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();

Sectie 2 — Taalspecifieke notities (per aanroep toegevoegd):

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

De translate worker

Hier is de volledige worker. Hij leest het EN-bestand, roept Claude aan en schrijft de uitvoer naar schijf.

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;

  // Idempotentie: overslaan als vertaling al bestaat
  const filename = path.basename(enFilePath);
  const outPath = path.join(outputDir, locale, filename);
  if (fs.existsSync(outPath)) {
    console.log(`[${locale}] Bestaat al — overgeslagen: ${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}] Geschreven: ${outPath}`);
  }

  return outPath;
}

De orchestrator

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() {
  // Specifiek bestand accepteren of alle EN-posts vertalen
  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(`${enFiles.length} post(s) × ${LOCALES.length} talen vertalen. Model: ${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]}] MISLUKT:`, r.reason);
      }
    });
  }

  console.log("Klaar.");
}

main();

Uitvoeren met:

sh
# Één nieuwe post vertalen
npx ts-node scripts/agent/translate-all.ts src/content/posts/en/mijn-nieuwe-post.md

# Alles vertalen (idempotent — slaat bestaande over)
npx ts-node scripts/agent/translate-all.ts

Kostenvergelijking: Haiku vs Sonnet

Dit is wat het werkelijk kost per post, gebaseerd op mijn gebruik:

ModelInvoertokens (gem.)Uitvoertokens (gem.)Kosten per taalKosten × 12 talen
claude-haiku-4-5~2.400~2.600~$0.0004~$0.005
claude-sonnet-4-5~2.400~2.600~$0.015~$0.18

Voor 341 posts × 12 talen met Haiku: ongeveer $1.70 totaal. Dat is de hele achterstand.

Sonnet levert marginaal betere idiomatische formulering, maar voor de meeste posts is het verschil de 36-voudige prijs niet waard. Ik gebruik Sonnet alleen voor posts waar genuanceerde overtuigende toon belangrijk is — zoals verkooppagina’s of cornerstone-content met hoog verkeer.

Je kunt het model per uitvoering wisselen met de TRANSLATE_MODEL omgevingsvariabele:

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

Echte resultaten: wat er met mijn traffic gebeurde

Ik publiceerde de volledige backlog-vertaling (341 posts) in december 2025. Binnen 60 dagen:

De Japanse en Koreaanse resultaten verrasten me. Beide talen hebben hoogwaardige AI-communities en blijkbaar goede vraag naar praktische operator-content.

De conclusie van de operator

Één agent, één uur setup, $1.70 aan API-kosten. Dat was alles wat nodig was om 341 posts vindbaar te maken in 12 extra talen. De SEO-stijging alleen al betaalde de rekenkosten in de eerste week terug. Als je een contentrijke site beheert en dit nog niet hebt gebouwd, laat je internationaal verkeer liggen. De code hierboven is de volledige implementatie — fork hem, vervang je voice-prompt-notities en draai hem vanavond tegen je backlog.

Lees verder

Ontvang het AI-playbook in je inbox

Elke woensdag. 28.400+ operators. Geen opvulling.

↵ alle resultaten bekijken esc esc om te sluiten