Как Перевести Один Пост Блога на 13 Языков с Помощью Одного Агента
Один TypeScript-агент параллельно вызывает Claude API и переводит англоязычный пост на 12 языков менее чем за 90 секунд. Сохранение авторского голоса требует двухчастного системного промпта: сначала ограничения стиля, затем заметки для конкретной локали. Стоимость — примерно $0.004–$0.02 за пост на Haiku. Мой сайт получил +34% международного трафика за 60 дней.
Каждую среду. 28 400+ читателей. Никакой воды.
✓ Check your inbox — click the confirmation link to complete sign-up.
✓ You're subscribed!
✓ You're already on the list.
Содержание
Обновлено май 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 и отправляет задание на перевод для каждой локали.
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 — Ограничения стиля (универсальные, добавляются к каждому вызову):
// 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 — Заметки для конкретной локали (добавляются при каждом вызове):
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 и записывает вывод на диск.
// 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;
}Оркестратор
// 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();Запуск:
# Перевести один новый пост
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:
TRANSLATE_MODEL=claude-sonnet-4-5 npx ts-node scripts/agent/translate-all.ts src/content/posts/en/flagship-post.mdРеальные результаты: что произошло с трафиком
Я опубликовал полный перевод бэклога (341 пост) в декабре 2025 года. За 60 дней:
- +34% органических сессий по всему сайту (Google Search Console, янв–фев 2026 vs окт–ноя 2025)
- Лучший новый язык по сессиям: Бразильский португальский (pt) — 11% нового международного трафика
- Лучший новый язык по конверсии: Немецкий (de) — 2,1% бронирований консультаций vs 1,8% в среднем по сайту
- Худший результат: Арабский (ar) — трафик пришёл, но ноль конверсий. Подозреваю, что поток бронирований не локализован за пределами контента постов.
- Японский (ja) и корейский (ko): значительный рост трафика (8% и 6% международных сессий соответственно) с вовлечённостью выше среднего (время на странице +40% к английскому базовому уровню)
Результаты по японскому и корейскому меня удивили. В обоих языках есть качественное AI-сообщество и, судя по всему, хороший спрос на практический контент для операторов.
Вывод оператора
Один агент, один час настройки, $1.70 на API. Вот и всё, что потребовалось, чтобы сделать 341 пост доступным ещё в 12 языках. Только рост SEO окупил вычислительные затраты за первую неделю. Если ты ведёшь контентный сайт и ещё не построил это — ты оставляешь международный трафик на столе. Код выше — это полная реализация. Сделай форк, замени заметки в своём voice-prompt и запусти против своего бэклога сегодня ночью.
Каждую среду. 28 400+ читателей. Никакой воды.
✓ Check your inbox — click the confirmation link to complete sign-up.
✓ You're subscribed!
✓ You're already on the list.
Получайте ИИ-руководство на почту
Каждую среду. 28 400+ читателей. Никакой воды.
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.