zudo-docはzfbのロケール対応ルーティングを通じて、任意の数の追加ロケールをサポートします。デフォルトロケールがプライマリのコンテンツツリーで、`locales`の各設定エントリが追加ロケールです。

## ロケールモデル

プライマリロケールは`defaultLocale`で選択され、プレフィックスのない`/docs/...`のルート空間を使用します。追加ロケールは`locales`マップに列挙し、コードをURLプレフィックスとして使用します。

- **プライマリ英語:** `/docs/...` — `src/content/docs/`
- **追加の日本語:** `/ja/docs/...` — `src/content/docs-ja/`
- **追加のドイツ語:** `/de/docs/...` — `src/content/docs-de/`

日本語や固定数のロケールに限定されません。ロケールコード、ラベル、ディレクトリはすべてプロジェクト設定です。

## 設定ベースのロケール構成

プライマリロケールとすべての追加ロケールを`zfb.config.ts`で設定します。

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    siteDescription: "Documentation for using the project.",
    defaultLocale: "en",
    locales: {
      ja: {
        label: "JA",
        dir: "src/content/docs-ja",
        description: "プロジェクトの使い方をまとめたドキュメント。",
      },
      de: { label: "DE", dir: "src/content/docs-de" },
    },
  }),
);
```

`locales`の各エントリには`label`と`dir`が必須です。この例では、日本語のヒーローには日本語の説明文が表示され、ドイツ語では共通の`siteDescription`が使われます。

`description`は、そのロケールのホームページのヒーローに表示する説明文です。文字列で指定でき、省略すると`settings.siteDescription`が使われます。空文字列（`""`）を指定した場合は、説明文を空欄にできます。`settings.locales`に登録するのはデフォルト以外のロケールだけなので、デフォルトロケールでは常に`settings.siteDescription`が使われ、ロケール別の上書きはできません。

現時点では、この設定が反映されるのはホームページのヒーローの説明文だけです。`llms.txt`はどのロケールでも共通の`siteDescription`を使い、ホームページには引き続き`<meta name="description">`が出力されません。

各エントリに対して自動的に次が行われます。

- `docs-{code}`という名前のzfbコンテンツコレクションを作成
- ロケール対応ルーティングにコードを登録
- 対応するページルートを生成
- 設定した`label`を言語切り替えに追加

コンポーネント内で`JP`、`JA`、または固定の言語リンク一覧をハードコードしないでください。切り替えはマップ順に設定済みラベルを表示するため、`fr-ca: { label: "Français (Canada)", dir: "src/content/docs-fr-ca" }`のようなカスタムロケールも同じように動作します。

## ジェネレーターの入力ルール

`create-zudo-doc` CLI、JSONプリセット、プログラムAPI、Preset Generatorは同じロケール契約を共有します。

- **追加ロケールの省略または空欄:** 対話UIの空欄、プリセットの`additionalLangs`フィールドの省略、明示的なリストなしでは単一ロケールプロジェクトになります。リストを持たない旧来の`i18n`ブール値には互換推論が残り、プライマリが`en`なら`ja`、それ以外なら`en`を推論します。
- **空でない明示リスト:** `--additional-langs ja,de`、`additionalLangs: ["ja", "de"]`、またはUIの同等の入力は完全な順序付きリストです。小文字に正規化されてi18nを有効にし、`docs-ja`、続いて`docs-de`を作成します。
- **プリセットよりCLIを優先:** プリセットとCLIの両方に`--additional-langs`がある場合、CLIのリストがプリセットを置き換えます。項目をマージや追加はしません。`--additional-langs`はプリセットや`--no-i18n`が無効を指定していてもi18nを有効にします。明示的な`--no-i18n`を上書きした場合、CLIは警告を表示します。
- **検証:** コードは`^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$`に一致し、プライマリコードと異なり、重複してはいけません。パス区切り文字、`..`、空白、アンダースコア、シェル記号、空の明示エントリはファイル書き込み前に拒否されます。これによりロケール値をディレクトリ名とURLセグメントとして安全に扱えます。

明示リストには完全なロケール計画を記述する情報があるため、これが優先されます。旧来の推論は、以前のブール値形式のi18n選択を提供する呼び出し元のためだけに残っています。

## ディレクトリ構造

各設定ロケールのツリーをプライマリツリーと揃えます。文章を翻訳し、ファイル名、コードブロック、JSX例は同じにします。

```
src/content/
├── docs/
│   ├── getting-started/
│   │   ├── introduction.mdx
│   │   └── installation.mdx
│   └── guides/
│       └── configuration.mdx
├── docs-ja/
│   ├── getting-started/
│   │   ├── introduction.mdx
│   │   └── installation.mdx
│   └── guides/
│       └── configuration.mdx
└── docs-de/
    ├── getting-started/
    │   ├── introduction.mdx
    │   └── installation.mdx
    └── guides/
        └── configuration.mdx
```

コードが`ja`の場合、スキャフォールドは日本語のスターター文章を配置します。日本語以外の任意のロケールコードでは、ルートをすぐ利用できるよう英語のプレースホルダー文章を配置します。すでに翻訳済みとは考えず、翻訳に置き換えてください。

## 言語切り替え

複数のロケールリンクが利用可能な場合、言語切り替えはヘッダー右端に表示されます。設定済みラベルを順番に並べ、現在のロケールをアクティブ項目として示し、他の各ロケールを同等ページへリンクします。同一ロケールのSPA遷移後も、バージョンプレフィックスやサイトベースを含めてリンクを再計算します。

言語切り替えにはCSSによるホバー/フォーカスのフォールバックもあります。JavaScriptを無効にしても、ポインターホバーまたは通常のキーボードフォーカス/Tab移動で利用可能なロケールアンカーを表示できます。狭い画面では、同じリンクがモバイルサイドバーのフッターにインライン表示されます。

`defaultLocaleOnlyPrefixes`に一致するページは、別ロケールのルートがないためアクティブなリンク1つに縮退します。生成されたClaude/Codexリソースとアセットビューアーのページは、デフォルトでは設定済みの全ロケールに存在するため、言語切り替えには各ロケールへのリンクが残ります。

## UI翻訳のフォールバック

組み込みUI文字列は、要求されたロケール、設定済みデフォルトロケール、パッケージの英語テーブル、最後に生のキーの順で解決されます。

```text
requested locale → default locale → package English → raw UI-string key
```

プロジェクトは翻訳済みの文字列だけを設定できます。ロケールラベルとコンテンツディレクトリのパスは設定駆動であり、翻訳テーブルには依存しません。

パッケージが完全な組み込みUIテーブルを同梱するのは英語と日本語です。ジェネレーターの`SUPPORTED_LANGS`に含まれる他のロケールコードまで翻訳済みという意味ではありません。プロジェクトが翻訳を渡さなければ、同じく要求ロケール → 設定済みデフォルト → パッケージ英語 → 生のキーという順で解決されます。

### HtmlPreviewコントロール

ルートにバインドされた MDX の`HtmlPreview`バインディングは、アクティブなページのロケールを次の7つの`htmlPreview.*`キーに対応付けます。

| 翻訳キー | 用途 |
|----------|------|
| `htmlPreview.viewport.mobile` | Mobileビューポートボタン |
| `htmlPreview.viewport.tablet` | Tabletビューポートボタン |
| `htmlPreview.viewport.full` | Full幅ビューポートボタン |
| `htmlPreview.viewport.label` | ビューポートプリセットグループの`aria-label` |
| `htmlPreview.source.show` | 折りたたまれたソース切り替えのラベル |
| `htmlPreview.source.hide` | 展開されたソース切り替えのラベル |
| `htmlPreview.iframe.title` | `title`を省略したときのiframeタイトル |

各キーは同じ検索順に従います。要求されたロケール → 設定済みの`defaultLocale` → パッケージの英語テーブル → 生のキーです。プロジェクトは翻訳するラベルだけを指定できます。ロケールごとの上書きは`translations`で設定します。

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    translations: {
      fr: {
        "htmlPreview.viewport.mobile": "Téléphone",
        "htmlPreview.viewport.tablet": "Tablette",
        "htmlPreview.viewport.full": "Plein écran",
        "htmlPreview.viewport.label": "Taille de la fenêtre",
        "htmlPreview.source.show": "Afficher le code",
        "htmlPreview.source.hide": "Masquer le code",
        "htmlPreview.iframe.title": "Aperçu",
      },
    },
  }),
);
```

呼び出し単位の`labels`プロップはロケールのデフォルトの後にキー単位でマージされます。指定したキーはロケール値を上書きし、省略したキーや`undefined`を設定したキーはロケール値を維持します。ルートにバインドされていないコンポーネントを直接インポートする場合は要求ロケールがないため、`labels`を渡さない限り組み込みの英語ラベルが使われます。技術的なコードパネル見出し`HTML`、`CSS`、`Head`、`JS`は固定されており、この翻訳APIの対象には意図的に含まれません。

表示専用のフラグは翻訳とは独立しており、`showSource`と`showViewportControls`はどちらもデフォルトが`true`です。`showSource={false}`を設定すると、iframeを残したままソース切り替え、コードパネル、ハイライト用マークアップを構造的に削除します。その場合`defaultOpen`は効果がありません。`showViewportControls={false}`を設定するとプリセットグループを削除しますが、Full幅のコンテナとドラッグリサイズ操作は残ります。2つのフラグは同時に`false`にでき、iframeは残ります。ビューポートコントロールが表示されているか空でない`title`が指定されている場合はタイトルバーが表示されます。両方のコントロールを隠してタイトルも指定しなければ、空のバーは省略されます。

生成リソースの概要とカテゴリラベルは`resource.*`名前空間を、アセット一覧とビューアーのクロームは`asset.*`名前空間を使用します。どちらも`ZudoDocConfig.translations`で上書きできます。

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    translations: {
      ja: {
        "resource.claude.description": "チーム用 Claude Code リファレンス。",
        "asset.details": "ファイル情報",
      },
    },
  }),
);
```

## ロケールを追加する

ドイツ語などのロケールを追加するには、次の手順を実行します。

1. 正確なコード、表示ラベル、コンテンツディレクトリを`locales`に追加します。ホームページのヒーローの説明文も翻訳する場合は、`description`を指定します。

   ```ts
   locales: {
     ja: { label: "JA", dir: "src/content/docs-ja" },
     de: {
       label: "DE",
       dir: "src/content/docs-de",
       description: "Dokumentation zur Verwendung des Projekts.",
     },
   },
   ```

2. `src/content/docs-de/`を作成し、プライマリのディレクトリツリーをミラーします。
3. ファイル名、コードブロック、JSXブロックを保持したままMDXページを翻訳します。
4. `de`用のプロジェクト固有UI翻訳を追加します。不足するキーは上記のフォールバックチェーンに従います。

[Dynamic content unavailable in text export]

`locales`マップが唯一の情報源です。エントリを追加するとコレクション、ルート、切り替えリンクが作成されます。ロケールごとのルートファイルや手書きコレクションは必要ありません。



## `defaultLocale`

`defaultLocale`はURLプレフィックスなしで配信するロケールを指定します。デフォルトは`"en"`で、`locales`のキーではなくプライマリロケールコードでなければなりません。

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    defaultLocale: "en",
    locales: {
      ja: { label: "JA", dir: "src/content/docs-ja" },
      de: { label: "DE", dir: "src/content/docs-de" },
    },
  }),
);
```

プライマリロケールは`/docs/...`で配信され、追加ロケールは`/ja/docs/...`や`/de/docs/...`のようにコードを使用します。これは`defaultLocale`と`pages/`のルート分割（`pages/docs/[[...slug]].tsx`および`pages/[locale]/docs/[[...slug]].tsx`）から導かれ、`prefixDefaultLocale`オプションはありません。

## ロケールごとの日付形式

`dateFormat` はロケールごとの上書きを受け付けます。多言語サイトでは通常こちらの形式を使うことになります。`Aug 22, 2026` も `2026年8月22日` もそれぞれのロケールでは正しく、1 つのパターンで両方を賄うことはできないからです。

日付が表示されるすべての箇所で、単一ロケールサイトを含め、レンダリングロケールはページのロケールになり、ロケール切り替えの有無から決まることはなくなりました。

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    defaultLocale: "en",
    locales: {
      ja: { label: "JA", dir: "src/content/docs-ja" },
      de: { label: "DE", dir: "src/content/docs-de" },
    },
    dateFormat: {
      full: "MMM D, YYYY",
      locales: {
        ja: { full: "YYYY年M月D日", yearMonth: "YYYY年M月" },
      },
    },
  }),
);
```

各ロールは `locales[locale][role]` → トップレベルの `[role]` → `"locale"` の順に解決されます。`dateFormat.locales` にエントリーのないロケールは、トップレベルのロールをそのまま使います。5 つのロール、トークンの語彙、その他の規則は [`dateFormat`](https://zudo-doc.takazudomodular.com/ja/docs/guides/configuration#%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E3%81%A8-i18n-dateformat) を参照してください。

[Dynamic content unavailable in text export]

月**名**、つまり `MMM`・`MMMM` トークンと組み込みの `"locale"` 表示は、`en` → `en-US`、`ja` → `ja-JP`、`de` → `de-DE` という固定のマップを通して解決されます。それ以外のロケールコードはすべて `en-US` にフォールバックします。

そのため、マップにないロケール（たとえば `fr`）では次のようになります。

- `"DD/MM/YYYY"` のような数値だけのパターンは正しく表示されます。数字はロケールに依存しないためです。
- `MMM` や `MMMM` を含むパターンは、月名が**英語**のままになります。
- ロールを `"locale"` のままにすると、そのロールは `en-US` の形式で表示されます。

こうしたロケールには数値のパターンを与えてください。月名を含むパターンではその言語の表記になりません。



## 生成ページとデフォルトロケールの本文

Claude/Codexリソースジェネレーターは、概要ページとカテゴリインデックスを設定済みの各ロケールディレクトリに出力します。アセットビューアーも`/{locale}/${assetViewerRoutePrefix}/...`にルートを生成します。タイトル、説明、ナビゲーション、パンくず、ヘッダー、操作ラベルなどのシェルは要求ロケールで解決されます。そのため言語切り替えはデフォルトロケールだけに縮退せず、対応する生成ルートへリンクします。

一方、リソースの**詳細本文**は設計上、設定済みのデフォルトロケールが所有します。これらは翻訳済みドキュメントではなく、プロジェクトソースの正本となるダンプです。本文の言語はソースコーパスに従うため、必ずしも英語ではありません。このリポジトリで実測したリソースコーパスでは、ロケールごとにコピーすると、追加ロケールごとにディスク上で約530 KBずつ重複するうえ、`plugins/internal/llms-txt/load.ts`がロケールディレクトリを直接走査して省略なしの本文を出力するため、各`dist/{locale}/llms-full.txt`でも同じ内容が重複します。また、他の未翻訳ページが従うフォールバック規約とも矛盾します。ロケール付きルートでは、デフォルトロケールの本文をローカライズ済みクロームの中に表示し、通常の未翻訳ページ用バナーは意図的に表示しません。

この設計には、意図したインデックス上の非対称性があります。各ロケールの`llms.txt`と検索には、ローカライズされたリソース概要ページが含まれますが、ルートフォールバックで提供される詳細本文は含まれません。どちらのジェネレーターも、そのロケールのコンテンツディレクトリに実在するファイルを対象とするため、他のフォールバックページと同じ挙動です。なお、検索インデックスは重複量の懸念ではありません。各ページの本文の抜粋は、設定可能な[`searchMaxBodyLength`](https://zudo-doc.takazudomodular.com/ja/docs/guides/configuration#%E6%A4%9C%E7%B4%A2-searchmaxbodylength)（デフォルト3,000文字）までに制限され、全文は保存されません。

## デフォルトロケール専用プレフィックス

翻訳がなく、プライマリロケールにだけ存在させたいコンテンツがあります。そのURLプレフィックスを`defaultLocaleOnlyPrefixes`に設定します。

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    defaultLocaleOnlyPrefixes: [
      "/files/",
      "/docs/claude-md/",
      "/docs/claude-skills/",
      "/docs/claude-agents/",
      "/docs/claude-commands/",
      "/docs/codex-agents-md/",
      "/docs/codex-config/",
      "/docs/codex-agents/",
      "/docs/codex-hooks/",
      "/docs/codex-rules/",
      "/docs/codex-skills/",
    ],
  }),
);
```

これらのプレフィックス配下のページはプライマリロケールにだけ出力されます。非プライマリのルート一覧から除外され、言語切り替えは現在のアクティブリンクだけを出力します。プレフィックスは末尾をスラッシュにし、翻訳対象のページまで隠さない範囲に絞ってください。ドキュメントルートは`/docs/`を起点とし、アセットビューアーには`/${assetViewerRoutePrefix}/`（上の例では`/files/`）を指定します。

これらはオプトアウト用の設定例であり、パッケージのデフォルト値ではありません。アセットページをデフォルトロケール限定に戻すには`/${assetViewerRoutePrefix}/`を追加し、リソースについては制限したいプレフィックスだけを追加します。ローカライズされたランディングページを残す場合は、トップレベルの`/docs/claude/`と`/docs/codex/`を制限しないでください。

### リソースセクションには概要プレフィックスも含まれる

上に挙げた4つのサブセクションプレフィックスだけでは、Claudeセクション全体にはなりません。概要ページは`/docs/claude/`（Codexなら`/docs/codex/`）という独立したプレフィックスを持つため、これを外した設定では、サブセクションだけが制限され、概要ページはローカライズされたままになります。

この違いはナビゲーションだけでなくビルドにも影響します。概要インデックスはロケール所有です。プレフィックスを列挙していないと、ジェネレーターは設定済みの各ロケールディレクトリへ概要インデックスを書き出します。その出力先に`generated: true`のない手書きの`src/content/docs-<locale>/claude/index.mdx`が既にある場合、ビルドは次のエラーで停止します。

```text
resource-docs: refusing to overwrite authored locale index
```

どちらの形にするかを決め、セクション全体に同じ方針を適用してください。

- **デフォルトロケール限定にする** — 概要プレフィックスをサブセクションのプレフィックスと合わせて列挙し、`/docs/claude/`配下を他ロケールに生成させません。
- **ローカライズする** — プレフィックスを列挙せず、手書きのロケールインデックスを削除またはリネームしてジェネレーターに任せます。タイトル・説明・ラベルは`ZudoDocConfig.translations`へ移します。