#Localization
Alepha provides $dictionary for registering translations and useI18n for accessing them in React components. Translations are lazy-loaded per language and support SSR with cookie-based language detection.
#Setup
Register translation dictionaries as class properties. The property name determines the language code.
1import { $dictionary } from "alepha/react/i18n";2 3class App {4 en = $dictionary({ lazy: () => import("./translations/en.ts") });5 fr = $dictionary({ lazy: () => import("./translations/fr.ts") });6}
Each translation file exports a default object mapping keys to strings:
1// translations/en.ts2export default {3 hello: "Hello",4 welcome: "Welcome, $1",5 items_count: "$1 items",6};
1// translations/fr.ts2export default {3 hello: "Bonjour",4 welcome: "Bienvenue, $1",5 items_count: "$1 elements",6};
Interpolation uses positional placeholders: $1, $2, $3, etc.
#$dictionary Options
| Option | Type | Description |
|---|---|---|
lazy |
() => Promise<{ default: T }> |
Async loader for the translation module. |
lang |
string |
Language code. Defaults to the property name. |
name |
string |
Dictionary name. Defaults to the property name. |
#Multiple Dictionaries Per Language
You can split translations across files by using the lang option:
1class App { 2 enCommon = $dictionary({ 3 lang: "en", 4 lazy: () => import("./translations/en-common.ts"), 5 }); 6 7 enDashboard = $dictionary({ 8 lang: "en", 9 lazy: () => import("./translations/en-dashboard.ts"),10 });11}
All dictionaries for the same language are merged. Key lookups search all dictionaries for the current language.
#Lazy Loading
Only the current language and the fallback language (default: "en") are loaded on startup. When the user switches languages, the new language's dictionaries are loaded on demand.
On the server, all languages are loaded at startup for SSR.
#useI18n Hook
1import { useI18n } from "alepha/react/i18n";
The hook accepts two type parameters for full type safety: the class containing the dictionaries and one of its dictionary keys (to infer available translation keys).
1function Header() { 2 const { tr, l, setLang, lang, languages } = useI18n<App, "en">(); 3 4 return ( 5 <div> 6 <h1>{tr("hello")}</h1> 7 <p>Current: {lang}</p> 8 <select value={lang} onChange={(e) => setLang(e.target.value)}> 9 {languages.map((l) => (10 <option key={l} value={l}>{l}</option>11 ))}12 </select>13 </div>14 );15}
#tr(key, options?)
Translate a key. Returns the translated string, or the key itself if not found.
1tr("hello")2// "Hello"3 4tr("welcome", { args: ["Alice"] })5// "Welcome, Alice"6 7tr("missing_key", { default: "Fallback text" })8// "Fallback text"
Options:
| Option | Type | Description |
|---|---|---|
args |
string[] |
Positional arguments for $1, $2, etc. |
default |
string |
Fallback if the key is not found. |
Fallback behavior: if the key is not found in the current language, the fallback language ("en" by default) is checked. If still not found, the raw key string is returned.
#l(value, options?)
Localize a value (date, number, or error) according to the current locale.
Number formatting:
1l(1234.56)2// "1,234.56" (en) / "1 234,56" (fr)3 4l(1234.56, { number: { style: "currency", currency: "USD" } })5// "$1,234.56"6 7l(0.85, { number: { style: "percent" } })8// "85%"
Uses Intl.NumberFormat under the hood. The number option accepts standard Intl.NumberFormatOptions.
Date formatting:
1l(new Date()) 2// "2/7/2026" (en) / "07/02/2026" (fr) 3 4l(new Date(), { date: "LLL" }) 5// "February 7, 2026 10:30 AM" (dayjs format string) 6 7l(new Date(), { date: "fromNow" }) 8// "2 hours ago" 9 10l(new Date(), { date: { year: "numeric", month: "long", day: "numeric" } })11// "February 7, 2026" (Intl.DateTimeFormatOptions)12 13l(new Date(), { date: "LLL", timezone: "America/New_York" })14// Formatted in Eastern time
The date option accepts:
- A dayjs format string (e.g.,
"LLL","YYYY-MM-DD","dddd, MMMM D YYYY") "fromNow"for relative timeIntl.DateTimeFormatOptionsfor native formatting
The timezone option accepts IANA timezone names.
TypeBox error localization:
1l(typeBoxError)2// Localized validation error message
#setLang(lang)
Switch the current language. On the browser, this:
- Loads the new language's dictionaries if not already loaded
- Sets a
langcookie (persists for 1 year) - Updates the store, causing all
useI18nconsumers to re-render
1setLang("fr")
#lang
The current language code string.
#languages
Array of all available language codes, derived from registered dictionaries.
#Localize Component
A component wrapper around l() for inline use in JSX:
1import { Localize } from "alepha/react/i18n";2 3<Localize value={new Date()} date="LLL" />4<Localize value={1234.56} number={{ style: "currency", currency: "EUR" }} />5<Localize value={new Date()} date="fromNow" timezone="Europe/Paris" />
Props:
| Prop | Type | Description |
|---|---|---|
value |
string | number | Date | DateTime | TypeBoxError |
The value to localize. |
number |
Intl.NumberFormatOptions |
Number formatting options. |
date |
string | Intl.DateTimeFormatOptions |
Date formatting options. |
timezone |
string |
IANA timezone name. |
#SSR Language Detection
On the server, the language is determined by:
- The
langcookie value (set bysetLang) - Falls back to the configured fallback language (
"en"by default)
The language state is set on each request via a server hook, so SSR renders use the correct language.
#Configuration
The fallback language defaults to "en". It can be changed on the I18nProvider:
1const i18n = alepha.inject(I18nProvider);2i18n.options.fallbackLang = "es";