Hoe Je Een Blogpost Vertaalt naar 13 Talen met Één Agent
Éé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%.
Elke woensdag. 28.400+ operators. Geen opvulling.
✓ Check your inbox — click the confirmation link to complete sign-up.
✓ You're subscribed!
✓ You're already on the list.
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.
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 builderDe 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):
// 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):
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.
// 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
// 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:
# Éé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.tsKostenvergelijking: Haiku vs Sonnet
Dit is wat het werkelijk kost per post, gebaseerd op mijn gebruik:
| Model | Invoertokens (gem.) | Uitvoertokens (gem.) | Kosten per taal | Kosten × 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:
TRANSLATE_MODEL=claude-sonnet-4-5 npx ts-node scripts/agent/translate-all.ts src/content/posts/en/flagship-post.mdEchte resultaten: wat er met mijn traffic gebeurde
Ik publiceerde de volledige backlog-vertaling (341 posts) in december 2025. Binnen 60 dagen:
- +34% organische sessies sitebreed (Google Search Console, jan–feb 2026 vs okt–nov 2025)
- Beste nieuwe taal per sessies: Braziliaans Portugees (pt) — 11% van het nieuwe internationale verkeer
- Beste nieuwe taal per conversieratio: Duits (de) — 2,1% consultatieboekingsratio vs 1,8% globaal gemiddelde
- Slechtste presteerder: Arabisch (ar) — er kwam traffic, maar nul conversies. Ik vermoed dat de boekingsflow niet verder is gelokaliseerd dan de post-content.
- Japans (ja) en Koreaans (ko): significante traffic-stijging (respectievelijk 8% en 6% van de internationale sessies) met bovengemiddelde betrokkenheid (tijd op pagina +40% vs EN-baseline)
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.
Elke woensdag. 28.400+ operators. Geen opvulling.
✓ Check your inbox — click the confirmation link to complete sign-up.
✓ You're subscribed!
✓ You're already on the list.
Ontvang het AI-playbook in je inbox
Elke woensdag. 28.400+ operators. Geen opvulling.
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.