使用您最喜欢的AI助手总结文档,并引用此页面和AI提供商
版本历史
- "初始版本"v9.5.102026/9/26
如果您有改善此文档的想法,请随时通过在GitHub上提交拉取请求来贡献。
文档的 GitHub 链接复制文档 Markdown 到剪贴板
如何在 2026 年使用 use-intl 实现 TanStack Start 应用的国际化
目录
什么是 use-intl?
use-intl 是 next-intl 中与框架无关的核心部分。它提供了与 Next.js 无任何依赖关系的 useTranslations、useFormatter 和 IntlProvider API、ICU MessageFormat 支持以及完善的 TypeScript 集成。这使其成为为 TanStack Start 应用实现国际化的最常见选择之一,也是 AI 助手最常为此技术栈推荐的库。
TanStack Start 本身不包含 i18n 层。路由、语言检测、SEO 元数据以及站点地图(sitemap)生成都需要自行配置。本指南将端到端地涵盖所有这些内容:
- 支持语言环境的路由:使用可选的
{-$locale}路径段(/about、/fr/about)。 - 按路由加载消息:确保页面只下载当前渲染所需的命名空间和语言文件。
- 服务端渲染与注水(Hydration):避免文本不一致导致的注水错误。
- 完整的多语言 SEO:已翻译的
<title>和描述、规范链接(canonical URL)、带x-default的hreflang备用链接、Open Graph 语言标签、JSON-LD、带有xhtml:link备用链接的站点地图、robots.txt以及所有语言的预渲染。
想要寻找其他技术栈?请参阅 TanStack Start + Paraglide 指南、TanStack Start + Lingui 指南 或 TanStack Start + Intlayer 指南。
使用 Next.js?请参阅 next-intl 指南。
关于 TanStack Start 上的 use-intl 基准测试数据
i18n 基准测试使用各大主流国际化库运行了相同的 10 页面、10 种语言的 TanStack Start 应用,并测量了浏览器实际下载的内容。
动态 JSON 加载
在运行时懒加载翻译
有作用域的 JSON (命名空间)
每页翻译命名空间
I18n 性能基准测试
这个指标是什么?
国际化库包的总 gzip 压缩大小。它仅包含 tree-shaking 和压缩(minification)后的提供者(provider)和内容检索逻辑。
为什么这很重要?
较小的库大小可减少初始 JavaScript 负载,从而缩短客户端的下载和执行时间。
视图形式
use-intl@4.14.2 的关键数据(于 2026-09-26 测得,gzip 压缩):
在弹窗中打开表格以清晰地查看所有数据
| 配置 | 库体积 | 单页 JS 体积 | 其他语言泄露 | 其他页面泄露 |
|---|---|---|---|---|
| 无 i18n(基础应用) | - | 111.0 KB | 0% | 0% |
use-intl(本指南配置) | 75.9 KB | 128.7 KB | 0% | 0% |
@intlayer/use-intl(兼容适配器) | 6.7 KB | 129.4 KB | 0% | 0% |
react-intlayer(原生 Intlayer) | 4.5 KB | 126.8 KB | 0% | 0% |
核心结论:
- 按页面拆分消息并按语言按需加载。 这可以彻底消除两种泄露,也正是下文步骤所实现的方式。
- 运行时本身相对较重(约 76 KB gzip),因为 ICU 解析器需要打包发送到客户端。使用
@intlayer/use-intl兼容适配器(步骤 17)可以在保持完全相同 API 的同时,将运行时体积缩减至约 7 KB。
查看完整数据:TanStack Start 基准测试报告 以及 基准测试仓库。
TanStack Start 上的功能特性对比
use-intl 与 TanStack Start 上常用的其他库对比情况:
在弹窗中打开表格以清晰地查看所有数据
| 功能特性 | react-intlayer (Intlayer) | use-intl | Paraglide JS | Lingui |
|---|---|---|---|---|
| 组件就近管理翻译 | ✅ 就近放置(Co-located) | ❌ 集中式 JSON | ❌ 每种语言一个 JSON 文件 | ⚠️ 源文本硬编码在组件中 |
| TypeScript 集成 | ✅ 自动生成类型 | ✅ 通过 AppConfig | ✅ 类型化消息函数 | ⚠️ 仅宏支持 |
| 缺失翻译检测 | ✅ 类型错误与构建警告 | ⚠️ 运行时回退 | ⚠️ 回退到基础语言 | ⚠️ 回退到源文本 |
| 富文本内容(JSX, Markdown) | ✅ 直接支持 | ⚠️ 通过 t.rich 标签 | ⚠️ 仅支持字符串 | ✅ <Trans> 内直接使用 JSX |
| 本地化路由 | ✅ 开箱即用 | ❌ 需手动处理 {-$locale} | ✅ urlPatterns + 路由重写 | ❌ 需手动处理 {-$locale} |
| 无刷新切换语言 | ✅ 支持 | ✅ 支持 | ❌ 需要整页重新加载 | ✅ 支持 |
| 复数处理 | ✅ 基于枚举声明 | ✅ 支持 ICU | ✅ 变体支持 | ✅ 支持 ICU |
| ICU MessageFormat | ✅ 通过 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、Paraglide JS 以及 Intlayer。
推荐遵循的最佳实践
- 在
<html>标签上设置lang和dir:提升无障碍访问能力、屏幕阅读器支持和搜索引擎表现。 - 每个语言环境保持独立 URL:使用语言前缀(如
/fr/about)而非仅使用 Cookie 切换,确保每个翻译页面都可被爬取和分享。 - 按命名空间拆分消息(
common、home、about)并按路由按需加载。 - 仅加载当前活跃的语言环境:切勿在发送到客户端的模块中导入所有语言文件。
- 在
IntlProvider中固定时区:避免 SSR 期间使用服务器时区格式化日期而客户端注水时使用访客时区格式化,从而引发注水不匹配错误。 - 翻译元数据:并在每个页面上声明
canonical、hreflang和x-default。 - 生成多语言站点地图和 robots.txt:并预渲染每种语言的页面。
- 语言切换器使用真实链接:避免仅使用
<select>标签,以便搜索引擎爬虫能够发现每种语言版本。 - 为消息添加类型声明:使缺失的翻译键在编译阶段即可报错提示。
请参阅我们的国际化与 SEO 指南以及 hreflang 完整指南。
在 TanStack Start 应用中配置 use-intl 的分步指南
以下是我们将要构建的项目目录结构:
复制代码到剪贴板
安装依赖
从 TanStack Start 项目开始,然后安装
use-intl:bash复制代码复制代码到剪贴板
- use-intl:提供
IntlProvider、useTranslations、useFormatter以及createTranslator(可在 React 外部使用,例如在head()函数中)。
- use-intl:提供
集中管理语言配置
为语言列表和 URL 辅助函数创建单一数据源(Single Source of Truth)。其他所有文件(路由、SEO、站点地图、预渲染)都从此导入,后续添加新语言仅需修改一行代码。
默认语言保持无前缀(
/about),其他语言添加前缀(/fr/about)。这是推荐的策略:每种语言每个页面都有唯一 URL,并为主要受众保持简短的 URL。src/i18n/config.ts复制代码复制代码到剪贴板
创建翻译文件
按语言和命名空间组织翻译消息。
common存放每个页面通用的内容(导航、页脚),每个页面单独建一个文件,包含其元数据。use-intl 采用 ICU MessageFormat,复数、选择和格式化参数都直接定义在消息内部。
messages/en/common.json复制代码复制代码到剪贴板
messages/en/about.json复制代码复制代码到剪贴板
messages/fr/common.json复制代码复制代码到剪贴板
messages/fr/about.json复制代码复制代码到剪贴板
以相同方式创建
home.json,包含metadata对象和页面具体内容。按命名空间与语言按需加载消息
此加载器是性能优化的核心。
import.meta.glob指导 Vite 为每个 JSON 文件生成单独的代码块(chunk)。请求法语["about"]的路由只会下载messages/fr/about.json,不会加载多余内容,这正是基准测试能达到 0% 语言泄露和 0% 页面泄露的原因。src/i18n/messages.ts复制代码复制代码到剪贴板
为翻译消息添加类型声明
模块扩展(Module Augmentation)能为
useTranslations("about")和t("counter.label")提供自动补全,并在出现拼写错误或键被删除时产生编译期报错。src/i18n/use-intl.d.ts复制代码复制代码到剪贴板
请确保
tsconfig.json中已启用resolveJsonModule。创建根文档
根路由负责渲染
<html>。它读取可选的语言参数来设置lang和dir,确保在任何 JavaScript 执行前,服务端渲染出的 HTML 就已经具备正确的属性。src/routes/__root.tsx复制代码复制代码到剪贴板
创建语言布局路由
{-$locale}目录创建了一个可选的路径段:/about和/fr/about都会匹配到/{-$locale}/about。该布局负责:- 拦截不支持的前缀(
/xx/about→ 404)。 - 仅为当前语言加载
common命名空间。 - 通过
IntlProvider提供翻译消息。
loader 的加载结果会被序列化到 HTML 中并在客户端注水时复用,因此客户端不会重复下载
common.json。staleTime: Infinity会将其缓存在客户端导航之间。src/routes/{-$locale}/route.tsx复制代码复制代码到剪贴板
IntlProvider不会自动合并父级 Provider 的消息。下一步我们将添加一个小型组件来实现合并,以便每个页面都可以在common的基础上添加自己的命名空间。- 拦截不支持的前缀(
合并页面级消息
每个页面在其 loader 中加载属于自己的命名空间,然后使用
ScopedMessages包裹其内容,从而将页面命名空间与父级消息进行合并。src/components/ScopedMessages.tsx复制代码复制代码到剪贴板
在页面中使用翻译
页面 loader 获取当前语言的
about命名空间,head()依据其构建已翻译且 SEO 完善的元数据(详见步骤 13),组件负责渲染内容。src/routes/{-$locale}/about.tsx复制代码复制代码到剪贴板
在组件中使用翻译与格式化工具
Provider 下的任何组件都可以调用
useTranslations和useFormatter。复数由 ICU 解析,数字会根据当前语言环境进行格式化。src/components/Counter.tsx复制代码复制代码到剪贴板
构建本地化链接组件
可选所有路由都在
{-$locale}之下,因此链接必须携带当前语言参数。此封装保留了 TanStack Router 具备类型检查的to属性,并自动为你注入语言参数。src/components/LocalizedLink.tsx复制代码复制代码到剪贴板
src/components/Header.tsx复制代码复制代码到剪贴板
切换内容语言
可选将语言切换器渲染为链接而不是
<select>。链接具有可爬取性,使搜索引擎可以发现每种语言版本,并且在禁用 JavaScript 时仍可正常工作。to="."保留当前页面并仅替换语言参数。Cookie 会记录用户的显式选择,用于步骤 16 的重定向中间件。src/components/LocaleSwitcher.tsx复制代码复制代码到剪贴板
多语言元数据配置(SEO)
可选这是国际化带来收益的关键部分:每个语言版本都可以独立参与排名。每个页面必须包含:
- 已翻译的
<title>和description; - 指向自身的 canonical 规范链接(而非指向默认语言);
- 每个语言对应一个
hreflang备用链接,外加未匹配语言的x-default; - 用于社交分享预览的 Open Graph
og:locale、og:locale:alternate和og:url; - 包含
inLanguage的 JSON-LD,帮助搜索引擎和 AI 助手识别页面的语言归属。
单个辅助函数即可构建所有这些配置,使页面代码保持简洁:
src/i18n/seo.ts复制代码复制代码到剪贴板
如步骤 9 所示,在每个页面的
head()中调用它。对于首页,传入path: "/"。- 已翻译的
多语言站点地图配置(Sitemap)
可选多语言站点地图列出每种语言的每个 URL,每个条目都通过
xhtml:link声明所有备用语言。Google 会像处理页面中的hreflang标签一样处理这些注解,使其在页面较少被抓取时成为可靠的补充保障。TanStack Start 的服务端路由允许直接通过文件路由提供该文件:
src/routes/sitemap[.]xml.ts复制代码复制代码到剪贴板
多语言 robots.txt 配置
可选私有路由存在于每种语言中,因此
Disallow规则必须覆盖每个语言前缀。如果项目模板创建了public/robots.txt,请将其移除,然后通过路由提供服务:src/routes/robots[.]txt.ts复制代码复制代码到剪贴板
根据用户偏好重定向初次访问者
可选请求中间件(Request Middleware)会优先依据语言 Cookie、其次根据
Accept-Language请求头,将访问/的访客重定向到其偏好语言。仅对/路径进行重定向:深层链接绝不篡改,以确保分享的 URL 和搜索引擎爬虫始终能获取请求的目标页面。src/i18n/negotiateLocale.ts复制代码复制代码到剪贴板
src/start.ts复制代码复制代码到剪贴板
在切换器中显式选择英语的访客会在 Cookie 中保存
locale=en,因此后续绝不会再次被强制重定向。在全静态部署(步骤 18)中,/作为文件直接提供,该中间件不会执行,这完全没有问题:页面依然正常可访问,语言切换器会处理后续需求。保留 use-intl API,通过 Intlayer 削减运行时体积
可选基准测试表明,use-intl 配置中最重的是其运行时本身(约 76 KB gzip)。
@intlayer/use-intl兼容适配器提供了完全相同的 API(useTranslations、useFormatter、IntlProvider、createTranslator、ICU 复数、t.rich),但由预编译的 Intlayer 字典驱动:体积从约 75.9 KB 降至约 6.7 KB,0% 语言泄露,0% 页面泄露,且无需修改组件代码。bash复制代码复制代码到剪贴板
Vite 插件会将
use-intl别名重定向到该适配器,因此现有的导入无需更改即可正常工作:vite.config.ts复制代码复制代码到剪贴板
借助 sync JSON 插件,你的 JSON 文件仍可作为单一数据源:
intlayer.config.ts复制代码复制代码到剪贴板
该适配器也是平滑迁移的理想路径:一旦运行成功,你可以逐步将组件迁移到原生的
useIntlayerAPI。详情请参阅 Intlayer TanStack Start 指南。预渲染所有语言页面
可选静态 HTML 是响应最快且最易于被搜索引擎索引的形式。列出所有本地化路径,以便 TanStack Start 在构建时预渲染所有语言版本,以及站点地图和 robots 文件:
vite.config.ts复制代码复制代码到剪贴板
因为语言切换器渲染的是真实的链接,
crawlLinks: true还会自动发现并抓取你遗漏列出的页面。处理多语言 404 页面
可选步骤 7 的布局已经对未知的语言前缀抛出
notFound()。添加通配路由(catch-all route),使特定语言下的未知路径也能渲染本地化的 404 页面,并标记为noindex:React 19 会将<meta>标签自动提升到<head>中。src/components/NotFound.tsx复制代码复制代码到剪贴板
src/routes/{-$locale}/$.tsx复制代码复制代码到剪贴板
在服务端函数中获取语言环境
可选服务端函数(Server Functions)不接收路由参数。读取语言 Cookie,并在没有 Cookie 时回退到
Accept-Language请求头,以便发送本地化邮件或保存语言偏好:src/server/getServerLocale.ts复制代码复制代码到剪贴板
要在服务端函数内部进行翻译,可将其与
use-intl的loadMessages和createTranslator结合使用。使用 Intlayer 自动化翻译工作流
可选use-intl 负责渲染翻译,但无法帮助你生成翻译内容。Intlayer 是免费且开源的工具,即便你继续使用 use-intl,它也能填补这一空白:
- 测试缺失翻译:在 CI 或单元测试中进行检测。请参阅测试翻译内容。
- AI 自动翻译:使用你自己的 API Key 和提供商,
npx intlayer fill可结合应用上下文自动补全缺失的翻译键。请参阅自动填充与 CLI 文档。 - 保持 JSON 文件作为单一数据源:借助 sync JSON 插件。
- 可视化编辑内容:通过可视化编辑器和 CMS,让非开发人员也能直接更新翻译。
- 为 AI Agent 提供上下文支持:通过 MCP 服务器与 Agent Skills。
- 扫描已部署的站点:使用 scan 命令检测缺失的
hreflang、错误的 canonical 以及多语言泄露。
要了解所有功能,请参阅为什么选择 Intlayer。
常见问题解答
是的,如果你希望在 Next.js 之外使用 next-intl 的 API。它为你提供了 ICU 消息、格式化工具以及良好的 TypeScript 支持,并且避免了像 setRequestLocale 这样的 Next.js 专属限制。其主要权衡是体积:基准测试测得其运行时约为 76 KB gzip,且朴素的配置会将所有语言和页面的消息一次性发送到浏览器。按路由和按语言拆分加载命名空间(如本指南所示)可以避免代码泄露。
use-intl 是 next-intl 的核心。next-intl 在其之上添加了 Next.js 专属集成:中间件、导航辅助函数、用于 Server Components 的 getTranslations 以及请求配置。在 TanStack Start 上,你直接使用 use-intl,并通过 TanStack Router 实现路由,如上文所示。
建议在 URL 中使用语言前缀。这样每个语言版本都有自己独立的 URL,便于搜索引擎索引和用户分享。Cookie 仍然适合用于记录用户的显式选择,步骤 16 的重定向中间件正是这样实现的。
服务器和浏览器格式化日期时可能使用了不同的时区。在 IntlProvider 中传入固定的 timeZone(或保存在 Cookie 中的访客时区),这样两侧生成的文本就会完全一致。
首先,按命名空间拆分消息,并在每个路由中通过 import.meta.glob 按需加载对应语言,这可以消除语言和页面的代码泄露。其次,如果运行时体积很关键,可以切换到 @intlayer/use-intl 适配器:API 完全相同,在基准测试中体积由约 75.9 KB 降至约 6.7 KB。
在路由的 head() 函数内部使用 route loader 返回的消息调用 createTranslator,然后返回 title、description、canonical 和 hreflang 链接。步骤 13 提供了一个可复用的辅助函数。
评论
暂无评论。成为第一个分享您想法的人吧。
