So you want to conquer the world market. First step: stop assuming everyone speaks English.
The @alepha/react/i18n module gives you SSR-friendly internationalization without the bloat. Lazy-loaded translations, type-safe keys, and automatic language detection.
Create translation files and register them with $dictionary:
1// AppRouter.ts 2import { $dictionary } from "@alepha/react/i18n"; 3 4class AppRouter { 5 en = $dictionary({ 6 lazy: () => import("./translations/en.ts"), 7 }); 8 9 fr = $dictionary({10 lazy: () => import("./translations/fr.ts"),11 });12 13 de = $dictionary({14 lazy: () => import("./translations/de.ts"),15 });16}
Each translation file exports a default object:
1// translations/en.ts 2export default { 3 // Simple strings 4 welcome: "Welcome", 5 goodbye: "Goodbye", 6 7 // With placeholders 8 greeting: "Hello, {name}!", 9 items: "{count} items",10 11 // Nested keys12 nav: {13 home: "Home",14 about: "About Us",15 contact: "Contact",16 },17 18 // Pluralization19 messages: {20 one: "You have 1 message",21 other: "You have {count} messages",22 },23};
1// translations/fr.ts 2export default { 3 welcome: "Bienvenue", 4 goodbye: "Au revoir", 5 greeting: "Bonjour, {name}!", 6 items: "{count} articles", 7 8 nav: { 9 home: "Accueil",10 about: "À propos",11 contact: "Contact",12 },13 14 messages: {15 one: "Vous avez 1 message",16 other: "Vous avez {count} messages",17 },18};
useI18n Hook 1import { useI18n } from "@alepha/react/i18n"; 2 3const Greeting = ({ user }) => { 4 const { tr, lang, setLang } = useI18n<AppRouter, "en">(); 5 6 return ( 7 <div> 8 <p>{tr("welcome")}</p> 9 <p>{tr("greeting", { name: user.name })}</p>10 <p>{tr("nav.home")}</p>11 12 {/* Language switcher */}13 <select value={lang} onChange={(e) => setLang(e.target.value)}>14 <option value="en">English</option>15 <option value="fr">Français</option>16 <option value="de">Deutsch</option>17 </select>18 </div>19 );20};
The type parameters are important:
AppRouter - Your router class with dictionaries"en" - The default/fallback language for type inferenceuseI18n Returns1const {2 tr, // Translation function3 lang, // Current language code ("en", "fr", etc.)4 setLang, // Change language5 l, // Localization helper (dates, numbers)6 dictionary, // Raw dictionary object7} = useI18n<AppRouter, "en">();
tr)1tr("welcome") // "Welcome"2tr("nav.home") // "Home"
1tr("greeting", { name: "Alice" }) // "Hello, Alice!"2tr("items", { count: 5 }) // "5 items"
Define pluralized translations:
1// en.ts2messages: {3 zero: "No messages",4 one: "You have 1 message",5 other: "You have {count} messages",6}
Use with count:
1tr("messages", { count: 0 }) // "No messages"2tr("messages", { count: 1 }) // "You have 1 message"3tr("messages", { count: 5 }) // "You have 5 messages"
If a key is missing, it falls back:
l)Format dates, numbers, and currencies:
1const { l } = useI18n<AppRouter, "en">(); 2 3// Dates 4l(new Date(), { date: "short" }) // "1/15/24" 5l(new Date(), { date: "long" }) // "January 15, 2024" 6l(new Date(), { date: "fromNow" }) // "2 hours ago" 7 8// Numbers 9l(1234567.89, { number: "decimal" }) // "1,234,567.89"10l(0.75, { number: "percent" }) // "75%"11 12// Currency13l(99.99, { currency: "USD" }) // "$99.99"14l(99.99, { currency: "EUR" }) // "€99.99"
Formatting respects the current locale:
1// In French2l(1234567.89, { number: "decimal" }) // "1 234 567,89"3l(99.99, { currency: "EUR" }) // "99,99 €"
The server detects language from:
?lang=fr)alepha.lang)Accept-Language headerLanguage persists in a cookie. When setLang is called:
1setLang("fr") // Updates cookie, triggers re-render
For /en/about, /fr/about patterns:
1home = $page({2 path: "/:lang/",3 loader: async ({ params }) => {4 // params.lang available for language selection5 },6});
Translations load on demand. Only the current language bundle is fetched.
1en = $dictionary({2 lazy: () => import("./translations/en.ts"),3});
When switching languages:
No flash of untranslated content.
The tr function is fully typed. Invalid keys are compile-time errors:
1// Given dictionary with "welcome", "greeting"2tr("welcome") // ✓ Valid3tr("welcom") // ✗ Type error: typo4tr("missing") // ✗ Type error: key doesn't exist
Placeholder types are inferred too:
1// greeting: "Hello, {name}!"2tr("greeting", { name: "Bob" }) // ✓ Valid3tr("greeting", { user: "Bob" }) // ✗ Type error: wrong placeholder4tr("greeting") // ✗ Type error: missing placeholder
Wrap your app to provide language context:
1// AppLayout.tsx 2const AppLayout = ({ children }) => { 3 const { lang } = useI18n<AppRouter, "en">(); 4 5 // Set HTML lang attribute 6 useEffect(() => { 7 document.documentElement.lang = lang; 8 }, [lang]); 9 10 return <div>{children}</div>;11};
1const LanguageSwitcher = () => { 2 const { lang, setLang } = useI18n<AppRouter, "en">(); 3 4 const languages = [ 5 { code: "en", label: "English", flag: "🇺🇸" }, 6 { code: "fr", label: "Français", flag: "🇫🇷" }, 7 { code: "de", label: "Deutsch", flag: "🇩🇪" }, 8 ]; 9 10 return (11 <div className="lang-switcher">12 {languages.map((l) => (13 <button14 key={l.code}15 onClick={() => setLang(l.code)}16 className={lang === l.code ? "active" : ""}17 >18 {l.flag} {l.label}19 </button>20 ))}21 </div>22 );23};
For utility functions or services:
1import { Alepha, $inject } from "alepha"; 2 3class NotificationService { 4 alepha = $inject(Alepha); 5 6 notify(key: string, params?: object) { 7 const tr = this.alepha.i18n.tr; 8 const message = tr(key, params); 9 // Send notification with translated message10 }11}
Detect right-to-left languages:
1const { lang } = useI18n<AppRouter, "en">();2const isRTL = ["ar", "he", "fa"].includes(lang);3 4return <div dir={isRTL ? "rtl" : "ltr"}>{children}</div>;
Organize translations by feature or flat:
src/
├── translations/
│ ├── en.ts # Flat structure
│ ├── fr.ts
│ └── de.ts
Or by feature:
src/
├── translations/
│ ├── en/
│ │ ├── common.ts
│ │ ├── auth.ts
│ │ └── dashboard.ts
│ ├── fr/
│ │ ├── common.ts
│ │ ├── auth.ts
│ │ └── dashboard.ts
Then merge in your main translation file:
1// translations/en.ts 2import common from "./en/common"; 3import auth from "./en/auth"; 4import dashboard from "./en/dashboard"; 5 6export default { 7 ...common, 8 auth, 9 dashboard,10};
1// Define dictionaries 2en = $dictionary({ lazy: () => import("./translations/en.ts") }); 3fr = $dictionary({ lazy: () => import("./translations/fr.ts") }); 4 5// Use in components 6const { tr, lang, setLang, l } = useI18n<AppRouter, "en">(); 7 8// Translate 9tr("key")10tr("key", { placeholder: value })11tr("nested.key")12 13// Localize14l(date, { date: "short" })15l(number, { number: "decimal" })16l(amount, { currency: "USD" })17 18// Switch language19setLang("fr")
Previous: Forms | Back to: React Integration