# i18n（国際化）

ICU MessageFormat 2、ビルド時チェック、ロケール対応ルーティングを備えた、Ox Content の組み込み国際化です。

## セットアップ

`vite.config.ts` で i18n を有効にします。

```ts
// vite.config.ts
import { defineConfig } from "vite";
import { oxContent } from "@ox-content/vite-plugin";

export default defineConfig({
  plugins: [
    oxContent({
      i18n: {
        enabled: true,
        defaultLocale: "en",
        locales: [
          { code: "en", name: "English" },
          { code: "ja", name: "日本語" },
        ],
      },
    }),
  ],
});
```

## オプション

| オプション          | 型               | 既定             | 説明                                               |
| ------------------- | ---------------- | ---------------- | -------------------------------------------------- |
| `enabled`           | `boolean`        | `false`          | i18n の有効 / 無効                                 |
| `dir`               | `string`         | `'content/i18n'` | 辞書ディレクトリへのパス（プロジェクトルート相対） |
| `defaultLocale`     | `string`         | `'en'`           | 既定ロケールタグ                                   |
| `locales`           | `LocaleConfig[]` | `[]`             | 利用可能なロケール                                 |
| `hideDefaultLocale` | `boolean`        | `true`           | URL で既定ロケールの接頭辞を隠す                   |
| `check`             | `boolean`        | `true`           | ビルド時に i18n チェックを実行する                 |
| `functionNames`     | `string[]`       | `['t', '$t']`    | ソースコードで検出する翻訳関数名                   |

### hideDefaultLocale

`true`（既定）のとき、既定ロケールには URL 接頭辞が付きません。

- `/page` は既定ロケール（`en`）を返します
- `/ja/page` は日本語ロケールを返します

`false` のとき、すべてのロケールに接頭辞が付きます。

- `/en/page` は英語を返します
- `/ja/page` は日本語を返します

## LocaleConfig

```ts
interface LocaleConfig {
  /** BCP 47 locale tag (e.g., 'en', 'ja', 'zh-Hans') */
  code: string;
  /** Display name for this locale (e.g., 'English', '日本語') */
  name: string;
  /** Text direction. @default 'ltr' */
  dir?: "ltr" | "rtl";
}
```

RTL 対応の例:

```ts
locales: [
  { code: "en", name: "English" },
  { code: "ja", name: "日本語" },
  { code: "ar", name: "العربية", dir: "rtl" },
];
```

## 辞書の構造

辞書は `dir` ディレクトリ内でロケールごとに整理します。

```
content/i18n/
  en/
    common.json
    navigation.json
    messages.yaml
  ja/
    common.json
    navigation.json
    messages.yaml
```

各ファイルが名前空間になります。たとえば `common.json` は `common.` で始まるキーを作ります。

### JSON 形式

```json
{
  "greeting": "Hello {$name}",
  "farewell": "Goodbye",
  "nav": {
    "home": "Home",
    "about": "About"
  }
}
```

平坦化されたキーは `common.greeting`、`common.farewell`、`common.nav.home`、`common.nav.about` になります。

### YAML 形式

```yaml
greeting: "Hello {$name}"
farewell: "Goodbye"
nav:
  home: "Home"
  about: "About"
```

## ICU MessageFormat 2

辞書の値は [ICU MessageFormat 2](https://github.com/unicode-org/message-format-wg) 構文を使えます。

### 単純な変数

```
Hello {$name}
```

### 複数形 / マッチ

```
.input {$count :number}
.match $count
one {{You have {$count} item.}}
* {{You have {$count} items.}}
```

### ローカル宣言

```
.local $host = {$name}
.local $guest = {$other}
{{Welcome {$host} and {$guest}!}}
```

## 仮想モジュール

プラグインは、翻訳ユーティリティ付きの `virtual:ox-content/i18n` モジュールを提供します。

```ts
import {
  t,
  createIntl,
  getLocaleFromPath,
  localePath,
  i18nConfig,
  dictionaries,
} from "virtual:ox-content/i18n";
```

### t(key, params?, locale?)

任意のパラメーター置換付きでキーを翻訳します。

```ts
t("common.greeting", { name: "World" }); // "Hello World"
t("common.greeting", { name: "World" }, "ja"); // "こんにちは World"
```

### getLocaleFromPath(pathname)

URL パス名からロケールコードを取り出します。

```ts
getLocaleFromPath("/ja/about"); // 'ja'
getLocaleFromPath("/about"); // 'en' (default locale)
```

### localePath(pathname, locale)

指定ロケール向けのローカライズパスを組み立てます。`hideDefaultLocale` を尊重します。

```ts
localePath("/about", "ja"); // '/ja/about'
localePath("/about", "en"); // '/about' (when hideDefaultLocale is true)
```

### Intl ヘルパー

仮想モジュールには、リッチなローカライズ UI 向けの Intl ベース整形ヘルパーがあります。

```ts
const ja = createIntl("ja-JP", { date: { timeZone: "Asia/Tokyo" } });
ja.date(new Date(), { dateStyle: "long" });
ja.number(1234.5, { style: "currency", currency: "JPY" });
ja.relativeTime(-1, "day", { numeric: "auto" });
ja.list(["docs", "api", "cli"]);
ja.displayName("en-US", "language");
```

### i18nConfig

解決済みの i18n 設定オブジェクトです。

```ts
const { enabled, defaultLocale, locales, hideDefaultLocale } = i18nConfig;
```

### dictionaries

読み込んだすべての辞書を、ロケールごとの平坦なキー・値マップとして持ちます。

```ts
// Record<string, Record<string, string>>
const { en, ja } = dictionaries;
console.log(en["common.greeting"]); // "Hello {$name}"
```

## ビルド時チェック

`check` が有効（既定）のとき、プラグインはビルド時に静的解析を行い、次を報告します。

| チェック   | 重大度  | 説明                                                  |
| ---------- | ------- | ----------------------------------------------------- |
| 欠落キー   | Error   | ソースコードで使われているが、辞書にないキー          |
| 未使用キー | Warning | 辞書にあるが、ソースコードで使われていないキー        |
| 型の不一致 | Error   | 同じキーで MF2 プレースホルダー変数がロケール間で違う |
| 構文エラー | Error   | 辞書の値の MF2 構文が不正                             |

### 出力例

```
[ox-content:i18n] error: Missing key 'common.title' in locale 'ja'
[ox-content:i18n] warning: Unused key 'common.legacy' in locale 'en'
[ox-content:i18n] error: Type mismatch for key 'common.greeting': locale 'en' uses {$name, $count}, locale 'ja' uses {$name}
```

## 翻訳キーの抽出

チェッカーは、ソースファイルから翻訳キーの使用を自動で走査します。

### TypeScript / JavaScript

```ts
t("common.greeting");
$t("common.greeting");
this.t("common.greeting");
i18n.t("common.greeting");
```

### Markdown

```markdown
{{t('common.greeting')}}
{{ $t('nav.home') }}
```

スキャナーは `src/` の `.ts`、`.tsx`、`.js`、`.jsx` と、`content/` の `.md`、`.mdx` を探します。

## NAPI API

プログラムから使う場合、次の関数が `@ox-content/napi` から使えます。

### loadDictionaries(dir)

ディレクトリから辞書を読み込み、メタデータを返します。

```ts
import { loadDictionaries } from "@ox-content/napi";

const result = loadDictionaries("content/i18n");
// { localeCount: 2, locales: ['en', 'ja'], errors: [] }
```

### loadDictionariesFlat(dir)

辞書を読み込み、ロケールごとの平坦なキー・値マップを返します。

```ts
import { loadDictionariesFlat } from "@ox-content/napi";

const dicts = loadDictionariesFlat("content/i18n");
// { en: { 'common.greeting': 'Hello {$name}', ... }, ja: { ... } }
```

### validateMf2(message)

ICU MessageFormat 2 文字列を検証します。

```ts
import { validateMf2 } from "@ox-content/napi";

const result = validateMf2("Hello {$name}");
// { valid: true, errors: [], astJson: '...' }

const invalid = validateMf2("Hello {$name");
// { valid: false, errors: ['...'], astJson: null }
```

### checkI18n(dictDir, usedKeys)

指定した辞書ディレクトリと使用キーに対して、すべての i18n チェックを実行します。

```ts
import { checkI18n } from "@ox-content/napi";

const result = checkI18n("content/i18n", ["common.greeting", "nav.home"]);
// { diagnostics: [...], errorCount: 0, warningCount: 1 }
```

### extractTranslationKeys(source, filePath, functionNames?)

TypeScript / JavaScript ソースから翻訳キーを抽出します。

```ts
import { extractTranslationKeys } from "@ox-content/napi";

const keys = extractTranslationKeys(`const msg = t('common.greeting');`, "src/App.tsx", [
  "t",
  "$t",
]);
// [{ key: 'common.greeting', filePath: 'src/App.tsx', line: 1, column: 18, endColumn: 35 }]
```

## CLI と LSP

### CLI

`@ox-content/vite-plugin` は、単体の i18n チェック用に `oxct` バイナリをインストールします。

```bash
vpx oxct i18n check --dict content/i18n --src src
```

### LSP サーバー

エディタ連携用の LSP サーバーがあり、`t()` 呼び出し内の翻訳キー補完を提供します。
