Alejandro Rioja.
AI Agents SEO

Como Traduzir um Post de Blog em 13 Idiomas com Um Agente

Alejandro Rioja
Alejandro Rioja
7 min de leitura
TL;DR

Um único agente TypeScript chama a API Claude em paralelo para traduzir um post em inglês para 12 idiomas em menos de 90 segundos. Preservar a voz exige um system prompt em duas partes: restrições de estilo primeiro, depois notas específicas por locale. O custo é de aproximadamente $0.004–$0.02 por post com Haiku. Meu site teve um aumento de 34% no tráfego internacional em 60 dias.

Newsletter gratuita

Toda quarta-feira. 28.400+ operadores. Zero enrolação.

Sumário

Atualizado em maio de 2026.

TL;DR: Um único agente TypeScript chama a API Claude em paralelo para traduzir um post em inglês para 12 idiomas em menos de 90 segundos. Preservar a voz exige um system prompt em duas partes: restrições de estilo primeiro, depois notas específicas por locale. O custo é de aproximadamente $0.004–$0.02 por post com Haiku. Meu site teve um aumento de 34% no tráfego internacional em 60 dias.

[Perspectiva do operador] Executo esse agente toda vez que publico um novo post. Ele processou 341 posts em 12 idiomas sem que eu tocasse em uma única tradução manualmente. Veja exatamente como funciona.

Por que construí um agente de tradução em vez de contratar tradutores

Vou pular o argumento para SEO multilíngue — você já sabe que importa. O problema que eu tinha era de fluxo de trabalho. Contratar tradutores por post é caro ($40–$120/post × 12 idiomas = $480–$1.440 por artigo), lento (prazo de 3 a 7 dias) e impossível de processar em lote quando você tem 341 posts existentes para recuperar.

A outra opção que as pessoas sugerem é Google Translate ou DeepL. Ambas são boas para precisão, mas destroem a voz. Meu estilo de escrita é direto, em primeira pessoa e levemente contrário. A tradução automática tende a tornar tudo formal e passivo. Isso é um problema quando a consistência de voz faz parte da sua marca.

Então construí um agente TypeScript com Claude. Ele roda em CI a cada merge para main, distribui as traduções em paralelo, escreve os arquivos de volta no disco e ignora qualquer idioma que já tenha um arquivo. O processo todo leva menos de 90 segundos para um novo post.

A estrutura do projeto

O agente vive em scripts/agent/translate-worker.ts. É chamado por um orquestrador de nível superior que lê o post em inglês, extrai o frontmatter e despacha um job de tradução por idioma.

code
scripts/
  agent/
    translate-worker.ts   # lógica de tradução por locale
    translate-all.ts      # orquestrador: lê EN, distribui para 12 idiomas
    lib/
      frontmatter.ts      # parsear/serializar frontmatter gray-matter
      voice-prompt.ts     # construtor de system prompt compartilhado

O orquestrador (translate-all.ts) usa Promise.allSettled para que um único locale com falha não bloqueie o resto.

O engineering do system prompt

É aqui que a maioria das pessoas erra. Elas escrevem algo como “traduza isso para o francês, mantendo a voz do autor.” Isso produz resultados mediocres.

Meu system prompt tem duas seções obrigatórias:

Seção 1 — Restrições de estilo (universal, adicionadas a cada chamada):

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

Seção 2 — Notas específicas por locale (adicionadas por chamada):

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

O translate worker

Aqui está o worker completo. Ele lê o arquivo em inglês, chama o Claude e escreve o resultado no disco.

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;

  // Idempotência: pular se a tradução já existir
  const filename = path.basename(enFilePath);
  const outPath = path.join(outputDir, locale, filename);
  if (fs.existsSync(outPath)) {
    console.log(`[${locale}] Já existe — pulando: ${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;
}

O orquestrador

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() {
  // Aceitar um arquivo específico ou traduzir todos os posts em 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(`Traduzindo ${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]}] FALHOU:`, r.reason);
      }
    });
  }

  console.log("Concluído.");
}

main();

Execute com:

sh
# Traduzir um novo post
npx ts-node scripts/agent/translate-all.ts src/content/posts/en/meu-novo-post.md

# Traduzir tudo (idempotente — pula os existentes)
npx ts-node scripts/agent/translate-all.ts

Comparativo de custos: Haiku vs Sonnet

Veja o que custa de verdade por post, com base no meu uso:

ModeloTokens de entrada (média)Tokens de saída (média)Custo por idiomaCusto × 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 com Haiku: aproximadamente $1.70 no total. Esse é o backlog inteiro.

Sonnet produz fraseologia idiomática marginalmente melhor, mas para a maioria dos posts a diferença não vale 36 vezes o preço. Uso Sonnet apenas para posts onde o tom persuasivo matizado importa — como páginas de vendas ou conteúdo âncora de alto tráfego.

Você pode trocar de modelo por execução com a variável de ambiente TRANSLATE_MODEL:

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

Resultados reais: o que aconteceu com meu tráfego

Publiquei a tradução completa do backlog (341 posts) em dezembro de 2025. Em 60 dias:

Os resultados de japonês e coreano me surpreenderam. Ambos os idiomas têm comunidades de IA de alta qualidade e aparentemente boa demanda por conteúdo prático de operadores.

A conclusão do operador

Um agente, uma hora de configuração, $1.70 em custos de API. Foi isso que bastou para tornar 341 posts descobríveis em 12 idiomas adicionais. O aumento de SEO por si só pagou pelo custo computacional na primeira semana. Se você gerencia um site com muito conteúdo e ainda não construiu isso, está deixando tráfego internacional na mesa. O código acima é a implementação completa — faça um fork, troque as notas do seu voice-prompt e execute contra o seu backlog hoje à noite.

Continue lendo

Receba o manual de IA na sua caixa de entrada

Toda quarta-feira. 28.400+ operadores. Zero enrolação.

↵ ver todos os resultados esc esc para fechar