Chapter 42: Internationalization (i18n)
Internationalization makes your application usable in multiple languages and locales. This chapter covers react-i18next setup, translation management, and patterns for a maintainable i18n system.
Setup with react-i18nextβ
pnpm add react-i18next i18next i18next-browser-languagedetector
// shared/lib/i18n.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
// Translation resources
import en from "@/locales/en.json";
import es from "@/locales/es.json";
import ja from "@/locales/ja.json";
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: { translation: en },
es: { translation: es },
ja: { translation: ja },
},
fallbackLng: "en",
interpolation: {
escapeValue: false, // React handles XSS protection
},
detection: {
order: ["localStorage", "navigator"],
caches: ["localStorage"],
},
});
export default i18n;
Translation Filesβ
// src/locales/en.json
{
"common": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"loading": "Loading...",
"error": "Something went wrong"
},
"auth": {
"login": "Sign In",
"register": "Create Account",
"logout": "Sign Out",
"email": "Email Address",
"password": "Password"
},
"projects": {
"title": "Projects",
"create": "Create Project",
"empty": "No projects yet",
"empty_description": "Create your first project to get started.",
"name": "Project Name",
"status": {
"active": "Active",
"archived": "Archived",
"completed": "Completed"
},
"task_count": "{{count}} task",
"task_count_plural": "{{count}} tasks"
}
}
Using Translations in Componentsβ
import { useTranslation } from "react-i18next";
function ProjectList() {
const { t } = useTranslation();
return (
<div>
<PageHeader
title={t("projects.title")}
actions={<Button>{t("projects.create")}</Button>}
/>
{projects.length === 0 ? (
<EmptyState
title={t("projects.empty")}
description={t("projects.empty_description")}
/>
) : (
projects.map((project) => (
<ProjectCard
key={project.id}
project={project}
statusLabel={t(`projects.status.${project.status}`)}
taskLabel={t("projects.task_count", { count: project.taskCount })}
/>
))
)}
</div>
);
}
Namespace-Based Code Splittingβ
For large applications, split translations by feature:
i18n.init({
ns: ["common", "auth", "projects", "tasks", "settings"],
defaultNS: "common",
// Load namespaces on demand
partialBundledLanguages: true,
});
// Only load the namespace this component needs
function ProjectSettings() {
const { t } = useTranslation("settings");
return <h1>{t("project_settings")}</h1>;
}
Language Switcherβ
function LanguageSwitcher() {
const { i18n } = useTranslation();
const languages = [
{ code: "en", label: "English" },
{ code: "es", label: "EspaΓ±ol" },
{ code: "ja", label: "ζ₯ζ¬θͺ" },
];
return (
<Select value={i18n.language} onValueChange={(lang) => i18n.changeLanguage(lang)}>
<SelectTrigger className="w-36">
<SelectValue />
</SelectTrigger>
<SelectContent>
{languages.map((lang) => (
<SelectItem key={lang.code} value={lang.code}>
{lang.label}
</SelectItem>
))}
</SelectContent>
</Select>
);
}
Summaryβ
- β react-i18next for React integration with hooks API
- β Language detection from browser settings with localStorage persistence
- β Namespace splitting for code-splitting translations by feature
- β
Pluralization with ICU-style
{{count}}interpolation - β Language switcher component for user preference