使用您最喜欢的AI助手总结文档,并引用此页面和AI提供商
版本历史
- "初始版本"v9.5.102026/9/26
此页面的内容已使用 AI 翻译。
查看英文原文的最新版本如果您有改善此文档的想法,请随时通过在GitHub上提交拉取请求来贡献。
文档的 GitHub 链接复制文档 Markdown 到剪贴板
如何在 2026 年使用 Lingui 实现 TanStack Start 应用的国际化
目录
什么是 Lingui?
Lingui 是一个围绕宏(macros)和消息提取(message extraction)构建的 i18n 库。你可以直接在组件中编写源文本( t`Hello` 、<Trans>Hello</Trans>),lingui extract 会将每条消息收集到目录文件中(默认是 PO 文件),翻译人员填写这些文件,然后 Vite 插件会将它们编译为紧凑的 JavaScript。消息采用 ICU MessageFormat 语法,因此原生支持复数和条件选择。
TanStack Start 本身不包含 i18n 层,因此本指南将从零开始将 Lingui 接入其中:
- 通过 Babel 编译宏:使用
@rolldown/plugin-babel(在@vitejs/plugin-reactv6 和 Vite 8 环境下必须)。 - 语言路由:使用可选的
{-$locale}路径段(/about、/fr/about)。 - 每个语言独立目录按需加载:每次渲染使用独立的
I18n实例,确保并发 SSR 请求绝不会共享或混淆语言环境。 - 完整的多语言 SEO:已翻译的
<title>和描述、规范链接(canonical URL)、带x-default的hreflang、Open Graph 多语言标签、JSON-LD、sitemap、robots.txt、预渲染以及本地化的 404 页面。
想要寻找其他技术栈?请参阅 TanStack Start + use-intl 指南、TanStack Start + Paraglide 指南 或 TanStack Start + Intlayer 指南。
使用 Next.js?请参阅 Next.js + Lingui 指南。对比不同国际化库?请阅读 Lingui vs Intlayer。
关于 TanStack Start 上的 Lingui 基准测试数据
i18n 基准测试使用各大主流国际化库运行了相同的 10 页面、10 种语言的 TanStack Start 应用,并测量了浏览器实际下载的内容。
动态 JSON 加载
在运行时懒加载翻译
有作用域的 JSON (命名空间)
每页翻译命名空间
I18n 性能基准测试
这个指标是什么?
国际化库包的总 gzip 压缩大小。它仅包含 tree-shaking 和压缩(minification)后的提供者(provider)和内容检索逻辑。
为什么这很重要?
较小的库大小可减少初始 JavaScript 负载,从而缩短客户端的下载和执行时间。
视图形式
@lingui/core@6.6.0 的关键数据(于 2026-09-26 测得,gzip 压缩):
在弹窗中打开表格以清晰地查看所有数据
| 配置 | 库体积 | 单页 JS 体积 | 其他语言泄露 | 其他页面泄露 |
|---|---|---|---|---|
| 无 i18n(基础应用) | - | 111.0 KB | 0% | 0% |
| Lingui(本指南配置) | 56.7 KB | 115.2 KB | 9.3% | 0% |
@intlayer/lingui(兼容模式) | 9.8 KB | 136.7 KB | 9.9% | 0% |
react-intlayer(原生 Intlayer) | 4.5 KB | 126.8 KB | 0% | 0% |
核心结论:
- 按需加载每个语言的目录文件:这能让页面体积保持接近基础应用的大小。
- 运行时体积相对较大(约 57 KB gzip)。
@intlayer/lingui兼容适配器(第 16 步)可以保留宏语法的同时将体积缩减至约 10 KB。
查看完整数据:TanStack Start 基准测试报告 以及 基准测试仓库。
TanStack Start 上的功能特性对比
以下是 Lingui 与 TanStack Start 上其他常用库的对比:
在弹窗中打开表格以清晰地查看所有数据
| 特性 | react-intlayer (Intlayer) | use-intl | Paraglide JS | Lingui |
|---|---|---|---|---|
| 组件就近翻译 | ✅ 集中就近放置 | ❌ 集中式 JSON | ❌ 每个语言一个 JSON 文件 | ⚠️ 组件中的源文本 |
| TypeScript 集成 | ✅ 自动生成类型 | ✅ 通过 AppConfig | ✅ 类型化消息函数 | ⚠️ 仅宏 |
| 缺失翻译检测 | ✅ 类型错误与构建警告 | ⚠️ 运行时回退 | ⚠️ 回退到基础语言 | ⚠️ 回退到源文本 |
| 富文本内容(JSX、Markdown) | ✅ 直接支持 | ⚠️ 通过 t.rich 标签 | ⚠️ 仅字符串 | ✅ <Trans> 中的 JSX |
| 本地化路由 | ✅ 内置 | ❌ 手动 {-$locale} | ✅ urlPatterns + 路由重写 | ❌ 手动 {-$locale} |
| 无需刷新切换语言 | ✅ 是 | ✅ 是 | ❌ 整页重新加载 | ✅ 是 |
| 复数处理 | ✅ 基于枚举 | ✅ ICU | ✅ 变体 | ✅ ICU |
| ICU 消息格式 | ✅ 通过 format: "icu" | ✅ 原生 | ⚠️ 通过 inlang 插件 | ✅ 原生 |
| 内容格式 | ✅ .ts、.json、.md、.yaml 等 | ⚠️ .json | ⚠️ inlang JSON | ✅ PO、JSON、CSV |
| AI 翻译 | ✅ 自定义提供商与 API Key | ❌ 否 | ❌ 否 | ❌ 否 |
| 可视化编辑器 / CMS | ✅ 本地编辑器 + 可选 CMS | ❌ 外部平台 | ⚠️ inlang 生态应用 | ❌ 外部平台 |
| SEO 辅助工具(hreflang、sitemap) | ✅ 内置 | ❌ 手动 | ⚠️ 本地化 URL,其余手动 | ❌ 手动 |
| 运行时体积(gzip,基准测试) | 4.5 KB | 75.9 KB | 1.8 KB | 56.7 KB |
| 资源泄露,最佳配置(语言 / 页面) | 0% / 0% | 0% / 0% | 49.7% / 0% | 8.6% / 0% |
| CI 中的缺失翻译检测 | ✅ npx intlayer test | ⚠️ 非内置 | ⚠️ 非内置 | ✅ lingui compile --strict |
运行时体积和资源泄露数据来自 TanStack Start 基准测试。资源泄露是在每个库的最佳配置下测得的。
其他 TanStack Start 指南:use-intl、Paraglide JS 以及 Intlayer。
推荐遵循的最佳实践
- 在
<html>标签上设置lang和dir:根据路由语言设置,确保服务端输出的 HTML 正确。 - 每个语言保持独立的 URL 并在路径中添加前缀:确保每个语言版本都能被搜索引擎独立索引。
- 每个语言创建一个
I18n实例:切勿在 SSR 期间修改全局实例,否则两个并发请求会互相覆盖对方的语言设置。 - 仅加载当前处于活动状态的语言目录:切勿在客户端代码中静态引入全部目录。
- 统一宏的使用风格(组件中使用
useLingui+t,延迟描述符使用msg)并保持一致。混用t、i18n._、i18n.t和<Trans>会降低代码对开发者及 AI 助手的可读性。 - 在 CI 中运行
lingui extract:确保新添加的消息不会在未翻译的情况下发布。 - 翻译页面的元数据:并在每个页面上声明
canonical、hreflang和x-default。 - 生成多语言 sitemap 和 robots.txt:并对每种语言进行预渲染。
- 在语言切换器中使用真实的链接:便于搜索引擎爬虫发现所有语言版本。
请参阅我们的 国际化与 SEO 指南 以及 hreflang 多语言 SEO 指南。
在 TanStack Start 应用中配置 Lingui 的分步指南
以下是我们将要创建的项目结构:
复制代码到剪贴板
安装依赖
bash复制代码复制代码到剪贴板
- @lingui/core / @lingui/react:运行时、
I18nProvider以及宏(@lingui/core/macro、@lingui/react/macro)。 - @lingui/cli:提供
lingui extract命令将消息收集到目录中。 - @lingui/vite-plugin:在 import 导入时编译
.po目录文件,因此无需手动执行lingui compile。 - @lingui/babel-plugin-lingui-macro + @rolldown/plugin-babel:在构建时转换宏语法。
- @lingui/core / @lingui/react:运行时、
集中管理语言配置
默认语言保持无前缀(
/about),其他语言添加前缀(/fr/about)。src/i18n/config.ts复制代码复制代码到剪贴板
配置 Lingui
Lingui 配置文件复用同一个语言列表,确保目录文件、路由和 sitemap 始终保持一致。
lingui.config.ts复制代码复制代码到剪贴板
添加提取脚本:
package.json复制代码复制代码到剪贴板
当组件中包含尚未提取并提交的消息时,
i18n:check命令在 CI 中将会报错退出。配置 Vite
在
@vitejs/plugin-reactv6 中,Babel 不再内置。@rolldown/plugin-babel负责运行 Lingui 宏插件,而linguiTransformerBabelPreset仅处理导入了宏的文件,从而保持快速构建。vite.config.ts复制代码复制代码到剪贴板
按语言加载目录文件
在
import()中使用模板字符串可以让 Vite 为每个语言生成一个单独的代码块(chunk),Lingui 插件会把.po文件编译到其中。法国访问者只需下载法语目录。编译后的消息是纯数据结构,因此可以在路由 loader 中返回、序列化到 HTML 中并在注水(hydration)时复用。
src/i18n/lingui.ts复制代码复制代码到剪贴板
为了让 TypeScript 能够识别
.po文件的导入,添加一次模块声明:src/i18n/po.d.ts复制代码复制代码到剪贴板
创建根文档
根路由读取可选的语言参数,以便在服务端渲染的
<html>标签上设置lang和dir。src/routes/__root.tsx复制代码复制代码到剪贴板
创建语言布局路由
{-$locale}文件夹创建了一个可选的路径段:/about和/fr/about都会匹配/{-$locale}/about。该布局会拒绝未知的语言前缀,加载当前语言的目录,并提供专用的I18n实例。src/routes/{-$locale}/route.tsx复制代码复制代码到剪贴板
在页面中使用翻译
直接在组件中编写源文本。宏会在构建时将其转换为消息 ID,随后
lingui extract会提取这些内容。<Trans>用于 JSX 内容(包括嵌套元素);useLingui().t用于字符串(属性、props);<Plural>用于 ICU 复数语法。
src/routes/{-$locale}/about.tsx复制代码复制代码到剪贴板
动态
import()目录会被模块系统缓存,因此在多个 loader 中调用loadI18n不会重复下载目录。提取并翻译消息
执行提取命令。Lingui 会将所有消息写入每个语言的目录文件中:
bash复制代码复制代码到剪贴板
然后翻译每个条目的
msgstr:src/locales/fr/messages.po复制代码复制代码到剪贴板
src/locales/es/messages.po复制代码复制代码到剪贴板
默认情况下,消息 ID 是源文本的哈希值:修改英文文本会生成一条新消息。对于经常变更的文本,可以使用显式 ID(例如
<Trans id="about.title">About us</Trans>)。构建本地化链接组件
可选每个路由都在
{-$locale}下,因此链接必须附带当前的语言参数。src/components/LocalizedLink.tsx复制代码复制代码到剪贴板
切换内容语言
可选将语言切换器渲染为链接(Links),以便搜索引擎爬虫发现所有语言版本。
to="."保留当前页面并替换语言参数。随后语言布局的 loader 会自动获取新语言的目录。src/components/LocaleSwitcher.tsx复制代码复制代码到剪贴板
国际化页面元数据
可选只要每个页面提供已翻译的
<title>和描述、自引用的规范链接(canonical)、每个语言的hreflang加上x-default、Open Graph 多语言标签以及带inLanguage的 JSON-LD,每个语言版本都能在搜索引擎中独立获得排名。元数据在 loader 中完成翻译(第 8 步),此辅助函数负责构建其余部分:src/i18n/seo.ts复制代码复制代码到剪贴板
国际化 Sitemap 和 robots.txt
可选Sitemap 列出每个语言的所有 URL,每个条目通过
xhtml:link声明其所有的备用语言版本。robots.txt会禁止所有语言下的私有路由并指向 sitemap。如果初始化模板创建了public/robots.txt,请将其移除。src/routes/sitemap[.]xml.ts复制代码复制代码到剪贴板
src/routes/robots[.]txt.ts复制代码复制代码到剪贴板
预渲染所有语言版本
可选列出所有本地化路径,以便 TanStack Start 在构建时预渲染所有语言版本:
vite.config.ts复制代码复制代码到剪贴板
重定向首次访问者并处理 404 页面
可选请求中间件会将访问
/根路径的用户重定向到其首选语言(优先读取 Cookie,其次读取Accept-Language请求头)。深层链接不会被重定向,因此爬虫和直接分享的链接总能访问到所请求的页面。src/i18n/negotiateLocale.ts复制代码复制代码到剪贴板
src/start.ts复制代码复制代码到剪贴板
对于 404 页面,通配路由会渲染布局中本地化的
notFoundComponent。将其标记为noindex:React 19 会将<meta>自动提升到<head>中。src/components/NotFound.tsx复制代码复制代码到剪贴板
src/routes/{-$locale}/$.tsx复制代码复制代码到剪贴板
保留宏语法,使用 Intlayer 精简运行时
可选@intlayer/lingui兼容适配器无需修改任何源代码:宏的编译方式完全保持原样,编译生成的i18n._()、useLingui()和<Trans>调用由编译后的 Intlayer 字典提供支持。在基准测试中,运行时体积从 ~56.7 KB 骤降至 ~9.8 KB gzip。bash复制代码复制代码到剪贴板
在宏转换之后添加该插件,使其将
@lingui/core和@lingui/react别名重定向到适配器:vite.config.ts复制代码复制代码到剪贴板
目录可以通过 sync JSON 插件(JSON 目录)或 sync PO 插件(PO 目录)进行同步。在 Lingui 兼容指南 中查看完整配置,并在 Lingui vs @intlayer/lingui 中查看详细对比。
使用 Intlayer 自动化翻译
可选Lingui 负责提取消息,但手动填写数十个目录文件往往耗费绝大部分时间。Intlayer 是免费且开源的,其工具链可以与 Lingui 无缝协作:
- 使用 AI 翻译:使用你自己的 API Key 和模型提供商。请参阅 自动填充 以及 CLI。
- 保留 PO 文件作为单一可信源:通过 sync PO 插件 实现。
- 在 CI 中测试缺失翻译:请参阅 测试翻译。
- 审计线上部署站点:使用 scan 命令 检查缺失的
hreflang、错误的 canonical 链接以及语言资源泄露。
常见问题解答
可以。Lingui 虽然没有专门针对 TanStack Start 的专属集成,但其 Vite 插件和 Babel 宏插件可以直接使用。需要注意的两个关键点是:通过 @rolldown/plugin-babel 运行宏(Vite 8 和 @vitejs/plugin-react v6 不再内置 Babel),以及在 SSR 期间为每个语言创建独立的 I18n 实例而不是激活全局实例。
在服务端,一个进程会同时渲染多个并发请求。在共享对象上调用 i18n.activate("fr") 会意外更改同时在以英文渲染的另一个请求的语言设置。setupI18n 为每个语言创建隔离的实例,这是安全可靠的做法。
不需要。@lingui/vite-plugin 会在导入 .po 目录文件时自动进行编译。你只需运行 lingui extract 来收集新添加的消息。
使用 msg 宏声明它们,并在路由 loader 中使用 i18n._(msg`...`) 进行翻译。loader 返回纯字符串,因此 head() 保持同步执行,且这些值会被序列化以供注水使用。第 8 步和第 12 步展示了完整实现。
基准测试 测得其运行时体积约为 56.7 KB gzip。采用每个语言独立目录按需加载时,页面体积约为 115 KB(相比之下无 i18n 基础应用为 111 KB)。如果静态导入所有语言目录,体积会上升至约 152 KB。
可以。@intlayer/lingui 适配器保留了宏语法并替换了底层运行时。随后你可以逐步将组件逐一迁移到 useIntlayer。详情请参阅 兼容适配器。
评论
暂无评论。成为第一个分享您想法的人吧。
