Перейти до вмісту

Система перекладу

Цей сайт опубліковано 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. Єдина відмінність — текст написаний іншою мовою.

Чому дзеркальна структура важлива

Section titled “Чому дзеркальна структура важлива”

Starlight (фреймворк документації) покладається на цю симетрію. Коли користувач перемикає мову, Starlight замінює /docs/getting-started/ на /fr/docs/getting-started/ — той самий шлях, інший префікс локалі. Якщо французький файл не існує саме за адресою fr/getting-started.md, перемикач мов ламається або мовчки повертається до англійської.

Мови впорядковані за чисельністю шахової аудиторії у світі на основі даних Lichess, Chess.com та реєстрацій FIDE. Англійська йде першою як мова-джерело; решта слідують за шаховим рейтингом:

РангКодМоваСтиль
1enАнглійськаПершоджерело
2esІспанськаСтандартний формальний
3hiГіндіПисьмо деванаґарі
4ruРосійськаСтандартний формальний
5deНімецькаСтандартний формальний
6frФранцузькаСтандартний формальний
7ptПортугальськаЄвропейська португальська
11plПольськаСтандартний формальний
12itІталійськаСтандартний формальний
13ukУкраїнськаСтандартний формальний
14trТурецькаСтандартний формальний
17koКорейськаФорма 합니다/습니다
18zhКитайська (спрощена)Спрощені ієрогліфи
zh-twКитайська (традиційна)Традиційні ієрогліфи
23nbНорвезька букмолСтандартний букмол
beБілоруськаСтандартна білоруська
34jaЯпонськаФорма です/ます

Колонка «стиль» має значення. Японська та корейська мають вибір формального регістру, який впливає на кожне речення. Промпт для перекладу містить ці інструкції, щоб модель створювала природний, відшліфований текст — а не механічний переклад.

Цей порядок також визначає розташування мов у випадному списку в заголовку сайту. Найпоширеніші шахові мови з’являються першими, тож користувачі швидше знаходять свою мову без прокрутки.

Крок 1: Написання англійською

Section titled “Крок 1: Написання англійською”

Уся документація починається як markdown-файли англійською в src/content/docs/. Frontmatter містить title та description:

---
title: "Getting Started"
description: "Install En Parlant~ and play your first game."
---
Download the latest release...

Python-скрипт (scripts/translate-docs.py) зчитує кожен англійський вихідний файл, надсилає його до Claude API та записує перекладений markdown:

Terminal window
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 хвилини.

Terminal window
pnpm build

Astro зчитує вихідний markdown, рендерить його через шаблони Starlight і генерує статичний HTML у dist/. Збірка займає близько 30 секунд для всіх ~500 сторінок.

Terminal window
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” стає “はじめに” японською та “Начало работы” російською.

Claude Opus 4.6 дає помітно кращі переклади, ніж швидші моделі. Різниця виявляється в:

  • Природне формулювання — Opus пише як носій мови, а не як перекладач. Він перебудовує речення, коли англійський порядок слів звучав би незграбно цільовою мовою.
  • Технічна точність — шахова термінологія, жаргон TTS та програмні концепції перекладаються з використанням правильних вузькоспеціалізованих термінів.
  • Послідовність — формальний регістр залишається однорідним усюди. Японською використовується форма です/ます скрізь, без перемикання між розмовним та ввічливим стилем посеред абзацу.
  • Обробка MDX — Opus коректно зберігає JSX-теги компонентів (<Card>, <CardGrid>) та оператори import у файлах .mdx, не спотворюючи їх.

Різниця у вартості реальна — близько $28 за повний переклад сайту з Opus проти $5 з Sonnet — але для 448 файлів, які користувачі справді читатимуть, якість того варта.

Архітектура маршрутизації локалей

Section titled “Архітектура маршрутизації локалей”

Це частина, на налагодження якої пішло найбільше часу.

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/:

Шлях файлуSlugURL
getting-started.mddocs/getting-started/docs/getting-started/
fr/getting-started.mdfr/docs/getting-started/fr/docs/getting-started/
ja/features/puzzles.mdja/docs/features/puzzles/ja/docs/features/puzzles/

Англійська використовує defaultLocale: "root", що означає відсутність префіксу — вона живе безпосередньо за адресою /docs/, а не /en/docs/.

Масив 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. Додавання нової мови означає додавання одного запису до кожного з цих семи блоків.

Зв’язок із застосунком

Section titled “Зв’язок із застосунком”

En Parlant~ посилається на цю документацію зі свого меню «Довідка». Посилання враховує локаль — якщо ви використовуєте застосунок французькою, воно відкриває французьку документацію:

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/ (через дефіс).

Складні частини вже зроблені:

  1. Архітектура маршрутизації вирішена. Функція generateId, масив localeKeys та конфігурація defaultLocale: "root" працюють разом, щоб Starlight генерував правильну URL-структуру. Це був найбільший больовий пункт — довелося простежити понад 6 вихідних файлів у Starlight та Astro, щоб знайти та виправити проблему.

  2. Скрипт перекладу обробляє все. Повний переклад сайту, додавання однієї мови, оновлення окремих файлів — усе тим самим скриптом з різними прапорцями. Він повторює спроби при досягненні лімітів швидкості, розпаралелює роботу між воркерами та чітко повідомляє про помилки.

  3. Додавання нової мови — це чотири правки конфігурації та одна команда. Додати локаль до astro.config.mjs, content.config.ts та translate-docs.py, додати переклади для бічної панелі, запустити скрипт. Приблизно 10 хвилин роботи плюс 4 хвилини на переклад.

  4. Оновлення контенту ще простіше. Відредагуйте англійське джерело, запустіть скрипт з --files та --overwrite лише для змінених файлів, перезберіть. Або для дрібних правок тексту — відредагуйте перекладені файли напряму.

  5. Увесь конвеєр зафіксований у навичці. Запуск /translate_docs проведе через весь процес — який режим використовувати, які прапорці передати, перевірки перед запуском, верифікація після перекладу. Ніяких «таємних знань» не потрібно.

Повний переклад 448 файлів на 16 мов коштував приблизно $28 і зайняв близько 70 хвилин. Одна нова мова коштує приблизно $1,70. Один файл, переперекладений на всі мови, коштує приблизно $1. Такі поточні витрати на підтримку документації актуальною 17 мовами.