--- createdAt: 2026-09-23 updatedAt: 2026-09-23 title: Intlayer 比 Paraglide 更轻量吗? description: Paraglide 在 i18n 基准测试中看起来几乎没有开销,因为其代码是直接生成到你的代码库中的。本文将深入分析其实际体积去了哪里、为什么按节点读取语言会带来性能损耗,以及 Intlayer 的动态加载如何做到仅下发单一语言而非全量语言包。 keywords: - Paraglide - Intlayer - 国际化 - i18n - Bundle size - Tree shaking - Benchmark - Blog slugs: - blog - is-intlayer-lighter-than-paraglide author: aymericzip --- # Intlayer 比 Paraglide 更轻量吗? 是的。 `Paraglide` 作为业界公认非常轻量的 i18n 解决方案享有良好声誉,初看之下,[基准测试](https://intlayer.org/zh/doc/benchmark/tanstack) 也印证了这一点:其库体积几乎为零。然而,库体积为零并不代表下发到浏览器的体积为零。这只意味着字节被转移到了该指标不统计的地方。 ## 核心要点 **库体积只是被隐藏了,并没有消失:** Paraglide 将其 runtime 和消息函数直接生成到你的代码库中。这部分代码虽然依然传输给浏览器,但被归类为_你的_代码,而不是库的代码。 **无 Provider 并非免费的午餐:** 每次调用 `m.my_key()` 都会自行解析语言,在渲染每个节点时都要读取 cookie 或本地存储,而不是从 context 中一次性读取。 **缺乏动态加载机制:** Paraglide 会将一条消息的所有语言全部引入客户端 bundle 中。而 Intlayer 在配置 `importMode: 'dynamic'` 或 `'fetch'` 时,仅加载当前正在渲染的单一语言。 **Tree Shaking 并非总是生效:** 在我们的部分基准测试中,Paraglide 宣传的 Tree Shaking 未能起效。建议务必检查你自己的 bundle 体积。 ## Paraglide 的体积究竟去了哪里? 在基准测试报告中,“库体积”指标衡量的是在添加任何业务内容之前,空组件中各 i18n 库的 Provider 和 Hook 体积。 | 库 (TanStack Start) | 库体积 (gz) | 库体积 (min) | | ----------------------------- | ----------- | ------------ | | `@inlang/paraglide-js@2.15.1` | 1.8 KB | 4.5 KB | | `react-intlayer@9.5.1` | 5.0 KB | 15.2 KB | 单看这项数据,Paraglide 确实获胜。但 Paraglide 本质上是一个编译器:它读取你的 `messages/*.json` 文件,并在你的仓库中生成一个 `paraglide/` 目录,其中包含 `runtime.js`(语言检测、cookie 和存储策略、URL 本地化)以及每个消息对应的 JavaScript 函数。 ```bash src/paraglide/ ├── runtime.js # 语言检测、策略、URL 辅助工具 ├── server.js ├── messages.js # 重新导出所有消息 └── messages/ ├── _index.js ├── en.js └── fr.js ``` 由于这些代码位于你的 `src/` 目录下且通过相对路径导入,打包工具会将其归算到你的应用程序中,而不是 `node_modules` 包。因此,库体积一栏几乎显示为零,但相同的业务逻辑依旧被完整打包进了你的页面 bundle 中。 生成代码本身并不是坏事:生成的 runtime 仅包含你配置中真正需要的逻辑(前缀策略、cookie 与 local storage 选择等)。Intlayer 则通过另一种方式达到相同效果,即在构建时注入环境变量,使打包工具自动剔除你配置未启用的代码分支。这两种方案最终都比 `i18next` 或 `next-intl` 轻量 3 到 10 倍。 因此,真正公平的衡量标准并不是库体积,而是**每个页面实际传输的 JavaScript 体积**。 ## 页面实测体积 TanStack Start 应用,10 个页面,在 `en` 和 `fr` 路由上进行实测(经 gzip 压缩): | 配置 | 页面 JS 平均 (gz) | 超出基准 | 语言泄露率 | 跨页面泄露率 | | ---------------------------------- | ----------------- | ----------- | ---------- | ------------ | | 基准(无 i18n) | 111.0 KB | - | 0.0% | 0.0% | | `paraglide`(任何策略) | 125.1 KB | +14.1 KB | 49.7% | 0.0% | | `intlayer` (`importMode: static`) | 125.8 KB | +14.8 KB | 50.0% | 0.0% | | `intlayer` (`importMode: dynamic`) | **118.6 KB** | **+7.6 KB** | **0.0%** | **0.0%** | Next.js 16 App Router,同一应用: | 配置 | 页面 JS 平均 (gz) | 超出基准 | | ---------------- | ----------------- | ----------- | | 基准(无 i18n) | 141.0 KB | - | | `paraglide-next` | 155.3 KB | +14.3 KB | | `next-intlayer` | **141.3 KB** | **+0.3 KB** | > 完整数据请参阅 [TanStack Start 基准测试报告](https://intlayer.org/zh/doc/benchmark/tanstack) 与 [Next.js 基准测试报告](https://intlayer.org/zh/doc/benchmark/nextjs)。所有 bundle 均可在 [基准测试仓库](https://github.com/intlayer-org/benchmark-i18n) 中核查。 实测结果呈现出两个明显特征: - 在 `static` 模式下,Intlayer 传输的内容体积与 Paraglide 基本持平(125.8 KB 对比 125.1 KB)。这符合预期:两者都包含了页面所需消息的所有语言版本。 - 无论使用何种策略,Paraglide 的体积始终保持在 125.1 KB,因为它没有动态加载模式。上表中其每一行都等同于静态引入。 ## 无 Provider:看似美妙却代价不菲的方案 Paraglide 不需要 Provider。你只需导入消息并直接调用: ```tsx fileName="Hero.tsx" import { m } from "../paraglide/messages.js"; export const Hero = () => (

{m.hero_title()}

{m.hero_description()}

); ``` 没有 context,没有外层包裹组件,没有 hook。表面上看简单直观。但是语言依然必须由某个地方解析。每个生成的消息函数大致如下所示(简化代码): ```js fileName="paraglide/messages/_index.js" export const hero_title = (inputs = {}, options = {}) => { const locale = options.locale ?? getLocale(); // 每次调用都要重新解析 if (locale === "en") return en.hero_title(inputs); if (locale === "fr") return fr.hero_title(inputs); // ...每个语言对应一个分支 }; ``` 而 `getLocale()` 会遍历已配置的各种策略(cookie、本地存储、URL、基础语言)以获取当前语言。这意味着你渲染的每个文本节点(`<>{m.my_key()}`)都会各自执行一次语言解析流程,包括在浏览器中读取 `document.cookie`。一个包含 200 个翻译字符串的页面,在单次渲染中就会解析 200 次语言,并且在每次重新渲染时周而复始。 基于 Provider 的库仅需读取**一次**语言,将其保存在 context(或 signal、store)中,所有节点直接读取已存在于内存中的值。Provider 的开销仅有几百字节。省去 Provider 会导致每次渲染都消耗 CPU 周期,这在基准测试中表现得尤为明显:在 TanStack Start 上,Paraglide 的页面加载时间与语言切换延迟均落后于 Intlayer(页面加载时间 22.1 ms 对比 14.6 ms,端到端响应度 4.3 ms 对比 3.2 ms)。 ## 开发者体验(DX) Paraglide 的事实来源是 JSON,但你从不直接导入这些 JSON 文件,而是导入生成的 `.js` 文件: ```json fileName="messages/en.json" { "hero_title": "Ship your app in every language" } ``` ```json fileName="messages/zh.json" { "hero_title": "以所有语言发布你的应用程序" } ``` ```tsx fileName="Hero.tsx" // 仅在编译器从 JSON 重新生成该文件后才存在 import { m } from "../paraglide/messages.js"; export const Hero = () =>

{m.hero_title()}

; ```
```ts fileName="Hero.content.ts" import { t, type Dictionary } from "intlayer"; export default { key: "hero", content: { title: t({ zh: "以所有语言发布你的应用程序", en: "Ship your app in every language", }), }, } satisfies Dictionary; ``` ```tsx fileName="Hero.tsx" import { useIntlayer } from "react-intlayer"; export const Hero = () => { const { title } = useIntlayer("hero"); return

{title}

; }; ```
这种开发循环需要付出相应代价: - 每次修改 JSON 文件,都必须等待重新生成完成,导入才能被识别或类型才能得到更新。 - 生成的 `paraglide/` 目录要么提交至 Git(这意味着每次涉及文案修改的 PR 都会在生成文件上产生合并冲突),要么将其忽略(这意味着在每次类型检查、测试和 CI 任务之前都必须额外执行一次生成步骤)。 - 每个字符串都被包裹成函数调用。常量在各处变成了 `m.key()`,即便在原本只需静态值的地方也是如此。 ## Tree Shaking:请核查你的实际 Bundle Paraglide 最主要的主打特性是:由于每个消息都是独立的 export,未使用的消息能被 Tree Shaking 剔除。在 Svelte + Vite 的基准测试中,它确实如宣传的那样生效了。 但在其它技术栈中并非如此。在我们针对 [Next.js](https://intlayer.org/zh/doc/benchmark/nextjs) 的测试中,Paraglide 的页面体积比基础应用多出 14 KB,而 `next-intlayer` 仅增加了 0.3 KB。在 TanStack Start 上的早期测试也表明,来自其他页面的消息同样混入了当前路由的 bundle 中。 Tree Shaking 效果取决于你的打包工具(Turbopack、Rolldown、Rollup)、消息导入语法(`import { m }` 与 `import * as m`)以及副作用分析能力。如果你因体积小巧而选择 Paraglide,请务必打开 bundle 可视化工具并在自己的项目中实际验证。 ## 无动态加载机制 这是架构上的硬伤。Paraglide 无法做到一次只加载一种语言:每个消息函数都会静态导入每种语言的实现代码,因此所有语言都会全部打包到你的客户端 bundle 中。 在支持 2 种语言时,有一半的翻译数据传输被浪费了,这与前文测得的 ~50% 语言泄露率完全吻合。当支持 10 种语言时,浪费率高达 90%;当支持 30 种语言时,浪费率高达 97%。 即使改成动态加载也无法根治:由于每个消息是一个独立函数,如果按函数进行懒加载,将会导致成千上万次碎小的网络请求。 Intlayer 允许你全局或按字典自由配置加载模式: ```ts fileName="intlayer.config.ts" import { Locales, type IntlayerConfig } from "intlayer"; const config: IntlayerConfig = { internationalization: { locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH], defaultLocale: Locales.ENGLISH, }, dictionary: { importMode: "dynamic", // 'static' | 'dynamic' | 'fetch' }, }; export default config; ``` | `importMode` | 传输至客户端的内容 | 对比 Paraglide | | ------------ | ------------------------------------------- | --------------------------- | | `static` | 页面所用字典的所有语言版本 | 理论体积完全一致 | | `dynamic` | 仅加载当前语言,按字典进行按需懒加载 | N 个语言时体积**轻量 N 倍** | | `fetch` | 仅加载当前语言,直接通过 Live Sync API 获取 | N 个语言时体积**轻量 N 倍** | 借助 [构建期转换优化](https://intlayer.org/zh/doc/concept/bundle-optimization) 与 `importMode: 'static'`,Intlayer 在理论上加载的内容与 Paraglide 毫无二致。而启用 `'dynamic'` 或 `'fetch'` 后,它仅加载当前语言所需的内容:对于支持 N 种语言的应用,翻译数据的网络传输量将比 Paraglide 小 N 倍。 ## Paraglide 依然适用的场景 如果你的技术栈是 Svelte 搭配 Vite,且仅需支持两到三种语言,Tree Shaking 可以按预期工作,多语言带来的体积开销也相对可控。 如果你的团队已经深度依赖 inlang 生态(Fink、Sherlock、消息格式插件),Paraglide 能够与其实现原生无缝集成。 ## 在你的应用中实际测验 使用免费的 [i18n SEO Scanner](https://intlayer.org/i18n-seo-scanner) 检测线上生产应用的传输体积与语言泄露情况: 安装并初始化 Intlayer: ```bash packageManager="npm" npx intlayer init --interactive ``` ```bash packageManager="pnpm" pnpm dlx intlayer init --interactive ``` ```bash packageManager="yarn" yarn dlx intlayer init --interactive ``` ```bash packageManager="bun" bunx intlayer init --interactive ``` ## 拓展阅读 - [TanStack Start i18n 基准测试](https://intlayer.org/zh/doc/benchmark/tanstack) - [Next.js i18n 基准测试](https://intlayer.org/zh/doc/benchmark/nextjs) - [Bundle 优化与 `importMode`](https://intlayer.org/zh/doc/concept/bundle-optimization) - [如何挑选合适的 React i18n 库](https://intlayer.org/zh/blog/how-to-pick-react-i18n-library) - [编译器驱动与声明式国际化的优劣权衡](https://intlayer.org/zh/blog/compiler-vs-declarative-i18n)