作者:
    Creation:2025-08-06Last update:2026-08-06

    使用 Intlayer 翻译你的 SolidStart 网站 | 国际化 (i18n)

    www.youtube.com

    目录

    本指南涵盖了一个服务端渲染的 SolidStart 应用程序:语言检测在请求时发生,页面在服务端以正确的语言渲染,并且搜索引擎所需的 <html lang>hreflang 和 sitemap 信号都是在服务端生成的。

    为什么选择 Intlayer 而不是其他替代方案?

    @solid-primitives/i18ni18next 等主流解决方案相比,Intlayer 是一个带有集成优化的解决方案,例如:

    Intlayer 经过优化,可与 Solid 完美配合,提供组件级内容划分响应式翻译以及扩展国际化 (i18n) 所需的所有功能。

    无需将庞大的 JSON 文件加载到页面中,只需加载必要的内容。Intlayer 有助于将打包文件和页面体积减少高达 50%

    对应用程序的内容进行局部作用域划分有助于大型应用程序的维护。你可以复制或删除单个功能文件夹,而无需心理负担去审查整个内容代码库。此外,Intlayer 是完全类型化的,以确保内容的准确性。

    将内容协同定位减少了大语言模型 (LLM) 所需的上下文。Intlayer 还附带了一套工具,例如用于测试缺失翻译的 CLILSPMCPagent skills,使 AI 代理的开发人员体验 (DX) 更加顺畅。

    在 CI/CD 流水线中使用你选择的 LLM 按照 AI 提供商的成本自动进行翻译。Intlayer 还提供了一个编译器来自动提取内容,以及一个 Web 平台 来帮助在后台进行翻译

    将庞大的 JSON 文件连接到组件可能会导致性能和响应性问题。Intlayer 在构建时优化了内容加载。

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


    在 SolidStart 应用程序中设置 Intlayer 的分步指南

    1. 安装依赖项

      使用 npm 安装必要的软件包:

      bash
      npx intlayer init --interactive
      --interactive 标志是可选的。如果你是 AI 代理,请使用 intlayer-cli init
      此命令将检测你的环境并安装所需的软件包。例如:
      bash
      npm install intlayer solid-intlayer vite-intlayer
      • intlayer

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

      • solid-intlayer

        将 Intlayer 与 Solid 应用程序集成的软件包。它为 Solid 国际化提供上下文提供程序 (context providers) 和钩子 (hooks)。

      • vite-intlayer

        包含用于将 Intlayer 与 Vite 打包器 集成的 Vite 插件,以及检测用户偏好语言、管理 cookie 和处理 URL 重定向的语言路由句柄。

      这里 vite-intlayer 是一个服务端关注点,不仅是构建时的关注点:它提供了 SolidStart 的 Nitro 服务器运行的请求句柄。将其保留在 dependencies 中是安全的默认设置 —— 仅当你要部署包含 Nitro 内联句柄的构建后的 .output 目录时,才可以将其移动到 devDependencies
    2. 配置你的项目

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

      intlayer.config.ts
      import { type IntlayerConfig, Locales } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [
            Locales.ENGLISH,
            Locales.FRENCH,
            Locales.SPANISH,
            // 你的其他语言
          ],
          defaultLocale: Locales.ENGLISH,
        },
        routing: {
          mode: "prefix-no-default",
        },
      };
      
      export default config;

      使用 prefix-no-default,默认语言从无前缀的 URL 提供:

      plaintext
      /            /about          → 英语  (默认语言)/fr          /fr/about       → 法语/es          /es/about       → 西班牙语
      通过此配置文件,你可以设置本地化 URL、中间件重定向、cookie 名称、内容声明的位置和扩展名、禁用控制台中的 Intlayer 日志等。有关可用参数的完整列表,请参阅配置文档
    3. 在 Vite 配置中集成 Intlayer

      将 Intlayer 插件添加到你的配置中:

      vite.config.ts
      import { solidStart } from "@solidjs/start/config";
      import { nitro } from "nitro/vite";
      import { defineConfig } from "vite";
      import { intlayer } from "vite-intlayer";
      
      export default defineConfig({
        plugins: [solidStart(), nitro(), intlayer()],
      });
      intlayer() Vite 插件构建你的内容声明文件,在开发模式下监视它们,并在应用程序内部定义 Intlayer 环境变量。它还提供可优化性能的别名。

      语言路由随插件一起提供

      SolidStart 运行在 Nitro 上,并且 intlayer() 将其语言路由句柄直接注册到 Nitro 的服务器管道中(通过 routing.enableProxy 选项,默认为 true)。无需配置其他内容:在构建好的服务器上,每个请求在到达路由器之前都会经过检查,并且

      • 语言从 URL 前缀读取,其次是 INTLAYER_LOCALE cookie,然后是 Accept-Language 请求头;
      • 当解析出的语言不是默认语言时,无前缀的 URL 会重定向到对应的本地化页面(//fr);
      • 冗余前缀的 URL 会重定向回其规范形式(/en/about/about);
      • 语言 cookie 会在响应中写回。
    4. 声明你的内容

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

      src/contents/home.content.ts
      import { type Dictionary, t } from "intlayer";
      
      const homeContent = {
        key: "home-page",
        content: {
          title: t({
            en: "Hello world!",
            fr: "Bonjour le monde !",
            es: "¡Hola mundo!",
          }),
          metaTitle: "SolidStart + Intlayer",
          metaDescription: t({
            en: "A SolidStart application internationalized with Intlayer.",
            fr: "Une application SolidStart internationalisée avec Intlayer.",
            es: "Una aplicación SolidStart internacionalizada con Intlayer.",
          }),
          documentation: t({
            en: "Visit start.solidjs.com to learn how to build SolidStart apps.",
            fr: "Visitez start.solidjs.com pour apprendre à créer des applications SolidStart.",
            es: "Visita start.solidjs.com para aprender a crear aplicaciones SolidStart.",
          }),
        },
      } satisfies Dictionary;
      
      export default homeContent;
      ⚠️ SolidStart 特别注意点src/routes 下的每个 .ts / .tsx 文件都会成为一个路由,而 .content.ts 文件具有默认导出,因此它会被误识别为一个页面。请将页面的内容声明保留在 routes 目录之外(src/contents/ 效果很好)。组件的内容可以保持协同定位,因为文件系统路由器不会扫描 src/components

      只要你的内容声明包含在 contentDir 目录(默认为 ./src)中,并匹配内容声明文件扩展名(默认为 .content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml}),就可以在应用程序的任何位置定义它们。

      有关更多详细信息,请参阅内容声明文档

    5. 添加本地化路由

      本步骤的目标是赋予每种语言自己的 URL,这也是搜索引擎进行索引的内容。

      将你的页面移动到可选动态段下。在 SolidStart 的文件系统路由器中,[[locale]] 编译为 :locale? 路径模式:

      plaintext
      src/routes/  [[locale]].tsx          ← 验证动态段的 layout  [[locale]]/    index.tsx             → /        以及 /fr        以及 /es    about.tsx             → /about   以及 /fr/about  以及 /es/about  [...404].tsx            → 捕获其他任何内容的 catch-all

      布局文件的唯一工作是将该动态段约束为已配置的语言:

      src/routes/[[locale]].tsx
      import type { RouteSectionProps } from "@solidjs/router";import { locales } from "intlayer";export const route = {  matchFilters: {    locale: locales,  },};export default function LocaleLayout(props: RouteSectionProps) {  return <>{props.children}</>;}

      @solidjs/router:locale? 扩展为两种模式 —— 一种带有段,一种不带段 —— 并按特异性递减进行匹配。matchFilters 是区分正常设置与令人困惑的设置的关键所在:

      URL 没有 matchFilters 带有 matchFilters
      /fr/about 法语关于页面 法语关于页面
      /about 关于页面 (静态段胜出) 关于页面
      /unknown 主页,静默处理,且 locale=unknown 不匹配 → 回退到 catch-all 404
      如果你使用 'prefix-all' 路由模式,请首选 [locale](必需),如果是 'no-prefix''search-params',则完全放弃该段。
    6. 为你的应用程序提供语言 locale

      URL 是语言 locale 的唯一真理来源:中间件已经将请求重定向到其本地化路径,因此在根布局中读取路径可使服务端渲染与客户端水化(hydration)保持一致,并使每次客户端导航都自动更新语言 locale。

      src/app.tsx
      import { MetaProvider } from "@solidjs/meta";import { Router, useLocation } from "@solidjs/router";import { FileRoutes } from "@solidjs/start/router";import { defaultLocale, getHTMLTextDir, getLocaleFromPath } from "intlayer";import { IntlayerProvider } from "solid-intlayer";import { createEffect, type ParentProps, Suspense } from "solid-js";import { isServer } from "solid-js/web";import { Nav } from "~/components/Nav";import "./app.css";const RootLayout = (props: ParentProps) => {  const location = useLocation();  const locale = () => getLocaleFromPath(location.pathname) ?? defaultLocale;  // 服务端在 entry-server.tsx 中渲染 <html>;  // 语言之间的客户端导航必须自行更新这些属性。  createEffect(() => {    if (isServer) return;    document.documentElement.lang = locale();    document.documentElement.dir = getHTMLTextDir(locale());  });  return (    <MetaProvider>      <IntlayerProvider locale={locale()}>        <Nav />        <Suspense>{props.children}</Suspense>      </IntlayerProvider>    </MetaProvider>  );};export default function App() {  return (    <Router root={RootLayout}>      <FileRoutes />    </Router>  );}
      IntlayerProvider 会对其 locale prop 作出响应,因此在 JSX 中传递访问器调用 locale() 就足够了 —— Solid 会将其编译为一个 getter,当 URL 改变时整个树都会以新语言重新渲染。
    7. 在服务端设置 HTML 的 lang 和 dir 属性

      <html> 元素由 entry-server.tsxRouter 之外渲染。改为从请求 URL 读取语言 locale:

      src/entry-server.tsx
      // @refresh reloadimport { createHandler, StartServer } from "@solidjs/start/server";import { defaultLocale, getHTMLTextDir, getLocaleFromPath } from "intlayer";import { getRequestEvent } from "solid-js/web";export default createHandler(() => (  <StartServer    document={({ assets, children, scripts }) => {      const url = getRequestEvent()?.request.url ?? "/";      const locale = getLocaleFromPath(url) ?? defaultLocale;      return (        <html dir={getHTMLTextDir(locale)} lang={locale}>          <head>            <meta charset="utf-8" />            <meta              name="viewport"              content="width=device-width, initial-scale=1"            />            <link rel="icon" href="/favicon.ico" />            {assets}          </head>          <body>            <div id="app">{children}</div>            {scripts}          </body>        </html>      );    }}  />));

      网络爬虫现在可以在首个字节接收到正确的语言:

      html
      <html dir="ltr" lang="fr"></html>
    8. 在页面中使用 Intlayer

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

      src/routes/[[locale]]/index.tsx
      import { Meta, Title } from "@solidjs/meta";import { useIntlayer } from "solid-intlayer";import Counter from "~/components/Counter";export default function Home() {  const content = useIntlayer("home-page");  return (    <main>      <Title>{content.metaTitle.value}</Title>      <Meta content={content.metaDescription.value} name="description" />      <h1>{content.title}</h1>      <Counter />      <p>{content.documentation}</p>    </main>  );}
      在 Solid 中,useIntlayer 返回响应式内容(例如 content)。你可以直接访问其属性。

      如果你想在 string 属性中使用内容,例如 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)}" />
      要了解有关 useIntlayer 钩子的更多信息,请参阅文档

      内容节点不仅限于纯文本翻译。例如复数形式的计数器:

      src/components/Counter.content.ts
      import { type Dictionary, plural, t } from "intlayer";const counterContent = {  key: "counter",  content: {    clicks: plural({      one: t({        en: "{{count}} click",        fr: "{{count}} clic",        es: "{{count}} clic",      }),      other: t({        en: "{{count}} clicks",        fr: "{{count}} clics",        es: "{{count}} clics",      }),    }),  },} satisfies Dictionary;export default counterContent;
      src/components/Counter.tsx
      import { useIntlayer } from "solid-intlayer";import { createSignal } from "solid-js";export default function Counter() {  const [count, setCount] = createSignal(0);  const content = useIntlayer("counter");  return (    <button onClick={() => setCount(count() + 1)} type="button">      {content.clicks(count())}    </button>  );}

      plural() 通过针对当前语言的 Intl.PluralRules 选择类别,因此拥有两种以上复数形式的语言无需任何额外代码即可工作。

    9. 创建本地化链接组件

      创建自定义 Link 组件,它会自动向内部 URL 添加当前语言的前缀:

      src/components/LocalizedLink.tsx
      import { A, type AnchorProps } from "@solidjs/router";import { getLocalizedUrl } from "intlayer";import { useLocale } from "solid-intlayer";import type { ParentComponent } from "solid-js";export const LocalizedLink: ParentComponent<AnchorProps> = (props) => {  const { locale } = useLocale();  const isExternal = () => /^[a-z][a-z0-9+.-]*:/i.test(props.href);  const localizedHref = () =>    isExternal() ? props.href : getLocalizedUrl(props.href, locale());  return <A {...props} href={localizedHref()} />;};
      src/components/Nav.tsx
      import { useIntlayer } from "solid-intlayer";import type { Component } from "solid-js";import { LocaleSwitcher } from "./LocaleSwitcher";import { LocalizedLink } from "./LocalizedLink";export const Nav: Component = () => {  const content = useIntlayer("nav");  return (    <nav>      <LocalizedLink href="/">{content.home}</LocalizedLink>      <LocalizedLink href="/about">{content.about}</LocalizedLink>      <LocaleSwitcher />    </nav>  );};

      现在只需编写一次 href="/about",即可根据活动语言生成 /about/fr/about/es/about —— 页面中的任何位置都无需手动添加前缀。

    10. 创建语言切换器组件

      将切换器渲染为**真实的 锚点**而非 <select>:当前页面的每种语言都会变为可爬取的链接,并且可以在新标签页中打开,这是仅依靠 JavaScript 的控件无法提供的。

      getPathWithoutLocale 会从当前路径中剥离语言段,而 getLocalizedUrl 会为目标语言重新构建它,因此这些链接会遵循你的路由模式,无需硬编码任何内容。导航是改变渲染语言的原因 —— [[locale]] 路由从 URL 中推导语言 —— 而 setLocale 会将选择保存在 INTLAYER_LOCALE cookie 中,以便以后访问无语言前缀的 URL 时能解析为相同的语言。

      src/components/LocaleSwitcher.tsx
      import { A, useLocation } from "@solidjs/router";
      import {
        getHTMLTextDir,
        getLocaleName,
        getLocalizedUrl,
        getPathWithoutLocale,
      } from "intlayer";
      import { useIntlayer, useLocale } from "solid-intlayer";
      import { type Component, For } from "solid-js";
      
      export const LocaleSwitcher: Component = () => {
        const content = useIntlayer("locale-switcher");
        const location = useLocation();
        const { locale, setLocale, availableLocales } = useLocale();
      
        // 当前显示页面的规范(无语言段)路径
        const pathWithoutLocale = () => getPathWithoutLocale(location.pathname);
      
        return (
          <div>
            <button
              aria-label={content.label.value}
              popoverTarget="localePopover"
              type="button"
            >
              {getLocaleName(locale())}
            </button>
            <div id="localePopover" popover="auto">
              <For each={availableLocales}>
                {(localeItem) => (
                  <A
                    dir={getHTMLTextDir(localeItem)}
                    // 仅精确定位,使默认语言链接不会在每个页面上都被标记为 active
                    end
                    href={getLocalizedUrl(pathWithoutLocale(), localeItem)}
                    hreflang={localeItem}
                    lang={localeItem}
                    onClick={() => setLocale(localeItem)}
                    // 确保浏览器的“后退”按钮返回到上一页
                    replace
                  >
                    {/* 各自语言下的语言名称 - 例如 Français */}
                    {getLocaleName(localeItem)}
                  </A>
                )}
              </For>
            </div>
          </div>
        );
      };

      在 Solid 中,来自 useLocalelocale 是一个 signal 访问器。使用带有括号的 locale() 响应式地读取其当前值。

      getLocaleName(localeItem) 会以各自的语言渲染每种语言名称 —— English / Français / Español。传递第二个参数可以将其翻译为当前显示语言:例如 getLocaleName(localeItem, locale()) 在英语中为 English / French / Spanish,在法语中为 anglais / français / espagnol

      <A> 已经在匹配当前 URL 的链接上设置了 aria-current="page",因此无需额外添加处理。replace 由路由器从渲染的属性中读取:它会替换历史记录条目而不是推入新条目,因此浏览器的“后退”按钮会返回切换前访问的页面,而不是返回前一种语言的同一页面。

      每个链接上的 dirhreflang 属性可使从右到左的语言名称保持正确的方向,并告知辅助技术和网络爬虫每个链接指向哪种语言。

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

    11. 生成规范 canonical 和 hreflang 链接

      可选

      hreflang 注释告知搜索引擎 /about/fr/about/es/about 是不同语言下的同一个页面。getMultilingualUrls 根据你的路由模式从规范(无语言段)路径中导出它们,因此无需硬编码任何内容:

      src/components/AlternateLinks.tsx
      import {  defaultLocale,  getMultilingualUrls,  getPathWithoutLocale,} from "intlayer";import { type Component, For } from "solid-js";export type AlternateLinksProps = {  /** 正在渲染的页面的绝对 URL。 */  url: string;};export const AlternateLinks: Component<AlternateLinksProps> = (props) => {  const multilingualUrls = () => {    const { origin, pathname } = new URL(props.url);    return Object.entries(      getMultilingualUrls(`${origin}${getPathWithoutLocale(pathname)}`)    );  };  const canonicalUrl = () =>    new URL(props.url).origin + new URL(props.url).pathname;  return (    <>      <link href={canonicalUrl()} rel="canonical" />      <For each={multilingualUrls()}>        {([locale, localizedUrl]) => (          <link href={localizedUrl} hreflang={locale} rel="alternate" />        )}      </For>      <link        href={          multilingualUrls().find(([locale]) => locale === defaultLocale)?.[1]        }        hreflang="x-default"        rel="alternate"      />    </>  );};

      在可获取请求 URL 的文档 head 中渲染它:

      src/entry-server.tsx
      import { AlternateLinks } from "~/components/AlternateLinks";// … 在 <head> 内部,在其他 meta 标签旁边:<AlternateLinks url={url} />;

      随后 GET /fr/about 将响应:

      html
      <link href="https://example.com/fr/about" rel="canonical" /><link href="https://example.com/about" hreflang="en" rel="alternate" /><link href="https://example.com/fr/about" hreflang="fr" rel="alternate" /><link href="https://example.com/es/about" hreflang="es" rel="alternate" /><link href="https://example.com/about" hreflang="x-default" rel="alternate" />
      关于 @solidjs/meta 的注意事项:在撰写本文时,@solidjs/meta 中的 <Title><Meta> 在客户端水化后应用,但不会发散到 SolidStart v2 的服务端渲染 <head> 中。在 upstream 修复此问题之前,请直接在 entry-server.tsx 中渲染爬虫无需 JavaScript 即可看到的标签 —— canonicalhreflang 以及需要的 title / description,如上所示。
    12. 处理未找到 (404) 页面

      可选

      处于 src/routes 根目录的通配符路由(splat route)可以捕获语言段未匹配到的所有路径 —— 包括被 matchFilters 拒绝的无效语言前缀。由于语言仍通过根布局来自 URL,因此 404 页面将以访问者的语言显示:

      src/routes/[...404].tsx
      import { Title } from "@solidjs/meta";import { HttpStatusCode } from "@solidjs/start";import { useIntlayer } from "solid-intlayer";import { LocalizedLink } from "~/components/LocalizedLink";export default function NotFound() {  const content = useIntlayer("not-found-page");  return (    <main>      <Title>{content.metaTitle.value}</Title>      <HttpStatusCode code={404} />      <h1>{content.title}</h1>      <LocalizedLink href="/">{content.backHome}</LocalizedLink>    </main>  );}
      请求 预期响应
      /xx 404xx 不是已配置的语言
      /nonexistent 默认语言下的 404
      /fr/nonexistent 法语下的 404 (Page introuvable)
    13. 生成多语言 sitemap 站点地图

      可选

      Intlayer 的 sitemap 生成器将每个路径扩展为每个语言对应一个条目,并在它们之间连接 xhtml:link 备用链接,因此路由只需列出规范的、无语言前缀的路径。

      与仅生成平铺 URL 的基础生成器不同,Intlayer 在每个页面的每个本地化变体之间建立双向链接,这有助于搜索引擎关联本地化 URL 并将正确的页面提供给正确的受众。

      SolidStart 将导出 HTTP 方法的文件转换为 API 路由,并从路径中剥离 .ts 扩展名 —— 因此 src/routes/sitemap.xml.ts/sitemap.xml 处提供服务:

      src/routes/sitemap.xml.ts
      import type { APIEvent } from "@solidjs/start/server";
      import { generateSitemap } from "intlayer";
      
      const SITE_URL = process.env.SITE_URL ?? "http://localhost:3000";
      
      export const GET = (_event: APIEvent) => {
        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" },
        });
      };
      output of GET /sitemap.xml
      <?xml version="1.0" encoding="UTF-8"?><urlset  xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"  xmlns:xhtml="http://www.w3.org/1999/xhtml">  <url>    <loc>https://example.com/about</loc>    <changefreq>monthly</changefreq>    <priority>0.8</priority>    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/about"/>    <xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/about"/>    <xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/about"/>    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/about"/>  </url></urlset>
      API 路由不支持可选参数,因此请将此文件保留在 src/routes 的根目录下,置于 [[locale]] 段之外。sitemap 已经包含了每种语言。

      你可以使用 getMultilingualUrls 以相同方式构建 robots.txt,以便 Disallow 条目涵盖敏感路径的每个本地化拼写:

      src/routes/robots.txt.ts
      import { getMultilingualUrls } from "intlayer";
      
      const SITE_URL = process.env.SITE_URL ?? "http://localhost:3000";
      
      const disallowedPaths = ["/admin", "/private"].flatMap((path) =>
        Object.values(getMultilingualUrls(path))
      );
      
      export const GET = () =>
        new Response(
          [
            "User-agent: *",
            "Allow: /",
            ...disallowedPaths.map((path) => `Disallow: ${path}`),
            "",
            `Sitemap: ${SITE_URL}/sitemap.xml`,
          ].join("\n"),
          { headers: { "Content-Type": "text/plain" } }
        );
    14. 在服务端函数中检索语言 locale

      可选

      你可能希望在服务端函数或 API 路由内部访问当前语言 locale。

      在像这样基于前缀的设置中,URL 具有权威性getLocaleFromPath 从请求 URL 中读取前缀。getLocale 是不带语言前缀的请求的回退机制 —— 它会检查 INTLAYER_LOCALE cookie,然后检查 x-intlayer-locale 请求头,接着协商 Accept-Language

      src/routes/[[locale]]/index.tsx
      import { createAsync } from "@solidjs/router";import { getCookie, getIntlayer, getLocale, getLocaleFromPath } from "intlayer";import { getRequestEvent } from "solid-js/web";const loadLocalizedData = async () => {  "use server";  const request = getRequestEvent()?.request;  const locale =    getLocaleFromPath(request?.url) ??    (await getLocale({      // 从请求中获取 cookie (默认为 'INTLAYER_LOCALE')      getCookie: (name) =>        getCookie(name, request?.headers.get("cookie") ?? ""),      // 从请求中获取 header (默认为 'x-intlayer-locale'),      // 回退到 Accept-Language 协商      getHeader: (name) => request?.headers.get(name) ?? undefined,    }));  // 使用 getIntlayer() 在组件外部检索部分内容  const content = getIntlayer("home-page", locale);  return { locale, title: String(content.title) };};export default function Page() {  const data = createAsync(() => loadLocalizedData());  return <p>{data()?.title}</p>;}
      此处不要仅依赖 getLocale:仅当访问者主动切换语言时才会写入语言 cookie,因此首次访问 /fr/... 将会被解析为默认语言。
    15. 提取组件的内容

      可选

      如果你有一个现有的代码库,转换数千个文件可能会非常耗时。

      为了简化此过程,Intlayer 提议使用 编译器 (compiler) / 提取器 (extractor) 来转换组件并提取内容。

      要进行设置,你可以在 intlayer.config.ts 文件中添加一个 compiler 部分:

      intlayer.config.ts
      import { type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        // ... 其他配置
        compiler: {
          /**
           * 指示是否启用编译器。
           */
          enabled: true,
      
          /**
           * 定义输出文件路径
           */
          output: ({ fileName, extension }) => `./${fileName}${extension}`,
      
          /**
           * 指示在转换组件后是否应保存组件。
           *
           * - 如果为 `true`,编译器将在磁盘中重写组件文件。因此转换将是永久性的,编译器在下一次进程中将跳过该转换。通过这种方式,编译器可以转换应用,然后将其移除。
           *
           * - 如果为 `false`,编译器将仅在构建输出的代码中注入 `useIntlayer()` 函数调用,并保持基础代码库完好。转换将仅在内存中完成。
           */
          saveComponents: false,
      
          /**
           * 字典键前缀
           */
          dictionaryKeyPrefix: "",
        },
      };
      
      export default config;

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

      bash
      npx intlayer extract
      之后,将生成的页面内容文件移出 src/routes,原因如步骤 5 所述。
    16. 配置 TypeScript

      Intlayer 使用模块增强 (module augmentation) 来获得 TypeScript 的优势并增强你的代码库。

      确保你的 TypeScript 配置包含自动生成的类型:

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

      字典键和内容路径现在会在编译时进行检查:

      tsx
      useIntlayer("home-page"); // ✅useIntlayer("hom-page"); // ❌ Argument of type '"hom-page"' is not assignable to parameter of type 'keyof __DictionaryRegistry'

    验证你的设置

    构建并启动服务器,然后检查这些请求是否按预期运行:

    bash
    npm run buildnode .output/server/index.mjs
    请求 预期响应
    GET / 200 — 英语
    GET / 带有 Accept-Language: fr 302/fr
    GET / 带有 cookie INTLAYER_LOCALE=es 302/es
    GET /fr 200 — 法语, <html lang="fr">
    GET /fr/about 200 — 法语关于页面
    GET /en/about 302/about (规范重定向)
    GET /xx 404
    GET /fr/nonexistent 404 法语
    GET /sitemap.xml 200 — 多语言 XML sitemap

    vite dev 下渲染页面的行行为相同。除非你自己将句柄注册为中间件,否则三个重定向行仅适用于构建后的服务器 —— 参见步骤 3。

    请在 Node (vite dev) 上运行开发服务器,而不是在 Bun (bun --bun vite dev) 上:SolidStart 的 SSR 目前在 Bun 运行时下会失败并显示 Expected a Response object, but received 'NodeResponse'。这与 Intlayer 无关 —— 它在纯模板上也会复现 —— 并且只影响开发服务器,不影响 vite build

    Git 配置

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

    为此,你可以将以下指令添加到你的 .gitignore 文件中:

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

    VS Code 插件

    为了提升你使用 Intlayer 的开发体验,你可以安装官方的 Intlayer VS Code 插件

    从 VS Code 插件市场安装

    此插件提供:

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

    深入了解

    要进一步了解,你可以实现可视化编辑器或使用 CMS 外包你的内容。


    文档参考