zudo-doc
GitHub リポジトリ

検索したい単語を入力

いつでも検索バーを開ける

国際化(i18n)

作成 2026年3月11日更新 2026年9月8日Takeshi Takatsudo
タグ:#i18n

ドキュメントに多言語サポートを追加する

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で設定します。

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の各エントリにはlabeldirが必須です。この例では、日本語のヒーローには日本語の説明文が表示され、ドイツ語では共通のsiteDescriptionが使われます。

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

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

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

  • docs-{code}という名前のzfbコンテンツコレクションを作成

  • ロケール対応ルーティングにコードを登録

  • 対応するページルートを生成

  • 設定したlabelを言語切り替えに追加

コンポーネント内でJPJA、または固定の言語リンク一覧をハードコードしないでください。切り替えはマップ順に設定済みラベルを表示するため、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,deadditionalLangs: ["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文字列は、要求されたロケール、設定済みデフォルトロケール、パッケージの英語テーブル、最後に生のキーの順で解決されます。

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

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

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

HtmlPreviewコントロール

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

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

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

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を渡さない限り組み込みの英語ラベルが使われます。技術的なコードパネル見出しHTMLCSSHeadJSは固定されており、この翻訳APIの対象には意図的に含まれません。

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

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

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

ロケールを追加する

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

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

    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翻訳を追加します。不足するキーは上記のフォールバックチェーンに従います。

Tip

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

defaultLocale

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

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/...のようにコードを使用します。これはdefaultLocalepages/のルート分割(pages/docs/[[...slug]].tsxおよびpages/[locale]/docs/[[...slug]].tsx)から導かれ、prefixDefaultLocaleオプションはありません。

ロケールごとの日付形式

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

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

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 を参照してください。

月名がローカライズされるのは 3 ロケールのみ

、つまり MMMMMMM トークンと組み込みの "locale" 表示は、enen-USjaja-JPdede-DE という固定のマップを通して解決されます。それ以外のロケールコードはすべて en-US にフォールバックします。

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

  • "DD/MM/YYYY" のような数値だけのパターンは正しく表示されます。数字はロケールに依存しないためです。

  • MMMMMMM を含むパターンは、月名が英語のままになります。

  • ロールを "locale" のままにすると、そのロールは en-US の形式で表示されます。

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

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

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

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

この設計には、意図したインデックス上の非対称性があります。各ロケールのllms.txtと検索には、ローカライズされたリソース概要ページが含まれますが、ルートフォールバックで提供される詳細本文は含まれません。どちらのジェネレーターも、そのロケールのコンテンツディレクトリに実在するファイルを対象とするため、他のフォールバックページと同じ挙動です。なお、検索インデックスは重複量の懸念ではありません。search-index/types.tsMAX_BODY_LENGTHにより、本文の抜粋は300文字に制限されます。

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

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

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/を制限しないでください。

Revision History

Takeshi Takatsudo作成: 2026-03-12T03:34:07+09:00更新: 2026-09-09T05:36:49+09:00

AI Assistant

Ask a question about the documentation.

Preview theme

Loading theme previews…