active-i18n
A translation runtime over dictionaries your application supplies - interpolation, CLDR plurals, a React provider and an optional server integration for Next.
@workspace/active-i18n 1.1.0Developmentunreleased, in development in Foundationbaseline ce081dc3
@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, its one workspace dependency.
Interpolation, plurals and a missing keyClient
Hello Ada
2 new messages
inbox.archived
Missing keys reported: inbox.archived
// 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
TranslationCapabilityand the runtimecreateTranslateFunction: key paths, interpolation, plural selection, and callbacks for a missing key, a warning or an error. - The client provider
<I18nProvider>withuseTranslations,useLocaleand 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
dirattribute; see Direction.
Entry points#
| Import | Runs in | Contents |
|---|---|---|
@workspace/ | any JavaScript runtime | create, types, language and currency names |
@workspace/ | React client components | <I18n, hooks, presentation and time-zone providers |
@workspace/ | a React Server Components server, such as Next | create |
@workspace/ | a request of the Next App Router | get |
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.
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
- Translations: keys, interpolation and plurals.