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
- 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;
- 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;
- Update Types: Ensure the namespace is added to the
TranslationNamespaces
type insrc/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.