著者:
    作成:2025-03-25最終更新:2026-08-25

    Intlayerを使用してTanstack Start + Solid.jsウェブサイトを翻訳する | 国際化 (i18n)

    目次

    このガイドでは、Solid.jsを使用したTanstack Startプロジェクトにおいて、Intlayerを統合してシームレスな国際化、ロケール対応ルーティング、TypeScriptサポート、および現代的な開発手法を実現する方法を説明します。

    代替手段ではなく Interlayer を使用する理由

    「react-i18next」や「i18next」などの主要なソリューションと比較して、Intlayer は次のような統合された最適化を備えたソリューションです。

    TanStack Start を完全にカバー

    Intlayer は、多言語ルーティングサイトマップ、および国際化の拡張 (i18n) に必要なすべての機能を提供することにより、TanStack Start および Solid と完全に連携するように最適化されています。

    バンドルサイズ

    大量の JSON ファイルをページにロードするのではなく、必要なコンテンツのみをロードします。 Intlayer は、バンドルとページのサイズを最大 50% 削減するのに役立ちます。

    保守性

    アプリケーションのコンテンツのスコープを設定すると、大規模なアプリケーションの メンテナンスが容易になります。コンテンツ コードベース全体を確認するという精神的な負担を負うことなく、単一の機能フォルダーを複製または削除できます。さらに、Intlayer は完全に型指定されており、コンテンツの正確性を保証します。

    AI エージェント

    コンテンツを同じ場所に配置すると、大規模言語モデル (LLM) によって 必要なコンテキストが削減されます。 Intlayer には、翻訳の欠落をテストする CLILSPMCP などのツール スイートも付属しています。および agent skills により、AI エージェントの開発者エクスペリエンス (DX) がさらにスムーズになります。

    オートメーション

    AI プロバイダーの費用で、選択した LLM を使用して CI/CD パイプラインで自動化を変換します。 Intlayer は、コンテンツ抽出を自動化する コンパイラー と、バックグラウンドでの翻訳を支援する Web プラットフォーム も提供します。

    パフォーマンス

    大量の JSON ファイルをコンポーネントに接続すると、パフォーマンスと反応性の問題が発生する可能性があります。 Intlayer は、ビルド時のコンテンツの読み込みを最適化します。

    非開発によるスケーリング

    Intlayer は単なる i18n ソリューションではなく、自己ホスト型 ビジュアル エディター完全な CMS を提供します。 リアルタイムで多言語コンテンツを管理できるようになり、翻訳者、コピーライター、その他のチーム メンバーとのコラボレーションがシームレスになります。コンテンツはローカルおよび/またはリモートに保存できます。


    Tanstack StartアプリケーションでIntlayerをセットアップするためのステップバイステップガイド

    www.youtube.com
    ide.intlayer.org
    intlayer-tanstack-start-solid.vercel.app

    GitHubでアプリケーションテンプレートを確認してください。

    1. プロジェクト作成

      まず、TanStack Startウェブサイトの新しいプロジェクトを開始ガイドに従って、新しいTanStack Startプロジェクトを作成します。

    2. Intlayerパッケージのインストール

      お好みのパッケージマネージャーを使用して、必要なパッケージをインストールします:

      bash
      npx intlayer init --interactive
      
      --interactive フラグはオプションです。AI エージェントの場合は intlayer-cli init を使用してください。
      このコマンドは環境を検出し、必要なパッケージをインストールします。例えば:
      bash
      npm install intlayer solid-intlayer
      npm install vite-intlayer --save-dev
      
      • intlayer

        構成管理、翻訳、コンテンツ宣言、トランスパイル、およびCLIコマンドのための国際化ツールを提供するコアパッケージ。

      • solid-intlayer IntlayerをSolidアプリケーションと統合するためのパッケージ。Solidの国際化のためのコンテキストプロバイダーとフックを提供します。

      • vite-intlayer IntlayerをViteバンドラーと統合するためのViteプラグイン、およびユーザーの優先ロケールの検出、Cookie管理、URLリダイレクトを処理するためのミドルウェアが含まれています。

    3. プロジェクトの構成

      アプリケーションの言語を構成するための構成ファイルを作成します:

      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を統合する

      Vite構成にintlayerプラグインを追加します:

      vite.config.ts
      import { intlayer } from "vite-intlayer";
      import { defineConfig } from "vite";
      import { devtools } from "@tanstack/devtools-vite";
      import { tanstackStart } from "@tanstack/solid-start/plugin/vite";
      import solidPlugin from "vite-plugin-solid";
      
      export default defineConfig({
        plugins: [
          devtools(),
          tanstackStart({
            router: {
              routeFileIgnorePattern:
                ".content.(ts|tsx|js|mjs|cjs|jsx|json|jsonc|json5|md|mdx|yaml|yml)$",
            },
          }),
          solidPlugin({ ssr: true }),
          intlayer({
            proxy: {
              ignore: (req) => req.url?.startsWith("/api"),
            },
          }),
        ],
      });
      
      intlayer() Viteプラグインは、IntlayerをViteと統合するために使用されます。コンテンツ宣言ファイルの構築を確実にし、開発モードでそれらを監視します。Viteアプリケーション内でIntlayer環境変数を定義します。さらに、パフォーマンスを最適化するためのエイリアスを提供します。
    5. ルートレイアウトの作成

      現在選択されているロケールを検出するために useParams を使用し、html タグに lang および dir 属性を設定することで、国際化をサポートするようにルートレイアウトを構成します。

      src/routes/__root.tsx
      import {
        HeadContent,
        Scripts,
        createRootRouteWithContext,
        getRouteApi,
      } from "@tanstack/solid-router";
      import { HydrationScript } from "solid-js/web";
      import { Suspense, type ParentComponent } from "solid-js";
      import { IntlayerProvider } from "solid-intlayer";
      import { defaultLocale, getHTMLTextDir } from "intlayer";
      
      const localeRoute = getRouteApi("/{-$locale}");
      
      export const Route = createRootRouteWithContext()({
        shellComponent: RootComponent,
      });
      
      const RootComponent: ParentComponent = (props) => {
        const params = localeRoute.useParams();
        const locale = params()?.locale ?? defaultLocale;
      
        return (
          <html dir={getHTMLTextDir(locale)} lang={locale}>
            <head>
              <HydrationScript />
              <HeadContent />
            </head>
            <body>
              <IntlayerProvider locale={locale}>
                <Suspense>{props.children}</Suspense>
              </IntlayerProvider>
              <Scripts />
            </body>
          </html>
        );
      };
      
    6. ロケールレイアウトの作成

      ロケールプレフィックスを処理し、検証を実行するレイアウトを作成します。このレイアウトにより、有効なロケールのみが処理されるようになります。

      ルートレベルでロケールプレフィックスを検証する必要がない場合、このステップは任意です。
      src/routes/{-$locale}/route.tsx
      import { createFileRoute, Outlet, redirect } from "@tanstack/solid-router";
      import { validatePrefix } from "intlayer";
      
      export const Route = createFileRoute("/{-$locale}")({
        beforeLoad: ({ params }) => {
          const localeParam = params.locale;
      
          // ロケールプレフィックスを検証
          const { isValid, localePrefix } = validatePrefix(localeParam);
      
          if (!isValid) {
            throw redirect({
              to: "/{-$locale}/404",
              params: { locale: localePrefix },
              replace: true,
            });
          }
        },
        component: Outlet,
      });
      
      ここで、{-$locale} は現在のロケールに置き換えられる動的なルートパラメータです。この表記によりスロットがオプションになり、'prefix-no-default' などのルーティングモードで動作できるようになります。

      同じルートで複数の動的セグメントを使用している場合 (例: /{-$locale}/other-path/$anotherDynamicPath/...)、このスロットが問題を引き起こす可能性があることに注意してください。 'prefix-all' モードの場合、代わりにスロットを $locale に切り替えることをお勧めします。 'no-prefix' または 'search-params' モードの場合、スロットを完全に削除できます。

    7. コンテンツの宣言

      翻訳を保存するためのコンテンツ宣言を作成および管理します:

      src/contents/page.content.ts
      import type { Dictionary } from "intlayer";
      
      import { t } from "intlayer";
      
      const appContent = {
        content: {
          links: {
            about: t({
              en: "About",
              es: "Acerca de",
              fr: "À propos",
            }),
            home: t({
              en: "Home",
              es: "Inicio",
              fr: "Accueil",
            }),
          },
          meta: {
            title: t({
              en: "Welcome to Intlayer + TanStack Router",
              es: "Bienvenido a Intlayer + TanStack Router",
              fr: "Bienvenue à Intlayer + TanStack Router",
            }),
            description: t({
              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}) に一致する必要があります。
      詳細については、コンテンツ宣言のドキュメントを参照してください。
    8. ロケール対応のコンポーネントとフックを利用する

      ロケール対応のナビゲーションのために LocalizedLink コンポーネントを作成します:

      src/components/LocalizedLink.tsx
      import { Link, type LinkProps } from "@tanstack/solid-router";
      import { getPrefix } from "intlayer";
      import { useLocale } from "solid-intlayer";
      import type { JSX } from "solid-js";
      
      export const LOCALE_ROUTE = "{-$locale}" as const;
      
      export type RemoveLocaleParam<TVal> = TVal extends string
        ? RemoveLocaleFromString<TVal>
        : TVal;
      
      export type To = RemoveLocaleParam<LinkProps["to"]>;
      
      type CollapseDoubleSlashes<TString extends string> =
        TString extends `${infer THead}//${infer TTail}`
          ? CollapseDoubleSlashes<`${THead}/${TTail}`>
          : TString;
      
      export type LocalizedLinkProps = Omit<LinkProps, "to"> & {
        to?: To;
      } & JSX.AnchorHTMLAttributes<HTMLAnchorElement>;
      
      type RemoveAll<
        TString extends string,
        TSub extends string,
      > = TString extends `${infer THead}${TSub}${infer TTail}`
        ? RemoveAll<`${THead}${TTail}`, TSub>
        : TString;
      
      type RemoveLocaleFromString<TString extends string> = CollapseDoubleSlashes<
        RemoveAll<TString, typeof LOCALE_ROUTE>
      >;
      
      export const LocalizedLink = (props: LocalizedLinkProps) => {
        const { locale } = useLocale();
      
        return (
          <Link
            {...props}
            params={{
              locale: getPrefix(locale()).localePrefix,
              ...(typeof props.params === "object" ? props.params : {}),
            }}
            to={`/${LOCALE_ROUTE}${props.to ?? ""}` as LinkProps["to"]}
          />
        );
      };
      

      このコンポーネントには2つの目的があります:

      • URLから不要な {-$locale} プレフィックスを削除する。
      • ユーザーがローカライズされたルートに直接リダイレクトされるように、URLにロケールパラメータを注入する。

      次に、プログラムによるナビゲーションのための useLocalizedNavigate フックを作成します:

      src/hooks/useLocalizedNavigate.tsx
      import { useNavigate } from "@tanstack/solid-router";
      import { getLocalizedUrl } from "intlayer";
      import { useLocale } from "solid-intlayer";
      
      export const useLocalizedNavigate = () => {
        const navigate = useNavigate();
        const { locale } = useLocale();
      
        const localizedNavigate = (to: string) => {
          const localizedTo = getLocalizedUrl(to, locale());
          return navigate({ to: localizedTo });
        };
      
        return localizedNavigate;
      };
      
    9. ページでIntlayerを活用する

      コンポーネント内では既定で useIntlayer を使用してください。コンパイラがレンダリング対象のロケールへ解決してくれるため、これが推奨される方法です。getIntlayer / getIntlayerAsync は Solid ツリーの外側(ルートの head、ローダー、サーバー関数)でのみ使用します。

      アプリケーション全体でコンテンツ辞書にアクセスします:

      ローカライズされたホームページ

      src/routes/{-$locale}/index.tsx
      import { createFileRoute } from "@tanstack/solid-router";
      import { useIntlayer } from "solid-intlayer";
      import { LocalizedLink } from "@/components/LocalizedLink";
      
      export const Route = createFileRoute("/{-$locale}/")({
        component: RouteComponent,
      });
      
      function RouteComponent() {
        const content = useIntlayer("index-page");
      
        return (
          <main>
            <h1>{content.heroTitle}</h1>
            <p>{content.heroDesc}</p>
            <div>
              <LocalizedLink to="/">{content.navHome}</LocalizedLink>
              <LocalizedLink to="/about">{content.navAbout}</LocalizedLink>
            </div>
          </main>
        );
      }
      
      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)}" />
      

      Solidでは、useIntlayerはリアクティブなコンテンツ(例:content)を返します。そのプロパティに直接アクセスできます。

      useIntlayer フックの詳細については、ドキュメントを参照してください。

    10. ロケール切り替えコンポーネントの作成

      ユーザーが言語を変更できるようにするコンポーネントを作成します:

      src/components/LocaleSwitcher.tsx
      import { useLocation } from "@tanstack/solid-router";
      import { getLocaleName, getPathWithoutLocale, getPrefix } from "intlayer";
      import { For } from "solid-js";
      import { useIntlayer, useLocale } from "solid-intlayer";
      import { LocalizedLink, type To } from "./LocalizedLink";
      
      export const LocaleSwitcher = () => {
        const content = useIntlayer("locale-switcher");
        const location = useLocation();
      
        const { availableLocales, locale, setLocale } = useLocale();
      
        const pathWithoutLocale = () => getPathWithoutLocale(location().pathname);
      
        return (
          <div class="flex flex-row gap-2">
            <For each={availableLocales}>
              {(localeEl) => (
                <LocalizedLink
                  aria-current={localeEl === locale() ? "page" : undefined}
                  onClick={() => setLocale(localeEl)}
                  params={{ locale: getPrefix(localeEl).localePrefix }}
                  to={pathWithoutLocale() as To}
                >
                  {getLocaleName(localeEl)}
                </LocalizedLink>
              )}
            </For>
          </div>
        );
      };
      
      export default LocaleSwitcher;
      

      Solidでは、useLocale からの localeシグナルアクセサー です。現在の値をリアクティブに読み取るには、locale() (括弧付き) を使用します。

      useLocale フックの詳細については、ドキュメントを参照してください。

    11. HTML属性の管理

      ステップ 5 で見たように、ルートコンポーネントで useParams を使用して html タグの lang および dir 属性を管理できます。これにより、サーバーとクライアントで正しい属性が設定されるようになります。

      src/routes/__root.tsx
      const RootComponent: ParentComponent = (props) => {
        const params = localeRoute.useParams();
        const locale = params()?.locale ?? defaultLocale;
      
        return (
          <html dir={getHTMLTextDir(locale)} lang={locale}>
            {/* ... */}
          </html>
        );
      };
      

    12. ミドルウェアの追加

      intlayerProxy を使用して、アプリケーションにサーバーサイドのルーティングを追加することもできます。このプラグインはURLに基づいて現在のロケールを自動的に検出し、適切なロケールCookieを設定します。ロケールが指定されていない場合、プラグインはユーザーのブラウザ言語設定に基づいて最も適切なロケールを決定します。ロケールが検出されない場合は、デフォルトのロケールにリダイレクトします。

      本番環境で intlayerProxy を使用するには、vite-intlayer パッケージを devDependencies から dependencies に切り替える必要があることに注意してください。
      Intlayer v9 以降、intlayerProxy()intlayer() プラグインに直接バンドルされており、routing.enableProxy オプション(デフォルトは true)を通じてデフォルトで有効になります。以下に示すように個別に登録することはオプションになりました。これは後方互換性とプラグインの順序を制御する必要があるセットアップのために保持されています。routing.enableProxy: false に設定してオプトアウトできます。v9 リリースノートを参照してください。
      vite.config.ts
      import { tanstackStart } from "@tanstack/solid-start/plugin/vite";
      import solid from "vite-plugin-solid";
      import { nitro } from "nitro/vite";
      import { defineConfig } from "vite";
      import { intlayer } from "vite-intlayer";
      
      export default defineConfig({
        plugins: [
          nitro(),
          intlayer({
            proxy: {
              ignore: (req) => req.url?.startsWith("/api"),
            },
          }),
          tanstackStart({
            router: {
              routeFileIgnorePattern:
                ".content.(ts|tsx|js|mjs|cjs|jsx|json|jsonc|json5|md|mdx|yaml|yml)$",
            },
          }),
          solid(),
        ],
      });
      

    13. メタデータの国際化

      getIntlayer は、宣言されたすべてのロケールを含む統合辞書に対して同期的に解決します。head は同期のままで待機も発生しませんが、多言語辞書全体がブラウザへ送られるルートチャンクに取り込まれます。

      src/routes/{-$locale}/index.tsx
      import { createFileRoute } from "@tanstack/solid-router";
      import {
        defaultLocale,
        getIntlayer,
        getLocalizedUrl,
        localeMap,
      } from "intlayer";
      
      export const Route = createFileRoute("/{-$locale}/")({
        component: RouteComponent,
        head: ({ params }) => {
          const { locale = defaultLocale } = 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 },
            ],
          };
        },
      });
      

      小さなメタデータ辞書、ロケール数が少ない場合、プロトタイピング時に適しています。

      getIntlayerAsyncv9.4 以降で利用可能)は getIntlayer と同じ振る舞いをしますが、ビルドプラグインが統合辞書ではなく .intlayer/dynamic_dictionaries/ 内のロケール別チャンクを参照させます。そのためページはレンダリングするロケールのみを配信します。このチャンクはオンデマンドで読み込まれるため、headasync になります。

      src/routes/{-$locale}/index.tsx
      import { createFileRoute } from "@tanstack/solid-router";
      import {
        defaultLocale,
        getIntlayerAsync,
        getLocalizedUrl,
        localeMap,
      } from "intlayer";
      
      export const Route = createFileRoute("/{-$locale}/")({
        component: RouteComponent,
        head: async ({ params }) => {
          const { locale = defaultLocale } = params;
          const path = "/"; // The path for this route
      
          const metaContent = await getIntlayerAsync("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 },
            ],
          };
        },
      });
      
      1 つの head で複数の辞書を読む場合は Promise.all でまとめて解決してください。getIntlayerAsync を 1 行ずつ await すると、リクエストが並列ではなく直列に連鎖してしまいます。

      トレードオフとして、動的インポートはドキュメントレンダリングのクリティカルパス上で head の実行中に解決されます。コールドなルートでは head が数ミリ秒遅れ、LCP がわずかに悪化する可能性があります。

      辞書をルートの loader で解決し、head では loaderData から読み戻します。マッチしたルートのローダーは並行して実行され、staleTime: Infinity によって TanStack Router は結果が古くならないと判断します。つまりロケール別チャンクは一度だけ解決され、以降はルーターのキャッシュから提供されるため、head は同期のままです。

      src/routes/{-$locale}/index.tsx
      import { createFileRoute } from "@tanstack/solid-router";
      import {
        defaultLocale,
        getIntlayerAsync,
        getLocalizedUrl,
        localeMap,
      } from "intlayer";
      
      export const Route = createFileRoute("/{-$locale}/")({
        component: RouteComponent,
        // Resolved in parallel with the other matched routes, off the head critical path
        loader: async ({ params }) => {
          const { locale = defaultLocale } = params;
      
          return { metaContent: await getIntlayerAsync("app", locale) };
        },
        // The dictionary never changes for a given locale: resolve the chunk once
        staleTime: Infinity,
        head: ({ params, loaderData }) => {
          const { locale = defaultLocale } = params;
          const path = "/"; // The path for this route
      
          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: loaderData?.metaContent.title },
              {
                name: "description",
                content: loaderData?.metaContent.meta.description,
              },
            ],
          };
        },
      });
      
      head はローダーの解決前に呼ばれることがあるため、loaderDataundefined の可能性がある型になります。オプショナルチェーンを残すか、フォールバックのタイトルを返してください。

      ロケール別チャンクの利点を保ちつつ、そのコストを head のクリティカルパスで支払わずに済みます。代償は DX です。コンテンツをローダーから headloaderData 経由で明示的に受け渡す必要があります。

      どの解決方法を選ぶべきか

      静的解決 動的解決 キャッシュ付き動的解決
      API getIntlayer getIntlayerAsync(v9.4+) loader 内の getIntlayerAsync(v9.4+)
      head のシグネチャ 同期 async 同期、loaderData を読む
      配信されるロケール 宣言されたすべてのロケール 要求されたロケールのみ 要求されたロケールのみ
      クライアント遷移 解決するものなし マッチのたびに再実行 ルーターのキャッシュから提供
      DX 最もシンプル await が 1 つ コンテンツを loaderData 経由で受け渡し

    14. サーバーアクションでロケールを取得する

      サーバーアクションやAPIエンドポイント内から現在のロケールにアクセスしたい場合があります。 これを行うには、intlayer から getLocale ヘルパーを使用します。

      TanStack Startのサーバー関数を使用した例を次に示します:

      src/routes/{-$locale}/index.tsx
      import { createServerFn } from "@tanstack/solid-start";
      import {
        getRequestHeader,
        getRequestHeaders,
      } from "@tanstack/solid-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);
          },
          // リクエストからヘッダーを取得する (デフォルト: 'x-intlayer-locale')
          // Accept-Language ネゴシエーションを使用したフォールバック
          getHeader: (name) => getRequestHeader(name),
        });
      
        // getIntlayer() を使用してコンテンツを取得する
        const content = getIntlayer("app", locale);
      
        return { locale, content };
      });
      

    15. 404ページを管理する

      ユーザーが存在しないページにアクセスしたときに、カスタム 404 ページを表示できます。ロケールプレフィックスは、404 ページがトリガーされる方法に影響を与える可能性があります。

      TanStack Router のロケールプレフィックス付き 404 処理について

      TanStack Router では、ローカライズされたルートでの 404 ページの処理に重層的なアプローチが必要です:

      1. 専用 404 ルート: 404 UI を表示するための特定のルート。
      2. ルートレベルの検証: ロケールプレフィックスを検証し、無効な場合は 404 にリダイレクトします。
      3. キャッチオールルート: ロケールセグメント内のあらゆる一致しないパスをキャプチャします。
      src/routes/{-$locale}/404.tsx
      import { createFileRoute } from "@tanstack/solid-router";
      
      // これにより専用の /[locale]/404 ルートが作成されます
      // 直接のルートとして、また他のファイルでコンポーネントとしてインポートされて使用されます
      export const Route = createFileRoute("/{-$locale}/404")({
        component: NotFoundComponent,
      });
      
      // notFoundComponent やキャッチオールルートで再利用できるように別途エクスポート
      export function NotFoundComponent() {
        return (
          <div>
            <h1>404</h1>
          </div>
        );
      }
      
      src/routes/{-$locale}/route.tsx
      import { createFileRoute, Outlet, redirect } from "@tanstack/solid-router";
      import { validatePrefix } from "intlayer";
      import { NotFoundComponent } from "./404";
      
      export const Route = createFileRoute("/{-$locale}")({
        // beforeLoad はルートがレンダリングされる前に実行されます (サーバーとクライアントの両方)
        // ここはロケールプレフィックスを検証するのに最適な場所です
        beforeLoad: ({ params }) => {
          const localeParam = params.locale;
      
          // validatePrefix は intlayer 構成に従ってロケールが有効かどうかを確認します
          const { isValid, localePrefix } = validatePrefix(localeParam);
      
          if (!isValid) {
            // 無効なロケールプレフィックス - 有効なロケールプレフィックスを使用して 404 ページにリダイレクト
            throw redirect({
              to: "/{-$locale}/404",
              params: { locale: localePrefix },
            });
          }
        },
        component: Outlet,
        // notFoundComponent は、子ルートが存在しないときに呼び出されます
        // 例: /en/non-existent-page は /en レイアウト内でこれをトリガーします
        notFoundComponent: NotFoundComponent,
      });
      
      src/routes/{-$locale}/$.tsx
      import { createFileRoute } from "@tanstack/solid-router";
      
      import { NotFoundComponent } from "./404";
      
      // $ (スプラット/キャッチオール) ルートは、他のルートに一致しないあらゆるパスに一致します
      // 例: /en/some/deeply/nested/invalid/path
      // これにより、ロケール内のすべての不一致パスが 404 ページを表示するようになります
      // これがないと、一致しない深いパスは空白のページまたはエラーを表示する可能性があります
      export const Route = createFileRoute("/{-$locale}/$")({
        component: NotFoundComponent,
      });
      
    16. コンポーネントのコンテンツを抽出する

      オプション

      既存のコードベースがある場合、数千ものファイルを変換するのは時間がかかる可能性があります。

      このプロセスを容易にするために、Intlayerはコンポーネントを変換してコンテンツを抽出するための コンパイラ / エクストラクタ を提案しています。

      セットアップするには、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
      
      Since v9, the intlayerCompiler is included in the intlayer plugin. So you don't need to add it manually.

      intlayerCompiler プラグインを含めるように vite.config.ts を更新します:

      vite.config.ts
      import { intlayer, intlayerCompiler } from "vite-intlayer";
      import { defineConfig } from "vite";
      import { devtools } from "@tanstack/devtools-vite";
      import { tanstackStart } from "@tanstack/solid-start/plugin/vite";
      import solidPlugin from "vite-plugin-solid";
      
      export default defineConfig({
        plugins: [
          devtools(),
          tanstackStart({
            router: {
              routeFileIgnorePattern:
                ".content.(ts|tsx|js|mjs|cjs|jsx|json|jsonc|json5|md|mdx|yaml|yml)$",
            },
          }),
          solidPlugin({ ssr: true }),
          intlayer(),
          intlayerCompiler(), // Adds the compiler plugin
        ],
      });
      
      bash
      npm run build # または npm run dev
      

    17. サイトマップの生成

      Intlayer によって生成されたサイトマップは、xhtml:link 名前空間 (Hreflang XML Extensions) をサポートしています。生の URL のみを表示するデフォルトのサイトマップ ジェネレーターとは異なり、Intlayer はページのすべての言語バージョン (例: /about/about?lang=fr/about?lang=es) 間に必要な双方向リンクを自動的に作成します。これにより、検索エンジンが正しい言語バージョンを正しい対象者に正しくインデックス付けして提供できるようになります。
      Intlayer によって生成されたサイトマップは、xhtml:link 名前空間 (Hreflang XML Extensions) をサポートしています。生の URL のみを表示するデフォルトのサイトマップ ジェネレーターとは異なり、Intlayer はページのすべての言語バージョン (例: /about/about?lang=fr/about?lang=es) 間に必要な双方向リンクを自動的に作成します。これにより、検索エンジンが正しい言語バージョンを正しい対象者に正しくインデックス付けして提供できるようになります。

      これを使用するには、まず vite.config.ts を構成して、ローカライズされたルートのプリレンダリングを有効にし、デフォルトの TanStack Start サイトマップ生成を無効にする必要があります。

      vite.config.ts
      import { localeMap, 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/solid-router";
      import { generateSitemap } from "intlayer";
      
      const SITE_URL = "http://localhost:3000";
      
      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" },
              });
            },
          },
        },
      });
      

    18. TypeScriptの構成

      Intlayerは、TypeScriptの利点を活用し、コードベースをより強固にするために、モジュール拡張(module augmentation)を使用します。

      TypeScript構成に自動生成された型が含まれていることを確認してください:

      tsconfig.json
      {
        // ...既存の構成
        include: [
          // ...既存のインクルード
          ".intlayer/**/*.ts", // 自動生成された型を含める
        ],
      }
      

    Git構成

    Intlayerによって生成されたファイルを無視することをお勧めします。これにより、それらをGitリポジトリにコミットすることを避けることができます。

    これを行うには、.gitignore ファイルに次の指示を追加します:

    .gitignore
    # Intlayerによって生成されたファイルを無視する
    .intlayer
    

    VS Code 拡張機能

    Intlayerでの開発体験を向上させるために、公式の Intlayer VS Code 拡張機能 をインストールできます。

    VS Code Marketplace からインストール

    この拡張機能は以下を提供します:

    • 翻訳キーの オートコンプリート
    • 不足している翻訳の リアルタイムエラー検出
    • 翻訳されたコンテンツの インラインプレビュー
    • 翻訳を簡単に作成および更新するための クイックアクション

    拡張機能の使用方法の詳細については、Intlayer VS Code 拡張機能ドキュメント を参照してください。


    さらに進む

    さらに進むには、ビジュアルエディタを実装したり、CMSを使用してコンテンツを外部化したりできます。


    ドキュメント参照