# 多言語対応（i18n）

Ovellum は同じサイトを複数の言語で公開できます。これは**オプトイン**です。単一言語のサイトはロケールフォルダを必要とせず、これまでとまったく同じように動作します。言語を宣言してオンにすると、コンテンツは言語ごとに 1 つのサブツリーへ移動します。

## 有効にする

config に [`site.locales`](/ja/docs/reference/config/)（および必要に応じて `site.defaultLocale`）を追加します。

```ts
export default {
  site: {
    defaultLocale: 'en-US',
    locales: [
      { code: 'en-US', label: 'English' },
      { code: 'ja', label: '日本語' },
      { code: 'zh-Hans', label: '简体中文' },
    ],
  },
} satisfies OvellumUserConfig;
```

- **`code`** は [BCP 47](https://www.w3.org/International/articles/language-tags/) の言語タグです — `en-US`、`ja`、`zh-Hans` / `zh-Hant`、`de`、`pt-BR`。これはコンテンツフォルダ名であり、`<html lang>` でもあります。
- **`label`** はピッカーが表示するものです — その言語の**自称**（その言語自身の名前: `日本語`、`简体中文`、`English`）を使ってください。読者は自分の言語をその固有の文字体系で探すからです。
- **`defaultLocale`** はサイトのルートで配信されます。デフォルトは `locales` の最初のエントリです。

> 正しいコードを使ってください。イギリス英語は **`en-GB`**（`en-uk` ではない）、日本語は **`ja`**（`jp` ではない）、中国語は国コードではなく**スクリプト**サブタグ — `zh-Hans`（簡体字）または `zh-Hant`（繁体字）— を使います。

## ロケールごとにコンテンツを整理する

ロケールをオンにすると、コンテンツは言語ごとに 1 つのサブツリーへ、そのコードを名前として配置されます。

```
content/
  public/              ← shared across all locales (copied to the root)
  en-US/               ← the default locale
    _landing.md
    docs/
      getting-started.md
      guides/install.md
  ja/                  ← Japanese
    docs/
      getting-started.md
```

ページは**同一の相対パス**によって言語間で対応します。`en-US/docs/guides/install.md` は `ja/docs/guides/install.md` の翻訳です。このマッピングを言語ピッカーが追います。各ロケールは独自のサイドバー、`_meta.json` の順序、フロントマターを持ちます。

予約された [`publicDir`](/ja/docs/reference/config/)（`public/`）は**共有**のままです — これはロケールではなく、出力ルートに一度だけコピーされます。

### 既存サイトの移行

単一言語のサイトに i18n を追加するのは、一度きりの移動です。既存のコンテンツを `content/<defaultLocale>/` に入れ、`locales` の config を追加します。コンテンツのルートにあったファイル（たとえば `CNAME`）は `public/` に移し、引き続き出力ルートに届くようにします。ほかには何も変わりません。

## URL

デフォルトロケールは**ルート**で配信され、それ以外のロケールはすべてその**コード**の下で配信されます。

| ページ                        | en-US（デフォルト） | ja              |
| ----------------------------- | --------------- | --------------- |
| `docs/guides/install.md`      | `/docs/guides/install/` | `/ja/docs/guides/install/` |
| ホーム / ランディング         | `/`             | `/ja/`          |

なので、デフォルトロケールがすでに使っていた言語であれば、既存の URL は変わりません。

## 言語ピッカー

トップバー（ナビリンクの後、アイコンクラスターの前）にグローブのドロップダウンが現れ、すべてのロケールをそのラベルで一覧表示します。切り替えると、読者はその言語の**同じページ**に移動します。ページがまだ翻訳されていない場合、ピッカーはそのロケールの**ホーム**にフォールバックします — だから、わずかなページだけを翻訳した状態で言語を出荷し、時間をかけて育てていくことができます。

各ページには `<html lang>` も付き、`site.baseUrl` が設定されているときは `hreflang` の alternate リンク（およびデフォルトロケール用の `x-default`）も付くので、検索エンジンが正しい言語を配信します。

## 何がローカライズされ、何がまだされていないか

**ローカライズされる:** すべてのページコンテンツとフロントマター、サイドバー/ナビ、ページ URL、`<html lang>`、hreflang、サイトマップ、**そしてテンプレート自身の UI クロム** — 「このページの内容」、「最終更新」とその日付、「分で読めます」、外観パネルのラベル、前後リンク、404 ページなど。クロムは組み込みの言語セット（現在は英語と日本語）向けに翻訳済みで出荷されます。それ以外のロケールは文字列ごとに英語へフォールバックし、不足分は自分で埋められます（下記参照）。右から左に書く言語には `<html dir="rtl">` も自動的に付きます。

config 由来のテキスト — `ovellum.config.*` に書くランディングのヒーロー／CTA／機能のコピーや、`topbarNav` / `footerNav` のリンクラベル — もローカライズできます。これらのフィールドは、プレーンな文字列か、ロケールごとのマップ（下記）のどちらかを受け取ります。

各ロケールは独自の RSS フィードも得ます — デフォルト言語は `/feed.xml`、その他は
`/<code>/feed.xml` で、そのロケールのページに絞られます（サイトマップは 1 つの
統合ファイルのままです）。`site.baseUrl` が必要です。

### config テキストをローカライズする

config がユーザー向けのラベルやコピー文字列を受け取る箇所では、プレーンな文字列の代わりにロケールごとのマップを渡せます — ロケール `code` をキーにし、デフォルトロケールにフォールバックします:

```ts
topbarNav: [{ label: { 'en-US': 'Docs', ja: 'ドキュメント' }, href: '/docs/' }],
landing: {
  hero: {
    title: { 'en-US': "Docs that don't drift.", ja: 'ドリフトしないドキュメント。' },
    ctas: [{ label: { 'en-US': 'Get started', ja: 'はじめる' }, href: '/docs/' }],
  },
},
```

プレーンな文字列も引き続き使え、すべてのロケールで表示されます — なので、実際に翻訳する文字列だけをマップ化すればよいのです。対象は `topbarNav`/`footerNav` のラベルと、ランディングのヒーロータイトル／サブタイトル、CTA ラベル、機能のタイトル／説明、install のタイトル、トラストストリップのテキストです。

### クロム文字列を上書き・追加する

組み込みにないロケールや、別の言い回しにしたい場合は、ロケールに `strings` を設定します — 組み込みテーブルの上にマージされます（省略した分は英語が埋めます）:

```ts
site: {
  locales: [
    { code: 'en-US', label: 'English' },
    {
      code: 'fr',
      label: 'Français',
      strings: { tocTitle: 'Sur cette page', editedLabel: 'Modifié', backToTop: 'Haut de page' },
    },
  ],
}
```

キーは UI 文字列の名前（`tocTitle`、`editedLabel`、`minRead`、`previous`、`next`、`backToTop`、外観パネルのラベルなど）です。

## 翻訳はあなたが書くか生成するもの

Ovellum は各ロケールフォルダにある Markdown をそのままレンダリングします。手作業で翻訳を書いても、好きな方法で事前翻訳してファイルを置いても構いません — ツールはあなたの代わりに翻訳することはなく、邪魔もしません。

## 翻訳を同期させ続ける

手作業で維持する翻訳の難しさは、書くことではありません — **元（ソース）**のページが変わったのに翻訳が気づかぬうちに遅れていく、それに気づくことです。`ovellum check` はそのドリフトを見張ります。

各翻訳ページは、フロントマターに `sourceHash` を持てます — それがミラーするデフォルトロケールのページの指紋です（ロケールフォルダをまたいで同一のパスで対応づけられます。例: `ja/docs/install.md` ↔ `en-US/docs/install.md`）。`check` はソースの指紋を再計算して比較します:

- **ソースが変わっていない** → 何も報告しません。
- **スタンプ後にソースが変わった** → `[i18n]` として stale（古い）と報告します（終了コード `1`。CI が検知できます）。
- **まだ `sourceHash` がない** → 報告します。新しい翻訳がスタンプされるようにするためです。
- **対応するソースページがない** → orphan（みなしご）翻訳として報告します。

ハッシュを手で書く必要はありません。翻訳をソースに合わせ直したら、スタンプします:

```bash
ovellum check --update-translations
```

これは各翻訳ページに現在の `sourceHash` を書き込み（変更するのはそのフロントマター 1 行だけ）、終了します。指紋はフロントマターではなくページの**本文**を対象にし、改行コードを正規化します — そのため整形やフロントマターの微修正で誤って「stale」になることはありません。典型的な流れ: 英語ページを編集 → `ovellum check` が日本語のミラーを指摘 → 変更を翻訳 → `ovellum check --update-translations` で再スタンプ。
