# i18n (Internationalization)

Built-in internationalization support for Ox Content with ICU MessageFormat 2, build-time checking, and locale-aware routing.

## Setup

Enable i18n in your `vite.config.ts`:

```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: "日本語" },
        ],
      },
    }),
  ],
});
```

## Options

| Option              | Type             | Default          | Description                                             |
| ------------------- | ---------------- | ---------------- | ------------------------------------------------------- |
| `enabled`           | `boolean`        | `false`          | Enable/disable i18n                                     |
| `dir`               | `string`         | `'content/i18n'` | Path to dictionary directory (relative to project root) |
| `defaultLocale`     | `string`         | `'en'`           | Default locale tag                                      |
| `locales`           | `LocaleConfig[]` | `[]`             | Available locales                                       |
| `hideDefaultLocale` | `boolean`        | `true`           | Hide default locale prefix in URLs                      |
| `check`             | `boolean`        | `true`           | Run i18n checks during build                            |
| `functionNames`     | `string[]`       | `['t', '$t']`    | Translation function names to detect in source code     |

### hideDefaultLocale

When `true` (default), the default locale does not have a URL prefix:

- `/page` serves the default locale (`en`)
- `/ja/page` serves the Japanese locale

When `false`, all locales get prefixed:

- `/en/page` serves English
- `/ja/page` serves Japanese

## 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";
}
```

Example with RTL support:

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

## Dictionary Structure

Dictionaries are organized by locale in the `dir` directory:

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

Each file becomes a namespace. For example, `common.json` produces keys prefixed with `common.`:

### JSON Format

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

This produces flattened keys: `common.greeting`, `common.farewell`, `common.nav.home`, `common.nav.about`.

### YAML Format

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

## ICU MessageFormat 2

Dictionary values support [ICU MessageFormat 2](https://github.com/unicode-org/message-format-wg) syntax.

### Simple Variables

```
Hello {$name}
```

### Plural / Match

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

### Local Declarations

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

## Virtual Module

The plugin provides a `virtual:ox-content/i18n` module with translation utilities.

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

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

Translates a key with optional parameter substitution.

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

### getLocaleFromPath(pathname)

Extracts the locale code from a URL pathname.

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

### localePath(pathname, locale)

Constructs a localized path for a given locale. Respects `hideDefaultLocale`.

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

### Intl helpers

The virtual module includes Intl-backed formatting helpers for rich localized UI.

```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

The resolved i18n configuration object.

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

### dictionaries

All loaded dictionaries as a flat key-value map per locale.

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

## Build-Time Checking

When `check` is enabled (default), the plugin performs static analysis at build time and reports:

| Check         | Severity | Description                                                      |
| ------------- | -------- | ---------------------------------------------------------------- |
| Missing keys  | Error    | Keys used in source code but missing from dictionaries           |
| Unused keys   | Warning  | Keys defined in dictionaries but not used in source code         |
| Type mismatch | Error    | MF2 placeholder variables differ across locales for the same key |
| Syntax errors | Error    | Invalid MF2 syntax in dictionary values                          |

### Example Output

```
[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}
```

## Translation Key Extraction

The checker automatically scans source files for translation key usage.

### 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') }}
```

The scanner searches `src/` for `.ts`, `.tsx`, `.js`, `.jsx` files and `content/` for `.md`, `.mdx` files.

## NAPI API

The following functions are available from `@ox-content/napi` for programmatic use:

### loadDictionaries(dir)

Loads dictionaries from a directory and returns metadata.

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

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

### loadDictionariesFlat(dir)

Loads dictionaries and returns flattened key-value maps per locale.

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

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

### validateMf2(message)

Validates an ICU MessageFormat 2 string.

```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)

Runs all i18n checks against the given dictionary directory and used keys.

```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?)

Extracts translation keys from TypeScript/JavaScript source code.

```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` installs the `oxct` binary for standalone i18n checking:

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

### LSP Server

An LSP server is available for editor integration, providing translation key completion inside `t()` calls.
