---
title: Internationalization (i18n)
description: ICU MessageFormat 2 messages, multi-locale dictionaries, a static checker, a runtime module, and editor tooling.
---

# Internationalization (i18n)

Ox Content ships a complete i18n toolkit built around **ICU MessageFormat 2
(MF2)**:

- Multi-locale **dictionaries** loaded from JSON/YAML with nested keys.
- A hand-written **MF2 parser** (simple messages, `.input`/`.local`
  declarations, `.match` variants).
- A build-time **static checker** (missing keys, unused keys, variable
  mismatches across locales, MF2 syntax errors).
- A **runtime virtual module** with `t()` and `Intl`-backed formatters.
- A **CLI** (`vpx oxct i18n`) and an **LSP** for editor integration.

This documentation site uses the file-tree locale layout: English pages live
at `/…`, Japanese pages at `/ja/…`. The header locale switcher jumps to the
sibling page when it exists. Sidebar and header links stay on the current
locale when that sibling exists. Japanese guides cover the user-facing
contract; generated API pages stay English.

## Enable

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

export default {
  plugins: [
    oxContent({
      i18n: {
        enabled: true,
        dir: "content/i18n",
        defaultLocale: "en",
        locales: [
          { code: "en", name: "English" },
          { code: "ja", name: "日本語" },
          { code: "ar", name: "العربية", dir: "rtl" },
        ],
      },
    }),
  ],
};
```

### Options

| Option              | Default          | Description                                                                                                   |
| ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------- |
| `enabled`           | `false`          | Enable i18n.                                                                                                  |
| `dir`               | `'content/i18n'` | Dictionary directory, relative to the project root.                                                           |
| `defaultLocale`     | `'en'`           | Default locale tag (BCP 47).                                                                                  |
| `locales`           | —                | Available locales: `{ code, name, dir? }` (`dir` is `'ltr'`/`'rtl'`).                                         |
| `hideDefaultLocale` | `true`           | When true, `/page` serves the default locale and `/ja/page` is prefixed; when false every locale is prefixed. |
| `check`             | `true`           | Run the static checker during build.                                                                          |
| `functionNames`     | `['t', '$t']`    | Translation function names to detect when scanning source for used keys.                                      |

## Dictionaries

Each locale is a JSON or YAML file under `dir`. Nested keys are flattened with
dots, so the dictionary and your call sites share one key space:

```yaml
# content/i18n/en.yaml
nav:
  home: "Home"
  docs: "Documentation"
cart:
  items: "{$count :number} items in your cart"
```

```jsonc
// content/i18n/ja.json
{
  "nav": { "home": "ホーム", "docs": "ドキュメント" },
  "cart": { "items": "カートに {$count :number} 件" },
}
```

Missing keys in a non-default locale fall back to the default locale at runtime.

## MessageFormat 2 messages

Values are MF2 messages. Beyond plain text:

```text
# Interpolation with a formatting function
{$count :number} items

# Declarations + a match (pluralization / selection)
.input {$count :number}
.match $count
  one  {{You have {$count} item.}}
  *    {{You have {$count} items.}}
```

Validate a message from the CLI:

```bash
vpx oxct i18n validate "{$count :number} items"
vpx oxct i18n validate ".match {$n}\n one {{1}}\n * {{many}}" --ast
```

## Static checking

With `check: true`, the build scans your source for used keys and compares them
against the dictionaries, reporting four classes of problem:

- **Missing keys** — used in code but absent from a locale.
- **Unused keys** — present in dictionaries but never referenced.
- **Type mismatches** — a placeholder/variable set differs across locales for
  the same key.
- **Syntax errors** — invalid MF2 in a dictionary value.

Run it standalone via the `oxct` binary installed by `@ox-content/vite-plugin`
(also handy in CI; errors exit non-zero, while warning-only findings exit successfully):

```bash
vpx oxct i18n check --dict-dir content/i18n --src src
vpx oxct i18n check --dict-dir content/i18n --src src --format json
vpx oxct i18n check --dict-dir content/i18n --src src --default-locale en
```

Keys are collected from TS/JS call sites (`t(...)`, `$t(...)`, `this.t(...)`,
`i18n.t(...)`) via the OXC parser and from Markdown (`{{t(...)}}`); customize the
detected function names with `functionNames`.

## Runtime module

Importing the virtual module gives you translation and formatting helpers backed
by the loaded dictionaries and the platform `Intl` APIs:

```ts
import {
  t,
  localePath,
  getLocaleFromPath,
  formatDate,
  formatNumber,
  formatRelativeTime,
  formatList,
  formatDisplayName,
  i18nConfig,
} from "virtual:ox-content/i18n";

t("cart.items", { count: 3 }); // "3 items in your cart"
t("nav.home", {}, "ja"); // force a locale → "ホーム"

localePath("/docs", "ja"); // "/ja/docs"
getLocaleFromPath("/ja/docs"); // "ja"

formatNumber(1234.5, "ja"); // "1,234.5"
formatRelativeTime(-2, "day", "en"); // "2 days ago"
```

`Intl` formatters are cached per locale, and locale metadata carries the
optional text direction so RTL locales render correctly.

## Editor tooling

The bundled language server (`vpx oxct lsp`) provides dictionary-key
completion, hover previews of every locale's translation, go-to-definition into
the dictionary file, inlay hints showing the default-locale value, and the same
missing/unused-key diagnostics as the checker. It runs over stdio:

```bash
vpx oxct lsp
```

and integrates with VS Code, Zed, and Neovim — see the editor tooling section of
the project README for wiring instructions.
