mcpField
W
web-i18n-next-intl

web-i18n-next-intl

github.com/agents-inc开发2696 次安装2 天前更新
代码仓库

关于 web-i18n-next-intl

web-i18n-next-intl 是面向 Next.js App Router 项目的 next-intl 国际化模式指南,适用于实现者和维护者。它汇总路由、消息渲染、格式化与静态生成的实践,并指出常见配置陷阱;不涵盖 Pages Router 或翻译内容管理。

主要功能

  1. 01配置语言路由与检测
  2. 02在组件及异步上下文读取消息
  3. 03处理复数、插值和富文本
  4. 04按地区格式化日期与数字
  5. 05生成静态语言版本
  6. 06为消息键和格式添加类型

适用场景

  1. 01构建基于语言段的路由
  2. 02本地化页面、元数据和 Server Actions
  3. 03按语言规则显示复数与日期
  4. 04静态生成各语言页面并检查类型

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill web-i18n-next-intl

安装后在 Claude Code 中输入/web-i18n-next-intl确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
reference.md
examples/
core.md
formatting.md
markup.md
pluralization.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>

评论

支持 Markdown