alepha@docs:~/docs/guides/frontend$
cat 5-i18n.md
2 min read
Last commit:

#Internationalization (i18n)

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.

#Setup

#Define Dictionaries

Create translation files and register them with $dictionary:

typescript
 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}

#Translation Files

Each translation file exports a default object:

typescript
 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};
typescript
 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};

#Using Translations

#The useI18n Hook

tsx
 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 inference

#What useI18n Returns

typescript
1const {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">();

#Translation Function (tr)

#Simple Translations

tsx
1tr("welcome")           // "Welcome"2tr("nav.home")          // "Home"

#With Placeholders

tsx
1tr("greeting", { name: "Alice" })  // "Hello, Alice!"2tr("items", { count: 5 })          // "5 items"

#Pluralization

Define pluralized translations:

typescript
1// en.ts2messages: {3  zero: "No messages",4  one: "You have 1 message",5  other: "You have {count} messages",6}

Use with count:

tsx
1tr("messages", { count: 0 })  // "No messages"2tr("messages", { count: 1 })  // "You have 1 message"3tr("messages", { count: 5 })  // "You have 5 messages"

#Fallbacks

If a key is missing, it falls back:

  1. Try the requested key in current language
  2. Try the same key in default language
  3. Return the key itself (useful for debugging)

#Localization Helper (l)

Format dates, numbers, and currencies:

tsx
 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:

tsx
1// In French2l(1234567.89, { number: "decimal" }) // "1 234 567,89"3l(99.99, { currency: "EUR" })        // "99,99 €"

#Language Detection

#Server-Side

The server detects language from:

  1. URL parameter (?lang=fr)
  2. Cookie (alepha.lang)
  3. Accept-Language header
  4. Default language

#Client-Side

Language persists in a cookie. When setLang is called:

tsx
1setLang("fr")  // Updates cookie, triggers re-render

#URL-Based Language

For /en/about, /fr/about patterns:

tsx
1home = $page({2  path: "/:lang/",3  loader: async ({ params }) => {4    // params.lang available for language selection5  },6});

#Lazy Loading

Translations load on demand. Only the current language bundle is fetched.

typescript
1en = $dictionary({2  lazy: () => import("./translations/en.ts"),3});

When switching languages:

  1. Request new translation bundle
  2. Wait for load
  3. Re-render with new strings

No flash of untranslated content.

#Type Safety

The tr function is fully typed. Invalid keys are compile-time errors:

tsx
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:

tsx
1// greeting: "Hello, {name}!"2tr("greeting", { name: "Bob" })  // ✓ Valid3tr("greeting", { user: "Bob" })  // ✗ Type error: wrong placeholder4tr("greeting")                   // ✗ Type error: missing placeholder

#Common Patterns

#Global Language Context

Wrap your app to provide language context:

tsx
 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};

#Language Switcher Component

tsx
 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};

#Translating Outside Components

For utility functions or services:

typescript
 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}

#RTL Support

Detect right-to-left languages:

tsx
1const { lang } = useI18n<AppRouter, "en">();2const isRTL = ["ar", "he", "fa"].includes(lang);3 4return <div dir={isRTL ? "rtl" : "ltr"}>{children}</div>;

#Project Structure

Organize translations by feature or flat:

bash
src/
├── translations/
│   ├── en.ts        # Flat structure
│   ├── fr.ts
│   └── de.ts

Or by feature:

bash
src/
├── translations/
│   ├── en/
│   │   ├── common.ts
│   │   ├── auth.ts
│   │   └── dashboard.ts
│   ├── fr/
│   │   ├── common.ts
│   │   ├── auth.ts
│   │   └── dashboard.ts

Then merge in your main translation file:

typescript
 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};

#Quick Reference

typescript
 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

On This Page
No headings found...
ready
mainTypeScript
UTF-8guides_frontend_i18n.md