---
id: "packages/active-i18n/examples"
title: "Examples"
description: "Every working example of active-i18n on one page. Each one shows the file that renders it."
url: "/packages/active-i18n/examples"
locale: "en"
revision: "9ebf809959fee623"
---

# Examples

The first two are **Client** examples: the dictionaries are in the example and everything runs in
your browser. The third is a **Server** example: a React Server Component that runs on this site's
server while the page renders; your browser receives only the result.

**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>
  );
}
```

**Example: Language, formats and reading direction**

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

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

import {useState} from 'react';

import {toIanaTimeZone} from '@workspace/active-format';
import type {LanguageConfig, LocaleTranslations} from '@workspace/active-i18n';
import {
  fixedTimeZone,
  I18nProvider,
  TimeZoneProvider,
  useFormatters,
  useLocale,
  useTranslations,
} from '@workspace/active-i18n/react';
import {DirectionProvider} from '@workspace/active-ui/components/primitives/direction';
import {Slider} from '@workspace/active-ui/components/primitives/slider';
import {
  Tabs,
  TabsList,
  TabsTrigger,
} from '@workspace/active-ui/components/primitives/tabs';

// The host's language set: the direction of each language is the host's data.
const LANGUAGES: readonly LanguageConfig[] = [
  {code: 'en', label: 'English', direction: 'ltr'},
  {code: 'ar', label: 'العربية', direction: 'rtl'},
];

const TRANSLATIONS: Record<string, LocaleTranslations> = {
  en: {
    order: {
      title: 'Order {{id}}',
      placed: 'Placed on',
      total: 'Total',
      progress: 'Progress',
    },
  },
  ar: {
    order: {
      title: 'الطلب {{id}}',
      placed: 'تاريخ الطلب',
      total: 'المجموع',
      progress: 'التقدم',
    },
  },
};

function OrderSummary() {
  const t = useTranslations();
  const locale = useLocale();
  const format = useFormatters();
  return (
    <div className="flex flex-col gap-3">
      <p className="font-medium" data-i18n-locale={locale}>
        {t('order.title', {id: '1042'})}
      </p>
      <dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-sm">
        <dt className="text-muted-foreground">{t('order.placed')}</dt>
        <dd>{format.dateTime('2026-10-21T09:30:00Z')}</dd>
        <dt className="text-muted-foreground">{t('order.total')}</dt>
        <dd>{format.currency({amount: 1234.5, currency: 'EUR'})}</dd>
      </dl>
      <Slider defaultValue={30} aria-label={t('order.progress')} />
    </div>
  );
}

/**
 * The language decides the words and the formats; the host decides the
 * direction from its language data and sets `dir` and `DirectionProvider`,
 * so the layout and the arrow keys follow the reading direction.
 */
export default function Direction() {
  const [code, setCode] = useState('ar');
  const language = LANGUAGES.find((entry) => entry.code === code)!;
  const direction = language.direction ?? 'ltr';
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <Tabs value={code} onValueChange={(value) => setCode(String(value))}>
        <TabsList aria-label="Language">
          {LANGUAGES.map((entry) => (
            <TabsTrigger key={entry.code} value={entry.code}>
              {entry.label}
            </TabsTrigger>
          ))}
        </TabsList>
      </Tabs>
      {/* A real host navigates to the other language (setLocale); this
          demonstration remounts the provider instead. */}
      <I18nProvider
        key={code}
        locale={code}
        translations={TRANSLATIONS[code]!}
        supportedLanguages={LANGUAGES}
        allowLocaleSwitching={false}
      >
        <TimeZoneProvider
          value={fixedTimeZone(toIanaTimeZone('Europe/Amsterdam'))}
        >
          <DirectionProvider direction={direction}>
            <div
              lang={code}
              dir={direction}
              className="rounded-lg border p-4"
              data-i18n-direction={direction}
            >
              <OrderSummary />
            </div>
          </DirectionProvider>
        </TimeZoneProvider>
      </I18nProvider>
    </div>
  );
}
```

**Example: Server translations for the requested language**

Server: Calls a real route of this site, which does the work on the server. Source: `examples/i18n/server-translations.tsx`.

```tsx
// apps/docs/examples/i18n/server-translations.tsx

// A Server Component: it runs on this site's server while the page renders.
// `@workspace/active-i18n/server` marks itself server-only, so this file can
// never end up in a browser bundle.
import {createServerTranslations} from '@workspace/active-i18n/server';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@workspace/active-ui/components/primitives/table';

// The host's resources. A real host imports JSON files per language and
// namespace; the loader receives the language and the namespace.
const RESOURCES: Record<string, Record<string, unknown>> = {
  en: {shop: {welcome: 'Welcome, {{name}}', basket: 'Your basket'}},
  nl: {shop: {welcome: 'Welkom, {{name}}', basket: 'Je mandje'}},
  'pt-BR': {shop: {welcome: 'Bem-vindo, {{name}}', basket: 'Seu carrinho'}},
};

// Bound once, at module scope, to the host's source and language set.
const translations = createServerTranslations(
  {
    namespaces: ['shop'],
    importNamespace: async (locale, namespace) => {
      const tree = RESOURCES[locale]?.[namespace];
      if (tree === undefined)
        throw Object.assign(new Error(`No ${locale}/${namespace}`), {
          code: 'MODULE_NOT_FOUND',
        });
      return {default: tree};
    },
  },
  {supportedLocales: ['en', 'nl', 'pt-BR'], fallbackLocale: 'en'},
);

// The locales a request might ask for; the host resolves its reader's
// language (a cookie, a profile, Accept-Language) and passes it in.
const REQUESTED = ['nl', 'en-GB', 'pt-BR', 'pt', 'fr'];

/** The server translator for each requested locale, matched against the host's set. */
export default async function ServerTranslations() {
  const rows = await Promise.all(
    REQUESTED.map(async (requested) => {
      const {t, locale} = await translations.getServerTranslator(requested);
      return {requested, locale, welcome: t('shop.welcome', {name: 'Ada'})};
    }),
  );
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Requested</TableHead>
          <TableHead>Loaded</TableHead>
          <TableHead>shop.welcome</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {rows.map((row) => (
          <TableRow key={row.requested} data-i18n-requested={row.requested}>
            <TableCell className="font-mono">{row.requested}</TableCell>
            <TableCell className="font-mono">{row.locale}</TableCell>
            <TableCell lang={row.locale}>{row.welcome}</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}
```
