TanStack Start v1で言語切替コンポーネントを構築する方法

同じページに留まりながら言語を切り替える

問題

多言語アプリケーションでユーザーが言語を切り替える際、同じページに留まり、新しい言語で同じコンテンツを閲覧することを期待しています。実装が不十分な言語切替機能は、言語選択を別の目的地へのナビゲーションとして扱い、ユーザーをホームページにリダイレクトさせ、現在の位置を完全に失わせることがよくあります。これはユーザーフローを中断し、特にユーザーがワークフローの途中にいたり、特定のコンテンツを閲覧している場合に不満を生じさせます。課題は、URLの残りのパス、検索パラメータ、ハッシュを保持しながら、URLの言語セグメントのみを変更する切替機能を構築することです。

解決策

現在のURLパス名を読み取り、パス内のロケールセグメントを置き換えることで、利用可能な各言語のリンクを構築する言語切替コンポーネントを作成します。URL構造から現在のロケールを抽出し、他のURLセグメントをそのまま保持しながらロケール部分を置き換えることで、サポートされている他の言語の新しいパスを生成します。これらのパスを使用して、ユーザーが現在のページコンテキストを失うことなく言語を切り替えることができるリンクをレンダリングします。

ステップ

1. サポートするロケールを定義する

アプリケーションがサポートするすべての言語をリストし、デフォルトのロケールを識別する設定ファイルを作成します。

export const locales = ["en", "fr", "es", "de"] as const;

export type Locale = (typeof locales)[number];

export const defaultLocale: Locale = "en";

この設定は、利用可能な言語の単一の情報源として機能し、切替リンクの生成に使用されます。

2. パス名から現在のロケールを抽出するヘルパーを作成する

パス名を解析して、存在する場合はロケールセグメントを抽出するユーティリティ関数を作成します。

import { defaultLocale, locales, type Locale } from "./locales";

export function getLocaleFromPathname(pathname: string): Locale {
  const segments = pathname.split("/").filter(Boolean);
  const firstSegment = segments[0];

  if (firstSegment && locales.includes(firstSegment as Locale)) {
    return firstSegment as Locale;
  }

  return defaultLocale;
}

この関数は最初のパスセグメントを検査し、サポートされているロケールと一致する場合はそれを返し、そうでない場合はデフォルトのロケールにフォールバックします。

3. ローカライズされたパスを構築するヘルパーを作成する

現在のパス名とターゲットロケールを受け取り、ロケールセグメントを置き換えた新しいパスを構築する関数を作成します。

import { defaultLocale, locales, type Locale } from "./locales";

export function getLocalizedPath(
  pathname: string,
  targetLocale: Locale,
): string {
  const segments = pathname.split("/").filter(Boolean);
  const firstSegment = segments[0];

  const hasLocalePrefix =
    firstSegment && locales.includes(firstSegment as Locale);

  if (hasLocalePrefix) {
    segments[0] = targetLocale;
  } else {
    segments.unshift(targetLocale);
  }

  return "/" + segments.join("/");
}

この関数は、既存のロケールセグメントを置き換えるか、ターゲットロケールをパスの先頭に追加し、新しいURLが異なる言語で同じページを指すようにします。

4. 言語切り替えコンポーネントを構築する

現在の位置を使用して、サポートされているすべての言語のリンクを生成するコンポーネントを作成します。

import { Link } from "@tanstack/react-router";
import { useLocation } from "@tanstack/react-router";
import { locales, type Locale } from "./locales";
import { getLocaleFromPathname, getLocalizedPath } from "./locale-helpers";

export function LanguageSwitcher() {
  const location = useLocation();
  const currentLocale = getLocaleFromPathname(location.pathname);

  return (
    <nav>
      <ul>
        {locales.map((locale) => {
          const isActive = locale === currentLocale;
          const localizedPath = getLocalizedPath(location.pathname, locale);

          return (
            <li key={locale}>
              <Link
                to={localizedPath}
                search={location.search}
                hash={location.hash}
                aria-current={isActive ? "page" : undefined}
              >
                {locale.toUpperCase()}
              </Link>
            </li>
          );
        })}
      </ul>
    </nav>
  );
}

このコンポーネントは現在のパス名を読み取り、アクティブなロケールを判断し、サポートされている各言語のリンクを現在のページ構造、検索パラメータ、ハッシュフラグメントを保持して表示します。ユーザーは同じ論理ページにとどまりながら言語を切り替えることができます。