Internationalization Implementation Stack

1. React-intl i18next Setup Configuration

Comprehensive i18n solutions for React apps with translation management, formatting, and pluralization support.

Library Core Features Best For Bundle Size
react-intl (FormatJS) ICU message format, date/number formatting, React hooks Enterprise apps, complex formatting ~45KB
react-i18next Plugin ecosystem, lazy loading, backend integration Large projects, dynamic translations ~30KB + i18next core
next-intl Next.js optimized, SSR/SSG support, type-safe Next.js apps ~15KB
LinguiJS CLI extraction, compile-time optimization, minimal runtime Performance-critical apps ~5KB runtime

Example: react-i18next setup with TypeScript

// i18n.ts - Configuration
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';

i18n
  .use(Backend) // Load translations from backend
  .use(LanguageDetector) // Detect user language
  .use(initReactI18next) // Pass i18n to react-i18next
  .init({
    fallbackLng: 'en',
    debug: process.env.NODE_ENV === 'development',
    interpolation: {
      escapeValue: false // React already escapes
    },
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json'
    },
    ns: ['common', 'auth', 'dashboard'],
    defaultNS: 'common',
    react: {
      useSuspense: true
    }
  });

export default i18n;

// locales/en/common.json
{
  "welcome": "Welcome, {{name}}!",
  "itemCount": "You have {{count}} item",
  "itemCount_plural": "You have {{count}} items",
  "updated": "Last updated: {{date, datetime}}"
}

// locales/es/common.json
{
  "welcome": "¡Bienvenido, {{name}}!",
  "itemCount": "Tienes {{count}} artículo",
  "itemCount_plural": "Tienes {{count}} artículos",
  "updated": "Última actualización: {{date, datetime}}"
}

// App.tsx
import './i18n';
import { Suspense } from 'react';

function App() {
  return (
    <Suspense fallback="Loading...">
      <MainApp />
    </Suspense>
  );
}

// Component usage
import { useTranslation } from 'react-i18next';

function Dashboard() {
  const { t, i18n } = useTranslation('common');
  
  return (
    <div>
      <h1>{t('welcome', { name: 'John' })}</h1>
      <p>{t('itemCount', { count: 5 })}</p>
      <p>{t('updated', { date: new Date() })}</p>
      
      <button onClick={() => i18n.changeLanguage('es')}>
        Español
      </button>
      <button onClick={() => i18n.changeLanguage('en')}>
        English
      </button>
    </div>
  );
}

Example: react-intl (FormatJS) implementation

// App.tsx
import { IntlProvider } from 'react-intl';
import { useState } from 'react';
import enMessages from './locales/en.json';
import esMessages from './locales/es.json';

const messages = {
  en: enMessages,
  es: esMessages
};

function App() {
  const [locale, setLocale] = useState('en');
  
  return (
    <IntlProvider 
      messages={messages[locale]} 
      locale={locale}
      defaultLocale="en"
    >
      <Dashboard onLocaleChange={setLocale} />
    </IntlProvider>
  );
}

// Component with react-intl hooks
import { 
  useIntl, 
  FormattedMessage, 
  FormattedNumber, 
  FormattedDate 
} from 'react-intl';

function Dashboard({ onLocaleChange }) {
  const intl = useIntl();
  
  return (
    <div>
      <h1>
        <FormattedMessage 
          id="welcome"
          defaultMessage="Welcome, {name}!"
          values={{ name: 'John' }}
        />
      </h1>
      
      <p>
        <FormattedNumber 
          value={1234.56} 
          style="currency" 
          currency="USD" 
        />
      </p>
      
      <p>
        <FormattedDate 
          value={new Date()} 
          year="numeric"
          month="long"
          day="numeric"
        />
      </p>
      
      {/* Imperative usage */}
      <input 
        placeholder={intl.formatMessage({ 
          id: 'search.placeholder',
          defaultMessage: 'Search...'
        })}
      />
    </div>
  );
}
Comparison: Use react-i18next for flexibility and ecosystem. Use react-intl for standardized ICU formatting. Use LinguiJS for smallest bundle and compile-time extraction.

2. Dynamic Locale Loading Lazy i18n

Load translation files on-demand to reduce initial bundle size and improve performance for multi-language apps.

Strategy Implementation Benefits Trade-offs
Code Splitting Dynamic import() per locale Smaller initial bundle Network request on language change
Namespace Splitting Split by feature/page Load only needed translations More HTTP requests
Backend Loading Fetch from API/CDN No rebuild for translation updates Runtime dependency
Preloading Prefetch likely locales Instant switching Additional bandwidth usage

Example: Dynamic locale loading with react-i18next

// i18n.ts - Lazy loading configuration
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

// Custom backend for dynamic imports
const loadResources = async (lng: string, ns: string) => {
  try {
    const resources = await import(`./locales/${lng}/${ns}.json`);
    return resources.default;
  } catch (error) {
    console.error(`Failed to load ${lng}/${ns}`, error);
    return {};
  }
};

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .use({
    type: 'backend',
    read(language, namespace, callback) {
      loadResources(language, namespace)
        .then(resources => callback(null, resources))
        .catch(error => callback(error, null));
    }
  })
  .init({
    fallbackLng: 'en',
    ns: ['common', 'auth', 'dashboard', 'settings'],
    defaultNS: 'common',
    react: {
      useSuspense: true
    },
    // Preload common namespaces
    preload: ['en'],
    load: 'languageOnly' // 'en-US' -> 'en'
  });

export default i18n;

// Hook for namespace loading
import { useTranslation } from 'react-i18next';
import { useEffect } from 'react';

function useLazyTranslation(namespace: string) {
  const { t, i18n, ready } = useTranslation(namespace, { useSuspense: false });
  
  useEffect(() => {
    if (!i18n.hasResourceBundle(i18n.language, namespace)) {
      i18n.loadNamespaces(namespace);
    }
  }, [i18n, namespace]);
  
  return { t, ready };
}

// Component usage
function SettingsPage() {
  const { t, ready } = useLazyTranslation('settings');
  
  if (!ready) return <div>Loading translations...</div>
  
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

Example: Webpack/Vite magic comments for chunk naming

// loadLocale.ts
export async function loadLocale(locale: string) {
  // Webpack magic comments for chunk naming
  const messages = await import(
    /* webpackChunkName: "locale-[request]" */
    /* webpackMode: "lazy" */
    `./locales/${locale}/messages.json`
  );
  
  return messages.default;
}

// Vite dynamic import
export async function loadLocaleVite(locale: string) {
  const modules = import.meta.glob('./locales/*/messages.json');
  const path = `./locales/${locale}/messages.json`;
  
  if (modules[path]) {
    const messages = await modules[path]();
    return messages.default;
  }
  
  throw new Error(`Locale ${locale} not found`);
}

// App.tsx - Progressive enhancement
import { Suspense, lazy } from 'react';

const LocaleProvider = lazy(() => 
  import(/* webpackChunkName: "i18n-provider" */ './LocaleProvider')
);

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <LocaleProvider>
        <Routes />
      </LocaleProvider>
    </Suspense>
  );
}

Example: Preloading strategy for better UX

// useLocalePreload.ts
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';

const COMMON_LOCALES = ['en', 'es', 'fr', 'de'];

export function useLocalePreload() {
  const { i18n } = useTranslation();
  
  useEffect(() => {
    // Preload on idle
    if ('requestIdleCallback' in window) {
      requestIdleCallback(() => {
        COMMON_LOCALES.forEach(locale => {
          if (locale !== i18n.language) {
            // Prefetch but don't block
            import(`./locales/${locale}/common.json`).catch(() => {});
          }
        });
      });
    }
  }, [i18n.language]);
}

// Link preload in HTML head
function injectPreloadLinks(locales: string[]) {
  locales.forEach(locale => {
    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.as = 'fetch';
    link.href = `/locales/${locale}/common.json`;
    link.crossOrigin = 'anonymous';
    document.head.appendChild(link);
  });
}

// Next.js implementation
import Head from 'next/head';

function LocalePreload({ locales }: { locales: string[] }) {
  return (
    <Head>
      {locales.map(locale => (
        <link
          key={locale}
          rel="prefetch"
          as="fetch"
          href={`/locales/${locale}/common.json`}
          crossOrigin="anonymous"
        />
      ))}
    </Head>
  );
}
Best Practice: Load common namespace eagerly, lazy-load feature-specific namespaces. Preload user's preferred alternate languages during idle time. Cache translations in localStorage.

3. Pluralization ICU Message Format

Handle complex pluralization rules across different languages using ICU MessageFormat standard.

Feature Syntax Use Case Example
Simple Plural {count, plural, one{#} other{#}} Item counts 1 item / 5 items
Select {gender, select, male{} female{}} Gender-based text He/She variations
SelectOrdinal {num, selectordinal, one{#st}} Ordinal numbers 1st, 2nd, 3rd
Nested Combine plural + select Complex messages Gender + count variations

Example: ICU MessageFormat pluralization rules

// English pluralization (2 forms)
{
  "itemCount": "{count, plural, one {# item} other {# items}}"
}

// Usage: 0 items, 1 item, 2 items, 100 items

// Polish pluralization (3 forms)
{
  "itemCount": "{count, plural, one {# przedmiot} few {# przedmioty} many {# przedmiotów} other {# przedmiotu}}"
}

// Arabic pluralization (6 forms!)
{
  "itemCount": "{count, plural, zero {لا عناصر} one {عنصر واحد} two {عنصران} few {# عناصر} many {# عنصرًا} other {# عنصر}}"
}

// Complex example with select + plural
{
  "taskStatus": "{taskCount, plural, =0 {No tasks} one {# task} other {# tasks}} {status, select, pending {pending} completed {completed} failed {failed} other {unknown}}"
}

// Nested gender + plural
{
  "friendRequest": "{gender, select, male {He has} female {She has} other {They have}} {count, plural, one {# friend request} other {# friend requests}}"
}

Example: react-intl with ICU MessageFormat

import { FormattedMessage, useIntl } from 'react-intl';

// Translation file (en.json)
{
  "cart.items": "You have {itemCount, plural, =0 {no items} one {# item} other {# items}} in your cart",
  "user.greeting": "{name} {gender, select, male {is online. Say hi to him!} female {is online. Say hi to her!} other {is online. Say hi!}}",
  "finish.position": "You finished in {position, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} place"
}

// Component usage
function ShoppingCart({ itemCount }: { itemCount: number }) {
  return (
    <div>
      <FormattedMessage 
        id="cart.items"
        values={{ itemCount }}
      />
    </div>
  );
}

function UserStatus({ name, gender }: { name: string; gender: 'male' | 'female' | 'other' }) {
  return (
    <FormattedMessage 
      id="user.greeting"
      values={{ name, gender }}
    />
  );
}

function RaceResult({ position }: { position: number }) {
  const intl = useIntl();
  
  return (
    <div>
      {intl.formatMessage(
        { id: 'finish.position' },
        { position }
      )}
    </div>
  );
}

Example: i18next with ICU plugin

// i18n.ts - Enable ICU format
import i18n from 'i18next';
import ICU from 'i18next-icu';
import { initReactI18next } from 'react-i18next';

i18n
  .use(ICU) // Add ICU plugin
  .use(initReactI18next)
  .init({
    fallbackLng: 'en',
    resources: {
      en: {
        translation: {
          "notifications": "You have {count, plural, =0 {no notifications} one {# notification} other {# notifications}}",
          "fileSize": "{size, number} {unit, select, KB {kilobytes} MB {megabytes} GB {gigabytes} other {bytes}}"
        }
      }
    }
  });

// Component usage
import { useTranslation } from 'react-i18next';

function Notifications({ count }: { count: number }) {
  const { t } = useTranslation();
  
  return <div>{t('notifications', { count })}</div>;
}

function FileInfo({ size, unit }: { size: number; unit: string }) {
  const { t } = useTranslation();
  
  return <div>{t('fileSize', { size, unit })}</div>;
}
Performance Note: ICU MessageFormat parsing has runtime cost. Use compile-time extraction (LinguiJS) for production apps or cache parsed messages. Simple plural/other is sufficient for many use cases.

4. RTL LTR CSS Logical Properties

Support right-to-left (RTL) languages like Arabic and Hebrew with CSS logical properties for automatic layout mirroring.

Physical Property Logical Property LTR Value RTL Value
margin-left margin-inline-start Left margin Right margin
margin-right margin-inline-end Right margin Left margin
padding-left padding-inline-start Left padding Right padding
border-left border-inline-start Left border Right border
text-align: left text-align: start Align left Align right
float: left float: inline-start Float left Float right

Example: CSS logical properties for RTL support

/* Traditional approach (manual RTL) */
.card {
  margin-left: 16px;
  padding-right: 24px;
  border-left: 2px solid blue;
}

[dir="rtl"] .card {
  margin-left: 0;
  margin-right: 16px;
  padding-right: 0;
  padding-left: 24px;
  border-left: none;
  border-right: 2px solid blue;
}

/* Modern approach (automatic RTL) */
.card {
  margin-inline-start: 16px;
  padding-inline-end: 24px;
  border-inline-start: 2px solid blue;
}
/* No RTL override needed! */

/* Complete example */
.sidebar {
  /* Inline = horizontal (left/right) */
  padding-inline-start: 20px;
  padding-inline-end: 10px;
  margin-inline: 8px; /* shorthand for start + end */
  
  /* Block = vertical (top/bottom) - no change in RTL */
  padding-block-start: 16px;
  padding-block-end: 16px;
  margin-block: 12px;
  
  /* Positioning */
  inset-inline-start: 0; /* left in LTR, right in RTL */
  
  /* Text alignment */
  text-align: start; /* left in LTR, right in RTL */
}

.icon {
  margin-inline-end: 8px; /* Space after icon */
}

.arrow {
  /* Transform for RTL */
  transform: scaleX(1);
}

[dir="rtl"] .arrow {
  transform: scaleX(-1); /* Flip horizontally */
}

Example: React RTL implementation with context

// DirectionProvider.tsx
import { createContext, useContext, useEffect } from 'react';

type Direction = 'ltr' | 'rtl';

const DirectionContext = createContext<Direction>('ltr');

export function DirectionProvider({ 
  children, 
  direction 
}: { 
  children: React.ReactNode; 
  direction: Direction 
}) {
  useEffect(() => {
    document.documentElement.setAttribute('dir', direction);
    document.documentElement.setAttribute('lang', direction === 'rtl' ? 'ar' : 'en');
  }, [direction]);
  
  return (
    <DirectionContext.Provider value={direction}>
      {children}
    </DirectionContext.Provider>
  );
}

export const useDirection = () => useContext(DirectionContext);

// App.tsx
import { useTranslation } from 'react-i18next';

const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur'];

function App() {
  const { i18n } = useTranslation();
  const direction = RTL_LANGUAGES.includes(i18n.language) ? 'rtl' : 'ltr';
  
  return (
    <DirectionProvider direction={direction}>
      <MainApp />
    </DirectionProvider>
  );
}

// Component with direction-aware styles
import styled from 'styled-components';

const Card = styled.div`
  padding-inline-start: 20px;
  padding-inline-end: 10px;
  border-inline-start: 3px solid var(--primary-color);
  
  .icon {
    margin-inline-end: 8px;
  }
`;

function MyCard() {
  const direction = useDirection();
  
  return (
    <Card>
      <span className="icon">→</span>
      {direction === 'rtl' ? 'النص العربي' : 'English text'}
    </Card>
  );
}

Example: Tailwind CSS with RTL plugin

// tailwind.config.js
module.exports = {
  plugins: [require('tailwindcss-rtl')],
};

// Component with RTL-aware utilities
function Navbar() {
  return (
    <nav className="flex items-center">
      <img 
        src="/logo.png" 
        className="ms-4 me-2" // ms = margin-inline-start, me = margin-inline-end
        alt="Logo" 
      />
      <ul className="flex gap-4">
        <li className="ps-4">Home</li> {/* ps = padding-inline-start */}
        <li className="ps-4">About</li>
      </ul>
      <button className="ms-auto">Login</button> {/* Push to end */}
    </nav>
  );
}

// Custom RTL utilities
<div className="ltr:text-left rtl:text-right">
  Directional text
</div>
Browser Support: Excellent - All modern browsers support CSS logical properties. Use dir="rtl" on HTML element. Test with Arabic/Hebrew content thoroughly.

5. Date-fns Timezone Locale Formatting

Format dates, times, and numbers according to user's locale and timezone with proper internationalization support.

Library Features Use Case Bundle Size
date-fns Modular, tree-shakeable, 80+ locales, immutable Modern apps, small bundles ~2KB per function
date-fns-tz Timezone support addon for date-fns Multi-timezone apps ~10KB + date-fns
Luxon Modern API, Intl wrapper, timezone native Complex date logic ~70KB
Day.js Moment.js alternative, plugins, small Simple date needs ~7KB
Intl API (Native) Browser built-in, no dependencies Basic formatting 0KB

Example: date-fns with locale formatting

import { format, formatDistance, formatRelative } from 'date-fns';
import { enUS, es, ar, ja, de } from 'date-fns/locale';

// Locale mapping
const locales = { en: enUS, es, ar, ja, de };

function formatDate(date: Date, locale: string) {
  return format(date, 'PPpp', { locale: locales[locale] });
}

// Usage examples
const date = new Date(2025, 0, 15, 14, 30);

// English: "Jan 15, 2025, 2:30 PM"
format(date, 'PPpp', { locale: enUS });

// Spanish: "15 ene 2025, 14:30"
format(date, 'PPpp', { locale: es });

// Arabic: "١٥ يناير ٢٠٢٥، ١٤:٣٠"
format(date, 'PPpp', { locale: ar });

// Relative time
formatDistance(date, new Date(), { 
  addSuffix: true, 
  locale: es 
}); // "hace 3 días"

// Custom formats
format(date, 'EEEE, MMMM do yyyy', { locale: de });
// "Mittwoch, Januar 15. 2025"

// React hook for localized dates
import { useTranslation } from 'react-i18next';

function useLocalizedDate() {
  const { i18n } = useTranslation();
  const locale = locales[i18n.language] || enUS;
  
  const formatLocalizedDate = (date: Date, formatStr: string) => {
    return format(date, formatStr, { locale });
  };
  
  const formatRelativeTime = (date: Date) => {
    return formatDistance(date, new Date(), { 
      addSuffix: true, 
      locale 
    });
  };
  
  return { formatLocalizedDate, formatRelativeTime };
}

Example: Timezone handling with date-fns-tz

import { format } from 'date-fns';
import { formatInTimeZone, toZonedTime, fromZonedTime } from 'date-fns-tz';
import { enUS } from 'date-fns/locale';

// Display date in user's timezone
function formatUserTimezone(date: Date, userTimezone: string) {
  return formatInTimeZone(
    date, 
    userTimezone, 
    'yyyy-MM-dd HH:mm:ss zzz',
    { locale: enUS }
  );
}

// Examples
const utcDate = new Date('2025-01-15T14:30:00Z');

formatUserTimezone(utcDate, 'America/New_York');
// "2025-01-15 09:30:00 EST"

formatUserTimezone(utcDate, 'Asia/Tokyo');
// "2025-01-15 23:30:00 JST"

formatUserTimezone(utcDate, 'Europe/London');
// "2025-01-15 14:30:00 GMT"

// Convert between timezones
const tokyoTime = toZonedTime(utcDate, 'Asia/Tokyo');
const nyTime = fromZonedTime(tokyoTime, 'America/New_York');

// React component with timezone display
function EventTime({ eventDate, timezone }: { 
  eventDate: Date; 
  timezone: string;
}) {
  const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
  
  return (
    <div>
      <p>Event time: {formatInTimeZone(eventDate, timezone, 'PPpp')}</p>
      <p>Your time: {formatInTimeZone(eventDate, userTimezone, 'PPpp')}</p>
    </div>
  );
}

Example: Native Intl API for formatting

// Date formatting with Intl.DateTimeFormat
const date = new Date('2025-01-15T14:30:00');

// English (US)
new Intl.DateTimeFormat('en-US', {
  dateStyle: 'full',
  timeStyle: 'short'
}).format(date);
// "Wednesday, January 15, 2025 at 2:30 PM"

// Spanish (Spain)
new Intl.DateTimeFormat('es-ES', {
  dateStyle: 'full',
  timeStyle: 'short'
}).format(date);
// "miércoles, 15 de enero de 2025, 14:30"

// Number formatting with Intl.NumberFormat
const number = 1234567.89;

// Currency
new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).format(number);
// "$1,234,567.89"

new Intl.NumberFormat('ja-JP', {
  style: 'currency',
  currency: 'JPY'
}).format(number);
// "¥1,234,568"

// Percentage
new Intl.NumberFormat('en-US', {
  style: 'percent',
  minimumFractionDigits: 2
}).format(0.1234);
// "12.34%"

// Relative time with Intl.RelativeTimeFormat
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
rtf.format(-1, 'day'); // "yesterday"
rtf.format(2, 'week'); // "in 2 weeks"

const rtfEs = new Intl.RelativeTimeFormat('es', { numeric: 'auto' });
rtfEs.format(-1, 'day'); // "ayer"
rtfEs.format(2, 'week'); // "dentro de 2 semanas"

// React hook for Intl formatting
function useIntlFormatting(locale: string) {
  const dateFormatter = new Intl.DateTimeFormat(locale, {
    dateStyle: 'medium',
    timeStyle: 'short'
  });
  
  const currencyFormatter = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: 'USD'
  });
  
  const relativeTimeFormatter = new Intl.RelativeTimeFormat(locale, {
    numeric: 'auto'
  });
  
  return {
    formatDate: (date: Date) => dateFormatter.format(date),
    formatCurrency: (amount: number) => currencyFormatter.format(amount),
    formatRelativeTime: (value: number, unit: Intl.RelativeTimeFormatUnit) => 
      relativeTimeFormatter.format(value, unit)
  };
}
Timezone Pitfall: Always store dates in UTC on backend. Convert to user's timezone only for display. Use Date.prototype.toISOString() for API communication. Avoid new Date(string) parsing across timezones.

6. Translation Keys TypeScript Validation

Enforce type safety for translation keys to catch missing translations at compile-time instead of runtime.

Approach Tool Benefits Setup Complexity
Type Generation i18next + typesafe-i18n Auto-complete, type errors for invalid keys Medium
CLI Extraction LinguiJS, react-intl CLI Extract keys from code, detect unused High
Const Assertion TypeScript as const Simple, no build step Low
Schema Validation Zod, Yup Runtime + compile validation Medium

Example: TypeScript with i18next typed translations

// locales/en/translation.json
{
  "common": {
    "welcome": "Welcome",
    "logout": "Logout"
  },
  "auth": {
    "login": {
      "title": "Sign In",
      "email": "Email Address",
      "password": "Password"
    }
  },
  "errors": {
    "required": "This field is required",
    "invalid_email": "Invalid email format"
  }
}

// i18next.d.ts - Type augmentation
import 'i18next';
import type translation from './locales/en/translation.json';

declare module 'i18next' {
  interface CustomTypeOptions {
    defaultNS: 'translation';
    resources: {
      translation: typeof translation;
    };
  }
}

// Now TypeScript knows all translation keys!
import { useTranslation } from 'react-i18next';

function MyComponent() {
  const { t } = useTranslation();
  
  // ✅ Valid - autocomplete works!
  t('common.welcome');
  t('auth.login.title');
  
  // ❌ TypeScript error - key doesn't exist
  t('common.invalid'); // Error: Property 'invalid' does not exist
  
  // ✅ Nested object access with type safety
  t('auth.login.email');
  
  return <div>{t('common.welcome')}</div>;
}

Example: Generate types from translation files

// scripts/generate-i18n-types.ts
import fs from 'fs';
import path from 'path';

type TranslationKeys<T, Prefix extends string = ''> = {
  [K in keyof T]: T[K] extends object
    ? TranslationKeys<T[K], `${Prefix}${K & string}.`>
    : `${Prefix}${K & string}`;
}[keyof T];

function generateTypes() {
  const enTranslations = JSON.parse(
    fs.readFileSync('./locales/en/translation.json', 'utf-8')
  );
  
  const types = `
// Auto-generated - do not edit
import type en from './locales/en/translation.json';

export type TranslationKey = TranslationKeys<typeof en>;

export type TranslationKeys<T, Prefix extends string = ''> = {
  [K in keyof T]: T[K] extends object
    ? TranslationKeys<T[K], \`\${Prefix}\${K & string}.\`>
    : \`\${Prefix}\${K & string}\`;
}[keyof T];
  `;
  
  fs.writeFileSync('./src/types/i18n.ts', types);
}

generateTypes();

// Usage with custom hook
import type { TranslationKey } from './types/i18n';
import { useTranslation as useI18next } from 'react-i18next';

export function useTranslation() {
  const { t, ...rest } = useI18next();
  
  const typedT = (key: TranslationKey, options?: any) => {
    return t(key, options);
  };
  
  return { t: typedT, ...rest };
}

Example: typesafe-i18n library (zero-dependency, full type safety)

// Installation: npm install typesafe-i18n

// locales/en.json
{
  "HI": "Hi {name:string}!",
  "ITEMS": "You have {count:number} {count:plural(item|items)}",
  "PRICE": "Price: {amount:number|currency(USD)}"
}

// Generated types (automatic)
type Translation = {
  HI: (params: { name: string }) => string;
  ITEMS: (params: { count: number }) => string;
  PRICE: (params: { amount: number }) => string;
}

// Usage
import { useI18nContext } from './i18nContext';

function MyComponent() {
  const { LL } = useI18nContext(); // LL = Localized Language
  
  // ✅ Type-safe with parameter validation
  LL.HI({ name: 'John' }); // "Hi John!"
  
  // ❌ TypeScript error - missing required parameter
  LL.HI({ }); // Error: Property 'name' is missing
  
  // ❌ TypeScript error - wrong type
  LL.HI({ name: 123 }); // Error: Type 'number' is not assignable to 'string'
  
  // ✅ Pluralization
  LL.ITEMS({ count: 1 }); // "You have 1 item"
  LL.ITEMS({ count: 5 }); // "You have 5 items"
  
  // ✅ Formatted values
  LL.PRICE({ amount: 99.99 }); // "Price: $99.99"
  
  return <div>{LL.HI({ name: 'World' })}</div>;
}

Example: Validation in CI/CD pipeline

// scripts/validate-translations.ts
import fs from 'fs';
import path from 'path';

interface ValidationResult {
  valid: boolean;
  errors: string[];
}

function getTranslationKeys(obj: any, prefix = ''): string[] {
  return Object.keys(obj).flatMap(key => {
    const value = obj[key];
    const fullKey = prefix ? `${prefix}.${key}` : key;
    
    if (typeof value === 'object' && value !== null) {
      return getTranslationKeys(value, fullKey);
    }
    return [fullKey];
  });
}

function validateTranslations(): ValidationResult {
  const errors: string[] = [];
  const localesDir = path.join(__dirname, '../locales');
  const locales = fs.readdirSync(localesDir);
  
  // Load base locale (English)
  const baseLocale = 'en';
  const baseTranslations = JSON.parse(
    fs.readFileSync(path.join(localesDir, baseLocale, 'translation.json'), 'utf-8')
  );
  const baseKeys = new Set(getTranslationKeys(baseTranslations));
  
  // Check each locale
  locales.forEach(locale => {
    if (locale === baseLocale) return;
    
    const translations = JSON.parse(
      fs.readFileSync(path.join(localesDir, locale, 'translation.json'), 'utf-8')
    );
    const keys = new Set(getTranslationKeys(translations));
    
    // Check for missing keys
    baseKeys.forEach(key => {
      if (!keys.has(key)) {
        errors.push(`[${locale}] Missing translation key: ${key}`);
      }
    });
    
    // Check for extra keys
    keys.forEach(key => {
      if (!baseKeys.has(key)) {
        errors.push(`[${locale}] Extra translation key: ${key}`);
      }
    });
  });
  
  return {
    valid: errors.length === 0,
    errors
  };
}

// Run validation
const result = validateTranslations();
if (!result.valid) {
  console.error('Translation validation failed:');
  result.errors.forEach(error => console.error(`  - ${error}`));
  process.exit(1);
}

console.log('✅ All translations are valid!');

// package.json script
{
  "scripts": {
    "validate:i18n": "ts-node scripts/validate-translations.ts",
    "precommit": "npm run validate:i18n && npm run type-check"
  }
}

I18n Implementation Checklist