使用您最喜欢的AI助手总结文档,并引用此页面和AI提供商
如果您有改善此文档的想法,请随时通过在GitHub上提交拉取请求来贡献。
文档的 GitHub 链接复制文档 Markdown 到剪贴板
next-intl VS @intlayer/next-intl | 相同的 API,不同的 Bundle

@intlayer/next-intl 是一个兼容适配器:它暴露 next-intl API(useTranslations、getTranslations、useLocale、t.rich()、ICU 复数、NextIntlClientProvider...),并从 Intlayer 编译的字典中提供服务。应用代码不会改变。但 bundle 会改变。
本文在同一个 Next.js 应用上对两者进行了比较,一次使用 next-intl 构建,一次使用适配器构建。这些数据来自 Benchmark Bloom,一个记录浏览器实际下载内容的开源套件。如果你想要 next-intl vs Intlayer 作为库的比较,请阅读 next-intl vs Intlayer。本文是关于当你保持组件原样不变时,适配器改变了什么。
简明摘要: 在同一个 Next.js 应用中,将next-intl替换为@intlayer/next-intl使每页 JavaScript 从 153.6 KB 降至 147.5 KB(gzip),平均组件从 21.8 KB 降至 8.1 KB,外页字符串泄漏从 ~90% 降至 0%,水合从 14.7 ms 降至 12.8 ms,期间未编辑任何组件。在 TanStack Start 上,use-intl等价物(@intlayer/use-intl)将组件从 76-87 KB 缩减至 9-11 KB,区域设置切换从 7-21 ms 缩减至 4-9 ms。适配器运行时成本为 8.0 KB,而next-intl为 14.7 KB,原生next-intlayer为 5.5 KB。导航和中间件在 Intlayer 的路由配置上重新实现;本地化pathnames是未被转移的唯一功能。
@intlayer/next-intl 是什么
next-intl 是一个运行时:getRequestConfig 在每个请求时加载 messages/{locale}.json,NextIntlClientProvider 将其传送到客户端,useTranslations("about") 在渲染时从该对象读取密钥。每个优化(命名空间、pick(messages, [...]) 按页面、懒加载)都需要你自己编写。
@intlayer/next-intl 保留了该链的第一部分和最后一部分,并替换了中间部分。你的组件仍然调用 useTranslations("about");它们接收的内容来自在构建时编译的 Intlayer 字典,作用域限制在该组件,仅限活动区域设置。
三种机制使其工作:
- 导入别名。
@intlayer/next-intl/plugin中的createNextIntlPlugin()包装了withIntlayer并添加了 Webpack / Turbopack 别名,使得next-intl、next-intl/server、next-intl/navigation和next-intl/middleware解析到@intlayer/next-intl。您的代码库中没有导入被重命名。 - JSON 作为真实来源。
syncJSON插件读取您现有的messages/{locale}.json,将其顶级键拆分为每个命名空间一个字典,当 CLI 或 CMS 更新它们时,将翻译写回相同的文件。您的翻译工作流程保持不变。 - 调用点绑定。 Intlayer 优化 pass(Babel 或 SWC)会将
useTranslations("about")重写为接收about字典的调用。组件不再访问全局消息树;而是访问其自己的内容。
复制代码到剪贴板
复制代码到剪贴板
这就是为什么下面的组件大小和页面泄漏列列会改变的原因:一个页面只拉取它渲染的组件的字典,并且只以正在提供的语言环境拉取。
adapter 保留、忽略和不替换的内容
在弹窗中打开表格以清晰地查看所有数据
next-intl API | 使用 @intlayer/next-intl |
|---|---|
useTranslations("ns") / getTranslations("ns") | ✅ 保留。在构建时绑定到 ns 字典。键根据您的内容进行类型检查。 |
getTranslations({ locale, namespace }) | ✅ 保留 |
t("key", { name }), t.rich(), t.markup(), t.raw() | ✅ 保留。ICU 复数、select、selectordinal、#、{ts, date, long} 通过 Intlayer 的 ICU 解析器运行 |
useLocale() / getLocale() / setRequestLocale() / setLocale | ✅ 保留 |
useFormatter() | ✅ 保留。dateTime、number、relativeTime、list、dateTimeRange 桥接到原生 Intl |
NextIntlClientProvider | ✅ 已保留。messages、timeZone 和 now props 被接受但被忽略(开发者警告会告诉你) |
getMessages() | ✅ 为了兼容性而保留;不再需要 |
getRequestConfig() in src/i18n.ts | ⚠️ 不需要。字典在构建时被编译;没有按请求加载消息 |
defineRouting() | ✅ 已保留。省略的字段(locales、defaultLocale、localePrefix)从 intlayer.config.ts 读取 |
createNavigation(), Link, redirect, usePathname, useRouter | ✅ 保留。在 Intlayer 的路由配置上重新实现;接受 routing 参数但忽略它 |
pathnames(本地化路由名称) | ❌ 接受用于类型提示,不进行插值。保持纯路由名称或将该映射移到 Intlayer 的 rewrite |
createMiddleware() | ✅ 保留。返回 Intlayer 的代理;设置 NEXT_LOCALE cookie 以便 useLocale() 和你的语言切换器继续工作 |
NEXT_LOCALE cookie | ✅ 默认读取(除非你自己配置 routing.storage) |
不带 namespace 的裸 useTranslations() | ⚠️ 可以工作,但调用点未绑定:它通过运行时注册表解析。传递一个 namespace 以获得 bundle 收益 |
基准测试
测量内容
Benchmark Bloom 套件使用每个设置构建相同的应用程序:10 个页面(主页、关于、博客、职业、联系、常见问题、定价、产品、设置、团队),10 个语言环境(en、fr、es、de、it、pt、zh、ja、ko、ru),相同的组件和相同的内容。页面在 en 和 fr 中进行测量。
next-intl 采用四种加载策略来构建,从朴素的设置(整个 messages/{locale}.json 加载)到最优的设置(每个路由一个命名空间 + 每页 pick())。该适配器基于与朴素设置相同的组件构建,仅更改了 next.config.ts 和 intlayer.config.ts。它没有"作用域"变体:编译器按组件限定内容范围,因此其 static 和 dynamic 行已经被限定了范围。
对于每个构建,该套件记录:
- Lib size:仅导入 i18n 库的空组件的 gzip 大小。运行时的固定成本。
- Page JS:每页下载的 gzip JavaScript,在所有页面和语言环境中平均。
- Locale leak %: 下载的 JavaScript 中找到的翻译字符串中,属于用户未查看的语言环境的份额。
- Page leak %: 下载的 JavaScript 中找到的翻译字符串中,属于用户未访问的页面的份额。
- Component avg: 单独编译的每个组件的平均 gzip 大小。显示单个组件需要加载多少 i18n runtime 和目录。
- E2E reactivity: 选择新语言环境和 DOM 中
html[lang]更新之间的实际耗时(Playwright,5 次迭代)。 - Hydration: React hydration 阶段的持续时间。
下面的数字来自于 2026-09-12 运行的测试,使用next-intl/use-intl4.14.2 和@intlayer/*9.5.1。测试应用程序故意保持较小规模(每个语言环境仅几十个字符串),因此泄漏百分比描述的是一个模式:随着内容的增加而增加,而运行时成本保持固定。
Next.js 上的结果
选择您关注的指标和库:
动态 JSON 加载
在运行时懒加载翻译
有作用域的 JSON (命名空间)
每页翻译命名空间
I18n 性能基准测试
这个指标是什么?
国际化库包的总 gzip 压缩大小。它仅包含 tree-shaking 和压缩(minification)后的提供者(provider)和内容检索逻辑。
为什么这很重要?
较小的库大小可减少初始 JavaScript 负载,从而缩短客户端的下载和执行时间。
视图形式
在弹窗中打开表格以清晰地查看所有数据
| 设置 | 策略 | 库大小 (gz) | 页面 JS 平均 (gz) | 语言环境泄漏 | 页面泄漏 | 组件平均 (gz) | E2E 响应性 | 水合 |
|---|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 141.0 KB | 0.0% | 0.0% | 0.9 KB | 13.4 ms | 11.8 ms |
next-intl | static | 14.7 KB | 153.6 KB | 4.2% | 89.8% | 21.8 KB | 16.0 ms | 14.7 ms |
next-intl | dynamic | 14.7 KB | 153.6 KB | 9.7% | 89.9% | 21.8 KB | 15.6 ms | 14.8 ms |
next-intl | scoped-static | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 80.1 KB | 17.9 ms | 17.4 ms |
next-intl | scoped-dynamic | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 22.9 KB | 17.8 ms | 16.8 ms |
@intlayer/next-intl | static | 8.0 KB | 147.5 KB | 0.0% | 0.0% | 8.1 KB | 14.5 ms | 12.8 ms |
@intlayer/next-intl | dynamic | 8.0 KB | 148.7 KB | 0.0% | 0.0% | 8.1 KB | 11.7 ms | 12.8 ms |
next-intlayer (native) | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer (native) | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
如何阅读
- 相同的组件,每页少 6 KB。 朴素应用的适配器构建落在 147.5 KB,低于每一个
next-intl配置,包括完全优化的配置(153.6 KB)。运行时本身是差异:8.0 KB 对 14.7 KB,在每个页面上支付。 - 泄漏降至 0%,无需修改任何组件。 朴素的
next-intl设置在每个页面上传送约 90% 的外语页面字符串。使用next-intl达到 0% 需要使用scoped-*设置:每个路由一个命名空间,以及在每个页面中使用pick(messages, [...])。适配器从朴素代码达到 0% 是因为优化过程将每个useTranslations("ns")绑定到其自己的字典。 - 组件缩小 2.7 倍。 独立编译的组件使用
next-intl平均为 21.8 KB(它到达提供程序和消息树),使用适配器则为 8.1 KB。在next-intl的scoped-static设置中,该数字会 上升 到 80 KB,因为每个路由的命名空间文件都可从选择它的页面访问。 - Hydration 快 2 ms(12.8 vs 14.7 ms):在 React 可以 hydrate 之前,无需从 RSC payload 反序列化消息对象。
- adapter 不是原生 runtime。
next-intlayer位于 141.3 KB,比基础应用多 0.3 KB,带有 5.5 KB 的 runtime。adapter 在 Intlayer 的核心之上提供next-intlAPI 表面(useFormatter、t.rich、ICU resolver),因此有 8.0 KB 和每页 +6 KB。它是桥接器,而不是目的地。
完整表格、所有库和策略请参阅 Next.js 基准测试报告。
TanStack Start 上的结果(use-intl)
use-intl 是 next-intl 的框架无关核心。其 adapter @intlayer/use-intl 采用相同的设计,配合 Vite plugin(@intlayer/use-intl/plugin)。
在弹窗中打开表格以清晰地查看所有数据
| 设置 | 策略 | 库大小 (gz) | 页面 JS 平均 (gz) | 语言泄漏 | 页面泄漏 | 组件平均 (gz) | E2E 响应性 | 水合 |
|---|---|---|---|---|---|---|---|---|
| base (无 i18n) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms | 21.6 ms |
use-intl | 静态 | 14.1 KB | 179.8 KB | 50.0% | 89.8% | 76.0 KB | 6.7 ms | 15.3 ms |
use-intl | 动态 | 14.1 KB | 119.4 KB | 0.0% | 89.8% | 75.9 KB | 7.0 ms | 15.4 ms |
use-intl | scoped-static | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 20.9 ms | 24.8 ms |
use-intl | scoped-dynamic | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 13.3 ms | 25.9 ms |
@intlayer/use-intl | static | 7.3 KB | 135.8 KB | 49.7% | 0.0% | 10.9 KB | 4.2 ms | 10.5 ms |
@intlayer/use-intl | dynamic | 7.3 KB | 129.7 KB | 0.0% | 0.0% | 9.3 KB | 8.7 ms | 16.1 ms |
intlayer (native) | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms | 11.5 ms |
intlayer (native) | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms | 14.1 ms |
如何理解
- 每页字节数与优化的
use-intl相当。@intlayer/use-intl在dynamic模式下(129.7 KB)与use-intl的scoped-dynamic(128.7 KB)相差 1 KB 以内,仅比use-intl的普通dynamic(119.4 KB)多 10 KB。那个普通dynamic行仍然会泄露 90% 的外部页面字符串;字节数较低是因为测试应用的内容较小。适配器的 0% 是随着内容增长保持平稳的结果。 - 组件体积减小 7-9 倍。
use-intl组件在每种策略中平均 76-87 KB,因为useTranslations绑定到提供程序的整个消息对象。适配器平均 9-11 KB。 - 区域设置切换更快。 优化的
use-intl设置需要 13-21 ms 来更新html[lang];适配器需要 4-9 ms。更少的组件重新渲染,并且不会从消息树中重新提取任何内容。 static保留每个区域设置。 适配器的static行显示 49.7% 的区域设置泄漏,与static模式下的本地 Intlayer 相同:所有区域设置都被打包,只有页面的字典。一行配置(importMode: 'dynamic')就可以消除它。
完整表格请参阅 TanStack Start 基准测试报告。
为什么数字会变动

组件中没有任何改变,所以所有收益完全来自于 useTranslations 绑定的内容。
使用 next-intl,绑定是 provider。NextIntlClientProvider 为该 locale 接收整个 messages 对象;每个 useTranslations("about") 都从中读取。bundler 看到一个组件导入一个读取一个 context 的 hook,无法知道只使用了 about 分支。下面的路由都共享同一个消息对象,所以 page-leak 列显示约 ~90%,直到你自己分割文件, 并且这种开销在页面和语言两个维度上同时增长:

复制代码到剪贴板
使用 @intlayer/next-intl,绑定是字典。syncJSON 将 messages/en.json 转换为每个顶级键一个字典;编译器解析哪个组件调用 useTranslations("about") 并直接将其传递给 about,以活动的语言环境,作为bundler可以追踪和分割的导入。
复制代码到剪贴板
src/i18n.ts 和 messages 属性消失了。其他一切相同。
三步迁移
安装
bash复制代码复制代码到剪贴板
该命令检测
next-intl并安装intlayer、next-intlayer、@intlayer/next-intl和@intlayer/sync-json-plugin。保持next-intl已安装:它是适配器的 peer 依赖项,并提供类型。将 Intlayer 指向你的消息
intlayer.config.ts复制代码复制代码到剪贴板
messages/{locale}.json保持原位置不变。每个顶级键都会成为一个字典;useTranslations("about")映射到about字典。包装 next.config.ts
next.config.ts复制代码复制代码到剪贴板
createNextIntlPlugin()组合了withIntlayer(内容监听、字典编译、优化pass)和next-intl→@intlayer/next-intl的 Webpack 和 Turbopack 别名。构建,上表中的数字就是你的。
之后你可以删除什么
在弹窗中打开表格以清晰地查看所有数据
| 文件 / 模式 | 原因 |
|---|---|
getRequestConfig in src/i18n.ts | 没有每个请求的消息加载。仅当该文件也导出 createNavigation helpers 时才保留该文件 |
messages={...} on NextIntlClientProvider | adapter 读取已编译的输出;该 prop 被忽略,在开发中会记录警告 |
await getMessages() in layouts | 同样原因 |
Per-page pick(messages, [...]) | compiler 按组件进行picking |
除了字节外你还能获得什么
- 类型化的 keys。
useTranslations("about")针对已编译的about字典进行类型检查。t("does.not.exist")是 TypeScript 错误,而不是运行时 fallback。 npx intlayer test在某个 locale 缺少 key 时使导致 CI 失败。npx intlayer fill使用你选择的提供商(OpenAI、Anthropic、Mistral、Gemini...)并使用你自己的 key 来翻译缺失的部分,并将结果写回messages/{locale}.json。- Visual Editor 和 CMS 在同一字典上工作,因此非开发者可以通过 UI 编辑
messages/fr.json,文件会自动更新。 - 逐步迁移到
.content.ts。 任何组件都可以一次性地从useTranslations("about")切换到useIntlayer("about")并配置共置的 content 文件。JSON 和.content.ts字典共存并合并。
开始前需要了解的限制
createNavigation(routing) 和 createMiddleware(routing) 保留其函数签名但忽略该参数:支持的语言、默认语言和前缀策略均来自 Intlayer 的 routing 配置。如果您使用 next-intl 的本地化路径 pathnames(/about 转 /a-propos),适配器不会对其进行插值;Intlayer 的 routing.rewrite 涵盖了这种情况,但这是一项单独的改动。
优化过程需要一个静态命名空间来确定要导入的字典。无参数调用仍可通过引用每个字典的运行时注册表运行,但这恰恰是您试图消除的内容泄漏。请显式传递命名空间。
运行时体积为 8.0 KB,而原生 next-intlayer 为 5.5 KB,且每页比原生构建多出 +6-7 KB。这是为了兼容 next-intl API 表面所付出的代价。如果每个组件都迁移到了 useIntlayer,请直接移除适配器。
格式化器由原生 Intl 提供支持,只有当前语言会影响其输出。如果您依赖强制的时区或固定的 now 来获得注水稳定的日期,请在调用处自行处理。参见日期、时间和数字格式化。
何时使用哪一个?
您的应用规模较小,无需担心打包体积,且团队乐于手动维护命名空间和按页面 pick()。
您目前已在使用 next-intl,希望在无需重写代码的情况下获得包体积、防泄漏和更快的注水收益,并获得类型化键以及 CLI / CMS 工具支持。这是任何现有 next-intl 代码库的最佳切入点。
适用于新项目,或适配器完成过渡任务后。它是三者中最轻量级的(5.5 KB,每页仅增加 +0.3 KB),并解锁同步服务端组件、按组件就近维护 .content.ts 文件以及完整的全套功能。请阅读 在 Next.js 中使用 Intlayer。
常见问题
在 Next.js 中,组件代码完全无需修改:基准测试构建仅修改了 next.config.ts 和 intlayer.config.ts。src/i18n.ts 中的 getRequestConfig、provider 上的 messages 属性以及每页的 pick() 调用都会变成无用代码,您后续可以将其删除。
它们可以继续正常工作。t("key", { count })、t.rich()、t.markup()、select、selectordinal、# 和 {ts, date, long} 均由 Intlayer 的 ICU 解析器处理。参见 ICU 消息格式。
它在 Intlayer 核心之上承载了整个 next-intl API 表面:useFormatter、t.rich、ICU 解析器、导航辅助工具。这使得体积达到 8.0 KB(原生为 5.5 KB),且每页多出 +6 KB。它是过渡桥梁,而不是最终目的地。
可以。任何组件都可以随时从 useTranslations("about") 切换到使用就近 .content.ts 文件的 useIntlayer("about")。JSON 和 .content.ts 字典可以共存并自动合并。
不能通过 next-intl 的 pathnames 生效:适配器接受其用于类型推断但不会进行插值。请改用 Intlayer 的 routing.rewrite,它会将本地化字面量生成到类型注册表中。
相关比较
同一适配器系列:
两款库的直接对比:
参考文档:
- Compat adapter: next-intl
- 迁移指南:从 next-intl 到 Intlayer
- Next.js 基准测试报告 与 TanStack Start 基准测试报告
- 包体积优化 与 Intlayer 编译器
- 可视化编辑器、CMS 与 AI 翻译
总结
@intlayer/next-intl 做的是一件事:它改变了 useTranslations 的绑定对象,从持有每条消息的提供者变为为该组件编译的字典。在同一个 Next.js 应用中,每页只需 6 KB,组件缩小 2.7 倍,0% 泄漏,以及在任何人打开组件文件之前就有 2 ms 的水合时间。Navigation 和 middleware 保持其在 Intlayer 路由配置之上的 API,而原生的 next-intlayer runtime 仍然更加轻量。
所有原始数据、测试应用和脚本都在 Benchmark Bloom repository 中。自己运行它。
有关更多详情,请参考 'Why Intlayer?' 文档。
评论
暂无评论。成为第一个分享您想法的人吧。
