web-i18n-next-intl
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesenext-intl Internationalization Patterns
next-intl 国际化模式
Quick Guide: Use next-intl for type-safe internationalization in Next.js App Router.for messages,useTranslationsfor dates/numbers, middleware for locale detection. CalluseFormatterfor static rendering.setRequestLocale(locale)
<critical_requirements>
快速指南: 使用next-intl实现Next.js App Router中的类型安全国际化。使用处理消息,useTranslations处理日期/数字,中间件用于区域检测。调用useFormatter以支持静态渲染。setRequestLocale(locale)
<critical_requirements>
CRITICAL: Before Using This Skill
重要提示:使用本方案前须知
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST call at the top of ALL page/layout components for static rendering)
setRequestLocale(locale)(You MUST validate locale against before using it)
routing.locales(You MUST use in the root layout to enable client-side hooks)
NextIntlClientProvider(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>
所有代码必须遵循 CLAUDE.md 中的项目规范(短横线命名、命名导出、导入排序、、命名常量)import type
(必须在所有页面/布局组件顶部调用 以支持静态渲染)
setRequestLocale(locale)(必须在使用区域前验证其是否在 列表中)
routing.locales(必须在根布局中使用 以启用客户端钩子)
NextIntlClientProvider(必须使用命名常量存储区域代码 - 禁止使用内联区域字符串)
</critical_requirements>
自动检测相关: next-intl, useTranslations, useFormatter, useLocale, NextIntlClientProvider, i18n 路由, 区域检测, ICU 消息格式
适用场景:
- 在Next.js App Router中实现国际化
- 渲染带插值和复数形式的本地化消息
- 根据区域格式化日期、数字和相对时间
- 基于区域配置路由和中间件
- 为多区域生成静态页面
涵盖的核心模式:
- 使用 routing.ts、request.ts 和中间件的项目配置
- 结合ICU语法的 useTranslations 钩子
- 用于日期、数字和列表格式化的 useFormatter 钩子
- 结合 generateStaticParams 和 setRequestLocale 的静态渲染
- 用于类型安全翻译键的TypeScript集成
不适用场景:
- 简单单区域应用(无需国际化复杂度)
- Pages Router(使用不同API - 参考Pages Router文档)
- 非Next.js的React应用(改用react-intl)
详细资源:
- 代码示例请查看 examples/(core.md、formatting.md、pluralization.md、markup.md)
- 决策框架与反模式请查看 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 enables static rendering at build time.
setRequestLocaleCore 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>
next-intl遵循类型安全、区域感知渲染原则,支持ICU消息格式。翻译内容组织为命名空间JSON对象,在Server Components中按请求加载,在Client Components中通过上下文提供。中间件自动处理区域检测,则在构建时启用静态渲染。
setRequestLocale核心原则:
- 优先服务端渲染:在Server Components中加载翻译以提升性能
- 类型安全键:TypeScript增强可在编译时捕获缺失的翻译
- ICU标准:使用行业标准的ICU消息语法处理复数和格式化
- 友好支持静态渲染:通过显式区域参数支持静态生成
<patterns>
Core Patterns
核心模式
Pattern 1: Project Setup
模式1:项目配置
Set up next-intl with the App Router using the standard file structure.
使用标准文件结构为App Router配置next-intl。
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 translationsNote: In Next.js 16+,was renamed tomiddleware.ts. If using Next.js 15 or earlier, useproxy.ts.middleware.ts
src/
i18n/
routing.ts # 区域配置
request.ts # 服务端区域解析
navigation.ts # 区域感知的Link、useRouter
proxy.ts # 区域检测与路由(Next.js 16之前为middleware.ts)
app/
[locale]/
layout.tsx # 包含NextIntlClientProvider的根布局
page.tsx # 区域分段内的页面
messages/
en.json # 英文翻译
de.json # 德文翻译注意: 在Next.js 16+中,已重命名为middleware.ts。若使用Next.js 15或更早版本,请使用proxy.ts。middleware.ts
Configuration Files
配置文件
typescript
// 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
typescript
// 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
typescript
// 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
typescript
// 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.
typescript
// 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];优势: 区域命名常量可在整个应用中实现类型安全使用,导出的Locale类型可对区域参数进行类型检查
typescript
// 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,
};
});优势: 验证区域是否在支持列表中,无效区域自动回退到默认值,仅动态导入所需的翻译文件
typescript
// src/i18n/navigation.ts
import { createNavigation } from "next-intl/navigation";
import { routing } from "./routing";
export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing);优势: 封装Next.js导航API以支持区域感知,Link会自动包含区域前缀
typescript
// src/proxy.ts (Next.js 16+) 或 src/middleware.ts (Next.js 15及更早版本)
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export default createMiddleware(routing);
export const config = {
matcher: "/((?!api|_next|_vercel|.*\\..*).*)",
};优势: 代理/中间件从URL、Cookie和Accept-Language头中自动检测区域,匹配器排除API路由和静态文件。可根据需要为API框架路由添加额外排除规则。
Pattern 2: Root Layout with Provider
模式2:包含Provider的根布局
Wrap the application with NextIntlClientProvider and validate the locale.
typescript
// 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+,auto-inherits messages from server config. PassingNextIntlClientProviderexplicitly is optional but recommended for clarity.messages
使用NextIntlClientProvider包裹应用并验证区域。
typescript
// 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>
);
}优势: 验证区域,无效区域返回404;setRequestLocale启用静态渲染;generateStaticParams预渲染所有区域变体;显式传递messages属性确保Client Components能获取翻译;html lang属性提升可访问性
注意: 在next-intl v4.0+中,会自动从服务端配置继承messages。显式传递NextIntlClientProvider为可选操作,但推荐保持以提升代码清晰度。messages
Pattern 3: useTranslations Hook
模式3:useTranslations钩子
Use the useTranslations hook for rendering localized messages.
使用useTranslations钩子渲染本地化消息。
Basic Usage
基础用法
typescript
// 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>
);
}json
// 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
typescript
// 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>
);
}json
// messages/en.json
{
"About": {
"title": "About Us",
"description": "Learn more about our company."
}
}优势: 命名空间翻译使消息结构清晰,组件顶部的setRequestLocale启用静态渲染
With Interpolation
插值用法
typescript
const t = useTranslations("Profile");
// Message: "Hello, {name}!"
t("greeting", { name: user.name }); // "Hello, Jane!"
// Message: "You have {count} unread messages"
t("unreadCount", { count: messages.length }); // "You have 5 unread messages"Why good: named placeholders are explicit and refactorable, TypeScript can validate placeholder names with augmentation
typescript
const t = useTranslations("Profile");
// 消息:"Hello, {name}!"
t("greeting", { name: user.name }); // "Hello, Jane!"
// 消息:"You have {count} unread messages"
t("unreadCount", { count: messages.length }); // "You have 5 unread messages"优势: 命名占位符清晰且易于重构,TypeScript可通过增强功能验证占位符名称
Pattern 4: Pluralization with ICU Syntax
模式4:ICU语法复数处理
Use ICU plural syntax for count-based messages.
typescript
const t = useTranslations("Notifications");
// Renders correct plural form based on locale rules
t("itemCount", { count: items.length });json
// messages/en.json
{
"Notifications": {
"itemCount": "{count, plural, =0 {No items} one {# item} other {# items}}"
}
}Plural categories by language:
- English: ,
oneother - Russian: ,
one,few,manyother - Arabic: ,
zero,one,two,few,manyother
Why good: ICU plural syntax handles locale-specific plural rules automatically, is replaced with the formatted count
#使用ICU复数语法处理基于数量的消息。
typescript
const t = useTranslations("Notifications");
// 根据区域规则渲染正确的复数形式
t("itemCount", { count: items.length });json
// messages/en.json
{
"Notifications": {
"itemCount": "{count, plural, =0 {No items} one {# item} other {# items}}"
}
}不同语言的复数分类:
- 英语:,
oneother - 俄语:,
one,few,manyother - 阿拉伯语:,
zero,one,two,few,manyother
优势: ICU复数语法自动处理区域特定的复数规则,会被替换为格式化后的数量
#Ordinal Pluralization
序数复数
typescript
// Message: "It's your {year, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} birthday!"
t("birthday", { year: 21 }); // "It's your 21st birthday!"typescript
// 消息:"It's your {year, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} birthday!"
t("birthday", { year: 21 }); // "It's your 21st birthday!"Pattern 5: Rich Text Formatting
模式5:富文本格式化
Use for messages containing markup.
t.rich()typescript
const t = useTranslations("Legal");
const content = t.rich("terms", {
link: (chunks) => <a href="/terms">{chunks}</a>,
bold: (chunks) => <strong>{chunks}</strong>,
});
return <p>{content}</p>;json
{
"Legal": {
"terms": "By signing up, you agree to our <link>Terms of Service</link> and <bold>Privacy Policy</bold>."
}
}Why good: keeps translation strings complete and translatable, markup tags are defined by developers and can be React components
使用处理包含标记的消息。
t.rich()typescript
const t = useTranslations("Legal");
const content = t.rich("terms", {
link: (chunks) => <a href="/terms">{chunks}</a>,
bold: (chunks) => <strong>{chunks}</strong>,
});
return <p>{content}</p>;json
{
"Legal": {
"terms": "By signing up, you agree to our <link>Terms of Service</link> and <bold>Privacy Policy</bold>."
}
}优势: 保持翻译字符串完整且可翻译,标记标签由开发者定义,可映射为React组件
Pattern 6: useFormatter Hook
模式6:useFormatter钩子
Use useFormatter for locale-aware formatting of dates, numbers, and lists.
使用useFormatter进行区域感知的日期、数字和列表格式化。
Date and Time Formatting
日期与时间格式化
typescript
import { useFormatter } from "next-intl";
function EventDate({ date }: { date: Date }) {
const format = useFormatter();
return (
<time dateTime={date.toISOString()}>
{format.dateTime(date, {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
);
}Why good: uses Intl.DateTimeFormat under the hood, respects locale-specific date formats automatically
typescript
import { useFormatter } from "next-intl";
function EventDate({ date }: { date: Date }) {
const format = useFormatter();
return (
<time dateTime={date.toISOString()}>
{format.dateTime(date, {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
);
}优势: 底层使用Intl.DateTimeFormat,自动遵循区域特定的日期格式
Relative Time with useNow
结合useNow的相对时间
typescript
import { useFormatter, useNow } from "next-intl";
const UPDATE_INTERVAL_MS = 60000;
function RelativeTime({ date }: { date: Date }) {
const format = useFormatter();
const now = useNow({ updateInterval: UPDATE_INTERVAL_MS });
return <time>{format.relativeTime(date, now)}</time>;
}Why good: useNow provides a reactive "now" value that updates on interval, relative time updates automatically
typescript
import { useFormatter, useNow } from "next-intl";
const UPDATE_INTERVAL_MS = 60000;
function RelativeTime({ date }: { date: Date }) {
const format = useFormatter();
const now = useNow({ updateInterval: UPDATE_INTERVAL_MS });
return <time>{format.relativeTime(date, now)}</time>;
}优势: useNow提供响应式的“当前时间”值并按间隔更新,相对时间会自动刷新
Number and Currency Formatting
数字与货币格式化
typescript
import { useFormatter } from "next-intl";
function Price({ amount, currency }: { amount: number; currency: string }) {
const format = useFormatter();
return (
<span>
{format.number(amount, {
style: "currency",
currency,
})}
</span>
);
}Why good: handles locale-specific number formatting (1,234.56 vs 1.234,56), currency symbols and positions vary by locale
typescript
import { useFormatter } from "next-intl";
function Price({ amount, currency }: { amount: number; currency: string }) {
const format = useFormatter();
return (
<span>
{format.number(amount, {
style: "currency",
currency,
})}
</span>
);
}优势: 处理区域特定的数字格式(如1,234.56 vs 1.234,56),货币符号和位置随区域变化
Pattern 7: Static Rendering with generateStaticParams
模式7:结合generateStaticParams的静态渲染
Enable static generation for all locale variants.
typescript
// src/app/[locale]/blog/[slug]/page.tsx
import { setRequestLocale } from "next-intl/server";
import { routing } from "@/i18n/routing";
type Props = {
params: Promise<{ locale: string; slug: string }>;
};
export function generateStaticParams() {
const slugs = ["getting-started", "advanced-features", "faq"];
return routing.locales.flatMap((locale) =>
slugs.map((slug) => ({ locale, slug })),
);
}
export default async function BlogPost({ params }: Props) {
const { locale, slug } = await params;
setRequestLocale(locale);
// Component implementation
}Why good: generates all combinations of locales and slugs at build time, setRequestLocale enables next-intl to work in static context
为所有区域变体启用静态生成。
typescript
// src/app/[locale]/blog/[slug]/page.tsx
import { setRequestLocale } from "next-intl/server";
import { routing } from "@/i18n/routing";
type Props = {
params: Promise<{ locale: string; slug: string }>;
};
export function generateStaticParams() {
const slugs = ["getting-started", "advanced-features", "faq"];
return routing.locales.flatMap((locale) =>
slugs.map((slug) => ({ locale, slug })),
);
}
export default async function BlogPost({ params }: Props) {
const { locale, slug } = await params;
setRequestLocale(locale);
// 组件实现
}优势: 在构建时生成所有区域和slug的组合,setRequestLocale使next-intl能在静态环境中工作
Pattern 8: Locale Switching
模式8:区域切换
Implement a locale switcher component using next-intl navigation.
typescript
"use client";
import { useLocale } from "next-intl";
import { usePathname, useRouter } from "@/i18n/navigation";
import { routing, type Locale } from "@/i18n/routing";
const LOCALE_LABELS: Record<Locale, string> = {
en: "English",
de: "Deutsch",
fr: "Francais",
};
export function LocaleSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const handleChange = (newLocale: Locale) => {
router.replace(pathname, { locale: newLocale });
};
return (
<select
value={locale}
onChange={(e) => handleChange(e.target.value as Locale)}
aria-label="Select language"
>
{routing.locales.map((loc) => (
<option key={loc} value={loc}>
{LOCALE_LABELS[loc]}
</option>
))}
</select>
);
}Why good: uses next-intl navigation APIs to preserve current path, aria-label provides accessibility, type-safe locale handling
使用next-intl导航API实现区域切换组件。
typescript
"use client";
import { useLocale } from "next-intl";
import { usePathname, useRouter } from "@/i18n/navigation";
import { routing, type Locale } from "@/i18n/routing";
const LOCALE_LABELS: Record<Locale, string> = {
en: "English",
de: "Deutsch",
fr: "Francais",
};
export function LocaleSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const handleChange = (newLocale: Locale) => {
router.replace(pathname, { locale: newLocale });
};
return (
<select
value={locale}
onChange={(e) => handleChange(e.target.value as Locale)}
aria-label="Select language"
>
{routing.locales.map((loc) => (
<option key={loc} value={loc}>
{LOCALE_LABELS[loc]}
</option>
))}
</select>
);
}优势: 使用next-intl导航API保留当前路径,aria-label提升可访问性,类型安全的区域处理
Pattern 9: TypeScript Integration
模式9:TypeScript集成
Enable type-safe translation keys with TypeScript augmentation using the interface (next-intl v4.0+).
AppConfig使用接口(next-intl v4.0+)通过TypeScript增强实现类型安全的翻译键。
AppConfigConfiguration
配置
typescript
// src/i18n/types.ts (or global.d.ts)
import type en from "../../messages/en.json";
import { routing } from "./routing";
import type { formats } from "./request";
declare module "next-intl" {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof en;
Formats: typeof formats;
}
}json
// tsconfig.json (add to compilerOptions)
{
"compilerOptions": {
"allowArbitraryExtensions": true
}
}Why good: typos in translation keys become compile-time errors, IDE autocomplete for translation keys, strictly-typed locales prevent invalid locale strings, Formats registration enables type-safe formatting
typescript
// src/i18n/types.ts (或 global.d.ts)
import type en from "../../messages/en.json";
import { routing } from "./routing";
import type { formats } from "./request";
declare module "next-intl" {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof en;
Formats: typeof formats;
}
}json
// tsconfig.json (添加到compilerOptions)
{
"compilerOptions": {
"allowArbitraryExtensions": true
}
}优势: 翻译键中的拼写错误会成为编译时错误,IDE可自动补全翻译键,严格类型化的区域可防止无效区域字符串,注册Formats可实现类型安全的格式化
Optional: Type-Safe Message Arguments (Experimental)
可选:类型安全的消息参数(实验性)
For automatic type inference of ICU message arguments, configure :
createMessagesDeclarationtypescript
// next.config.mjs
import { createNextIntlPlugin } from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin({
experimental: {
createMessagesDeclaration: "./messages/en.json",
},
});
export default withNextIntl({});This generates type declarations enabling autocomplete for message arguments like or .
{name}{count, plural, ...}若要自动推断ICU消息参数的类型,请配置:
createMessagesDeclarationtypescript
// next.config.mjs
import { createNextIntlPlugin } from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin({
experimental: {
createMessagesDeclaration: "./messages/en.json",
},
});
export default withNextIntl({});这会生成类型声明,为或等消息参数提供自动补全。
{name}{count, plural, ...}Pattern 10: Server Actions and Metadata
模式10:Server Actions与元数据
Use async getTranslations for Server Actions and Metadata.
typescript
// src/app/[locale]/page.tsx
import { getTranslations, setRequestLocale } from "next-intl/server";
type Props = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata({ params }: Props) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Metadata" });
return {
title: t("title"),
description: t("description"),
};
}
export default async function HomePage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations("Home");
return <h1>{t("welcome")}</h1>;
}Why good: getTranslations works in async contexts like generateMetadata, locale parameter is required for metadata since it runs outside component tree
</patterns>
<integration>
在Server Actions和元数据中使用异步的getTranslations。
typescript
// src/app/[locale]/page.tsx
import { getTranslations, setRequestLocale } from "next-intl/server";
type Props = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata({ params }: Props) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Metadata" });
return {
title: t("title"),
description: t("description"),
};
}
export default async function HomePage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations("Home");
return <h1>{t("welcome")}</h1>;
}优势: getTranslations可在generateMetadata等异步上下文中工作,元数据运行在组件树之外,因此需要显式传递区域参数给getTranslations
</patterns>
<integration>
Integration Notes
集成说明
- Server Components first: Load translations in Server Components for performance; use (sync) or
useTranslations(async)getTranslations - Client Components: Wrap with for hooks access; locale switching and interactive features live here
NextIntlClientProvider - Locale state: Managed entirely by next-intl - read with , never store separately
useLocale()
<red_flags>
- 优先使用Server Components:在Server Components中加载翻译以提升性能;使用(同步)或
useTranslations(异步)getTranslations - Client Components:使用包裹以访问钩子;区域切换和交互功能应放在此处
NextIntlClientProvider - 区域状态:完全由next-intl管理 - 使用读取,切勿单独存储
useLocale()
<red_flags>
RED FLAGS
注意事项
High Priority Issues:
- Missing in page/layout components -- breaks static rendering
setRequestLocale(locale) - Not awaiting in App Router -- params is a Promise in Next.js 15+, causes runtime errors
params - Missing in root layout -- Client Components cannot access translations
NextIntlClientProvider - Hardcoded locale strings -- use named constants from routing.ts
- Not validating locale against -- invalid locales cause cryptic errors
routing.locales - Using on Next.js 16+ -- must rename to
middleware.tsproxy.ts
Medium Priority Issues:
- Missing for static routes -- forces dynamic rendering
generateStaticParams - Using instead of
t()for messages with markup -- returns string, not ReactNodet.rich() - Missing namespace in -- all keys become global, conflicts likely
useTranslations - Using in
useTranslations-- usegenerateMetadatainsteadgetTranslations
Gotchas & Edge Cases:
- must be called at the TOP of components, before any hooks
setRequestLocale(locale) - runs outside the component tree -- requires explicit locale param to
generateMetadatagetTranslations - tag functions receive
t.rich()(ReactNode[]), not a single elementchunks - only updates on client -- SSR shows initial value until hydration
useNow() - v4.0+ GDPR cookie changes: locale cookies now expire when browser closes and are only set when user switches locale
- Next.js 16: runs on Node.js runtime, not Edge
proxy.ts
For full anti-patterns with code examples, see reference.md.
</red_flags>
<critical_reminders>
高优先级问题:
- 页面/布局组件中缺少-- 会破坏静态渲染
setRequestLocale(locale) - 在App Router中未等待-- Next.js 15+中params是Promise,会导致运行时错误
params - 根布局中缺少-- Client Components无法访问翻译
NextIntlClientProvider - 硬编码区域字符串 -- 使用routing.ts中的命名常量
- 未验证区域是否在中 -- 无效区域会导致难以排查的错误
routing.locales - 在Next.js 16+中使用-- 必须重命名为
middleware.tsproxy.ts
中优先级问题:
- 静态路由中缺少-- 会强制使用动态渲染
generateStaticParams - 对包含标记的消息使用而非
t()-- 返回字符串而非ReactNodet.rich() - 中缺少命名空间 -- 所有键变为全局,易引发冲突
useTranslations - 在中使用
generateMetadata-- 改用useTranslationsgetTranslations
陷阱与边缘情况:
- 必须在组件顶部调用,早于任何钩子
setRequestLocale(locale) - 运行在组件树之外 -- 需要显式传递区域参数给
generateMetadatagetTranslations - 标签函数接收
t.rich()(ReactNode[]),而非单个元素chunks - 仅在客户端更新 -- SSR会显示初始值直到完成 hydration
useNow() - v4.0+ GDPR Cookie变更:区域Cookie现在会在浏览器关闭时过期,且仅在用户切换区域时设置
- Next.js 16:运行在Node.js运行时,而非Edge
proxy.ts
完整的反模式及代码示例请查看 reference.md。
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
重要提醒
All code must follow project conventions in CLAUDE.md
(You MUST call at the top of ALL page/layout components for static rendering)
setRequestLocale(locale)(You MUST validate locale against before using it)
routing.locales(You MUST use in the root layout to enable client-side hooks)
NextIntlClientProvider(You MUST use named constants for locale codes - NO inline locale strings)
Failure to follow these rules will break static generation and cause runtime errors with invalid locales.
</critical_reminders>
所有代码必须遵循 CLAUDE.md 中的项目规范
(必须在所有页面/布局组件顶部调用 以支持静态渲染)
setRequestLocale(locale)(必须在使用区域前验证其是否在 列表中)
routing.locales(必须在根布局中使用 以启用客户端钩子)
NextIntlClientProvider(必须使用命名常量存储区域代码 - 禁止使用内联区域字符串)
不遵循这些规则会破坏静态生成,并导致无效区域引发的运行时错误。
</critical_reminders>