---
id: "packages/active-i18n"
title: "active-i18n"
description: "A translation runtime over dictionaries your application supplies - interpolation, CLDR plurals, a React provider and an optional server integration for Next."
url: "/packages/active-i18n"
locale: "en"
revision: "4cb0cfb397f2a52d"
---

# active-i18n

`@workspace/active-i18n` translates keys into text from dictionaries **your application supplies**:
`{{name}}` interpolation, plurals by `Intl.PluralRules`, a React provider with hooks that also hand
the reader's presentation to your components, and an optional server integration for Next that
loads your resources. Formatting and parsing come from
[active-format](/packages/active-format), its one workspace dependency.

**Example: Interpolation, plurals and a missing key**

Client: Runs entirely in your browser. Source: `examples/i18n/translator.tsx`.

```tsx
// apps/docs/examples/i18n/translator.tsx
'use client';

import {useState} from 'react';

import {
  createTranslateFunction,
  type LocaleTranslations,
} from '@workspace/active-i18n';
import {Button} from '@workspace/active-ui/components/primitives/button';
import {
  Tabs,
  TabsList,
  TabsTrigger,
} from '@workspace/active-ui/components/primitives/tabs';

// The host's dictionaries: one tree per language, keyed by namespace.
const DICTIONARIES: Record<'en' | 'nl' | 'ar', LocaleTranslations> = {
  en: {
    inbox: {
      greeting: 'Hello {{name}}',
      messages: {one: '{{count}} new message', other: '{{count}} new messages'},
    },
  },
  nl: {
    inbox: {
      greeting: 'Hallo {{name}}',
      messages: {
        one: '{{count}} nieuw bericht',
        other: '{{count}} nieuwe berichten',
      },
    },
  },
  ar: {
    inbox: {
      greeting: 'مرحبًا {{name}}',
      messages: {
        zero: 'لا توجد رسائل جديدة',
        one: 'رسالة جديدة واحدة',
        two: 'رسالتان جديدتان',
        few: '{{count}} رسائل جديدة',
        many: '{{count}} رسالة جديدة',
        other: '{{count}} رسالة جديدة',
      },
    },
  },
};

/** A translator per language: interpolation, CLDR plurals and a missing key. */
export default function Translator() {
  const [locale, setLocale] = useState<'en' | 'nl' | 'ar'>('en');
  const [count, setCount] = useState(2);
  // Filled while this render translates: the translator reports a miss
  // through the callback and still returns the raw key.
  const missing: string[] = [];
  const t = createTranslateFunction(DICTIONARIES[locale], locale, {
    onMissingKey: ({key}) => missing.push(key),
  });
  return (
    <div className="flex flex-col gap-4">
      <Tabs
        value={locale}
        onValueChange={(value) => setLocale(value as 'en' | 'nl' | 'ar')}
      >
        <TabsList aria-label="Language">
          <TabsTrigger value="en">English</TabsTrigger>
          <TabsTrigger value="nl">Nederlands</TabsTrigger>
          <TabsTrigger value="ar">العربية</TabsTrigger>
        </TabsList>
      </Tabs>
      <div
        lang={locale}
        dir={locale === 'ar' ? 'rtl' : 'ltr'}
        className="flex flex-col gap-1"
      >
        <p data-i18n-greeting>{t('inbox.greeting', {name: 'Ada'})}</p>
        <p data-i18n-count={count}>{t('inbox.messages', {count})}</p>
        <p className="text-muted-foreground font-mono text-sm">
          {t('inbox.archived')}
        </p>
      </div>
      <div className="flex flex-wrap items-center gap-2">
        <Button
          variant="outline"
          size="sm"
          onClick={() => setCount((n) => Math.max(0, n - 1))}
        >
          Fewer
        </Button>
        <Button
          variant="outline"
          size="sm"
          onClick={() => setCount((n) => n + 1)}
        >
          More
        </Button>
        <span className="text-muted-foreground text-sm">count = {count}</span>
      </div>
      <p className="text-muted-foreground text-sm">
        Missing keys reported:{' '}
        {missing.length > 0 ? missing.join(', ') : 'none'}
      </p>
    </div>
  );
}
```

## What it owns

- The translation contract `TranslationCapability` and the runtime `createTranslateFunction`: key
  paths, interpolation, plural selection, and callbacks for a missing key, a warning or an error.
- The client provider `<I18nProvider>` with `useTranslations`, `useLocale` and the one language
  switch, `setLocale`.
- The server integration `createServerTranslations` (Next, React Server Components).
- Language, currency and region names from the runtime's CLDR data.
- The React distribution of a presentation: `usePresentation`, `useFormatters`, the time-zone
  providers, `<RecencyTime>` and `<ViewerDateTime>`.

## What your application owns

- **The dictionaries**, their files and namespaces. The package knows no key of yours.
- **The language set and the fallback**, and which language a reader gets (a cookie, a profile,
  `Accept-Language`). The package resolves nothing on its own and reads no cookie.
- **Where a choice is kept** (`persistLocale`) and **the logger** (a factory you pass in). Without
  them the package touches no storage and reports nothing.
- **The reading direction** of each language and the `dir` attribute; see
  [Direction](/packages/active-i18n/direction).

## Entry points

| Import | Runs in | Contents |
| --- | --- | --- |
| `@workspace/active-i18n` | any JavaScript runtime | `createTranslateFunction`, types, language and currency names |
| `@workspace/active-i18n/react` | React client components | `<I18nProvider>`, hooks, presentation and time-zone providers |
| `@workspace/active-i18n/server` | a React Server Components server, such as Next | `createServerTranslations` |
| `@workspace/active-i18n/server/get-locale` | a request of the Next App Router | `getAcceptLanguageCandidates` |

The two server entries mark themselves `server-only`: they throw in plain Node and in a browser
bundle. The root and `/react` never reach them, so a site without Next uses those two only; see
[Without Next](/packages/active-i18n/without-next).

## Dependencies

`@workspace/active-format`, `date-fns`, `@date-fns/tz` and `server-only`. `react` (19) and `next`
(15 or 16) are optional peers.

## Next

- [Getting started](/packages/active-i18n/getting-started)
- [Translations](/packages/active-i18n/translations): keys, interpolation and plurals.
