Система перевода
Этот сайт опубликован на 17 языках. Не командой переводчиков, а Python-скриптом, моделью Claude Opus и файловой структурой, в которой всё встаёт на свои места. Вот как это устроено.
Дерево языков
Заголовок раздела «Дерево языков»Документация хранится в src/content/docs/. Английский язык — это корень, все остальные языки точно повторяют его структуру:
src/content/docs/├── index.mdx ← English (root)├── getting-started.md├── features/│ ├── play-chess.md│ ├── multiplayer.md│ └── ...├── setup/│ ├── tts-overview.md│ └── ...├── under-the-hood/│ ├── architecture.md│ └── ...│├── fr/ ← French│ ├── index.mdx│ ├── getting-started.md│ ├── features/│ │ ├── play-chess.md│ │ └── ...│ └── ...│├── ja/ ← Japanese│ ├── index.mdx│ ├── getting-started.md│ └── ...│└── ... (16 language directories total)Каждый переведённый файл — это структурное зеркало английского оригинала. Те же имена файлов, те же пути подкаталогов, те же ключи frontmatter. Единственное отличие — текст написан на другом языке.
Почему зеркальная структура важна
Заголовок раздела «Почему зеркальная структура важна»Starlight (фреймворк документации) опирается на эту симметрию. Когда пользователь переключает язык, Starlight заменяет /docs/getting-started/ на /fr/docs/getting-started/ — тот же путь, другой префикс локали. Если французский файл не находится точно по пути fr/getting-started.md, переключатель ломается или молча откатывается к английской версии.
17 языков
Заголовок раздела «17 языков»Языки упорядочены по численности шахматного сообщества в мире на основе данных Lichess, Chess.com и регистраций FIDE. Английский идёт первым как исходный язык; остальные следуют в порядке шахматного рейтинга:
| Ранг | Код | Язык | Стиль |
|---|---|---|---|
| 1 | en | Английский | Первоисточник |
| 2 | es | Испанский | Стандартный формальный |
| 3 | hi | Хинди | Письменность деванагари |
| 4 | ru | Русский | Стандартный формальный |
| 5 | de | Немецкий | Стандартный формальный |
| 6 | fr | Французский | Стандартный формальный |
| 7 | pt | Португальский | Европейский португальский |
| 11 | pl | Польский | Стандартный формальный |
| 12 | it | Итальянский | Стандартный формальный |
| 13 | uk | Украинский | Стандартный формальный |
| 14 | tr | Турецкий | Стандартный формальный |
| 17 | ko | Корейский | Форма 합니다/습니다 |
| 18 | zh | Китайский (упрощённый) | Упрощённые иероглифы |
| — | zh-tw | Китайский (традиционный) | Традиционные иероглифы |
| 23 | nb | Норвежский букмол | Стандартный букмол |
| — | be | Белорусский | Стандартный белорусский |
| 34 | ja | Японский | Форма です/ます |
Столбец «Стиль» имеет значение. В японском и корейском языках есть формальные регистры, которые влияют на каждое предложение. Промпт для перевода включает эти инструкции, чтобы модель создавала естественный, отполированный текст, а не сухой машинный перевод.
Этот порядок также определяет выпадающий список языков в шапке сайта. Языки с наибольшим шахматным сообществом отображаются первыми, поэтому пользователи с большей вероятностью найдут свой язык без прокрутки.
Конвейер перевода
Заголовок раздела «Конвейер перевода»Шаг 1: Написание на английском
Заголовок раздела «Шаг 1: Написание на английском»Вся документация начинается как markdown на английском языке в src/content/docs/. Frontmatter содержит title и description:
---title: "Getting Started"description: "Install En Parlant~ and play your first game."---
Download the latest release...Шаг 2: Запуск скрипта
Заголовок раздела «Шаг 2: Запуск скрипта»Python-скрипт (scripts/translate-docs.py) считывает каждый английский исходный файл, отправляет его в API Claude и записывает переведённый markdown:
python3 scripts/translate-docs.py \ --anthropic-key $ANTHROPIC_API_KEY \ --model claude-opus-4-6 \ --workers 5Скрипту требуется около 60–70 минут для перевода всех 28 исходных файлов на все 16 целевых языков (448 файлов в сумме). Он выполняет 5 параллельных API-запросов, чтобы не превышать лимиты частоты обращений.
Для одного нового языка — около 4 минут.
Шаг 3: Сборка
Заголовок раздела «Шаг 3: Сборка»pnpm buildAstro считывает исходный markdown, рендерит его через шаблоны Starlight и генерирует статический HTML в dist/. Сборка занимает около 30 секунд для всех ~500 страниц.
Шаг 4: Развёртывание
Заголовок раздела «Шаг 4: Развёртывание»pnpm run deployОтправляет собранный сайт на Cloudflare Workers.
Как работает скрипт
Заголовок раздела «Как работает скрипт»Скрипт перевода намеренно прост — около 300 строк на Python. Вот его логика:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐│ Read English │────▶│ Claude API │────▶│ Write target ││ source file │ │ (Opus 4.6) │ │ language file ││ │ │ │ │ ││ getting- │ │ "Translate into │ │ fr/getting- ││ started.md │ │ French..." │ │ started.md │└─────────────────┘ └──────────────────┘ └─────────────────┘ × 5 parallel × 16 languages × 28 filesПромпт точно указывает Claude, что переводить, а что оставлять без изменений:
- Переводить: прозу, заголовки, подписи таблиц, frontmatter title и description
- Оставлять на английском: блоки кода, инлайн-код, примеры команд, пути файлов, URL-адреса, названия продуктов, имена людей, ASCII-диаграммы
Это разделение критически важно. Команда вроде pnpm build должна оставаться pnpm build на любом языке. Название продукта «En Parlant~» или «Stockfish» не переводится. Но «Getting Started» становится «はじめに» на японском и «Начало работы» на русском.
Почему Opus?
Заголовок раздела «Почему Opus?»Claude Opus 4.6 даёт заметно лучшие переводы, чем более быстрые модели. Разница проявляется в следующем:
- Естественные формулировки — Opus пишет как носитель языка, а не как переводчик. Он перестраивает предложения, когда английский порядок слов звучал бы неестественно в целевом языке.
- Техническая точность — шахматная терминология, жаргон TTS и программные концепции переводятся с использованием корректных предметно-специфичных терминов.
- Последовательность — формальный регистр сохраняется единообразно на протяжении всего текста. Японский использует форму です/ます повсюду, не переключаясь между разговорным и вежливым стилем посреди абзаца.
- Обработка MDX — Opus корректно сохраняет теги JSX-компонентов (
<Card>,<CardGrid>) и выраженияimportв файлах.mdx, не искажая их.
Разница в стоимости ощутима — около $28 за полный перевод сайта с Opus против $5 с Sonnet — но для 448 файлов, которые пользователи действительно будут читать, качество того стоит.
Архитектура маршрутизации локалей
Заголовок раздела «Архитектура маршрутизации локалей»Это та часть, на отладку которой ушло больше всего времени.
Проблема
Заголовок раздела «Проблема»Starlight определяет язык страницы по первому сегменту slug контента. Когда он видит fr/docs/getting-started, он понимает, что это французский, потому что fr — первый сегмент. Но изначальная реализация генерировала slug вида docs/fr/getting-started — локаль оказывалась вложена внутрь docs/. Starlight видел docs как первый сегмент, считал всё английским и генерировал 7000+ дублирующихся страниц вместо ~500.
Решение
Заголовок раздела «Решение»Пользовательская функция generateId в src/content.config.ts управляет тем, как пути файлов превращаются в slug контента:
generateId({ entry }) { const slug = entry.replace(/\.[^.]+$/, ""); const firstSeg = slug.split("/")[0]; if (firstSeg && localeKeys.includes(firstSeg)) { return `${firstSeg}/docs/${slug.slice(firstSeg.length + 1)}`; } return `docs/${slug}`;}Это помещает префикс локали перед docs/:
| Путь файла | Slug | URL |
|---|---|---|
getting-started.md | docs/getting-started | /docs/getting-started/ |
fr/getting-started.md | fr/docs/getting-started | /fr/docs/getting-started/ |
ja/features/puzzles.md | ja/docs/features/puzzles | /ja/docs/features/puzzles/ |
Английский использует defaultLocale: "root", что означает отсутствие префикса — он живёт непосредственно по пути /docs/, а не /en/docs/.
Массив localeKeys
Заголовок раздела «Массив localeKeys»Массив localeKeys в том же файле должен перечислять каждую неанглийскую локаль. Если локаль существует в конфигурации Astro, но отсутствует в этом массиве, её переведённый контент будет обработан как английский — переключатель языков сломается и количество страниц резко возрастёт.
Кэш хранилища данных
Заголовок раздела «Кэш хранилища данных»Astro кэширует маппинг slug в .astro/data-store.json. После любого изменения конфигурации локалей этот файл необходимо удалить перед пересборкой, иначе сборка завершится успешно, но с устаревшей (неправильной) маршрутизацией.
Структура меню
Заголовок раздела «Структура меню»Боковая панель определяется в astro.config.mjs. Элементы, указывающие на страницу (свойство slug), автоматически используют заголовок из frontmatter переведённой страницы — ручной перевод не требуется:
{ slug: "docs/getting-started" }// English: "Getting Started" (from English frontmatter)// French: "Premiers pas" (from French frontmatter)// Japanese: "はじめに" (from Japanese frontmatter)Но подписи групп и внешние ссылки требуют явных переводов:
{ label: "Features", translations: { fr: "Fonctionnalités", es: "Características", de: "Funktionen", ja: "機能", // ... all 16 languages }, items: [ { slug: "docs/features/play-chess" }, { slug: "docs/features/multiplayer" }, // ... ],}Семь элементов боковой панели требуют ручного перевода: Welcome, Features, App Menus, Setup Guides, Under the Hood, Credits и Accessibility. Добавление нового языка означает добавление одной записи в каждый из этих семи блоков.
Связь с приложением
Заголовок раздела «Связь с приложением»En Parlant~ ссылается на эту документацию из меню Help. Ссылка учитывает локаль — если вы используете приложение на французском, откроется французская документация:
const docsLocalePrefix = useMemo(() => { const lang = i18n.language; // e.g. "fr_FR", "zh_TW" if (!lang || lang.startsWith("en")) return ""; if (lang === "zh_TW") return "/zh-tw"; return `/${lang.slice(0, 2)}`;}, [i18n.language]);Для каждого языка, доступного в приложении, на сайте есть соответствующий перевод. Маппинг выполняется автоматически — fr_FR ведёт на /fr/docs/, ja_JP ведёт на /ja/docs/. Единственный особый случай — традиционный китайский: zh_TW ведёт на /zh-tw/docs/ (через дефис).
Почему теперь это просто
Заголовок раздела «Почему теперь это просто»Сложные части уже решены:
-
Архитектура маршрутизации отлажена. Функция
generateId, массивlocaleKeysи конфигурацияdefaultLocale: "root"работают вместе, чтобы Starlight генерировал корректную структуру URL. Это была самая болезненная проблема — для её обнаружения и исправления потребовалось проследить логику через 6+ исходных файлов Starlight и Astro. -
Скрипт перевода справляется со всем. Полный повторный перевод сайта, добавление одного языка, обновление отдельных файлов — один и тот же скрипт с разными флагами. Он повторяет запросы при превышении лимитов, параллелизирует работу между воркерами и чётко сообщает об ошибках.
-
Добавление нового языка — это четыре правки в конфигурации и одна команда. Добавьте локаль в
astro.config.mjs,content.config.tsиtranslate-docs.py, добавьте переводы боковой панели, запустите скрипт. Около 10 минут работы плюс 4 минуты на перевод. -
Обновление контента ещё проще. Отредактируйте английский исходник, запустите скрипт с флагами
--filesи--overwriteтолько для изменённых файлов, пересоберите. Или для небольших правок текста редактируйте переведённые файлы напрямую. -
Весь конвейер описан в навыке. Запуск
/translate_docsпроведёт через весь процесс — какой режим использовать, какие флаги передать, предварительные проверки, верификация после перевода. Никаких скрытых знаний не требуется.
Полный перевод 448 файлов на 16 языков обошёлся примерно в $28 и занял около 70 минут. Один новый язык стоит около $1.70. Повторный перевод одного файла на все языки стоит около $1. Таковы текущие расходы на поддержание документации в актуальном состоянии на 17 языках.