Alejandro Rioja.
AI Agents SEO

Как Перевести Один Пост Блога на 13 Языков с Помощью Одного Агента

Alejandro Rioja
Alejandro Rioja
7 мин чтения
TL;DR

Один TypeScript-агент параллельно вызывает Claude API и переводит англоязычный пост на 12 языков менее чем за 90 секунд. Сохранение авторского голоса требует двухчастного системного промпта: сначала ограничения стиля, затем заметки для конкретной локали. Стоимость — примерно $0.004–$0.02 за пост на Haiku. Мой сайт получил +34% международного трафика за 60 дней.

Бесплатная рассылка

Каждую среду. 28 400+ читателей. Никакой воды.

Содержание

Обновлено май 2026.

TL;DR: Один TypeScript-агент параллельно вызывает Claude API и переводит англоязычный пост на 12 языков менее чем за 90 секунд. Сохранение авторского голоса требует двухчастного системного промпта: сначала ограничения стиля, затем заметки для конкретной локали. Стоимость — примерно $0.004–$0.02 за пост на Haiku. Мой сайт получил +34% международного трафика за 60 дней.

[Взгляд оператора] Я запускаю этого агента каждый раз, когда публикую новый пост. Он обработал 341 пост на 12 языках, и я ни разу не трогал ни один перевод вручную. Вот как это работает.

Почему я построил агента-переводчика вместо того, чтобы нанять переводчиков

Аргументы в пользу многоязычного SEO пропущу — ты и так знаешь, что это важно. Проблема была в рабочем процессе. Нанимать переводчиков для каждого поста дорого ($40–$120/пост × 12 языков = $480–$1440 за статью), медленно (срок 3–7 дней) и невозможно при пакетной обработке, когда нужно наверстать 341 существующий пост.

Другой вариант, который советуют, — Google Translate или DeepL. Оба точны, но разрушают авторский голос. Мой стиль письма — прямой, от первого лица, слегка контрарный. Машинный перевод делает всё формальным и пассивным. Это проблема, когда последовательность голоса — часть бренда.

Поэтому я построил TypeScript-агент на Claude. Он запускается в CI при каждом мердже в main, параллельно распределяет переводы, записывает файлы обратно на диск и пропускает языки, для которых уже есть файл. Всё занимает менее 90 секунд для нового поста.

Структура проекта

Агент живёт в scripts/agent/translate-worker.ts. Его вызывает оркестратор верхнего уровня, который читает английский пост, извлекает frontmatter и отправляет задание на перевод для каждой локали.

code
scripts/
  agent/
    translate-worker.ts   # логика перевода для каждой локали
    translate-all.ts      # оркестратор: читает EN, распределяет на 12 языков
    lib/
      frontmatter.ts      # парсинг/сериализация frontmatter через gray-matter
      voice-prompt.ts     # общий конструктор системного промпта

Оркестратор (translate-all.ts) использует Promise.allSettled, поэтому провал одной локали не блокирует остальные.

Проектирование системного промпта

Именно здесь большинство людей ошибается. Они пишут что-то вроде: «Переведи это на французский, сохрани голос автора». Это даёт посредственный результат.

Мой системный промпт имеет два обязательных раздела.

Раздел 1 — Ограничения стиля (универсальные, добавляются к каждому вызову):

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

Раздел 2 — Заметки для конкретной локали (добавляются при каждом вызове):

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

Воркер перевода

Вот полный воркер. Он читает EN-файл, вызывает Claude и записывает вывод на диск.

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;

  // Идемпотентность: пропустить, если перевод уже существует
  const filename = path.basename(enFilePath);
  const outPath = path.join(outputDir, locale, filename);
  if (fs.existsSync(outPath)) {
    console.log(`[${locale}] Уже существует — пропускаем: ${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}] Записано: ${outPath}`);
  }

  return outPath;
}

Оркестратор

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() {
  // Принимаем конкретный файл или переводим все 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(`Переводим ${enFiles.length} пост(ов) × ${LOCALES.length} языков. Модель: ${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]}] ОШИБКА:`, r.reason);
      }
    });
  }

  console.log("Готово.");
}

main();

Запуск:

sh
# Перевести один новый пост
npx ts-node scripts/agent/translate-all.ts src/content/posts/en/my-new-post.md

# Перевести всё (идемпотентно — пропускает существующее)
npx ts-node scripts/agent/translate-all.ts

Сравнение стоимости: Haiku vs Sonnet

Реальная стоимость за пост на основе моего использования:

МодельВходные токены (ср.)Выходные токены (ср.)Стоимость за языкСтоимость × 12 языков
claude-haiku-4-5~2 400~2 600~$0.0004~$0.005
claude-sonnet-4-5~2 400~2 600~$0.015~$0.18

Для 341 поста × 12 языков на Haiku: примерно $1.70 всего. Это весь бэклог.

Sonnet даёт немного более идиоматичные формулировки, но для большинства постов разница не стоит 36-кратной цены. Sonnet я использую только для постов, где важен нюансированный убедительный тон — например, продающие страницы или высокотрафиковый cornerstone-контент.

Модель можно переключать для каждого запуска через переменную окружения TRANSLATE_MODEL:

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

Реальные результаты: что произошло с трафиком

Я опубликовал полный перевод бэклога (341 пост) в декабре 2025 года. За 60 дней:

Результаты по японскому и корейскому меня удивили. В обоих языках есть качественное AI-сообщество и, судя по всему, хороший спрос на практический контент для операторов.

Вывод оператора

Один агент, один час настройки, $1.70 на API. Вот и всё, что потребовалось, чтобы сделать 341 пост доступным ещё в 12 языках. Только рост SEO окупил вычислительные затраты за первую неделю. Если ты ведёшь контентный сайт и ещё не построил это — ты оставляешь международный трафик на столе. Код выше — это полная реализация. Сделай форк, замени заметки в своём voice-prompt и запусти против своего бэклога сегодня ночью.

Читать дальше

Получайте ИИ-руководство на почту

Каждую среду. 28 400+ читателей. Никакой воды.

↵ — все результаты esc esc — закрыть