接收有关即将发布的Intlayer的通知
    Creation:2025-09-09Last update:2025-09-09

    使用 Intlayer 和 Tanstack Start 开始国际化 (i18n)

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

    什么是 Intlayer?

    Intlayer 是一个创新的开源国际化(i18n)库,旨在简化现代 Web 应用中的多语言支持。

    使用 Intlayer,您可以:

    • 通过组件级声明式字典轻松管理翻译
    • 动态本地化元数据、路由和内容
    • 通过自动生成的类型确保 TypeScript 支持,提升自动补全和错误检测能力。
    • 享受高级功能,如动态区域设置检测和切换。
    • 通过 Tanstack Start 的基于文件的路由系统启用区域设置感知路由

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

    第一步:创建项目

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

    第二步:安装 Intlayer 包

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

    npm install intlayer react-intlayernpm install vite-intlayer --save-dev
    • intlayer

    • 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 日志等。有关可用参数的完整列表,请参阅配置文档

    第4步:在您的 Vite 配置中集成 Intlayer

    将 intlayer 插件添加到您的配置中:

    vite.config.ts
    import { reactRouter } from "@react-router/dev/vite";import { defineConfig } from "vite";import { intlayer } from "vite-intlayer";import tsconfigPaths from "vite-tsconfig-paths";export default defineConfig({  plugins: [reactRouter(), tsconfigPaths(), intlayer()],});

    intlayer() Vite 插件用于将 Intlayer 集成到 Vite 中。它确保构建内容声明文件并在开发模式下监视它们。它在 Vite 应用中定义了 Intlayer 环境变量。此外,它还提供别名以优化性能。

    第5步:创建布局组件

    设置您的根布局和特定语言环境的布局:

    根布局

    src/routes/{-$locale}/route.tsx
    import { createFileRoute, Navigate, Outlet } from "@tanstack/react-router";import { IntlayerProvider, useLocale } from "react-intlayer";import { useI18nHTMLAttributes } from "@/hooks/useI18nHTMLAttributes";export const Route = createFileRoute("/{-$locale}")({  component: LayoutComponent,});function LayoutComponent() {  const { defaultLocale } = useLocale();  const { locale } = Route.useParams();  return (    <IntlayerProvider locale={defaultLocale}>      <Outlet />    </IntlayerProvider>  );}

    第6步:声明您的内容

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

    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: {      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",      }),    },    title: t({      zh: "欢迎使用 Intlayer + TanStack Router",      en: "Welcome to Intlayer + TanStack Router",      es: "Bienvenido a Intlayer + TanStack Router",      fr: "Bienvenue à Intlayer + TanStack Router",    }),  },  key: "app",} satisfies Dictionary;export default appContent;

    您的内容声明可以在应用程序中的任何位置定义,只要它们被包含在 contentDir 目录中(默认是 ./app)。并且文件扩展名需匹配内容声明文件扩展名(默认是 .content.{json,ts,tsx,js,jsx,mjs,mjx,cjs,cjx})。

    更多详情,请参阅内容声明文档

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

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

    src/components/localized-link.tsx
    import type { FC } from "react";import { Link, type LinkComponentProps } from "@tanstack/react-router";import { useLocale } from "intlayer";export const LOCALE_ROUTE = "{-$locale}" as const;// 主要工具类型export type RemoveLocaleParam<T> = T extends string  ? RemoveLocaleFromString<T>  : T;export type To = RemoveLocaleParam<LinkComponentProps["to"]>;type CollapseDoubleSlashes<S extends string> =  S extends `${infer H}//${infer T}` ? CollapseDoubleSlashes<`${H}/${T}`> : S;type LocalizedLinkProps = {  to?: To;} & Omit<LinkComponentProps, "to">;// 辅助类型type RemoveAll<  S extends string,  Sub extends string,> = S extends `${infer H}${Sub}${infer T}` ? RemoveAll<`${H}${T}`, Sub> : S;type RemoveLocaleFromString<S extends string> = CollapseDoubleSlashes<  RemoveAll<S, typeof LOCALE_ROUTE>>;export const LocalizedLink: FC<LocalizedLinkProps> = (props) => {  const { locale } = useLocale();  return (    <Link      {...props}      params={{        locale,        ...(typeof props?.params === "object" ? props?.params : {}),      }}      to={`/${LOCALE_ROUTE}${props.to}` as LinkComponentProps["to"]}    />  );};

    该组件有两个目标:

    • 移除 URL 中不必要的 {-$locale} 前缀。
    • 将 locale 参数注入 URL,确保用户被直接重定向到本地化路由。

    接下来我们可以创建一个用于编程导航的 useLocalizedNavigate 钩子:

    src/hooks/useLocalizedNavigate.tsx
    import { useLocale } from "react-intlayer";import { useNavigate } from "@tanstack/react-router";import { LOCALE_ROUTE } from "@/components/localized-link";import type { FileRouteTypes } from "@/routeTree.gen";export const useLocalizedNavigate = () => {  const navigate = useNavigate();  const { locale } = useLocale();  type StripLocalePrefix<T extends string> = T extends    | `/${typeof LOCALE_ROUTE}`    | `/${typeof LOCALE_ROUTE}/`    ? "/" // 去除本地化前缀后返回根路径    : T extends `/${typeof LOCALE_ROUTE}/${infer Rest}`      ? `/${Rest}` // 去除本地化前缀后返回剩余路径      : never;  type LocalizedTo = StripLocalePrefix<FileRouteTypes["to"]>;  interface LocalizedNavigate {    (to: LocalizedTo): ReturnType<typeof navigate>;    (      opts: { to: LocalizedTo } & Record<string, unknown>    ): ReturnType<typeof navigate>;  }  const localizedNavigate: LocalizedNavigate = (args: any) => {    if (typeof args === "string") {      return navigate({ to: `/${LOCALE_ROUTE}${args}`, params: { locale } });    }    const { to, ...rest } = args;    const localedTo = `/${LOCALE_ROUTE}${to}` as any;    return navigate({ to: localedTo, params: { locale, ...rest } as any });  };  return localizedNavigate;};

    第8步:在您的页面中使用 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,  head: ({ params }) => {    const { locale } = params;    const metaContent = getIntlayer("app", locale);    return {      meta: [        { title: metaContent.title },        { content: metaContent.meta.description, name: "description" },      ],    };  },});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 钩子的内容,请参阅文档

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

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

    src/components/locale-switcher.tsx
    import type { FC } from "react";import { useLocation } from "@tanstack/react-router";import { getHTMLTextDir, getLocaleName, getPathWithoutLocale } from "intlayer";import { setLocaleCookie, useIntlayer, useLocale } from "react-intlayer";import { LocalizedLink, To } from "./localized-link";export const LocaleSwitcher: FC = () => {  const { localeSwitcherLabel } = useIntlayer("locale-switcher"); // 使用 useIntlayer 钩子获取语言切换器标签  const { pathname } = useLocation(); // 获取当前路径名  const { availableLocales, locale } = useLocale(); // 获取可用语言列表和当前语言  const pathWithoutLocale = getPathWithoutLocale(pathname); // 获取不带语言前缀的路径  return (    <ol>      {availableLocales.map((localeEl) => (        <li key={localeEl}>          <LocalizedLink            aria-current={localeEl === locale ? "page" : undefined} // 当前语言时设置 aria-current 为 page            aria-label={`${localeSwitcherLabel.value} ${getLocaleName(localeEl)}`} // 设置无障碍标签,显示语言切换器标签和语言名称            onClick={() => setLocaleCookie(localeEl)} // 点击时设置语言 cookie            params={{ locale: localeEl }} // 传递语言参数            to={pathWithoutLocale as To} // 跳转到不带语言前缀的路径          >            <span>              {/* 语言环境 - 例如 FR */}              {localeItem}            </span>            <span>              {/* 语言在其自身语言环境中的名称 - 例如 Français */}              {getLocaleName(localeItem, locale)}            </span>            <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>              {/* 语言在当前语言环境中的名称 - 例如当前语言环境为 Locales.SPANISH 时显示 Francés */}              {getLocaleName(localeItem)}            </span>            <span dir="ltr" lang={Locales.ENGLISH}>              {/* 语言的英文名称 - 例如 French */}              {getLocaleName(localeItem, Locales.ENGLISH)}            </span>          </LocalizedLink>        </li>      ))}    </ol>  );};

    要了解有关 useLocale 钩子的更多信息,请参阅文档

    第10步:添加 HTML 属性管理(可选)

    创建一个钩子来管理 HTML 的 lang 和 dir 属性:

    src/hooks/useI18nHTMLAttributes.tsx
    // src/hooks/useI18nHTMLAttributes.tsximport { getHTMLTextDir } from "intlayer";import { useEffect } from "react";import { useLocale } from "react-intlayer";export const useI18nHTMLAttributes = () => {  const { locale } = useLocale();  useEffect(() => {    document.documentElement.lang = locale;    document.documentElement.dir = getHTMLTextDir(locale);  }, [locale]);};

    然后在你的根组件中使用它:

    src/routes/{-$locale}/index.tsx
    import { createFileRoute, Navigate, Outlet } from "@tanstack/react-router";import { IntlayerProvider, useLocale } from "react-intlayer";import { useI18nHTMLAttributes } from "@/hooks/useI18nHTMLAttributes"; // 导入钩子export const Route = createFileRoute("/{-$locale}")({  component: LayoutComponent,});function LayoutComponent() {  useI18nHTMLAttributes(); // 添加此行  const { defaultLocale } = useLocale();  const { locale } = Route.useParams();  return (    <IntlayerProvider locale={locale ?? defaultLocale}>      <Outlet />    </IntlayerProvider>  );}

    第11步:添加中间件(可选)

    您还可以使用 intlayerMiddleware 为您的应用程序添加服务器端路由。该插件将根据 URL 自动检测当前语言环境,并设置相应的语言环境 Cookie。如果未指定语言环境,插件将根据用户浏览器的语言偏好确定最合适的语言环境。如果未检测到语言环境,它将重定向到默认语言环境。

    注意,要在生产环境中使用 intlayerMiddleware,您需要将 vite-intlayer 包从 devDependencies 切换到 dependencies

    vite.config.ts
    import { reactRouter } from "@react-router/dev/vite";import tailwindcss from "@tailwindcss/vite";import { defineConfig } from "vite";import { intlayer, intlayerMiddleware } from "vite-intlayer";import tsconfigPaths from "vite-tsconfig-paths";export default defineConfig({  plugins: [    tailwindcss(),    reactRouter(),    tsconfigPaths(),    intlayer(),    intlayerMiddleware(),  ],});

    第12步:配置 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)将内容外部化。


    文档参考

    本综合指南提供了将 Intlayer 与 Tanstack Start 集成所需的一切,支持完全国际化的应用程序,具备基于区域设置的路由和 TypeScript 支持。

    文档历史

    版本 日期 变更内容
    5.8.1 2025-09-09 为 Tanstack Start 添加支持
    接收有关即将发布的Intlayer的通知