翻訳システム
このサイトは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)すべての翻訳ファイルは、英語ソースの構造的なミラーです。同じファイル名、同じサブディレクトリパス、同じフロントマターキー。唯一の違いは、文章が別の言語で書かれていることです。
ミラーリングが重要な理由
Section titled “ミラーリングが重要な理由”Starlight(ドキュメントフレームワーク)はこの対称性に依存しています。ユーザーが言語を切り替えると、Starlightは /docs/getting-started/ を /fr/docs/getting-started/ に置き換えます — 同じパスで、ロケールプレフィックスだけが異なります。フランス語のファイルが正確に fr/getting-started.md に存在しない場合、言語切り替えが壊れるか、黙って英語にフォールバックします。
言語は、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 | 日本語 | です/ます形 |
「スタイル」列は重要です。日本語と韓国語には、すべての文に影響する敬語レベルの選択があります。翻訳プロンプトにはこれらの指示が含まれているため、モデルは硬いマシン出力ではなく、自然で洗練された文章を生成します。
この順序は、サイトヘッダーの言語ドロップダウンの表示順も制御しています。最も話者の多いチェス言語が最初に表示されるため、ユーザーはスクロールせずに自分の言語を見つけやすくなります。
翻訳パイプライン
Section titled “翻訳パイプライン”ステップ1:英語で書く
Section titled “ステップ1:英語で書く”すべてのドキュメントは、src/content/docs/ 内の英語マークダウンとして始まります。フロントマターには title と description があります:
---title: "Getting Started"description: "Install En Parlant~ and play your first game."---
Download the latest release...ステップ2:スクリプトを実行する
Section titled “ステップ2:スクリプトを実行する”Pythonスクリプト(scripts/translate-docs.py)がすべての英語ソースファイルを読み取り、Claude APIに送信し、翻訳されたマークダウンを書き出します:
python3 scripts/translate-docs.py \ --anthropic-key $ANTHROPIC_API_KEY \ --model claude-opus-4-6 \ --workers 5このスクリプトは、28のソースファイルすべてを16の対象言語に翻訳するのに約60〜70分かかります(合計448ファイル)。レート制限内に収めるため、5つの並列APIコールを実行します。
新しい言語を1つだけ追加する場合は、約4分です。
ステップ3:ビルド
Section titled “ステップ3:ビルド”pnpm buildAstroがソースマークダウンを読み取り、Starlightのテンプレートを通じてレンダリングし、静的HTMLを dist/ に出力します。ビルドは約500ページすべてで約30秒かかります。
ステップ4:デプロイ
Section titled “ステップ4:デプロイ”pnpm run deployビルドされたサイトをCloudflare Workersにプッシュします。
スクリプトの仕組み
Section titled “スクリプトの仕組み”翻訳スクリプトは意図的にシンプルに設計されています — 約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に正確に指示します:
- 翻訳する: 文章、見出し、表のラベル、フロントマターのtitleとdescription
- 英語のまま残す: コードブロック、インラインコード、コマンド例、ファイルパス、URL、製品名、人名、ASCIIダイアグラム
この分離は極めて重要です。pnpm build のようなコマンドは、すべての言語で pnpm build のままでなければなりません。「En Parlant~」や「Stockfish」のような製品名は翻訳されません。しかし「Getting Started」は日本語では「はじめに」に、ロシア語では「Начало работы」になります。
なぜOpusなのか?
Section titled “なぜOpusなのか?”Claude Opus 4.6は、より高速なモデルと比べて明らかに優れた翻訳を生成します。その差は以下の点に現れます:
- 自然な言い回し — Opusは翻訳者のようにではなく、ネイティブスピーカーのように書きます。英語の語順がターゲット言語で不自然に聞こえる場合、文を再構成します。
- 技術的正確性 — チェス用語、TTS専門用語、ソフトウェアの概念が、正しいドメイン固有の用語で翻訳されます。
- 一貫性 — 敬語レベルが全体を通じて一貫しています。日本語はどこでもです/ます形を使用し、段落の途中でカジュアルと丁寧を切り替えることはありません。
- MDXの処理 — Opusは
.mdxファイル内のJSXコンポーネントタグ(<Card>、<CardGrid>)やimport文を壊すことなく正しく保持します。
コストの差は実際にあります — Opusでのサイト全体翻訳は約$28に対し、Sonnetでは約$5です — しかし、ユーザーが実際に読む448ファイルに対しては、品質に見合う価値があります。
ロケールルーティングアーキテクチャ
Section titled “ロケールルーティングアーキテクチャ”これが正しく動作させるのに最も時間がかかった部分です。
Starlightは、コンテンツスラッグの最初のセグメントをチェックしてページの言語を検出します。fr/docs/getting-started を見ると、fr が最初のセグメントであるためフランス語だと認識します。しかし、初期実装では docs/fr/getting-started のようなスラッグが生成されていました — ロケールが docs/ の下に埋もれていたのです。Starlightは docs を最初のセグメントとして認識し、すべてを英語として扱い、約500ページではなく7,000以上の重複ページを生成してしまいました。
src/content.config.ts のカスタム generateId 関数が、ファイルパスからコンテンツスラッグへの変換方法を制御します:
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/ の前に配置されます:
| ファイルパス | スラッグ | 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" を使用しています。つまりプレフィックスなしで、/en/docs/ ではなく /docs/ に直接配置されます。
localeKeys配列
Section titled “localeKeys配列”同じファイル内の localeKeys 配列には、英語以外のすべてのロケールをリストする必要があります。ロケールがAstroの設定に存在するがこの配列に含まれていない場合、翻訳されたコンテンツが英語コンテンツとして扱われ、言語切り替えが壊れてページ数が爆発的に増加します。
データストアキャッシュ
Section titled “データストアキャッシュ”Astroはスラッグマッピングを .astro/data-store.json にキャッシュします。ロケール設定を変更した後は、リビルド前にこのファイルを削除する必要があります。そうしないと、ビルドは成功しますが古い(誤った)ルーティングのままになります。
メニュー構造
Section titled “メニュー構造”サイドバーは astro.config.mjs で定義されています。ページを指す項目(slug プロパティ)は、翻訳されたページのフロントマタータイトルを自動的に使用します — 手動翻訳は不要です:
{ 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" }, // ... ],}7つのサイドバー要素に手動翻訳が必要です:Welcome、Features、App Menus、Setup Guides、Under the Hood、Credits、Accessibilityです。新しい言語を追加する場合、これら7つのブロックそれぞれに1つのエントリを追加する必要があります。
アプリとの連携
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/(ハイフン付き)にマッピングされます。
なぜ今は簡単なのか
Section titled “なぜ今は簡単なのか”難しい部分はすでに解決済みです:
-
ルーティングアーキテクチャは解決済みです。
generateId関数、localeKeys配列、defaultLocale: "root"設定が連携して、Starlightが正しいURL構造を生成します。これが最大の苦労ポイントでした — StarlightとAstroの6つ以上のソースファイルを追跡して原因を見つけ修正する必要がありました。 -
翻訳スクリプトがすべてを処理します。 サイト全体の再翻訳、単一言語の追加、個別ファイルの更新 — すべて同じスクリプトで異なるフラグを使うだけです。レート制限時にはリトライし、ワーカー間で並列化し、エラーを明確に報告します。
-
新しい言語の追加は4つの設定変更と1つのコマンドです。
astro.config.mjs、content.config.ts、translate-docs.pyにロケールを追加し、サイドバーの翻訳を追加して、スクリプトを実行します。約10分の作業と4分の翻訳時間です。 -
コンテンツの更新はさらにシンプルです。 英語ソースを編集し、変更されたファイルだけに
--filesと--overwriteを付けてスクリプトを実行し、リビルドするだけです。小さな文章の修正であれば、翻訳ファイルを直接編集することもできます。 -
パイプライン全体がスキルとして記録されています。
/translate_docsを実行すると、プロセス全体を案内してくれます — どのモードを使うか、どのフラグを渡すか、事前チェック、翻訳後の検証まで。暗黙知は不要です。
16言語にわたる448ファイルの翻訳総コストは約$28で、約70分かかりました。新しい言語1つの追加コストは約$1.70です。1ファイルを全言語で再翻訳するコストは約$1です。これらが、17言語でドキュメントを最新に保つための継続的なコストです。