Localization (i18n)

Localization Package (@repo/i18n)

This shared package handles internationalization (i18n) and localization (l10n) for the application, supporting both frontend (web/native) and potentially backend use cases.

Why You Need This

Proper i18n lets you reach a global audience. By centralizing translations you can add languages without rewriting components. See Getting Started for how to run the project with multiple locales.

Features

  • Integration: Easy setup for React applications.
  • Environment Support: Works on both client-side and server-side.
  • Language Detection: Automatic language detection in browser environments.
  • Type Safety: Provides type-safe translation keys and interpolation.
  • Namespaces: Organizes translations into logical groups (e.g., common, auth, settings).
  • RTL Support: Includes helpers for Right-to-Left language direction.

Core Concepts

  • Resources: Translations are stored in TypeScript files under src/resources/{language_code}/{namespace}.ts.
  • Namespaces: Each file typically represents a namespace.
  • Initialization: Different initialization functions (initI18nClient, initI18nServer) are used depending on the environment.
  • Hooks: React hooks (useTranslation, useLanguage) provide access to translations and language management in components.
  • Trans Component: Allows embedding JSX within translations for complex formatting.

Setup & Initialization

Client-side (Web/Native)

Typically done once in your application's entry point.

import { initI18nClient, defaultResources } from '@repo/i18n'; // Initialize i18n const i18nInstance = initI18nClient( { defaultLanguage: 'en', // Or detect from user preferences/browser supportedLanguages: ['en', 'fr', 'es'], // List supported languages defaultNamespace: 'common', // Default namespace to load debug: process.env.NODE_ENV === 'development', }, defaultResources // Pass the imported resources ); // Ensure initialization completes if it involves async loading (though typically sync with defaultResources) i18nInstance.then(() => { // Render your React application });

Server-side (e.g., React Router Loader, Next.js Layout)

Initialize within the request context.

import { initI18nServer, defaultResources } from '@repo/i18n'; async function handleRequest(request) { const language = detectLanguageFromRequest(request); // Your logic here // Initialize i18n for this request const i18n = await initI18nServer( defaultResources, { defaultLanguage: language, supportedLanguages: ['en', 'fr', 'es'], defaultNamespace: 'common', } ); // Use i18n instance for server-rendering const greeting = i18n.t('common:welcome'); // Pass language and initial state to client if needed return renderApp({ language, initialI18nStore: i18n.store }); }

Usage in Components

Basic Translation (useTranslation)

import { useTranslation } from '@repo/i18n'; function Greeting() { // Specify namespace(s) needed by this component const { t } = useTranslation(['common', 'auth']); return ( <div> <h1>{t('common:welcome')}</h1> <p>{t('auth:signInPrompt')}</p> {/* Interpolation */} <p>{t('common:greeting', { name: 'Alice' })}</p> </div> ); }

Component Interpolation (Trans)

Use the Trans component when you need to include JSX elements within your translation string.

// Example translation key in en/common.ts: // userAgreement: "Please accept the <1>Terms of Service</1>." import { Trans, useTranslation } from '@repo/i18n'; function Agreement() { const { t } = useTranslation('common'); return ( <p> <Trans i18nKey="common:userAgreement"> Please accept the <a href="/terms">Terms of Service</a>. </Trans> </p> ); }

Language Switching (useLanguage)

import { useLanguage } from '@repo/i18n'; function LanguageSwitcher() { const { language, languages, changeLanguage } = useLanguage(); return ( <select value={language} onChange={(e) => changeLanguage(e.target.value)}> {languages.map((lng) => ( <option key={lng} value={lng}> {lng.toUpperCase()} {/* Display language name appropriately */} </option> ))} </select> ); }

Adding Translations

  1. Create/Edit Files: Add or modify files in src/resources/{lang}/{namespace}.ts.
    // src/resources/en/profile.ts const profile = { editButton: "Edit Profile", updateSuccess: "Profile updated successfully!", } as const; // Use 'as const' for type safety export default profile;
  2. Update Index: Add the new namespace/language to src/resources/index.ts.
    // src/resources/index.ts import enCommon from './en/common'; import enProfile from './en/profile'; // ... import other languages/namespaces export const defaultResources = { en: { common: enCommon, profile: enProfile, }, // ... other languages } as const;
  3. Update Types: Ensure the namespace is added to the TranslationNamespaces type in src/types.ts for full type checking.
    // src/types.ts import common from './resources/en/common'; import profile from './resources/en/profile'; export interface TranslationNamespaces { common: typeof common; profile: typeof profile; // Add other namespaces here }

Following these steps ensures that new translation keys are type-checked when used with the t function.