使用您最喜欢的AI助手总结文档,并引用此页面和AI提供商
版本历史
- "初始版本"v9.5.102026/9/26
如果您有改善此文档的想法,请随时通过在GitHub上提交拉取请求来贡献。
文档的 GitHub 链接复制文档 Markdown 到剪贴板
如何在 2026 年使用 Paraglide JS 实现 TanStack Start 应用的国际化
目录
什么是 Paraglide JS?
Paraglide JS(由 inlang 开发)是一个基于编译器的 i18n 库。它不再在运行时通过 JSON 对象查找键值,而是将每条消息编译为一个具有类型定义的 JavaScript 函数(m.about_title())。打包工具可以自动移除未使用的消息,而键名拼写错误在编译期就会报错。
Paraglide 是 TanStack Router 官方示例中采用的国际化方案,它通过以下三个核心部分与 TanStack Start 集成:
- 一个 Vite 插件:将消息与运行时编译生成至
src/paraglide; - 一个服务端中间件:解析每个请求的目标语言环境;
- 一个路由重写机制:将本地化 URL(
/fr/about)映射到你的路由树(/about),因此你不需要额外的$locale路径段。
本指南将完成这三部分的配置,并涵盖 Paraglide 未内置的其他全部功能:lang 与 dir 属性、语言切换器、已翻译的元数据、canonical、带 x-default 的 hreflang、Open Graph、JSON-LD、sitemap、robots.txt、预渲染以及本地化的 404 页面。
想要寻找其他技术栈?请参阅 TanStack Start + use-intl 指南、TanStack Start + Lingui 指南 或 TanStack Start + Intlayer 指南。
对比基于编译器的两种方案?请阅读 Intlayer 是否比 Paraglide 更轻量?。
关于 TanStack Start 上的 Paraglide 基准测试数据
i18n 基准测试使用各大主流库运行了相同的 10 页面、10 种语言的 TanStack Start 应用,并测量了浏览器实际下载的数据量。
动态 JSON 加载
在运行时懒加载翻译
有作用域的 JSON (命名空间)
每页翻译命名空间
I18n 性能基准测试
这个指标是什么?
国际化库包的总 gzip 压缩大小。它仅包含 tree-shaking 和压缩(minification)后的提供者(provider)和内容检索逻辑。
为什么这很重要?
较小的库大小可减少初始 JavaScript 负载,从而缩短客户端的下载和执行时间。
视图形式
@inlang/paraglide-js@2.15.1 的关键数据(于 2026-09-26 测得,gzip 压缩):
在弹窗中打开表格以清晰地查看所有数据
| 配置 | 库体积 | 单页 JS 体积 | 其他语言泄露 | 其他页面泄露 | 页面加载耗时 |
|---|---|---|---|---|---|
| 无 i18n(基础应用) | - | 111.0 KB | 0% | 0% | 15.7 ms |
| Paraglide JS | 1.8 KB | 125.1 KB | 49.7% | 0% | 22.1 ms |
react-intlayer | 4.5 KB | 126.8 KB | 0% | 0% | 14.8 ms |
use-intl | 75.9 KB | 128.7 KB | 0% | 0% | 17.4 ms |
| Lingui | 56.7 KB | 120.2 KB | 8.6% | 0% | 21.9 ms |
核心结论:
- 运行时非常小巧,且页面之间无资源泄露。运行时针对你的具体配置生成,消息仅在被引用的位置导入。
- 存在语言包泄露。每个消息函数都包含了所有语言的翻译,因此打包到页面的翻译字符串中,大约有一半属于访问者当前未使用的语言。添加的语言越多,这部分冗余比例越大。
- 页面加载耗时在该组中较慢,部分原因是每次调用都会通过策略解析语言环境,而不是直接从 React 上下文中读取。
查看完整数据:TanStack Start 基准测试报告 以及 基准测试仓库。
TanStack Start 上的功能特性对比
以下是 Paraglide JS 与 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 指南:Lingui、use-intl 以及 Intlayer。
推荐遵循的最佳实践
- 在服务端根据解析出的语言环境在
<html>标签上设置lang和dir。 - 使用前缀策略为每种语言保持独立的 URL(
/fr/about),以确保所有语言版本均可被索引。 - 将
url置于语言解析策略的首位,使 URL 成为唯一可信来源,确保爬虫始终能获取到目标页面。 - 使用扁平且具描述性的消息键名(
about_title),以便干净地映射为函数名称。 - 提交
messages/*.json文件,而不是生成的src/paraglide目录,以避免生成文件出现合并冲突。 - 翻译元数据,并在每个页面声明
canonical、hreflang和x-default。 - 生成多语言 sitemap 和 robots.txt,并对每种语言进行预渲染。
- 语言切换器使用真实的链接,以便搜索引擎爬虫能发现所有语言版本。
详情请参阅关于国际化与 SEO的指南以及 hreflang 指南。
在 TanStack Start 应用中配置 Paraglide JS 的分步指南
以下是我们将要创建的项目结构:
复制代码到剪贴板
请注意,项目中并没有 $locale 目录:路由重写会在进行路由匹配前自动剥离语言前缀。
安装依赖
从 TanStack Start 项目开始,然后初始化 Paraglide。初始化命令会创建
project.inlang/settings.json、初始的messages/en.json并安装相关依赖包。bash复制代码复制代码到剪贴板
- @inlang/paraglide-js:编译器及其 Vite 插件。无需安装单独的运行时依赖包:运行时会直接生成到你的项目中。
配置语言环境
project.inlang/settings.json是语言环境的唯一可信源。消息格式插件会为每种语言读取一个对应的 JSON 文件。project.inlang/settings.json复制代码复制代码到剪贴板
配置 Vite 插件与 URL 策略
插件会在每次代码修改时编译消息。对于 TanStack Start,有三个配置项至关重要:
strategy:读取语言环境的优先级顺序。将url置于首位使 URL 成为唯一可信源。当 URL 无法确定语言时,中间件会使用cookie和preferredLanguage。urlPatterns:语言映射到 URL 的方式。非默认语言放在前面,因为优先匹配最先符合的规则。在此配置中,默认语言不带前缀(/about),其他语言添加前缀(/fr/about)。outputStructure: "message-modules":每个消息生成一个独立模块,允许打包工具剔除当前页面未导入的消息。
vite.config.ts复制代码复制代码到剪贴板
将生成的目录添加到
.gitignore中。它会在dev和build时自动重新构建:.gitignore复制代码复制代码到剪贴板
创建翻译文件
每个键名都会成为从
src/paraglide/messages导出的函数。扁平的蛇形命名(snake_case)可以生成最干净的函数名。变量使用{name}占位符。messages/en.json复制代码复制代码到剪贴板
messages/fr.json复制代码复制代码到剪贴板
复数处理使用 inlang 消息格式的变体语法:
messages/en.json复制代码复制代码到剪贴板
添加服务端中间件
中间件会根据你的策略解析每个请求的语言环境,并通过
AsyncLocalStorage作用域在整个服务端渲染期间供getLocale()使用。这也确保了不同语言的并发请求之间互不干扰、安全隔离。在 TanStack Start 中,包装默认的服务端入口:
src/server.ts复制代码复制代码到剪贴板
在路由器中重写本地化 URL
TanStack Router 的
rewrite选项在路由器边界处转换 URL:- 输入:
/fr/about在匹配前被去本地化为/about,因此单个about.tsx路由即可处理所有语言; - 输出:每个生成的
href(链接、重定向、导航)都会根据当前激活的语言进行本地化,所以在法语页面中<Link to="/about">会自动渲染为/fr/about。
src/router.tsx复制代码复制代码到剪贴板
由于链接已由重写机制自动本地化,你无需编写自定义的
LocalizedLink组件:直接像平常一样使用 TanStack Router 的Link即可。- 输入:
创建根文档
getLocale()在服务端返回中间件解析出的语言,在浏览器中返回来自 URL 的语言,因此在服务端 HTML 和注水(hydration)之后,lang与dir保持完全一致。src/i18n/config.ts复制代码复制代码到剪贴板
src/routes/__root.tsx复制代码复制代码到剪贴板
在页面中使用翻译
消息就是普通的函数:导入
m,调用该函数,并将变量作为对象传入。所有内容(包括变量)都具有完整的类型提示。src/routes/index.tsx复制代码复制代码到剪贴板
src/routes/about.tsx复制代码复制代码到剪贴板
消息函数也支持显式传入语言环境:
m.about_title({}, { locale: "fr" })。这在服务端渲染非当前请求语言的代码(如发送邮件)时非常实用。切换内容语言
可选使用
localizeHref将切换器渲染为链接,以便搜索引擎爬虫能够发现所有语言版本。setLocale将选择保存到 Cookie 中,并以新语言重新加载页面:整页重新加载是 Paraglide 的预期行为,因为消息函数在每次调用时读取语言环境,而不是订阅 React 状态。src/components/LocaleSwitcher.tsx复制代码复制代码到剪贴板
国际化你的元数据
可选每个语言版本都可以独立排名,前提是每个页面都提供:
- 已翻译的
<title>和description; - 指向自身的规范(canonical) URL;
- 每个语言环境对应的
hreflang备用链接,加上x-default; - Open Graph 的
og:locale、og:locale:alternate和og:url; - 带有
inLanguage的 JSON-LD 数据。
Paraglide 的
localizeUrl会根据你的urlPatterns构建备用 URL,因此它们绝不会与实际路由产生偏差:src/i18n/seo.ts复制代码复制代码到剪贴板
- 已翻译的
国际化你的站点地图(Sitemap)
可选多语言站点地图列出每种语言的每个 URL,并且每个条目都通过
xhtml:link声明其所有备用版本:src/routes/sitemap[.]xml.ts复制代码复制代码到剪贴板
国际化你的 robots.txt
可选私有路由存在于每种语言中,因此
Disallow规则必须覆盖所有本地化路径。如果脚手架生成了public/robots.txt,请将其删除,然后通过路由动态提供:src/routes/robots[.]txt.ts复制代码复制代码到剪贴板
预渲染所有语言版本
可选列出每个页面的本地化路径,以便 TanStack Start 预渲染所有语言版本。
localizeHref是无浏览器依赖的生成代码,因此可以在vite.config.ts中运行,但该文件仅在初次编译后才会存在。如下所示手动列出路径可以避免此执行顺序问题:vite.config.ts复制代码复制代码到剪贴板
由于语言切换器渲染的是真实链接,
crawlLinks: true也会自动发现你遗漏列出的页面。处理本地化的 404 页面
可选通过重写机制,
/fr/does-not-exist会被作为/does-not-exist进行匹配,且getLocale()依然返回fr,因此第 7 步中的根notFoundComponent会以法语渲染。通配路由(catch-all route)确保深层路径也能正确进入 404 页面。将页面标记为noindex:React 19 会将<meta>自动提升到<head>中。src/components/NotFound.tsx复制代码复制代码到剪贴板
src/routes/$.tsx复制代码复制代码到剪贴板
在服务端函数中获取语言环境
可选服务端函数运行在 Paraglide 中间件的作用域内,因此
getLocale()在此处同样可用:src/server/sendWelcomeEmail.ts复制代码复制代码到剪贴板
与 Intlayer 对比
可选目前没有从 Paraglide 到 Intlayer 的直接开箱即用适配器,因为两者遵循相同的理念:在构建时编译内容并尽可能减少运行时体积。两者的差异主要体现在交付到浏览器的内容和内容组织方式上:
- 语言环境处理:Intlayer 按语言加载动态字典(在基准测试中为 0% 语言包泄露),而 Paraglide 的每个消息函数都携带所有语言(泄露达 49.7%)。
- 内容组织:内容可以存放在每个组件旁的
.content.ts文件中,也可以存放在集中式文件中。请参阅单组件管理 vs 集中式 i18n。 - 语言切换:内容从 React 上下文中读取,因此切换语言时无需刷新页面即可重新渲染。
- 生成代码:
src目录内不生成任何额外代码,因此在 git 提交前无需重新生成文件。
如果你是从其他库而非 Paraglide 迁移,兼容适配器可以保留
use-intl、next-intl、react-i18next、react-intl或 Lingui 的 API 并替换底层运行时。请参阅 Intlayer 是否比 Paraglide 更轻量? 以及 Intlayer TanStack Start 配置指南。
使用 Intlayer 自动化你的翻译流程
可选Paraglide 负责渲染翻译,但它无法帮助你生成翻译内容。Intlayer 是免费且开源的,其配套工具即使在 Paraglide 项目中也能提供极大帮助:
- 使用 AI 进行翻译:使用你自己的 API Key 和提供商。请参阅自动填充(auto fill)与 CLI 工具。
- 保留你的 JSON 文件作为唯一可信源:通过 JSON 同步插件 实现。
- 在 CI 中检测缺失的翻译:请参阅测试你的翻译。
- 扫描已部署的站点:使用 scan 命令 检查缺失的
hreflang、错误的 canonical 规范链接以及语言包泄露。
常见问题解答
是一个可靠的选择:它被用于 TanStack Router 的官方示例中,拥有基准测试中最小的运行时体积(gzip 压缩后约 1.8 KB),且消息具备完整的类型定义。其权衡点在于每个消息函数都包含所有语言,这会导致大约一半的翻译字符串泄露给使用其他语言的访客,并且切换语言时需要重新加载页面。
不需要。路由器的 rewrite 功能会在路由匹配之前移除语言前缀,并在生成链接时自动添加回去,因此单个 about.tsx 文件即可同时为 /about、/fr/about 和 /es/about 提供服务。
消息函数在被调用时直接读取语言环境,并没有订阅 React 状态。因此 setLocale 默认会重新加载页面,使所有消息以新语言重新渲染。你可以传入 { reload: false },但随后必须自行手动重新渲染组件树。
建议不要提交。该目录在每次 dev 和 build 时都会重新生成,将其纳入版本控制容易导致生成文件的合并冲突。建议仅提交 messages/*.json 和 project.inlang/settings.json。
在路由的 head() 中使用 localizeUrl 为每种语言构建一个绝对 URL,并添加一个指向基础语言的 x-default。第 10 步提供了可复用的辅助函数,第 11 步将相同的备用链接添加到了站点地图中。
当使用 outputStructure: "message-modules" 时,未使用的消息会被移除,因此其他页面的内容不会泄露。但未使用的语言不会被移除:每个消息函数都包含所有语言的翻译,这也是基准测试测得 49.7% 语言包泄露的原因。
评论
暂无评论。成为第一个分享您想法的人吧。
