使用您最喜欢的AI助手总结文档,并引用此页面和AI提供商
此页面的内容已使用 AI 翻译。
查看英文原文的最新版本如果您有改善此文档的想法,请随时通过在GitHub上提交拉取请求来贡献。
文档的 GitHub 链接复制文档 Markdown 到剪贴板
Lingui VS @intlayer/lingui | 相同宏,不同运行时
@intlayer/lingui 是针对 @lingui/core 和 @lingui/react 的兼容适配器。你的 t`...` 、<Trans>、useLingui() 和 i18n._() 调用保持完全不变;宏继续正常编译;改变的是运行时消息的来源。每个调用点不再依赖每个语言环境一个编译好的全局目录,而是绑定到专门为其编译的 Intlayer 字典。
本文在相同的 TanStack Start 应用程序上测量了这种替换,该程序分别使用纯 Lingui 和使用适配器构建了一次。这些数据来自 Benchmark Bloom。关于这两个库作为独立库的对比,请阅读 Lingui vs Intlayer。本文主要关注适配器改变了什么,以及在哪些方面它没有帮助。
摘要 (tl;dr):在同一个 TanStack Start 应用程序中,@intlayer/lingui在不改动宏的情况下,将平均组件体积从 85.5 KB 降至 12.8 KB gzip,水合时间从 28 ms 降至 19.7 ms,语言切换时间从 5.9 ms 降至 2.9 ms。在朴素设置(预先加载每个目录)中,它还消除了 90% 的页面泄漏 并减少了每页 12 KB。但在延迟加载设置中,它每页交付 137 KB,而纯 Lingui 为 115 KB:适配器在运行时解析 ICU,而 Lingui 交付预编译的标记数组。源语言环境泄漏(约 9-10%)在两边完全相同,因为它来自于嵌入在组件中的message回退,而不是来自运行时。该适配器是一个 Vite 插件;测试是在 TanStack Start 上进行的。
什么是 @intlayer/lingui
Lingui 由编译器和运行时组成。源码中的宏被提取到每个语言环境的 .po(或 JSON)目录中,编译为每个语言环境的 JS 模块,并通过 i18n.load() + i18n.activate() 加载到全局 I18n 实例中。每个 useLingui() 订阅该实例;每个 _() 调用都会在活动目录中查找其 ID。
@intlayer/lingui 保留了宏和 API,并替换了目录查找逻辑:
- 导入别名化。 来自
@intlayer/lingui/plugin的lingui()插件包装了vite-intlayer并添加了resolve.alias条目,使得@lingui/core和@lingui/react解析为@intlayer/lingui。你的导入代码无需改动。 - 目录作为单一事实来源。
syncJSON插件(或用于.po文件的syncPO)读取你现有的目录并将其转换为 Intlayer 字典,当 CLI 或 CMS 更新时将翻译写回。通过splitKeys: "key-prefix",带点分 ID(footer.github、hero.title)的扁平目录会拆分成每个前缀一个的小字典,而不是一个 244 KB 的庞大文件。 - 调用点绑定。 Intlayer 优化通道收集每个文件中传递给
_、t和<Trans>的 ID,并将匹配的字典交付给组件。<Trans id="hero.title">独立绑定;useLingui()绑定到该文件中使用的每个前缀。不带点分的 ID(哈希 ID、mockBanner)将回退到 Lingui 的单一messages字典。
复制代码到剪贴板
复制代码到剪贴板
组件不再需要访问全局实例及其背后的整个目录。它仅访问 hero。这就是下表中组件大小列下降 7 倍的全部原因。
适配器保留、忽略和不替换的内容
在弹窗中打开表格以清晰地查看所有数据
| Lingui API | 搭配 @intlayer/lingui |
|---|---|
t`...` 、msg、plural、select、<Trans> 宏 | ✅ 保留。在 Intlayer 优化通道之前,在构建配置中保留 @lingui/babel-plugin-lingui-macro 或 @lingui/swc-plugin |
useLingui() → { i18n, _, t } | ✅ 保留。在 Provider 外部也能工作(语言环境派生自 react-intlayer) |
i18n._(id, values)、i18n.t() | ✅ 保留。显式 ID 和哈希 ID 均可解析 |
ICU 复数、select、selectordinal、# | ✅ 保留,通过 Intlayer 的 ICU 解析器处理 |
i18n.date()、i18n.number()、formats | ✅ 保留,由原生 Intl 支持 |
I18nProvider | ✅ 保留。包装 IntlayerProvider;监听 i18n.on("change") 以确保 activate() 仍能触发重新渲染 |
i18n.activate(locale) | ✅ 保留 |
i18n.load(locale, messages) / loadAndActivate() | ⚠️ 作为运行时回退接受。编译后的字典优先;开发环境警告会建议移除该导入 |
setupI18n({ messages, missing }) | ⚠️ messages 作为运行时回退合并;missing 被忽略 |
lingui extract / lingui compile | ✅ 依然是你的日常工作流程。将 syncPO / syncJSON 指向提取的目录 |
I18nProvider 上的 defaultComponent | ⚠️ 存储在上下文中,但在渲染时未应用 |
| Next.js | ❌ 该插件包装了 vite-intlayer。仅支持 Vite、TanStack Start 和 React Router |
基准测试
测试衡量了什么
Benchmark Bloom 测试套件在每种设置下构建了相同的应用程序:10 个页面(home, about, blog, careers, contact, FAQ, pricing, products, settings, team),10 种语言(en, fr, es, de, it, pt, zh, ja, ko, ru),相同的组件和相同的内容。页面以 en 和 fr 进行测量。
Lingui 在四种加载策略下进行了构建,从预先导入每个编译好的目录(static)到按路由延迟导入目录(scoped-dynamic)。适配器在相同的组件上构建,仅更改了 vite.config.ts 和 intlayer.config.ts。其 static 行打包所有语言环境;其 dynamic 行(importMode: 'dynamic')按需加载当前活动的语言环境。没有 "scoped" 变体:因为优化通道自动在每个调用点进行作用域划分。
对于每个构建,测试套件记录:
- Lib size:仅导入 i18n 库的空组件的 gzip 体积。
- Page JS:每页下载的 gzip JavaScript 体积,在所有页面和语言环境中取平均值。
- Locale leak %:下载的 JS 中属于用户未查看的语言环境的翻译字符串比例。
- Page leak %:下载的 JS 中属于用户未访问的页面的翻译字符串比例。
- Component avg:每个组件单独编译时的平均 gzip 体积。
- E2E reactivity:选择新语言环境到 DOM 中的
html[lang]更新之间的挂钟时间(Playwright,5 次迭代)。 - Hydration:React 水合阶段耗时。
以下数据来自 2026-09-12 的测试运行,使用@lingui/react6.6.0 和@intlayer/lingui9.5.1。测试应用程序刻意设计得很小(每个语言环境几十个字符串),因此泄漏百分比反映的是一种结构模式:随着内容的增长,它们会不断增加,而运行时成本保持固定。
TanStack Start 上的测试结果
在弹窗中打开表格以清晰地查看所有数据
| 配置 | 策略 | 库体积 (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 |
| Lingui | static | 11.2 KB | 152.2 KB | 50.0% | 90.0% | 58.0 KB | 3.9 ms | 19.9 ms |
| Lingui | dynamic | 11.2 KB | 115.2 KB | 9.3% | 0.0% | 85.5 KB | 5.9 ms | 28.0 ms |
| Lingui | scoped-static | 11.2 KB | 120.8 KB | 4.0% | 0.0% | 147.9 KB | 7.1 ms | 33.9 ms |
| Lingui | scoped-dynamic | 11.2 KB | 120.2 KB | 8.6% | 0.0% | 83.7 KB | 42.1 ms | 32.9 ms |
@intlayer/lingui | static | 10.3 KB | 140.5 KB | 50.0% | 0.0% | 14.9 KB | 3.3 ms | 11.3 ms |
@intlayer/lingui | dynamic | 10.3 KB | 137.0 KB | 9.9% | 0.0% | 12.8 KB | 2.9 ms | 19.7 ms |
intlayer (原生) | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms | 11.5 ms |
intlayer (原生) | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms | 14.1 ms |
如何解读数据
- 组件体积缩小 7 倍。 这是适配器的主要效果。根据策略不同,单独编译的 Lingui 组件平均体积为 58-148 KB,因为
useLingui()会触及全局实例以及加载到其中的每个目录。使用适配器的相同组件平均只有 12.8-14.9 KB:它只触及自己的字典和 ICU 解析器,别无其他。 - 水合快 8-14 ms。
i18n.load()+i18n.activate()在 React 开始水合前在客户端执行;Lingui 设置越偏向懒加载,这个过程耗时越长(28-34 ms)。使用适配器时,字典作为打包器已放置在页面分块中的普通导入到达:static下为 11.3 ms,dynamic下为 19.7 ms。 - 语言切换快 2 倍,且无卡顿。 Lingui 优化后的
scoped-dynamic设置需要 42 ms 才能更新html[lang],因为路由目录在更改可见前必须经过获取、加载和激活。适配器在两种模式下均保持在 2.9-3.3 ms。 - 朴素设置免费获得优化。 静态 Lingui 在每个页面上打包每个目录:152.2 KB,90% 页面泄漏。静态适配器:140.5 KB,0% 页面泄漏,组件完全相同。
- 每页字节数:Lingui 在
dynamic中以 22 KB 胜出。 这是一个需要坦诚面对的数据。Lingui 在构建时将消息编译为标记数组,并仅附带一个遍历它们的 11 KB 运行时。适配器则包含 Intlayer 的 ICU 解析器(比原生构建多出约 15 KB 的@intlayer/core)、适配器层(~10 KB)和react-intlayer(~6 KB)。在此应用中,结果是 137.0 KB 对 115.2 KB。如果每页字节数是你的唯一预算考量,并且你已经在使用延迟加载的 Lingui,适配器在这方面不会带来体积缩减。 - 两边的语言环境泄漏几乎相同。 在
dynamic下 Lingui 为 9.3%,适配器为 9.9%。这源于组件内部:i18n._({ id: "careers-benefits.pay", message: "Top-of-market compensation" })携带了英文源码作为回退,宏输出也是如此,除非显式剔除 message 字段。无论通过什么提供翻译,这部分英文都会落入fr分块中。原生intlayer(.content.ts,无内联源码)则为 0%。
为什么数据会变化,以及为什么有一项保持不变
决定这些列的有两个因素:组件绑定到了什么,以及消息以何种格式传输。
绑定方式。 在 Lingui 中,划分的基本单元是语言环境。fr 的 messages.mjs 是一个单一模块;任何导入了加载该模块的实例的组件都可以访问其全部内容,因此打包工具无法进行比语言环境更细粒度的拆分。使用适配器时,基本单元是调用点:hero 和 footer 是独立的导入,按组件进行拆分和延迟加载。这就是组件体积、水合和页面泄漏大幅改善的原因。
复制代码到剪贴板
复制代码到剪贴板
传输格式。 Lingui 的编译步骤将 {count, plural, one {# item} other {# items}} 转换为标记数组;运行时从不需要解析 ICU。适配器将消息保留为文本,并使用 Intlayer 的 ICU 解析器进行解析。这是每页固定支付一次的约 15 KB 开销,也是 dynamic 行在体积上落后而在其他所有方面领先的原因。原生 Intlayer 避免了这一点,因为 .content.ts 字典使用编译器提前解析的 enu() / insert() 节点。
迁移只需三步
安装
bash复制代码复制代码到剪贴板
该命令会检测 Lingui,读取
lingui.config.ts以选择syncPO(.po目录)或syncJSON(JSON 目录),安装intlayer、react-intlayer、@intlayer/lingui及对应的同步插件,并在vite.config.ts中将@lingui/vite-plugin替换为适配器插件。保留@lingui/core、@lingui/react和你的宏插件:宏仍会继续编译,适配器也会复用 Lingui 的类型。将 Intlayer 指向你的目录
对于 JSON 目录(
lingui.config.ts中的format: "minimal"):intlayer.config.ts复制代码复制代码到剪贴板
对于
.po目录,将syncJSON替换为来自@intlayer/sync-po-plugin的syncPO,并使用带有.po扩展名的相同source模式。请参阅 Sync PO 插件文档。splitKeys: "key-prefix"是让组件体积大幅缩小的关键。目录文件保留其扁平形态;拆分仅存在于生成的字典中,写回时会自动重新拼接 ID。添加插件
vite.config.ts复制代码复制代码到剪贴板
lingui()封装了vite-intlayer(内容监听、字典编译、优化通道),并将@lingui/core和@lingui/react别名重定向到适配器。重新构建,上述性能收益便可立即生效。
之后可以删除的内容
在弹窗中打开表格以清晰地查看所有数据
| 文件 / 模式 | 原因 |
|---|---|
await import(`./locales/${locale}/messages.mjs`) | 字典由使用它们的组件直接导入。i18n.load() 变为回退逻辑 | |
i18n.load() / i18n.loadAndActivate() | 保留 i18n.activate(locale);移除手动加载目录的代码 |
构建脚本中的 lingui compile | 仅当你完全迁移到 JSON / .po 作为来源并且不再导入编译后的模块时可删除 |
除了字节之外的收获
- 缺失翻译检测。 当某个语言环境缺少 ID 时,
npx intlayer test会使 CI 报错失败;而lingui extract仅输出统计数据。 npx intlayer fill使用你选择的 AI 提供商(OpenAI、Anthropic、Mistral、Gemini 等)自动补全缺失的条目,并直接写回你的目录中。- 可视化编辑器和 CMS 在相同的字典上运行,非技术人员也能通过友好的图形界面轻松编辑
.po/ JSON 文件。 - 平滑迁移到
.content.ts。 任何组件都可以随时从useLingui()切换到带有同目录内容文件的useIntlayer("hero")。两种字典类型可以完美共存与合并。
开始前需了解的限制
dynamic模式下的每页体积开销。 如前所述:在小型应用中,相对于延迟加载的 Lingui 设置,预计每页大约增加 20 KB。这一差距不会随着内容的增加而扩大(因为它来自解析器而非目录),但也不会缩小。- 源语言环境泄漏仍然存在。 消息描述符和宏输出会嵌入英文源码作为回退。如果对此介意,解决方法是去除
message字段或将该组件迁移到.content.ts,适配器本身无法解决此问题。 i18n.load()仅作为回退手段。 如果你继续导入编译好的目录并调用load(),将会同时加载旧包与新包。请务必移除这些导入。- 仅支持 Vite。
@intlayer/lingui没有提供 Next.js 插件。在 Lingui 上的 Next.js 项目应直接查阅next-intlayer。 defaultComponent未生效。 如果你依赖它来自动包裹每个<Trans>,请在组件中显式编写包装器。
应该选择哪种方案?
- 继续留在 Lingui:如果你已经配置好了
scoped-dynamic,你的唯一性能考量是极致的每页字节体积,且 42 ms 的语言切换和 30 ms 的水合耗时完全在应用承受范围之内。 - 使用
@intlayer/lingui:如果你正在使用 Lingui,并希望在无需改动任何宏的前提下,获得更小的组件、更快的首屏水合与语言切换、在简单配置下实现 0% 页面泄漏、类型化 ID、CI 检查以及 AI 翻译填充。这是现有 Lingui 代码库的理想升级桥梁。 - 迁移到原生 Intlayer(
react-intlayer):当你的团队开始重构组件时。它是测试表中唯一实现 0% 语言环境泄漏、5 KB 运行时且相比基准应用仅增加 7.6 KB/页的方案。
相关对比文章
- Lingui vs Intlayer(库级别深度对比,相同基准测试)
- next-intl vs @intlayer/next-intl(同系列适配器测评)
- i18next vs @intlayer/i18next(同系列适配器测评)
- vue-i18n vs @intlayer/vue-i18n(同系列适配器测评)
- 兼容适配器参考:Lingui
- 编译器型 vs 声明式 i18n
总结
@intlayer/lingui 彻底改变了 Lingui 调用点的绑定机制:从绑定到全局实例及其各语言整体目录,转变为绑定到专门为该组件编译的独立字典。在相同的 TanStack Start 应用中,无需编辑任何宏即可实现 组件体积缩小 7 倍、水合加快 8-14 ms、语言切换提速 2 倍且消除了 42 ms 的性能骤降。它不会修改组件内置的回退内容(因此保留了源语言泄漏),并在运行时解析 ICU(使得动态加载模式下每页比纯 Lingui 多出约 20 KB)。在选型前,请务必明确团队的核心性能预算。
所有原始测试数据、测试应用和评测脚本均可在 Benchmark Bloom 代码仓库 中找到。欢迎自行复现并验证。
详情请参考 “为什么选择 Intlayer?”文档。
评论
暂无评论。成为第一个分享您想法的人吧。
