# Features

Everything Comvi i18n does, one short section per capability with a snippet and a link to the guide.

Canonical: https://comvi.io/docs/i18n/features

What the library does, one section per capability with the shortest snippet that shows it. The
i18n instance and `t()` are the same in every framework; the snippets use React where a
component is needed.

## Translate text with t()

Call `t()` with a key and get the text for the current locale. Keys are dot paths, so a nested
catalog and a flat one with dotted keys are the same catalog to the instance, and `{ ns }` in the
params reads another namespace.

`src/Greeting.tsx`

```tsx

export function Greeting() {
const { t } = useI18n();
return <h1>{t('welcome', { name: 'Alice' })}</h1>;
}
```

Guide: [Translate text with t()](/docs/i18n/translation-function)

## Insert values and markup

Fill `{param}` placeholders with `t()` and render tags such as `<link>` inside a translation
with `<T>`. `<T>` hands each tag to the component under its name, so the translator can move
the tag to where the sentence needs it and your code still decides what it renders.

`src/Help.tsx`

```tsx

// "help": "Click <link>here</link> for help"
export function Help() {
return <T i18nKey="help" components={{ link: <a href="/help" /> }} />;
}
```

Plain `t()` leaves tags in the string until the bundle imports `@comvi/core/tags`.

Guide: [Insert values and markup](/docs/i18n/tag-interpolation)

## Pluralize with ICU

Write `{count, plural, …}` in a message and let `t()` pick the form the locale needs.
`compiler: icuCompiler` adds `plural`, `select` and `selectordinal` to the default compiler,
which handles text and `{param}` only. The option is set once on the instance and covers every
catalog a loader or the CDN adds later.

`src/i18n.ts`

```ts

export const i18n = createI18n({
locale: 'en',
compiler: icuCompiler,
messages: { en: { 'cart.items': '{count, plural, =0 {Your cart is empty} one {# item} other {# items}}' } },
});
```

`count` goes to `Intl.PluralRules` and `#` becomes the number; without the compiler, an ICU
message throws at ingestion in development and renders literally in production.

Guide: [Pluralize with ICU](/docs/i18n/pluralization)

## Format numbers, dates and currency

Format numbers, dates, currency and relative time in the current locale with the `Intl`
wrappers that `useI18n()` returns. In every binding each wrapper follows the current locale, so
the output follows a locale switch on its own. The `options` argument forwards to the `Intl`
constructor.

`src/PriceTag.tsx`

```tsx

export function PriceTag({ amount, at }: { amount: number; at: Date }) {
const { formatCurrency, formatDate } = useI18n();
return <span>{formatCurrency(amount, 'EUR')} · {formatDate(at, { dateStyle: 'long' })}</span>;
}
```

Outside a component, the root exports of `@comvi/core` take the instance as their first argument.
Messages do not evaluate number or date skeletons: format the value first and pass the string
as a parameter.

Guide: [Format numbers, dates and currency](/docs/i18n/formatting)

## Switch the locale

Switch the locale at runtime and every component that reads the instance re-renders.
`setLocale()` emits `localeChanged` and resolves once the locale is applied; with a loader
composed, it loads the new locale's namespaces first, so the page never shows keys between the
two. A key missing from the current locale is looked up along the `fallbackLocale` chain before
`missingKey` fires.

`src/switch.ts`

```ts

export const i18n = createI18n({ locale: 'de', fallbackLocale: 'en' });

i18n.setFallbackLocale(['de-AT', 'de', 'en']); // tried in order
await i18n.setLocale('fr'); // resolves once the locale is applied
```

Guide: [Switch the locale](/docs/i18n/language-switching)

## Load translations on demand

Load each locale when the user needs it instead of bundling every catalog. A loader is composed
with `.use()` in the same expression that creates the instance. `loader(map)` turns each entry
into a dynamic `import()`, so the bundler emits one chunk per locale and a locale is fetched
only when it is active or in the fallback chain.

`src/i18n.ts`

```ts

export const i18n = createI18n({ locale: 'en', fallbackLocale: 'en' }).use(
loader({
    en: () => import('./locales/en.json'),
    de: () => import('./locales/de.json'),
}),
);
```

`init()` loads the active locale and its fallback chain; `setLocale()` and `reloadMessages()` go through the
same map later. The framework providers call `init()` for you.

Guide: [Load translations on demand](/docs/i18n/loading-translations)

## Split translations into namespaces

Split the catalog into namespaces and load each one only on the screens that use it. A map key
is `locale` for the default namespace or `locale:namespace` for one namespace of one locale;
`ns` names the namespaces loaded during `init()`, and `addActiveNamespace()` loads another
later.

`src/i18n.ts`

```ts

export const i18n = createI18n({ locale: 'en', fallbackLocale: 'en', ns: ['default', 'dashboard'] }).use(
loader({
    'en': () => import('./locales/en.json'),
    'en:dashboard': () => import('./locales/dashboard/en.json'),
}),
);
```

`src/Dashboard.tsx`

```tsx

export function Dashboard() {
const { t } = useI18n('dashboard'); // every t() in here reads the dashboard namespace
return <h1>{t('page.title')}</h1>;
}
```

Guide: [Split translations into namespaces](/docs/i18n/namespaces)

## Type-check translation keys

Generate a `TranslationKeys` declaration from your catalogs so a misspelled key, a missing
parameter or a wrong parameter type is a TypeScript error in your editor. `comviTypes()` from
`@comvi/vite-plugin` writes it from local JSON with no account; `comvi typegen` writes the same
file from the key schema of a Comvi project. Import it once, in the module that builds the instance.

`src/types/i18n.d.ts`

```ts

declare module '@comvi/react' {
interface TranslationKeys {
    "greeting": { "name": string };
    "hello": never;
}
}

export {};
```

Guide: [Type-check translation keys](/docs/i18n/type-safety)

## Render on the server

Render translated text into the server response so crawlers, link previews and the first
paint see real copy. One question decides the shape of every setup: may the instance live at
module scope on the server? Next.js is the one framework where it may, because `getI18n()` binds
the locale per request; every other framework builds an instance per request and hands the
client a serializable `messages` payload.

`src/lib/i18n.server.ts`

```ts

export async function loadI18n(locale: string) {
const i18n = createI18n({ locale, fallbackLocale: 'en' }).use(
    cdnLoader({ cdnUrl: 'https://cdn.comvi.io/<project-token>' }),
);
await i18n.init();
const namespace = i18n.getDefaultNamespace();
return { locale, messages: { [`${locale}:${namespace}`]: i18n.getMessages(locale, namespace) ?? {} } };
}
```

The client creates its instance from that payload as `messages` and passes `autoInit: false`
to the provider, which is where the option lives: `autoInit={false}` on `<I18nProvider>`, or
the second argument of `setI18nContext()` in Svelte.
`@comvi/next` does the per-request work with `<ComviProvider locale>` and `getI18n({ locale })`,
and the Nuxt module builds a fresh instance per request from `comvi.i18n.ts`.

Guide: [Render on the server](/docs/i18n/ssr)

## Handle missing keys and errors

Keep the app rendering when a key is missing or a catalog fails to load. `t()` never throws on
a missing key, and every failure reaches a hook you can wire to your error tracker. The lookup
walks the fallback chain, emits `missingKey` and returns whatever `onMissingKey` returns, or the
key itself; `onError` receives every reported failure with a `context.source` naming where it
came from.

`src/i18n.ts`

```ts

declare function report(error: unknown, source?: string): void;

export const i18n = createI18n({
locale: 'de-AT',
fallbackLocale: ['de', 'en'],
onMissingKey: ({ key }) => (import.meta.env.DEV ? `[MISSING: ${key}]` : ''),
onError: (error, context) => report(error, context?.source),
});
```

`onLoadError` fires once per `locale:namespace` pair a loader failed to load.

Guide: [Handle missing keys and errors](/docs/i18n/error-handling)

## Also

- [Installation](/docs/i18n/installation): one package per framework; each binding depends on `@comvi/core`, so the core comes with it.
- [Plugins overview](/docs/i18n/plugins): an installer takes the i18n instance and returns it, composed with `.use(installer)` before `init()`; the fetch loader, the locale detector and the in-context editor are installers.
- [Connect to Comvi](/docs/i18n/connect-comvi): replace the inline catalog with one the Comvi platform serves over its CDN, and ship a wording change without a redeploy.
