Creation:2025-09-09Last update:2026-05-31

    使用Intlayer翻译您的Tanstack Start | 国际化(i18n)

    目录

    本指南演示如何在 Tanstack Start 项目中集成 Intlayer,实现无缝国际化,支持基于区域设置的路由、TypeScript 支持以及现代开发实践。

    为什么选择 Inlayer 而不是替代品?

    与“react-i18next”或“use-intl”或“paraglide”等主要解决方案相比,Intlayer是一个具有集成优化的解决方案,例如:

    完整的 TanStack Start 覆盖

    Intlayer 针对 TanStack Start 进行了全面优化,提供多语言路由cookie 管理站点地图生成动态内容加载以及扩展国际化 (i18n) 工作所需的所有功能。

    捆绑尺寸

    不要将大量 JSON 文件加载到页面中,而只需加载必要的内容。 Intlayer 有助于将捆绑包和页面大小减少多达 50%

    可维护性

    确定应用程序内容的范围有利于大型应用程序的维护。您可以复制或删除单个功能文件夹,而无需承担检查整个内容代码库的精神负担。此外,Intlayer 具有完全类型化 (fully typed),以确保您的内容的准确性。

    人工智能代理

    共置内容减少大型语言模型 (LLM) 所需的上下文。 Intlayer 还附带了一套工具,例如用于测试缺失翻译的 CLILSPMCPagent技能,使 AI 代理的开发者体验 (DX) 更加流畅。

    自动化

    使用您选择的法学硕士,通过自动化在 CI/CD 管道中进行翻译,而费用由您的 AI 提供商承担。 Intlayer 还提供了一个编译器来自动提取内容,以及一个网络平台来帮助在后台翻译

    表现

    将大量 JSON 文件连接到组件可能会导致性能和反应性问题。 Intlayer 可在构建时 (build time)优化您的内容加载。

    无需开发即可扩展

    Intlayer 不仅仅是一个 i18n 解决方案,还提供了一个自托管的可视化编辑器和一个完整的 CMS 来帮助您管理多语言内容实时,与译员、文案人员和其他团队成员无缝协作。内容可以本地和/或远程存储。


    在 Tanstack Start 应用中设置 Intlayer 的分步指南

    www.youtube.com

    在 GitHub 上查看应用程序模板

    第一步:创建项目

    首先,按照 TanStack Start 网站上的新建项目指南创建一个新的 TanStack Start 项目。

    第二步:安装 Intlayer 包

    使用您喜欢的包管理器安装所需的包:

    bash
    npm install intlayer react-intlayernpm install vite-intlayer --save-devnpx intlayer init
    • intlayer

      核心包,提供用于配置管理、翻译、内容声明,转译以及命令行工具的国际化工具。

    • react-intlayer
      将 Intlayer 集成到 React 应用中的包。它为 React 国际化提供上下文提供者和钩子。

    • vite-intlayer
      包含用于将 Intlayer 集成到Vite 打包器的 Vite 插件,以及用于检测用户首选语言、管理 Cookie 和处理 URL 重定向的中间件。

    第三步:项目配置

    创建一个配置文件来配置您的应用程序语言:

    intlayer.config.ts
    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 插件添加到您的配置中:

    vite.config.ts
    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 标签上设置 langdir 属性。

    src/routes/__root.tsx
    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>  );}
    如果您想在字符串属性中使用内容,比如 alttitlehrefaria-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 前缀并执行验证的布局。

    src/routes/{-$locale}/route.tsx
    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' 模式,您可以完全删除插槽。

    第七步:声明您的内容

    创建并管理您的内容声明以存储翻译:

    src/contents/page.content.ts
    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,md,mdx,yaml,yml})。
    更多详情,请参阅内容声明文档

    第八步:创建支持多语言的组件和钩子

    创建一个用于多语言导航的 LocalizedLink 组件:

    src/components/localized-link.tsx
    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 钩子:

    src/hooks/useLocalizedNavigate.tsx
    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

    在整个应用程序中访问您的内容字典:

    本地化首页

    src/routes/{-$locale}/index.tsx
    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 钩子的内容,请参阅文档

    第十步:创建语言切换组件

    创建一个组件,允许用户切换语言:

    src/components/locale-switcher.tsx
    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 标签的 langdir 属性。这确保在服务器和客户端上正确设置属性。

    src/routes/__root.tsx
    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
    vite.config.ts
    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 钩子在整个应用程序中访问您的内容字典:

    src/routes/{-$locale}/index.tsx
    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 的示例:

    src/routes/{-$locale}/index.tsx
    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 页面需要采用多层方法:

    1. 专用 404 路由:用于显示 404 UI 的特定路由
    2. 路由级验证:验证区域设置前缀并将无效的前缀重定向到 404
    3. 捕获所有路由:捕获区域设置段内任何不匹配的路径
    src/routes/{-$locale}/404.tsx
    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>  );}
    src/routes/{-$locale}/route.tsx
    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,});
    src/routes/{-$locale}/$.tsx
    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 部分:

    intlayer.config.ts
    import { type IntlayerConfig } from "intlayer";const config: IntlayerConfig = {  // ... 您的其他配置  compiler: {    /**     * 指示是否应启用编译器。     */    enabled: true,    /**     * 定义输出文件路径     */    output: ({ fileName, extension }) => `./${fileName}${extension}`,    /**     * 指示在转换后是否应保存组件。这样,编译器只需运行一次即可转换应用程序,然后即可将其删除。     */    saveComponents: false,    /**     * 字典键前缀     */    dictionaryKeyPrefix: "",  },};export default config;

    运行提取器以转换组件并提取内容

    bash
    npx intlayer extract

    第十六步:生成站点地图 (Sitemap)(可选)

    Intlayer 附带一个内置的站点地图生成器,可帮助您轻松为应用程序创建站点地图。它能够处理本地化路由,并为搜索引擎添加必要的元数据。

    Intlayer 生成的站点地图支持 xhtml:link 命名空间(Hreflang XML 扩展)。与仅列出原始 URL 的默认站点地图生成器不同,Intlayer 会自动在页面的所有语言版本(例如 /about/about?lang=fr/about?lang=es)之间创建所需的双向链接。这确保了搜索引擎能够正确索引并向合适的受众提供正确的语言版本。

    要使用它,您首先需要配置 vite.config.ts 文件,以启用本地化路由的预渲染,并禁用默认的 TanStack Start 站点地图生成。

    vite.config.ts
    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

    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 配置中。

    tsconfig.json
    {  // ... 现有配置  include: [    // ... 现有包含    ".intlayer/**/*.ts", // 包含自动生成的类型  ],}

    Git 配置

    建议忽略 Intlayer 生成的文件。这样可以避免将它们提交到您的 Git 仓库中。

    要做到这一点,您可以将以下指令添加到您的 .gitignore 文件中:

    .gitignore
    # 忽略 Intlayer 生成的文件.intlayer

    VS Code 扩展

    为了提升您使用 Intlayer 的开发体验,您可以安装官方的 Intlayer VS Code 扩展

    从 VS Code 市场安装

    该扩展提供:

    • 翻译键的自动补全
    • 缺失翻译的实时错误检测
    • 翻译内容的内联预览
    • 轻松创建和更新翻译的快速操作

    有关如何使用该扩展的更多详细信息,请参阅Intlayer VS Code 扩展文档


    深入探索

    要进一步使用,您可以实现可视化编辑器或使用内容管理系统(CMS)将内容外部化。


    文档参考