关于 web-i18n-next-intl
web-i18n-next-intl 是面向 Next.js App Router 项目的 next-intl 国际化模式指南,适用于实现者和维护者。它汇总路由、消息渲染、格式化与静态生成的实践,并指出常见配置陷阱;不涵盖 Pages Router 或翻译内容管理。
主要功能
- 01配置语言路由与检测
- 02在组件及异步上下文读取消息
- 03处理复数、插值和富文本
- 04按地区格式化日期与数字
- 05生成静态语言版本
- 06为消息键和格式添加类型
适用场景
- 01构建基于语言段的路由
- 02本地化页面、元数据和 Server Actions
- 03按语言规则显示复数与日期
- 04静态生成各语言页面并检查类型
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-i18n-next-intl安装后在 Claude Code 中输入/web-i18n-next-intl确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlreference.mdexamples/core.mdformatting.mdmarkup.mdpluralization.md详细介绍
next-intl 国际化模式
快速指南:
useTranslations用于渲染消息,useFormatter用于渲染日期、数字和 列表,createMiddleware用于检测区域设置。在页面或布局顶部调用setRequestLocale(locale),才能保持静态渲染能力。v4.0+ 通过AppConfig接口注册类型,并且仅当用户切换到其 Accept-Language 偏好之外的区域设置时才设置区域设置 Cookie。此处的所有模式均适用于 App Router — Pages Router 集成使用独立的 API,这些内容均不适用于它。
详细资源:
- examples/core.md — 设置、提供程序、
useTranslations、复数、格式化、静态渲染、区域设置切换、类型 - examples/formatting.md — 自动更新的相对时间、列表格式化、组合格式模式
- examples/pluralization.md — 序数、嵌套在 select 中的复数、零值处理
- examples/markup.md — 用于 HTML 字符串的
t.markup():电子邮件正文、信息流、净化 - reference.md — 决策树、反模式代码、ICU 语法表、设置清单
适用的路径
- 在组件内渲染 — 使用
useTranslations和useFormatter,并在调用它们的任何客户端组件上层放置NextIntlClientProvider。请参阅 examples/core.md。 - 在组件树外渲染 — 元数据、服务器操作和其他异步上下文使用
getTranslations({ locale, namespace }),需要显式传入区域设置。详见 examples/core.md。 - 生成 HTML 字符串而非元素 — 使用
t.markup()而不是t.rich(),并进行相应的 净化。请参阅 examples/markup.md。
<critical_requirements>
编写 next-intl 代码前
在每个页面和布局的顶部、调用任何钩子之前,调用 setRequestLocale(locale)。 这样 next-intl 就能在没有请求的情况下解析区域设置,从而让路由保持静态可渲染。
使用前,先用 hasLocale(routing.locales, locale) 验证区域设置。 未验证的路由段会传到消息加载器,并在那里失败,离生成它的路由很远。
使用 NextIntlClientProvider 包裹组件树。 客户端组件从该上下文读取消息,没有它就不会渲染任何内容。
</critical_requirements>
自动检测: next-intl、useTranslations、useFormatter、useLocale、getTranslations、setRequestLocale、NextIntlClientProvider、defineRouting、createNavigation、hasLocale、ICU 消息格式
适用范围:
- 区域设置路由段、区域设置检测和区域设置感知型导航 API
- 使用插值、复数形式和嵌入式标记渲染消息
- 按区域设置格式化日期、数字、相对时间和列表
- 在构建时为路由生成每种区域设置的变体
- 对消息键和格式进行类型检查,以便缺失的键在编译时就会报错
其他内容另行处理:
- 区域设置路由段以外的框架路由和渲染——本技能说明 next-intl 为路由添加了什么,而非如何定义路由
- 翻译文件的编写和与翻译供应商的同步——消息以 JSON 格式提供,其来源不属于本技能的关注范围
- 区域设置以外的客户端状态——使用
useLocale()读取区域设置,绝不对其进行镜像 - 日期运算——格式化
Date属于本技能的工作,生成Date则不属于
翻译以命名空间组织为 JSON,在服务器上按请求解析,再通过上下文传递给客户端。由此得出两个决定。默认情况下,区域设置感知型渲染属于服务器的职责,因此客户端组件树只承载交互所需的内容。而由于通常由请求提供区域设置,静态渲染需要通过其他方式提供区域设置——这正是 setRequestLocale 的用途,也是它必须在任何内容读取区域设置之前运行的原因。
核心模式
模式 1:项目设置
需要四个模块和一个代理:routing.ts 声明区域设置,request.ts 按请求解析区域设置,navigation.ts 生成区域设置感知型导航 API,代理则从 URL、cookie 和 Accept-Language 检测区域设置。
// i18n/routing.ts
import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
locales: ["en", "de", "fr"],
defaultLocale: "en",
});
export type Locale = (typeof routing.locales)[number];
从 Next.js 16 起,代理文件名为 proxy.ts;在此之前为 middleware.ts。无论哪种情况,导出的都是 createMiddleware(routing)。
完整代码:examples/core.md
模式 2:使用 Provider 的根布局
验证区域设置、设置区域设置、加载消息,然后包裹组件树。
if (!hasLocale(routing.locales, locale)) notFound();
setRequestLocale(locale);
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={await getMessages()}>{children}</NextIntlClientProvider>
</body>
</html>
);
从 v4.0 起,Provider 会从服务器配置继承消息,因此 messages 属性可选。
完整代码:examples/core.md
模式 3:useTranslations
命名空间用于限定键的范围,值则使用具名占位符。
const t = useTranslations("Profile");
t("greeting", { name: user.name }); // "Hello, Jane!"
t("unreadCount", { count: messages.length });
完整代码:examples/core.md
模式 4:使用 ICU 语法实现复数形式
复数形式根据区域设置自身的 CLDR 规则选择,# 会渲染格式化后的计数。=0 精确匹配零,与 CLDR 的 zero 类别不同。
{
"itemCount": "{count, plural, =0 {No items} one {# item} other {# items}}"
}
序数使用 selectordinal;枚举值消息使用 select。
完整代码:examples/core.md,序数和嵌套内容见 examples/pluralization.md
模式 5:使用 t.rich() 渲染富文本
消息中的标签由开发者定义并映射到组件,因此句子可以完整地交给译者处理。
t.rich("terms", {
link: (chunks) => <a href="/terms">{chunks}</a>,
bold: (chunks) => <strong>{chunks}</strong>,
});
完整代码:examples/core.md
模式 6:useFormatter
一个钩子即可处理日期、数字、列表和相对时间,分别由对应的 Intl 格式化器提供支持。
const format = useFormatter();
format.dateTime(date, { year: "numeric", month: "long", day: "numeric" });
format.number(amount, { style: "currency", currency });
format.relativeTime(date, useNow({ updateInterval: 60_000 }));
完整代码:examples/core.md,自动更新的相对时间和列表见 examples/formatting.md
### 模式 7:静态渲染generateStaticParams 会枚举各个语言变体,而 setRequestLocale 则使每个变体都能在无需请求的情况下进行渲染。
export function generateStaticParams() {
return routing.locales.flatMap((locale) =>
slugs.map((slug) => ({ locale, slug })),
);
}
完整代码:examples/core.md
模式 8:切换语言
createNavigation 提供的导航 API 可以在保留当前路径的同时切换语言。
const router = useRouter();
const pathname = usePathname();
router.replace(pathname, { locale: newLocale });
完整代码:examples/core.md
模式 9:类型安全的键
在 AppConfig 接口上注册消息结构(v4.0+),错误的键就会成为编译错误。
declare module "next-intl" {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof en;
Formats: typeof formats;
}
}
在 tsconfig.json 中设置 allowArbitraryExtensions: true,以导入 JSON。若要推断参数类型,插件的 experimental.createMessagesDeclaration 可以生成这些类型。
完整代码:examples/core.md
模式 10:异步上下文
getTranslations 可在无法使用钩子的地方工作。元数据在组件树之外运行,因此需要传入语言,而不能依赖推断。
const t = await getTranslations({ locale, namespace: "Metadata" });
return { title: t("title"), description: t("description") };
完整代码:examples/core.md
<red_flags>
注意事项
会导致运行时问题:
- 未等待就读取
params——从 Next.js 15 开始,它是一个 Promise - 在钩子已读取语言后才调用
setRequestLocale——钩子会失败,且报错会指出钩子,而不是调用顺序 - 在
NextIntlClientProvider之外渲染客户端组件——它们无法收到消息 - 使用未经验证的语言段——消息导入会在路由未声明的路径上失败
- 在 Next.js 16 中仍将代理命名为
middleware.ts——系统不会加载它,因此无法检测语言 - 对包含标记的消息使用
t()——开发环境会报告FORMATTING_ERROR并渲染namespace.key;生产环境中,不带值的调用会将标签渲染为字面文本 - 在
generateMetadata或 Server Action 中使用useTranslations——它是一个钩子,而这两者都在组件树之外运行;在这些场景中,应使用getTranslations({ locale, namespace })
容易出乎意料的行为:
- 忽略
setRequestLocale会悄无声息地失去静态渲染能力:路由仍能运行,但会动态渲染。generateStaticParams是另一个必要条件,缺少其中任意一项的路由都会按请求渲染 t.rich()的标签函数收到的chunks是一个数组,而不是单个元素useNow()只会在客户端更新,因此 SSR 会一直显示初始值,直到完成水合- 在
useTranslations中省略命名空间,会将所有键放入同一全局空间,导致名称冲突 - 从 v4.0 开始,语言 Cookie 是会话 Cookie,且仅当用户切换到不同于其
Accept-Language偏好的语言时才会写入——路由配置中的localeCookie会同时改变这两项行为 - 在 Next.js 16 中,代理运行于 Node 运行时,而非 Edge 运行时
包含修复方案代码的反模式:reference.md。
</red_flags>