next-intl Internationalization Patterns
Quick Guide: Use next-intl for type-safe internationalization in Next.js App Router.
useTranslationsfor messages,useFormatterfor dates/numbers, middleware for locale detection. CallsetRequestLocale(locale)for static rendering.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST call setRequestLocale(locale) at the top of ALL page/layout components for static rendering)
(You MUST validate locale against routing.locales before using it)
(You MUST use NextIntlClientProvider in the root layout to enable client-side hooks)
(You MUST use named constants for locale codes - NO inline locale strings)
</critical_requirements>
Auto-detection: next-intl, useTranslations, useFormatter, useLocale, NextIntlClientProvider, i18n routing, locale detection, ICU message format
When to use:
- Implementing internationalization in Next.js App Router
- Rendering localized messages with interpolation and pluralization
- Formatting dates, numbers, and relative time per locale
- Setting up locale-based routing and middleware
- Generating static pages for multiple locales
Key patterns covered:
- Project setup with routing.ts, request.ts, and middleware
- useTranslations hook for messages with ICU syntax
- useFormatter hook for dates, numbers, and lists
- Static rendering with generateStaticParams and setRequestLocale
- TypeScript integration for type-safe translation keys
When NOT to use:
- Simple single-locale applications (skip i18n complexity)
- Pages Router (different API - use Pages Router docs)
- Non-Next.js React applications (use react-intl instead)
Detailed Resources:
- For code examples, see examples/ (core.md, formatting.md, pluralization.md, markup.md)
- For decision frameworks and anti-patterns, see reference.md
<philosophy>
Philosophy
next-intl follows the principle of type-safe, locale-aware rendering with ICU message format support. Translations are organized as namespaced JSON objects, loaded per-request for Server Components and provided via context for Client Components. The middleware handles locale detection automatically, while setRequestLocale enables static rendering at build time.
Core principles:
- Server-first: Load translations in Server Components for better performance
- Type-safe keys: TypeScript augmentation catches missing translations at compile time
- ICU standard: Use industry-standard ICU message syntax for pluralization and formatting
- Static-friendly: Support static generation with explicit locale parameters
<patterns>
Core Patterns
Pattern 1: Project Setup
Set up next-intl with the App Router using the standard file structure.
File Structure
src/
i18n/
routing.ts # Locale configuration
request.ts # Server-side locale resolution
navigation.ts # Locale-aware Link, useRouter
proxy.ts # Locale detection and routing (middleware.ts before Next.js 16)
app/
[locale]/
layout.tsx # Root layout with NextIntlClientProvider
page.tsx # Pages within locale segment
messages/
en.json # English translations
de.json # German translations
Note: In Next.js 16+,
middleware.tswas renamed toproxy.ts. If using Next.js 15 or earlier, usemiddleware.ts.
Configuration Files
// src/i18n/routing.ts
import { defineRouting } from "next-intl/routing";
export const SUPPORTED_LOCALES = ["en", "de", "fr"] as const;
export const DEFAULT_LOCALE = "en";
export const routing = defineRouting({
locales: SUPPORTED_LOCALES,
defaultLocale: DEFAULT_LOCALE,
});
export type Locale = (typeof routing.locales)[number];
Why good: named constants for locales enable type-safe usage throughout app, exported Locale type enables type checking of locale parameters
// src/i18n/request.ts
import { getRequestConfig } from "next-intl/server";
import { hasLocale } from "next-intl";
import { routing } from "./routing";
export default getRequestConfig(async ({ requestLocale }) => {
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default,
};
});
Why good: validates locale against supported list, falls back to default for invalid locales, dynamically imports only needed translation file
// src/i18n/navigation.ts
import { createNavigation } from "next-intl/navigation";
import { routing } from "./routing";
export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing);
Why good: wraps Next.js navigation APIs with locale awareness, Link automatically includes locale prefix
// src/proxy.ts (Next.js 16+) or src/middleware.ts (Next.js 15 and earlier)
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export default createMiddleware(routing);
export const config = {
matcher: "/((?!api|_next|_vercel|.*\\..*).*)",
};
Why good: proxy/middleware handles locale detection from URL, cookies, and Accept-Language header, matcher excludes API routes and static files. Add additional exclusions for your API framework routes as needed.
Pattern 2: Root Layout with Provider
Wrap the application with NextIntlClientProvider and validate the locale.
// src/app/[locale]/layout.tsx
import { NextIntlClientProvider, hasLocale } from "next-intl";
import { notFound } from "next/navigation";
import { getMessages, setRequestLocale } from "next-intl/server";
import { routing, type Locale } from "@/i18n/routing";
type Props = {
children: React.ReactNode;
params: Promise<{ locale: string }>;
};
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params;
if (!hasLocale(routing.locales, locale)) {
notFound();
}
setRequestLocale(locale);
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
Why good: validates locale and returns 404 for invalid locales, setRequestLocale enables static rendering, generateStaticParams pre-renders all locale variants, explicit messages prop ensures Client Components receive translations, html lang attribute improves accessibility
Note: In next-intl v4.0+,
NextIntlClientProviderauto-inherits messages from server config. Passingmessagesexplicitly is optional but recommended for clarity.
Pattern 3: useTranslations Hook
Use the useTranslations hook for rendering localized messages.
Basic Usage
// src/app/[locale]/about/page.tsx
import { useTranslations } from "next-intl";
import { setRequestLocale } from "next-intl/server";
type Props = {
params: Promise<{ locale: string }>;
};
export default async function AboutPage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = useTranslations("About");
return (
<article>
<h1>{t("title")}</h1>
<p>{t("description")}</p>
</article>
);
}
// messages/en.json
{
"About": {
"title": "About Us",
"description": "Learn more about our company."
}
}
Why good: namespaced translations keep messages organized, setRequestLocale at top of component enables static rendering