Cómo Traducir un Post a 13 Idiomas con un Solo Agente
Un solo agente en TypeScript llama a la API de Claude en paralelo para traducir un post en inglés a 12 idiomas en menos de 90 segundos. Preservar la voz requiere un system prompt en dos partes: restricciones de estilo primero, luego notas por idioma. El costo es aproximadamente $0.004–$0.02 por post con Haiku. Mi sitio tuvo un aumento del 34% en tráfico internacional en 60 días.
Cada miércoles. 28.400+ operadores. Sin relleno.
✓ Check your inbox — click the confirmation link to complete sign-up.
✓ You're subscribed!
✓ You're already on the list.
Tabla de contenidos
Actualizado mayo 2026.
TL;DR: Un solo agente en TypeScript llama a la API de Claude en paralelo para traducir un post en inglés a 12 idiomas en menos de 90 segundos. Preservar la voz requiere un system prompt en dos partes: restricciones de estilo primero, luego notas por idioma. El costo es aproximadamente $0.004–$0.02 por post con Haiku. Mi sitio tuvo un aumento del 34% en tráfico internacional en 60 días.
[Perspectiva del operador] Ejecuto este agente cada vez que publico un post nuevo. Ha procesado 341 posts en 12 idiomas sin que yo haya tocado una sola traducción manualmente. Así es exactamente como funciona.
Por qué construí un agente de traducción en lugar de contratar traductores
Me salto el argumento de venta sobre SEO multilingüe — ya sabes que importa. El problema que tenía era de flujo de trabajo. Contratar traductores por post es caro ($40–$120/post × 12 idiomas = $480–$1,440 por artículo), lento (3–7 días de entrega) e imposible de procesar en lote cuando tienes 341 posts existentes por traducir.
La otra opción que sugiere la gente es Google Translate o DeepL. Ambas son precisas pero destruyen la voz. Mi estilo de escritura es directo, en primera persona y ligeramente contrariano. La traducción automática tiende a hacer todo sonar formal y pasivo. Eso es un problema cuando la consistencia de voz es parte de tu marca.
Así que construí un agente en TypeScript respaldado por Claude. Se ejecuta en CI en cada merge a main, distribuye las traducciones en paralelo, escribe los archivos de vuelta al disco y salta cualquier idioma que ya tenga un archivo. Todo tarda menos de 90 segundos para un post nuevo.
La estructura del proyecto
El agente vive en scripts/agent/translate-worker.ts. Lo llama un orquestador de nivel superior que lee el post en inglés, extrae el frontmatter y despacha un trabajo de traducción por idioma.
scripts/
agent/
translate-worker.ts # lógica de traducción por idioma
translate-all.ts # orquestador: lee EN, distribuye a 12 idiomas
lib/
frontmatter.ts # parsear/serializar frontmatter con gray-matter
voice-prompt.ts # constructor de system prompt compartidoEl orquestador (translate-all.ts) usa Promise.allSettled para que un solo idioma fallido no bloquee el resto.
La ingeniería del system prompt
Aquí es donde la mayoría de la gente se equivoca. Escriben algo como “traduce esto al francés, mantén la voz del autor.” Eso produce resultados mediocres.
Mi system prompt tiene dos secciones obligatorias:
Sección 1 — Restricciones de estilo (universal, añadidas a cada llamada):
// 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();Sección 2 — Notas específicas por idioma (añadidas por llamada):
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]}`;
}El translate worker
Aquí está el worker completo. Lee el archivo en inglés, llama a Claude y escribe el resultado en disco.
// 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;
// Idempotencia: saltar si la traducción ya existe
const filename = path.basename(enFilePath);
const outPath = path.join(outputDir, locale, filename);
if (fs.existsSync(outPath)) {
console.log(`[${locale}] Ya existe — saltando: ${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}] Escrito: ${outPath}`);
}
return outPath;
}El orquestador
// 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() {
// Acepta un archivo específico o traduce todos los posts en inglés
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(`Traduciendo ${enFiles.length} post(s) × ${LOCALES.length} idiomas. Modelo: ${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]}] FALLIDO:`, r.reason);
}
});
}
console.log("Listo.");
}
main();Ejecutar con:
# Traducir un post nuevo
npx ts-node scripts/agent/translate-all.ts src/content/posts/en/mi-nuevo-post.md
# Traducir todo (idempotente — salta los existentes)
npx ts-node scripts/agent/translate-all.tsDesglose de costos: Haiku vs Sonnet
Esto es lo que cuesta realmente por post, basado en mi uso:
| Modelo | Tokens de entrada (promedio) | Tokens de salida (promedio) | Costo por idioma | Costo × 12 idiomas |
|---|---|---|---|---|
| claude-haiku-4-5 | ~2,400 | ~2,600 | ~$0.0004 | ~$0.005 |
| claude-sonnet-4-5 | ~2,400 | ~2,600 | ~$0.015 | ~$0.18 |
Para 341 posts × 12 idiomas con Haiku: aproximadamente $1.70 en total. Eso es todo el backlog.
Sonnet produce una fraseología idiomática marginalmente mejor, pero para la mayoría de los posts la diferencia no vale 36 veces el precio. Uso Sonnet solo para posts donde el tono persuasivo matizado importa — como páginas de ventas o contenido principal de alto tráfico.
Puedes cambiar los modelos por ejecución con la variable de entorno TRANSLATE_MODEL:
TRANSLATE_MODEL=claude-sonnet-4-5 npx ts-node scripts/agent/translate-all.ts src/content/posts/en/post-insignia.mdResultados reales: qué pasó con mi tráfico
Publiqué la traducción completa del backlog (341 posts) en diciembre de 2025. En 60 días:
- +34% sesiones orgánicas en todo el sitio (Google Search Console, enero–febrero 2026 vs octubre–noviembre 2025)
- Principal nuevo idioma por sesiones: Portugués brasileño (pt) — 11% del nuevo tráfico internacional
- Principal nuevo idioma por tasa de conversión: Alemán (de) — 2.1% de tasa de reserva de consulta vs 1.8% de promedio global
- Peor rendimiento: Árabe (ar) — llegó tráfico pero cero conversiones. Sospecho que el flujo de reservas no está localizado más allá del contenido del post.
- Japonés (ja) y coreano (ko): aumento significativo de tráfico (8% y 6% de las sesiones internacionales respectivamente) con interacción por encima del promedio (tiempo en página +40% vs línea base en inglés)
Los resultados de japonés y coreano me sorprendieron. Ambos idiomas tienen comunidades de IA de alta calidad y aparentemente buena demanda de contenido práctico de operadores.
La conclusión del operador
Un agente, una hora de configuración, $1.70 en costos de API. Eso es lo que se necesitó para hacer que 341 posts fueran descubribles en 12 idiomas adicionales. El aumento de SEO solo pagó el cómputo en la primera semana. Si tienes un sitio con mucho contenido y todavía no has construido esto, estás dejando tráfico internacional sobre la mesa. El código de arriba es la implementación completa — fórcalo, cambia las notas de tu voice-prompt y ejecútalo contra tu backlog esta noche.
Cada miércoles. 28.400+ operadores. Sin relleno.
✓ Check your inbox — click the confirmation link to complete sign-up.
✓ You're subscribed!
✓ You're already on the list.
Recibe el manual de IA en tu buzón
Cada miércoles. 28.400+ operadores. Sin relleno.
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.