Alejandro Rioja.
AI Agents SEO

Come Tradurre un Post del Blog in 13 Lingue con Un Solo Agente

Alejandro Rioja
Alejandro Rioja
7 min di lettura
TL;DR

Un singolo agente TypeScript chiama l'API Claude in parallelo per tradurre un post EN in 12 lingue in meno di 90 secondi. Preservare la voce richiede un system prompt in due parti: prima i vincoli di stile, poi le note specifiche per locale. Il costo è di circa $0.004–$0.02 per post con Haiku. Il mio sito ha registrato un aumento del traffico internazionale del 34% in 60 giorni.

Newsletter gratuita

Ogni mercoledì. 28.400+ operatori. Zero riempitivo.

Indice

Aggiornato maggio 2026.

TL;DR: Un singolo agente TypeScript chiama l’API Claude in parallelo per tradurre un post EN in 12 lingue in meno di 90 secondi. Preservare la voce richiede un system prompt in due parti: prima i vincoli di stile, poi le note specifiche per locale. Il costo è di circa $0.004–$0.02 per post con Haiku. Il mio sito ha registrato un aumento del traffico internazionale del 34% in 60 giorni.

[Prospettiva dell’operatore] Eseguo questo agente ogni volta che pubblico un nuovo post. Ha elaborato 341 post in 12 lingue senza che io abbia toccato manualmente una singola traduzione. Ecco esattamente come funziona.

Perché ho costruito un agente di traduzione invece di assumere traduttori

Salto l’argomento a favore della SEO multilingue — sai già che conta. Il problema che avevo era il flusso di lavoro. Assumere traduttori per post è costoso ($40–$120/post × 12 lingue = $480–$1.440 per articolo), lento (tempi di consegna di 3–7 giorni) e impossibile da gestire in batch quando hai 341 post esistenti da recuperare.

L’altra opzione che le persone suggeriscono è Google Translate o DeepL. Entrambi sono accurati ma distruggono la voce. Il mio stile di scrittura è diretto, in prima persona e leggermente controcorrente. La traduzione automatica tende a rendere tutto formale e passivo. Questo è un problema quando la coerenza della voce fa parte del tuo brand.

Quindi ho costruito un agente TypeScript basato su Claude. Gira in CI ad ogni merge su main, distribuisce le traduzioni in parallelo, riscrive i file su disco e salta qualsiasi lingua che abbia già un file. Il tutto richiede meno di 90 secondi per un nuovo post.

La struttura del progetto

L’agente vive in scripts/agent/translate-worker.ts. È chiamato da un orchestratore di alto livello che legge il post EN, estrae il frontmatter e invia un job di traduzione per ogni lingua.

code
scripts/
  agent/
    translate-worker.ts   # logica di traduzione per locale
    translate-all.ts      # orchestratore: legge EN, distribuisce a 12 lingue
    lib/
      frontmatter.ts      # parse/serialize frontmatter gray-matter
      voice-prompt.ts     # costruttore di system prompt condiviso

L’orchestratore (translate-all.ts) usa Promise.allSettled in modo che un singolo locale fallito non blocchi il resto.

L’engineering del system prompt

Qui è dove la maggior parte delle persone sbaglia. Scrivono qualcosa come “traduci questo in francese, mantieni la voce dell’autore.” Questo produce risultati mediocri.

Il mio system prompt ha due sezioni obbligatorie:

Sezione 1 — Vincoli di stile (universali, aggiunti a ogni chiamata):

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

Sezione 2 — Note specifiche per locale (aggiunte per ogni chiamata):

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

Il translate worker

Ecco il worker completo. Legge il file EN, chiama Claude e scrive l’output su 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;

  // Idempotenza: salta se la traduzione esiste già
  const filename = path.basename(enFilePath);
  const outPath = path.join(outputDir, locale, filename);
  if (fs.existsSync(outPath)) {
    console.log(`[${locale}] Già esistente — salto: ${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}] Scritto: ${outPath}`);
  }

  return outPath;
}

L’orchestratore

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() {
  // Accetta un file specifico o traduce tutti i post 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(`Traduzione di ${enFiles.length} post × ${LOCALES.length} lingue. Modello: ${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]}] FALLITO:`, r.reason);
      }
    });
  }

  console.log("Fatto.");
}

main();

Esegui con:

sh
# Tradurre un nuovo post
npx ts-node scripts/agent/translate-all.ts src/content/posts/en/mio-nuovo-post.md

# Tradurre tutto (idempotente — salta gli esistenti)
npx ts-node scripts/agent/translate-all.ts

Confronto dei costi: Haiku vs Sonnet

Ecco cosa costa davvero per post, basandomi sul mio utilizzo:

ModelloToken in ingresso (media)Token in uscita (media)Costo per linguaCosto × 12 lingue
claude-haiku-4-5~2.400~2.600~$0.0004~$0.005
claude-sonnet-4-5~2.400~2.600~$0.015~$0.18

Per 341 post × 12 lingue con Haiku: circa $1.70 in totale. Questo è l’intero backlog.

Sonnet produce una fraseologia idiomatica marginalmente migliore, ma per la maggior parte dei post la differenza non vale 36 volte il prezzo. Uso Sonnet solo per i post dove il tono persuasivo sfumato conta — come le pagine di vendita o i contenuti pillar ad alto traffico.

Puoi cambiare modello per ogni esecuzione con la variabile di ambiente TRANSLATE_MODEL:

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

Risultati reali: cosa è successo al mio traffico

Ho pubblicato la traduzione completa del backlog (341 post) a dicembre 2025. In 60 giorni:

I risultati giapponesi e coreani mi hanno sorpreso. Entrambe le lingue hanno comunità AI di alta qualità e apparentemente buona domanda per contenuti pratici da operatore.

La conclusione dell’operatore

Un agente, un’ora di configurazione, $1.70 in costi API. È tutto quello che ci è voluto per rendere 341 post scopribili in 12 lingue aggiuntive. Il solo aumento SEO ha ripagato i costi di calcolo nella prima settimana. Se gestisci un sito ricco di contenuti e non hai ancora costruito questo, stai lasciando traffico internazionale sul tavolo. Il codice sopra è l’implementazione completa — forkalo, sostituisci le note del tuo voice-prompt e lancialo sul tuo backlog stasera.

Continua a leggere

Ricevi il manuale dell'IA nella tua casella di posta

Ogni mercoledì. 28.400+ operatori. Zero riempitivo.

↵ per tutti i risultati esc esc per chiudere