使用您最喜欢的AI助手总结文档,并引用此页面和AI提供商
版本历史
- "添加 GitHub 明星对比"v8.9.82026/5/18
- "初始化基准测试"v8.7.52026/1/6
此页面的内容已使用 AI 翻译。
查看英文原文的最新版本如果您有改善此文档的想法,请随时通过在GitHub上提交拉取请求来贡献。
文档的 GitHub 链接复制文档 Markdown 到剪贴板
Next.js i18n 库 - 2026 年基准测试报告
本页面是关于 Next.js i18n 解决方案的基准测试报告。
目录
交互式基准测试
结果参考:
查看完整的基准测试数据
查看完整的基准测试仓库点击这里。
简介
国际化库对你的应用程序有很大影响。主要的风险在于当用户只访问一个页面时,却加载了所有页面和所有语言的内容。
随着应用规模的增长,打包体积会呈指数级增长,这会明显影响性能。
例如,在最糟糕的情况下,国际化后的页面体积可能会增加接近 4 倍。
i18n 库的另一个影响是开发速度变慢。将组件转换为支持多语言的内容非常耗时。
因为这个问题很棘手,所以存在许多解决方案--有些侧重于 DX(开发体验),有些侧重于性能或可扩展性等。
Intlayer 尝试在这些维度上进行优化。
TL;DR
- Intlayer & next-translate: Next.js 性能的最佳选择,提供最小的体积和最佳的静态渲染支持。
- next-intl: 最热门的选择,但体积庞大且对于大型应用优化复杂。
- next-i18next: 流行且插件丰富,但打包体积显著(约为 Intlayer 的 3 倍)。
- 应避免: gt-next 和 lingo.dev,因为存在严重的性能问题、供应商锁定以及导致构建失败的 Bug。
测试你的应用
为了发现这些问题,我构建了一个免费扫描器,你可以在这里试用。
问题所在
有两种主要方法可以限制多语言应用对打包体积的影响:
- 将 JSON(或内容)拆分到不同的文件/变量/命名空间中,以便打包工具可以对特定页面未使用的内容进行 Tree-shaking。
- 仅按用户的语言动态加载页面内容。
这些方法的技术限制:
动态加载
即使你使用 Webpack 或 Turbopack 声明了类似 [locale]/page.tsx 的路由,并且定义了 generateStaticParams,打包工具也不会将 locale 视为静态常量。这意味着它可能会将所有语言的内容都拉入每个页面。限制这种情况的主要方法是通过动态导入(例如 import('./locales/${locale}.json'))加载内容。
在构建时,Next.js 会为每个语言环境生成一个 JS 包(例如 ./locales_fr_12345.js)。当站点发送到客户端并运行时,浏览器会为所需的 JS 文件发出额外的 HTTP 请求。
解决同一问题的另一种方法是使用fetch()动态加载 JSON。这就是当 JSON 存储在/public下时Tolgee的工作方式,或者是依赖getStaticProps加载内容的next-translate的工作方式。流程是一样的:浏览器会发起额外的 HTTP 请求来下载资源。
内容拆分
如果你使用类似 const t = useTranslation() + t('my-object.my-sub-object.my-key') 的语法,通常整个 JSON 都必须包含在打包包中,以便库可以解析它并解析键。即使页面上没有用到,大部分内容也会被发送。
为了减轻这种情况,一些库要求你声明每个页面要加载哪些命名空间--例如 next-i18next、next-intl、lingui、next-translate、next-international。
相比之下,Paraglide 在构建前增加了一个额外步骤,将 JSON 转换为扁平的符号,如 const en_my_var = () => 'my value'。理论上,这可以在页面上实现 Tree-shaking 掉未使用的内容。正如我们将看到的,这种方法仍然存在权衡。
最后,Intlayer 应用了构建时优化,使得 useIntlayer('my-key') 会被直接替换为相应的内容。
方法论
在此基准测试中,我们对比了以下库:
Base App(无 i18n 库)next-intlayer(v8.7.12)next-i18next(v16.0.5)next-intl(v4.9.1)@lingui/core(v5.3.0)next-translate(v3.1.2)next-international(v1.3.1)@inlang/paraglide-js(v2.15.1)@tolgee/react(v7.0.0)@lingo.dev/compiler(v0.4.0)wuchale(v0.22.11)gt-next(v6.16.5)
我使用了 Next.js 版本 16.2.4 以及 App Router。
我构建了一个拥有 10 个页面和 10 种语言的多语言应用。
我对比了四种加载策略:
在弹窗中打开表格以清晰地查看所有数据
| 策略 | 无命名空间(全局) | 有命名空间(局部/作用域) |
|---|---|---|
| 静态加载 | Static: 启动时全部加载到内存。 | Scoped static: 按命名空间拆分;启动时全部加载。 |
| 动态加载 | Dynamic: 按语言环境按需加载。 | Scoped dynamic: 按命名空间和语言环境进行更细粒度的加载。 |
策略总结
- Static (静态): 简单;初始加载后无网络延迟。缺点:打包体积大。
- Dynamic (动态): 减轻初始重量(懒加载)。适用于语言环境较多的情况。
- Scoped static (局部静态): 保持代码组织良(逻辑分离),且无需复杂的额外网络请求。
- Scoped dynamic (局部动态): 代码分割和性能的最佳方案。通过仅加载当前视图和活动语言环境所需的内容来最小化内存占用。
我测量了什么:
我在真实浏览器中为每个技术栈运行了相同的多语言应用,然后记录了网络上传输的内容以及所需的时间。数值以常规网页压缩后的体积报告,因为这比原始源码计数更接近用户的实际下载量。
国际化库体积: 在打包、Tree-shaking 和压缩后,i18n 库的体积,即一个空组件中 Provider(如
NextIntlClientProvider)+ Hook(如useTranslations)代码的体积。这不包括翻译文件的加载。它反映了在引入内容之前,库本身的昂贵程度。每页 JavaScript 量: 对于基准测试中的每条路由,浏览器访问该页面时拉取的脚本量,按套件中的页面进行平均(如果报告有汇总,则按语言环境平均)。臃肿的页面即是慢页面。
来自其他语言环境的泄漏: 同一页面但为其他语言的内容被错误地加载到了审计页面中。这些内容是不必要的,应当避免(例如
/fr/about的页面内容出现在/en/about的页面包中)。来自其他路由的泄漏: 应用中其他屏幕的相同情况:当你只打开一个页面时,其他页面的文案是否也被打包了(例如
/en/about的页面内容出现在/en/contact的页面包中)。得分高意味着分割薄弱或打包范围过广。平均组件打包体积: 通用 UI 组件被逐个测量,而不是隐藏在应用的总数据中。这显示了国际化是否会悄悄增加日常组件的体积。例如,如果你的组件重新渲染,它将从内存中加载所有这些数据。给任何组件附加一个巨大的 JSON,就像连接了一个庞大的未使用数据仓库,会降低组件性能。
语言切换响应性: 我使用应用自带的控制组件切换语言,并计时直到页面明显完成切换所需的时间--这是访问者能感知到的时间,而非实验室微秒级步骤。
语言更改后的渲染工作: 进一步的跟进:一旦切换开始,界面以新语言重新绘制所需的开销。当“感知”时间和框架开销不一致时非常有用。
页面初始加载时间: 从导航开始到浏览器认为页面已完全加载所需的时间。适用于冷启动对比。
注水 (Hydration) 时间: 当应用暴露该数据时,客户端将服务端 HTML 转换为可交互状态所需的时间。表格中的破折号表示该实现在本基准测试中未提供可靠的注水数值。
GitHub 星数
GitHub 星数是项目受欢迎程度、社区信任和长期相关性的有力指标。虽然星数不是技术质量的直接衡量标准,但它们反映了有多少开发人员发现该项目有用、关注其进展并可能采用它。在评估项目价值时,星数有助于比较不同方案的吸引力,并提供对生态系统增长的见解。
结果详情
1 - 应当避免的解决方案
应当明确避免诸如 gt-next 或 lingo.dev 之类的解决方案。它们结合了供应商锁定和代码库污染。尽管投入了大量时间尝试实施,但我从未让它们正常工作过--无论是在 TanStack Start 还是 Next.js 上。
遇到的问题:
(General Translation) ([email protected]):
- 对于一个 110kb 的应用,
gt-next额外增加了超过 440kb。 - 第一次使用 General Translation 构建就提示
Quota Exceeded, please upgrade your plan(配额超出,请升级计划)。 - 翻译未渲染;我收到了错误
Error: <T> used on the client-side outside of <GTProvider>,这似乎是库的一个 Bug。 - 在实施 gt-next 时,我还遇到了该库的一个问题:
does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser,这导致应用崩溃。在报告此问题后,维护者在 24 小时内修复了它。 - 该库阻塞了 Next.js 页面的静态渲染。
(Lingo.dev) (@lingo.dev/[email protected]):
- AI 配额超出,完全阻塞了构建--这意味着不付钱就无法发布到生产环境。
- 编译器丢失了近 40% 的翻译内容。我不得不将所有的
.map重写为扁平的组件块才使其工作。 - 它们的 CLI 存在 Bug,会无故重置配置文件。
- 构建时,当添加新内容时,它会完全抹除生成的 JSON 文件。结果是几个键的添加可能会导致 300 多个现有键被清除。
2 - 实验性解决方案
(Wuchale) ([email protected]):
Wuchale 背后的想法很有趣,但尚不可行。我遇到了响应性问题,不得不强制重新渲染 Provider 才能使应用工作。文档也相当不清晰,增加了上手难度。
(Paraglide) (@inlang/[email protected]):
Paraglide 提供了一种创新且经过深思熟虑的方法。即便如此,在这次基准测试中,其公司宣称的 Tree-shaking 在我的 Next.js 或 TanStack Start 设置中并未生效。工作流和 DX 比其他选项更复杂。
就个人而言,我不喜欢每次推送到代码库前都要重新生成 JS 文件,这通过 PR 产生了持续的合并冲突风险。该工具似乎也更关注 Vite 而非 Next.js。
最后,与其他解决方案相比,Paraglide 不使用存储(如 React Context)来检索当前语言环境以渲染内容。对于解析的每个节点,它都会从 localStorage / Cookie 等请求语言环境。这导致了影响组件响应性的不必要逻辑执行。
关于 Paraglide 的说明:该解决方案通过将代码注入到你的代码库中进行导入,因此在基准测试报告中,“库体积”指标几乎为 0。代码生成是一件好事,因为所使用的函数将仅包含必要的逻辑(全前缀 vs 无前缀、Cookie vs 存储等)。相比之下,Intlayer 通过在构建中注入环境变量来强制打包工具根据逻辑对内容进行 Tree-shaking。得益于此,Paraglide 和 Intlayer 最终成为比 i18next 或 next-intl 轻 6 到 10 倍的解决方案。
3 - 可接受的解决方案
(Tolgee) (@tolgee/[email protected]):
Tolgee 解决了前面提到的许多问题。我发现它比类似的工具更难采用。它不提供类型安全,这增加了在编译时捕捉缺失键的难度。我不得不使用自己的函数封装 Tolgee 的函数,以添加缺失键检测。
(Next Intl) ([email protected]):
next-intl 是目前最热门的选项,也是 AI Agent 推荐最多的,但在我看来这是错误的。入门很容易,但在实践中,减少泄漏的优化非常复杂。结合动态加载 + 命名空间 + TypeScript 类型会极大降低开发速度。包体积也相当大(NextIntlClientProvider + useTranslations 约为 13kb,是 next-intlayer 的两倍多)。next-intl 曾会阻塞 Next.js 页面的静态渲染。它提供了一个名为 setRequestLocale() 的辅助工具。对于 en.json / fr.json 这样的集中式文件,这似乎得到了部分解决,但当内容拆分为 en/shared.json / fr/shared.json / es/shared.json 等命名空间时,静态渲染仍然会失效。
(Next I18next) ([email protected]):
next-i18next 可能最受欢迎,因为它是 JavaScript 应用中最早的 i18n 解决方案之一。它拥有许多社区插件。它与 next-intl 有着相同的重大缺点。包体积特别大(I18nProvider + useTranslation 约为 18kb,约为 next-intlayer 的 3 倍)。
消息格式也不同:next-intl 使用 ICU MessageFormat,而 i18next 使用自己的格式。
(Next International) ([email protected]):
next-international 也解决了上述问题,但与 next-intl 或 next-i18next 差异不大。它包含了用于特定命名空间翻译的 scopedT(),但使用它对打包体积几乎没有影响。
(Lingui) (@lingui/[email protected]):
Lingui 常受赞誉。就个人而言,我觉得围绕 lingui extract / lingui compile 的工作流比其他方案更复杂,且没有明显的优势。我还注意到语法不统一,容易误导 AI(例如 t()、t''、i18n.t()、<Trans>)。
4 - 推荐方案
(Next Translate) ([email protected]):
如果你喜欢 t() 风格的 API,next-translate 是我的主要推荐方案。它通过 next-translate-plugin 优雅运作,利用 Webpack / Turbopack loader 通过 getStaticProps 加载命名空间。它也是这些方案中最轻量的(约 2.5kb)。对于命名空间拆分,在配置中为每个页面或路由定义命名空间的设计非常周到,比 next-intl 或 next-i18next 等主要替代方案更易于维护。在版本 3.1.2 中,我注意到静态渲染无法工作,Next.js 会回退到动态渲染。
(Intlayer) ([email protected]):
出于客观性考量,我不会亲自评价 next-intlayer,因为这是我自己的解决方案。
个人见解
此见解纯属个人观点,不影响基准测试结果。在 i18n 领域,常能看到关于 const t = useTranslation('xx') + <>{t('xx.xx')}</> 模式的共识。
在 React 应用中,我个人认为将函数作为 ReactNode 注入是一种反模式。它还会增加可避免的复杂性和 JavaScript 执行开销(即使几乎察觉不到)。