# i18n

> Default. The locale registry: locales, default locale, display names, og:locale map and a flat translation table.

## What it does

**Isomorphic default.** `i18n` holds the site's locales and translations as plain config. It has
no state, no events and no lifecycle work beyond a check while `createApp` runs. `router`,
`head`, `content` and `build` read it with `ctx.require(i18nPlugin)`.

## How it works

`createApp` checks two rules: `locales` is not empty, and `defaultLocale` is one of them. Every
lookup after that is total: a miss returns `undefined`, and `t()` falls back.

`t(locale, key)` resolves in a fixed order: the requested locale, then the default locale, then
the key itself. It never returns `undefined`.

Inside the framework, plugins that find no `i18n` use the same defaults it ships with: one
locale, `"en"`. Because `i18n` is a default plugin, that only matters for plugins you test
alone.

## Config

`pluginConfigs.i18n`.

| Option | Type | Default | Description |
|---|---|---|---|
| `locales` | `readonly string[]` | `["en"]` | Supported locales, in priority order. Must not be empty. |
| `defaultLocale` | `string` | `"en"` | The fallback locale, served at bare paths. Must be in `locales`. |
| `localeNames` | `Record<string, string>` | `{}` | Display names, e.g. `{ uk: "Українська" }`. |
| `ogLocaleMap` | `Record<string, string>` | `{}` | `og:locale` values, e.g. `{ en: "en_US" }`. |
| `translations` | `Record<string, Record<string, string>>` | `{}` | `locale → key → text`. |

## API

Mounted at `app.i18n`, reachable with `ctx.require(i18nPlugin)`.

| Method | Signature | Description |
|---|---|---|
| `locales` | `() => readonly string[]` | The configured locales, in order. |
| `defaultLocale` | `() => string` | The fallback locale. |
| `isLocale` | `(x: string) => boolean` | Whether `x` is one of the locales. Case-sensitive. |
| `localeName` | `(locale: string) => string \| undefined` | The display name. |
| `ogLocale` | `(locale: string) => string \| undefined` | The `og:locale` value. |
| `t` | `(locale: string, key: string) => string` | The translation, with fallback. |

## Events

### Emits

None.

### Listens to

None.

## Depends on

Nothing.

## Example

```ts
import { createApp } from "@moku-labs/web";

const app = createApp({
  pluginConfigs: {
    site: { name: "My Blog", url: "https://blog.dev", author: "Ada", description: "Notes." },
    i18n: {
      locales: ["en", "uk"],
      defaultLocale: "en",
      localeNames: { en: "English", uk: "Українська" },
      ogLocaleMap: { en: "en_US", uk: "uk_UA" },
      translations: {
        en: { "nav.home": "Home", "nav.about": "About" },
        uk: { "nav.home": "Головна" }
      }
    }
  }
});

console.log(app.i18n.t("uk", "nav.home")); // "Головна"
console.log(app.i18n.t("uk", "nav.about")); // "About": falls back to the default locale
console.log(app.i18n.t("uk", "nav.missing")); // "nav.missing": falls back to the key
console.log(app.i18n.localeName("uk"), app.i18n.ogLocale("en")); // "Українська" "en_US"
```

```sh
$ bun scripts/i18n.ts
Головна
About
nav.missing
Українська en_US
```

- **`defaultLocale` must be in `locales`.** `createApp` throws `[web] i18n.defaultLocale "en" is not in i18n.locales [uk].` with the fix on the next line.
- **An empty `locales` throws.** `[web] i18n.locales must contain at least one locale.`
- **hreflang is not here.** `i18n` owns `og:locale` values. `head` builds the hreflang links from `locales()` and the router.
- **The config is shallow-merged.** Passing `translations` replaces the whole table; it is not merged key by key.

## Related

- [i18n](/docs/web/guides/i18n/): the guide, with URLs per locale.
- [router plugin](/docs/web/plugins/router/): the `{lang:?}` slot.
- [content plugin](/docs/web/plugins/content/): locale files and fallback.


Source: https://moku-labs.com/docs/web/plugins/i18n/ · @moku-labs/web@2.4.11
