Internationalization (i18n) Workflow
Overview
This project uses next-intl for internationalization. Translations are stored in JSON message files.
Key Functions
-
Server Components: Use getTranslations (no locale needed)
-
Client Components: Use useTranslations
-
Metadata/Server Actions: Use getTranslations with locale parameter
Critical Rules
- No Dynamic Values in Translation Keys
IMPORTANT: The t function does NOT support dynamic values as keys.
// BAD: Dynamic key - will NOT work const category = getCategory(); t(category); // Error! t(MYLABELS[category]);
// GOOD: String literals only t("Arts courses"); t("Business courses"); t("Technology courses");
- String Interpolation IS Supported
You CAN use string interpolation for dynamic values within translations:
// GOOD: Interpolation with static key t("Explore all {category} courses", { category: "arts" }); t("Welcome, {name}!", { name: user.name }); t("{count} items remaining", { count: 5 });
- Don't Pass t Function Around
You can't pass the t function to other functions or components:
// BAD: Passing t function function myFunction(t, label) { return t(label); } myFunction(t, "Some label");
// GOOD: Call t directly function myFunction(translatedLabel: string) { return translatedLabel; } myFunction(t("Some label"));
- Locale Parameter Rules
// Server Component - no locale needed const t = await getTranslations(); t("Hello");
// generateMetadata - needs locale export async function generateMetadata({ params }) { const { locale } = await params; const t = await getTranslations({ locale }); return { title: t("Page Title") }; }
Workflow for Adding Translations
Add translation call in code:
const t = await getTranslations(); return <h1>{t("New page title")}</h1>;
Add the key to message files:
Find JSON message files in your locales directory (e.g., messages/en.json , messages/es.json ) and add the new key:
{ "New page title": "New page title" }
Translate for each locale:
// messages/es.json { "New page title": "Nuevo título de página" }
Common Patterns
Conditional Text
// Use separate translation keys const status = isActive ? t("Active") : t("Inactive");
Pluralization
// Use ICU message format t("items", { count: 5 }); // "5 items"
In your messages file:
{ "items": "{count, plural, =0 {No items} =1 {1 item} other {# items}}" }
Rich Text (with components)
t.rich("Read our {link}", { link: (chunks) => <Link href="/terms">{chunks}</Link>, });
In your messages file:
{ "Read our {link}": "Read our <link>terms of service</link>" }
File Structure
messages/ en.json # English translations es.json # Spanish translations pt.json # Portuguese translations
Namespaces
For larger apps, organize translations by namespace:
// Server Component const t = await getTranslations("Dashboard"); t("welcome"); // Looks up "Dashboard.welcome"
// Client Component const t = useTranslations("Dashboard"); t("welcome");
{ "Dashboard": { "welcome": "Welcome to your dashboard" } }