使用您最喜欢的AI助手总结文档,并引用此页面和AI提供商
版本历史
- "更新 Solid useIntlayer API 用法以直接访问属性"v8.9.02026/5/4
- "添加 init 命令"v7.5.92025/12/30
- "引入 validatePrefix 并添加步骤 14: 处理带有本地化路由的 404 页面。"v7.4.02025/12/11
- "添加步骤 13: 在您的 server actions 中获取 locale (可选)"v7.3.92025/12/5
- "添加步骤 13: 适配 Nitro"v7.2.32025/11/18
- "通过添加 getPrefix 函数修复 useLocalizedNavigate、LocaleSwitcher 和 LocalizedLink 的前缀默认值。"v7.1.02025/11/17
- "更新文档"v6.5.22025/10/3
- "为 Tanstack Start 添加支持"v5.8.12025/9/9
此页面的内容已使用 AI 翻译。
查看英文原文的最新版本如果您有改善此文档的想法,请随时通过在GitHub上提交拉取请求来贡献。
文档的 GitHub 链接复制文档 Markdown 到剪贴板
使用Intlayer翻译您的Tanstack Start | 国际化(i18n)
目录
本指南演示如何在 Tanstack Start 项目中集成 Intlayer,实现无缝国际化,支持基于区域设置的路由、TypeScript 支持以及现代开发实践。
什么是 Intlayer?
Intlayer 是一个创新的开源国际化(i18n)库,旨在简化现代 Web 应用中的多语言支持。
使用 Intlayer,您可以:
- 通过组件级声明式字典轻松管理翻译。
- 动态本地化元数据、路由和内容。
- 通过自动生成的类型确保 TypeScript 支持,提升自动补全和错误检测能力。
- 享受高级功能,如动态区域设置检测和切换。
- 通过 Tanstack Start 的基于文件的路由系统启用区域设置感知路由。
在 Tanstack Start 应用中设置 Intlayer 的分步指南
在 GitHub 上查看应用程序模板。
第一步:创建项目
首先,按照 TanStack Start 网站上的新建项目指南创建一个新的 TanStack Start 项目。
第二步:安装 Intlayer 包
使用您喜欢的包管理器安装所需的包:
复制代码到剪贴板
npm install intlayer react-intlayernpm install vite-intlayer --save-devnpx intlayer initintlayer
react-intlayer
将 Intlayer 集成到 React 应用中的包。它为 React 国际化提供上下文提供者和钩子。vite-intlayer
包含用于将 Intlayer 集成到Vite 打包器的 Vite 插件,以及用于检测用户首选语言、管理 Cookie 和处理 URL 重定向的中间件。
第三步:项目配置
创建一个配置文件来配置您的应用程序语言:
复制代码到剪贴板
import type { IntlayerConfig } from "intlayer";import { Locales } from "intlayer";const config: IntlayerConfig = { internationalization: { defaultLocale: Locales.ENGLISH, locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH], },};export default config;通过此配置文件,您可以设置本地化 URL、中间件重定向、cookie 名称、内容声明的位置和扩展名、禁用控制台中的 Intlayer 日志等。有关可用参数的完整列表,请参阅配置文档。
第四步:在您的 Vite 配置中集成 Intlayer
将 intlayer 插件添加到您的配置中:
复制代码到剪贴板
import { tanstackStart } from "@tanstack/react-start/plugin/vite";import viteReact from "@vitejs/plugin-react";import { nitro } from "nitro/vite";import { defineConfig } from "vite";import { intlayer } from "vite-intlayer";const config = defineConfig({ plugins: [ nitro(), intlayer(), tanstackStart({ router: { routeFileIgnorePattern: ".content.(ts|tsx|js|mjs|cjs|jsx|json|jsonc|json5)$", }, }), viteReact(), ],});export default config;intlayer() Vite 插件用于将 Intlayer 集成到 Vite 中。它确保构建内容声明文件并在开发模式下监视它们。它在 Vite 应用中定义了 Intlayer 环境变量。此外,它还提供别名以优化性能。
第五步:创建根布局
配置您的根布局以支持国际化,使用 useParams 检测当前 locale 并在 html 标签上设置 lang 和 dir 属性。
复制代码到剪贴板
import { createRootRouteWithContext, HeadContent, Scripts,} from "@tanstack/react-router";import { defaultLocale, getHTMLTextDir } from "intlayer";import { type ReactNode } from "react";import { IntlayerProvider } from "react-intlayer";import { Route as LocaleRoute } from "./{-$locale}/route";export const Route = createRootRouteWithContext<{}>()({ head: () => ({ meta: [ { charSet: "utf-8", }, { content: "width=device-width, initial-scale=1", name: "viewport", }, { title: "TanStack Start Starter", }, ], }), shellComponent: RootDocument,});function RootDocument({ children }: { children: ReactNode }) { const params = LocaleRoute.useParams(); const locale = params?.locale ?? defaultLocale; return ( <html dir={getHTMLTextDir(locale)} lang={locale}> <head> <HeadContent /> </head> <body> <IntlayerProvider locale={locale}>{children}</IntlayerProvider> <Scripts /> </body> </html> );}如果您想在字符串属性中使用内容,比如alt、title、href、aria-label等,可以使用函数的值,例如:
html复制代码复制代码到剪贴板
<img src="{content.image.src.value}" alt="{content.image.value}" /><img src="{content.image.src.toString()}" alt="{content.image.toString()}" /><img src="{String(content.image.src)}" alt="{String(content.image)}" />
第六步:创建 Locale 布局
创建一个处理 locale 前缀并执行验证的布局。
复制代码到剪贴板
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";import { validatePrefix } from "intlayer";export const Route = createFileRoute("/{-$locale}")({ beforeLoad: ({ params }) => { const localeParam = params.locale; // 验证 locale 前缀 const { isValid, localePrefix } = validatePrefix(localeParam); if (!isValid) { throw redirect({ to: "/{-$locale}/404", params: { locale: localePrefix }, }); } }, component: Outlet,});这里,{-$locale}是一个动态路由参数,会被当前 locale 替换。此表示法使插槽可选,允许它与'prefix-no-default'等路由模式一起工作。
请注意,如果您在同一路由中使用多个动态段(例如,
/{-$locale}/other-path/$anotherDynamicPath/...),此插槽可能会导致问题。 对于'prefix-all'模式,您可能更喜欢将插槽切换为$locale。 对于'no-prefix'或'search-params'模式,您可以完全删除插槽。
第七步:声明您的内容
创建并管理您的内容声明以存储翻译:
复制代码到剪贴板
import type { Dictionary } from "intlayer";import { t } from "intlayer";const appContent = { content: { links: { about: t({ zh: "关于", en: "About", es: "Acerca de", fr: "À propos", }), home: t({ zh: "首页", en: "Home", es: "Inicio", fr: "Accueil", }), }, meta: { title: t({ zh: "欢迎使用 Intlayer + TanStack Router", en: "Welcome to Intlayer + TanStack Router", es: "Bienvenido a Intlayer + TanStack Router", fr: "Bienvenue à Intlayer + TanStack Router", }), description: t({ zh: "这是一个使用 Intlayer 和 TanStack Router 的示例", en: "This is an example of using Intlayer with TanStack Router", es: "Este es un ejemplo de uso de Intlayer con TanStack Router", fr: "Ceci est un exemple d'utilisation d'Intlayer avec TanStack Router", }), }, }, key: "app",} satisfies Dictionary;export default appContent;您的内容声明可以在应用程序中的任何位置定义,只要它们被包含在contentDir目录中(默认是./app)。并且文件扩展名需匹配内容声明文件扩展名(默认是.content.{json,ts,tsx,js,jsx,mjs,cjs})。
更多详情,请参阅内容声明文档。
第八步:创建支持多语言的组件和钩子
创建一个用于多语言导航的 LocalizedLink 组件:
复制代码到剪贴板
import type { FC } from "react";import { Link, type LinkComponentProps } from "@tanstack/react-router";import { useLocale } from "react-intlayer";import { getPrefix } from "intlayer";export const LOCALE_ROUTE = "{-$locale}" as const;export type To = StripLocalePrefix<LinkComponentProps["to"]>;export type StripLocalePrefix<T extends string | undefined> = T extends | `/${typeof LOCALE_ROUTE}/` | `/${typeof LOCALE_ROUTE}` ? "/" : T extends `/${typeof LOCALE_ROUTE}/${infer Rest}` ? `/${Rest}` : T;type LocalizedLinkProps = { to?: To;} & Omit<LinkComponentProps, "to">;export const LocalizedLink: FC<LocalizedLinkProps> = (props) => { const { locale } = useLocale(); const { localePrefix } = getPrefix(locale); return ( <Link {...props} params={{ locale: localePrefix, ...(typeof props?.params === "object" ? props?.params : {}), }} to={`/${LOCALE_ROUTE}${props.to}` as LinkComponentProps["to"]} /> );};该组件有两个目标:
- 移除 URL 中不必要的
{-$locale}前缀。 - 将 locale 参数注入 URL,确保用户被直接重定向到本地化路由。
接下来我们可以创建一个用于编程导航的 useLocalizedNavigate 钩子:
复制代码到剪贴板
import { useNavigate } from "@tanstack/react-router";import { getPrefix } from "intlayer";import { useLocale } from "react-intlayer";import type { StripLocalePrefix } from "@/components/localized-link";import type { FileRouteTypes } from "@/routeTree.gen";type NavigateFn = ReturnType<typeof useNavigate>;type BaseNavigateOptions = Parameters<NavigateFn>[0];type LocalizedTo = StripLocalePrefix<FileRouteTypes["to"]>;export type LocalizedNavigateOptions = Omit< BaseNavigateOptions, "to" | "params"> & { to: LocalizedTo; params?: Omit<NonNullable<BaseNavigateOptions["params"]>, "locale">;};type LocalizedNavigate = ( options: LocalizedNavigateOptions) => ReturnType<NavigateFn>;export const useLocalizedNavigate = () => { const navigate = useNavigate(); const { locale } = useLocale(); const localizedNavigate: LocalizedNavigate = (args: any) => { const { localePrefix } = getPrefix(locale); if (typeof args === "string") { return navigate({ to: `/${LOCALE_ROUTE}${args}`, params: { locale: localePrefix }, }); } const { to, ...rest } = args; const localizedTo = `/${LOCALE_ROUTE}${to}` as any; return navigate({ to: localizedTo, params: { locale: localePrefix, ...rest } as any, }); }; return localizedNavigate;};第九步:在您的页面中使用 Intlayer
在整个应用程序中访问您的内容字典:
本地化首页
复制代码到剪贴板
import { createFileRoute } from "@tanstack/react-router";import { getIntlayer } from "intlayer";import { useIntlayer } from "react-intlayer";import LocaleSwitcher from "@/components/locale-switcher";import { LocalizedLink } from "@/components/localized-link";import { useLocalizedNavigate } from "@/hooks/useLocalizedNavigate";export const Route = createFileRoute("/{-$locale}/")({ component: RouteComponent,});function RouteComponent() { const content = useIntlayer("app"); const navigate = useLocalizedNavigate(); return ( <div> <div> {content.title} <LocaleSwitcher /> <div> <LocalizedLink to="/">{content.links.home}</LocalizedLink> <LocalizedLink to="/about">{content.links.about}</LocalizedLink> </div> <div> <button onClick={() => navigate({ to: "/" })}> {content.links.home} </button> <button onClick={() => navigate({ to: "/about" })}> {content.links.about} </button> </div> </div> </div> );}要了解更多关于 useIntlayer 钩子的内容,请参阅文档。
第十步:创建语言切换组件
创建一个组件,允许用户切换语言:
复制代码到剪贴板
import { useLocation } from "@tanstack/react-router";import { getHTMLTextDir, getLocaleName, getPathWithoutLocale, getPrefix, Locales,} from "intlayer";import type { FC } from "react";import { useLocale } from "react-intlayer";import { LocalizedLink, type To } from "./localized-link";export const LocaleSwitcher: FC = () => { const { pathname } = useLocation(); const { availableLocales, locale, setLocale } = useLocale(); const pathWithoutLocale = getPathWithoutLocale(pathname); return ( <ol> {availableLocales.map((localeEl) => ( <li key={localeEl}> <LocalizedLink aria-current={localeEl === locale ? "page" : undefined} onClick={() => setLocale(localeEl)} params={{ locale: getPrefix(localeEl).localePrefix }} to={pathWithoutLocale as To} > <span> {/* 语言环境 - 例如 FR */} {localeEl} </span> <span> {/* 语言在其自身语言环境中的名称 - 例如 Français */} {getLocaleName(localeEl, locale)} </span> <span dir={getHTMLTextDir(localeEl)} lang={localeEl}> {/* 语言在当前语言环境中的名称 - 例如当前语言环境为 Locales.SPANISH 时显示 Francés */} {getLocaleName(localeEl)} </span> <span dir="ltr" lang={Locales.ENGLISH}> {/* 语言的英文名称 - 例如 French */} {getLocaleName(localeEl, Locales.ENGLISH)} </span> </LocalizedLink> </li> ))} </ol> );};要了解有关 useLocale 钩子的更多信息,请参阅文档。
第十一步:HTML 属性管理
如第5步所示,您可以在根组件中使用 useParams 管理 html 标签的 lang 和 dir 属性。这确保在服务器和客户端上正确设置属性。
复制代码到剪贴板
function RootDocument({ children }: { children: ReactNode }) { const params = LocaleRoute.useParams(); const locale = params?.locale ?? defaultLocale; return ( <html dir={getHTMLTextDir(locale)} lang={locale}> {/* ... */} </html> );}第十二步:添加中间件(可选)
您还可以使用 intlayerProxy 为您的应用程序添加服务器端路由。该插件将根据 URL 自动检测当前语言环境,并设置相应的语言环境 Cookie。如果未指定语言环境,插件将根据用户浏览器的语言偏好确定最合适的语言环境。如果未检测到语言环境,它将重定向到默认语言环境。
注意,要在生产环境中使用intlayerProxy,您需要将vite-intlayer包从devDependencies切换到dependencies。
复制代码到剪贴板
import { tanstackStart } from "@tanstack/react-start/plugin/vite";import viteReact from "@vitejs/plugin-react";import { nitro } from "nitro/vite";import { defineConfig } from "vite";import { intlayer, intlayerProxy } from "vite-intlayer";export default defineConfig({ plugins: [ intlayerProxy(), // 如果使用 Nitro,代理应放在服务器之前 nitro(), intlayer(), tanstackStart({ router: { routeFileIgnorePattern: ".content.(ts|tsx|js|mjs|cjs|jsx|json|jsonc|json5)$", }, }), viteReact(), ],});第十二步:国际化您的元数据(可选)
您还可以使用 getIntlayer 钩子在整个应用程序中访问您的内容字典:
复制代码到剪贴板
import { createFileRoute } from "@tanstack/react-router";import { getIntlayer } from "intlayer";export const Route = createFileRoute("/{-$locale}/")({ component: RouteComponent, head: ({ params }) => { const { locale } = params; const path = "/"; // The path for this route const metaContent = getIntlayer("app", locale); return { links: [ // Canonical link: Points to the current localized page { rel: "canonical", href: getLocalizedUrl(path, locale) }, // Hreflang: Tell Google about all localized versions ...localeMap(({ locale: mapLocale }) => ({ rel: "alternate", hrefLang: mapLocale, href: getLocalizedUrl(path, mapLocale), })), // x-default: For users in unmatched languages // Define the default fallback locale (usually your primary language) { rel: "alternate", hrefLang: "x-default", href: getLocalizedUrl(path, defaultLocale), }, ], meta: [ { title: metaContent.title }, { name: "description", content: metaContent.meta.description }, ], }; },});第十三步:在您的 server actions 中获取 locale(可选)
您可能希望从 server actions 或 API 端点内部访问当前 locale。
您可以使用 intlayer 中的 getLocale 辅助函数来实现这一点。
以下是使用 TanStack Start 的 server functions 的示例:
复制代码到剪贴板
import { createServerFn } from "@tanstack/react-start";import { getRequestHeader, getRequestHeaders,} from "@tanstack/react-start/server";import { getCookie, getIntlayer, getLocale } from "intlayer";export const getLocaleServer = createServerFn().handler(async () => { const locale = await getLocale({ // 从请求中获取 cookie(默认:'INTLAYER_LOCALE') getCookie: (name) => { const cookieString = getRequestHeader("cookie"); return getCookie(name, cookieString); }, // 从请求中获取 header(默认:'x-intlayer-locale') // 使用 Accept-Language 协商作为后备 getHeader: (name) => getRequestHeader(name), }); // 使用 getIntlayer() 检索一些内容 const content = getIntlayer("app", locale); return { locale, content };});第十四步:管理未找到的页面(可选)
当用户访问不存在的页面时,您可以显示自定义的未找到页面,并且区域设置前缀可能会影响未找到页面的触发方式。
了解 TanStack Router 使用区域设置前缀的 404 处理
在 TanStack Router 中,使用本地化路由处理 404 页面需要采用多层方法:
- 专用 404 路由:用于显示 404 UI 的特定路由
- 路由级验证:验证区域设置前缀并将无效的前缀重定向到 404
- 捕获所有路由:捕获区域设置段内任何不匹配的路径
复制代码到剪贴板
import { createFileRoute } from "@tanstack/react-router";// 这将创建一个专用的 /[locale]/404 路由// 它既作为直接路由使用,也可以在其他文件中作为组件导入export const Route = createFileRoute("/{-$locale}/404")({ component: NotFoundComponent,});// 单独导出,以便可以在 notFoundComponent 和 catch-all 路由中重用export function NotFoundComponent() { return ( <div> <h1>404</h1> </div> );}复制代码到剪贴板
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";import { validatePrefix } from "intlayer";import { NotFoundComponent } from "./404";export const Route = createFileRoute("/{-$locale}")({ // beforeLoad 在路由渲染之前运行(在服务器和客户端上) // 这是验证 locale 前缀的理想位置 beforeLoad: ({ params }) => { const localeParam = params.locale; // validatePrefix 检查 locale 是否根据您的 intlayer 配置有效 const { isValid, localePrefix } = validatePrefix(localeParam); if (!isValid) { // 无效的 locale 前缀 - 重定向到具有有效 locale 前缀的 404 页面 throw redirect({ to: "/{-$locale}/404", params: { locale: localePrefix }, }); } }, component: Outlet, // notFoundComponent 在子路由不存在时被调用 // 例如,/en/不存在的页面 在 /en 布局内触发此操作 notFoundComponent: NotFoundComponent,});复制代码到剪贴板
import { createFileRoute } from "@tanstack/react-router";import { NotFoundComponent } from "./404";// $ (splat/catch-all) 路由匹配任何与其他路由不匹配的路径// 例如,/en/某个/深度/嵌套/无效/路径// 这确保区域设置内所有不匹配的路径都显示 404 页面// 没有这个,不匹配的深层路径可能会显示空白页面或错误export const Route = createFileRoute("/{-$locale}/$")({ component: NotFoundComponent,});第十五步:提取组件中的内容(可选)
如果您有现有的代码库,转换数千个文件可能会非常耗时。
为了简化此过程,Intlayer 提供了 编译器 / 提取器 来转换您的组件并提取内容。
要进行设置,您可以在 intlayer.config.ts 文件中添加 compiler 部分:
复制代码到剪贴板
import { type IntlayerConfig } from "intlayer";const config: IntlayerConfig = { // ... 您的其他配置 compiler: { /** * 指示是否应启用编译器。 */ enabled: true, /** * 定义输出文件路径 */ output: ({ fileName, extension }) => `./${fileName}${extension}`, /** * 指示在转换后是否应保存组件。这样,编译器只需运行一次即可转换应用程序,然后即可将其删除。 */ saveComponents: false, /** * 字典键前缀 */ dictionaryKeyPrefix: "", },};export default config;运行提取器以转换组件并提取内容
复制代码到剪贴板
npx intlayer extract第十六步:生成站点地图 (Sitemap)(可选)
Intlayer 附带一个内置的站点地图生成器,可帮助您轻松为应用程序创建站点地图。它能够处理本地化路由,并为搜索引擎添加必要的元数据。
Intlayer 生成的站点地图支持xhtml:link命名空间(Hreflang XML 扩展)。与仅列出原始 URL 的默认站点地图生成器不同,Intlayer 会自动在页面的所有语言版本(例如/about、/about?lang=fr和/about?lang=es)之间创建所需的双向链接。这确保了搜索引擎能够正确索引并向合适的受众提供正确的语言版本。
要使用它,您首先需要配置 vite.config.ts 文件,以启用本地化路由的预渲染,并禁用默认的 TanStack Start 站点地图生成。
复制代码到剪贴板
import { localeFlatMap } from "intlayer";// ... 其他导入export const pathList = ["", "/about", "/404"];const localizedPages = localeFlatMap(({ urlPrefix }) => pathList.map((path) => ({ path: `${urlPrefix}${path}`, prerender: { enabled: true, }, })));export default defineConfig({ plugins: [ // ... 其他插件 tanstackStart({ // ... 其他配置 sitemap: { enabled: false, }, prerender: { enabled: true, crawlLinks: false, concurrency: 10, }, pages: localizedPages, }), ],});然后,创建一个使用 generateSitemap 函数的路由 src/routes/sitemap[.]xml.ts:
复制代码到剪贴板
import { createFileRoute } from "@tanstack/react-router";import { generateSitemap } from "intlayer";const SITE_URL = ( import.meta.env.VITE_SITE_URL ?? "http://localhost:3000").replace(/\/$/, "");export const Route = createFileRoute("/sitemap.xml")({ server: { handlers: { GET: async () => { const sitemap = generateSitemap( [ { path: "/", changefreq: "daily", priority: 1.0 }, { path: "/about", changefreq: "monthly", priority: 0.8 }, ], { siteUrl: SITE_URL } ); return new Response(sitemap, { headers: { "Content-Type": "application/xml" }, }); }, }, },});第十七步:TypeScript 配置 (可选)
Intlayer 通过模块扩充来利用 TypeScript 的优势,增强您的代码库。
确保自动生成的类型已包含在您的 TypeScript 配置中。
复制代码到剪贴板
{ // ... 现有配置 include: [ // ... 现有包含 ".intlayer/**/*.ts", // 包含自动生成的类型 ],}Git 配置
建议忽略 Intlayer 生成的文件。这样可以避免将它们提交到您的 Git 仓库中。
要做到这一点,您可以将以下指令添加到您的 .gitignore 文件中:
复制代码到剪贴板
# 忽略 Intlayer 生成的文件.intlayerVS Code 扩展
为了提升您使用 Intlayer 的开发体验,您可以安装官方的 Intlayer VS Code 扩展。
该扩展提供:
- 翻译键的自动补全。
- 缺失翻译的实时错误检测。
- 翻译内容的内联预览。
- 轻松创建和更新翻译的快速操作。
有关如何使用该扩展的更多详细信息,请参阅Intlayer VS Code 扩展文档。
深入探索
要进一步使用,您可以实现可视化编辑器或使用内容管理系统(CMS)将内容外部化。