System tłumaczeń
Ta strona jest publikowana w 17 językach. Nie przez zespół tłumaczy, lecz za pomocą skryptu Python, modelu Claude Opus i struktury plików zaprojektowanej tak, aby wszystko pasowało na swoje miejsce. Oto jak to całe rozwiązanie działa.
Drzewo języków
Dział zatytułowany „Drzewo języków”Dokumentacja znajduje się w katalogu src/content/docs/. Angielski jest wersją źródłową — każdy inny język odzwierciedla go dokładnie:
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)Każdy przetłumaczony plik jest strukturalnym lustrzanym odbiciem swojego angielskiego źródła. Ta sama nazwa pliku, ta sama ścieżka podkatalogu, te same klucze frontmatter. Jedyna różnica to tekst w innym języku.
Dlaczego lustrzane odbicia mają znaczenie
Dział zatytułowany „Dlaczego lustrzane odbicia mają znaczenie”Starlight (framework dokumentacji) opiera się na tej symetrii. Gdy użytkownik przełącza język, Starlight zamienia /docs/getting-started/ na /fr/docs/getting-started/ — ta sama ścieżka, inny prefiks lokalizacji. Jeśli plik francuski nie istnieje dokładnie pod ścieżką fr/getting-started.md, przełącznik języków się psuje lub po cichu wraca do wersji angielskiej.
17 języków
Dział zatytułowany „17 języków”Języki są uporządkowane według globalnej populacji graczy szachowych, na podstawie danych z Lichess, Chess.com oraz rejestracji FIDE. Angielski jest na pierwszym miejscu jako język źródłowy; reszta podąża za rankingami szachowymi:
| Pozycja | Kod | Język | Styl |
|---|---|---|---|
| 1 | en | Angielski | Wersja źródłowa |
| 2 | es | Hiszpański | Standardowy formalny |
| 3 | hi | Hindi | Pismo dewanagari |
| 4 | ru | Rosyjski | Standardowy formalny |
| 5 | de | Niemiecki | Standardowy formalny |
| 6 | fr | Francuski | Standardowy formalny |
| 7 | pt | Portugalski | Portugalski europejski |
| 11 | pl | Polski | Standardowy formalny |
| 12 | it | Włoski | Standardowy formalny |
| 13 | uk | Ukraiński | Standardowy formalny |
| 14 | tr | Turecki | Standardowy formalny |
| 17 | ko | Koreański | forma 합니다/습니다 |
| 18 | zh | Chiński (uproszczony) | Znaki uproszczone |
| — | zh-tw | Chiński (tradycyjny) | Znaki tradycyjne |
| 23 | nb | Norweski Bokmål | Standardowy Bokmål |
| — | be | Białoruski | Standardowy białoruski |
| 34 | ja | Japoński | forma です/ます |
Kolumna „Styl” ma znaczenie. Japoński i koreański mają wybór rejestru formalnego, który wpływa na każde zdanie. Prompt tłumaczenia zawiera te instrukcje, dzięki czemu model generuje naturalną, dopracowaną prozę — a nie sztywny maszynowy tekst.
Ta kolejność kontroluje również listę rozwijaną języków w nagłówku strony. Języki najczęściej używane przez szachistów pojawiają się jako pierwsze, więc użytkownicy mają większą szansę znaleźć swój język bez przewijania.
Pipeline tłumaczeń
Dział zatytułowany „Pipeline tłumaczeń”Krok 1: Pisanie po angielsku
Dział zatytułowany „Krok 1: Pisanie po angielsku”Cała dokumentacja zaczyna się jako angielski markdown w src/content/docs/. Frontmatter zawiera pola title i description:
---title: "Getting Started"description: "Install En Parlant~ and play your first game."---
Download the latest release...Krok 2: Uruchomienie skryptu
Dział zatytułowany „Krok 2: Uruchomienie skryptu”Skrypt Python (scripts/translate-docs.py) odczytuje każdy angielski plik źródłowy, wysyła go do API Claude i zapisuje przetłumaczony markdown:
python3 scripts/translate-docs.py \ --anthropic-key $ANTHROPIC_API_KEY \ --model claude-opus-4-6 \ --workers 5Skrypt potrzebuje około 60–70 minut na przetłumaczenie wszystkich 28 plików źródłowych na 16 języków docelowych (łącznie 448 plików). Uruchamia 5 równoległych wywołań API, aby mieścić się w limitach zapytań.
Dla pojedynczego nowego języka zajmuje to około 4 minuty.
Krok 3: Budowanie
Dział zatytułowany „Krok 3: Budowanie”pnpm buildAstro odczytuje źródłowy markdown, renderuje go przez szablony Starlight i generuje statyczny HTML do katalogu dist/. Budowanie zajmuje około 30 sekund dla wszystkich ~500 stron.
Krok 4: Wdrożenie
Dział zatytułowany „Krok 4: Wdrożenie”pnpm run deployPublikuje zbudowaną stronę na Cloudflare Workers.
Jak działa skrypt
Dział zatytułowany „Jak działa skrypt”Skrypt tłumaczenia jest celowo prosty — około 300 linii Pythona. Oto przebieg działania:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐│ 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 filesPrompt mówi Claude dokładnie, co tłumaczyć, a co zostawić bez zmian:
- Tłumaczyć: prozę, nagłówki, etykiety tabel, frontmatter title i description
- Zostawić po angielsku: bloki kodu, kod inline, przykłady poleceń, ścieżki plików, adresy URL, nazwy produktów, imiona i nazwiska osób, diagramy ASCII
Ten podział jest kluczowy. Polecenie takie jak pnpm build musi pozostać pnpm build w każdym języku. Nazwa produktu jak „En Parlant~” czy „Stockfish” nie jest tłumaczona. Ale „Getting Started” staje się „はじめに” po japońsku i „Начало работы” po rosyjsku.
Dlaczego Opus?
Dział zatytułowany „Dlaczego Opus?”Claude Opus 4.6 generuje zauważalnie lepsze tłumaczenia niż szybsze modele. Różnica widoczna jest w:
- Naturalne sformułowania — Opus pisze jak rodzimy użytkownik języka, nie jak tłumacz. Przebudowuje zdania, gdy angielski szyk wyrazów brzmiałby niezręcznie w języku docelowym.
- Dokładność terminologiczna — Terminologia szachowa, żargon TTS i pojęcia programistyczne są tłumaczone z użyciem właściwych terminów dziedzinowych.
- Spójność — Formalny rejestr jest utrzymany konsekwentnie w całym tekście. Japoński używa formy です/ます wszędzie, bez przeskakiwania między stylem potocznym a grzecznościowym w środku akapitu.
- Obsługa MDX — Opus poprawnie zachowuje tagi komponentów JSX (
<Card>,<CardGrid>) i instrukcjeimportw plikach.mdx, nie uszkadzając ich.
Różnica w kosztach jest realna — około 28$ za pełne tłumaczenie strony z Opus w porównaniu z 5$ przy użyciu Sonnet — ale dla 448 plików, które użytkownicy faktycznie będą czytać, jakość jest tego warta.
Architektura routingu lokalizacji
Dział zatytułowany „Architektura routingu lokalizacji”To część, która wymagała najwięcej czasu, aby działała poprawnie.
Problem
Dział zatytułowany „Problem”Starlight wykrywa język strony, sprawdzając pierwszy segment sluga treści. Gdy widzi fr/docs/getting-started, wie, że to francuski, ponieważ fr jest pierwszym segmentem. Jednak początkowa implementacja generowała slugi typu docs/fr/getting-started — lokalizacja zagrzebana pod docs/. Starlight widział docs jako pierwszy segment, traktował wszystko jako angielski i generował ponad 7000 zduplikowanych stron zamiast ~500.
Rozwiązanie
Dział zatytułowany „Rozwiązanie”Niestandardowa funkcja generateId w src/content.config.ts kontroluje, jak ścieżki plików stają się slugami treści:
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}`;}To umieszcza prefiks lokalizacji przed docs/:
| Ścieżka pliku | 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/ |
Angielski używa defaultLocale: "root", co oznacza brak prefiksu — strona znajduje się bezpośrednio pod /docs/, nie pod /en/docs/.
Tablica localeKeys
Dział zatytułowany „Tablica localeKeys”Tablica localeKeys w tym samym pliku musi zawierać listę wszystkich nieanglojęzycznych lokalizacji. Jeśli lokalizacja istnieje w konfiguracji Astro, ale nie w tej tablicy, przetłumaczona treść jest traktowana jako treść anglojęzyczna — przełącznik języków się psuje i liczba stron eksploduje.
Pamięć podręczna data store
Dział zatytułowany „Pamięć podręczna data store”Astro buforuje mapowania slugów w pliku .astro/data-store.json. Po jakiejkolwiek zmianie konfiguracji lokalizacji ten plik musi zostać usunięty przed ponownym budowaniem, w przeciwnym razie budowanie zakończy się sukcesem z nieaktualnymi (błędnymi) trasami.
Struktura menu
Dział zatytułowany „Struktura menu”Pasek boczny jest zdefiniowany w astro.config.mjs. Elementy wskazujące na stronę (właściwość slug) automatycznie używają przetłumaczonego tytułu z frontmatter strony — nie trzeba ręcznie tłumaczyć:
{ slug: "docs/getting-started" }// English: "Getting Started" (from English frontmatter)// French: "Premiers pas" (from French frontmatter)// Japanese: "はじめに" (from Japanese frontmatter)Ale etykiety grup i linki zewnętrzne wymagają jawnych tłumaczeń:
{ 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" }, // ... ],}Siedem elementów paska bocznego wymaga ręcznych tłumaczeń: Welcome, Features, App Menus, Setup Guides, Under the Hood, Credits i Accessibility. Dodanie nowego języka oznacza dodanie jednego wpisu do każdego z tych siedmiu bloków.
Połączenie z aplikacją
Dział zatytułowany „Połączenie z aplikacją”En Parlant~ linkuje do tej dokumentacji ze swojego menu Pomoc. Link jest świadomy lokalizacji — jeśli używasz aplikacji po francusku, otwiera francuską dokumentację:
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]);Każdy język dostępny w aplikacji ma odpowiadające mu tłumaczenie na stronie. Mapowanie jest automatyczne — fr_FR mapuje się na /fr/docs/, ja_JP mapuje się na /ja/docs/. Jedynym przypadkiem specjalnym jest chiński tradycyjny: zh_TW mapuje się na /zh-tw/docs/ (z łącznikiem).
Dlaczego teraz jest łatwo
Dział zatytułowany „Dlaczego teraz jest łatwo”Trudne części są już za nami:
-
Architektura routingu jest rozwiązana. Funkcja
generateId, tablicalocaleKeysi konfiguracjadefaultLocale: "root"współpracują ze sobą, dzięki czemu Starlight generuje poprawną strukturę URL. To był największy punkt bólu — wymagał śledzenia przez ponad 6 plików źródłowych w Starlight i Astro, aby znaleźć i naprawić problem. -
Skrypt tłumaczenia obsługuje wszystko. Pełne retłumaczenie strony, dodanie pojedynczego języka, aktualizacja poszczególnych plików — to ten sam skrypt z różnymi flagami. Ponawia próby przy limitach zapytań, zrównolegla pracę między workerami i raportuje błędy w czytelny sposób.
-
Dodanie nowego języka to cztery edycje konfiguracji i jedno polecenie. Dodaj lokalizację do
astro.config.mjs,content.config.tsitranslate-docs.py, dodaj tłumaczenia paska bocznego, uruchom skrypt. Około 10 minut pracy plus 4 minuty tłumaczenia. -
Aktualizacja treści jest jeszcze prostsza. Edytuj angielskie źródło, uruchom skrypt z flagami
--filesi--overwritetylko dla zmienionych plików, przebuduj. Albo przy drobnych poprawkach tekstu edytuj przetłumaczone pliki bezpośrednio. -
Cały pipeline jest utrwalony jako skill. Uruchomienie
/translate_docsprowadzi przez cały proces — jaki tryb wybrać, jakie flagi przekazać, kontrole przed startem, weryfikacja po tłumaczeniu. Nie wymaga wiedzy instytucjonalnej.
Całkowite tłumaczenie 448 plików na 16 języków kosztowało około 28$ i zajęło około 70 minut. Pojedynczy nowy język kosztuje około 1,70$. Pojedynczy plik przetłumaczony na wszystkie języki kosztuje około 1$. To bieżące koszty utrzymywania dokumentacji aktualnej w 17 językach.