React Router v7で言語切り替えコンポーネントを構築する方法

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

問題

ユーザーは言語切り替え機能が現在のコンテキストを保持することを期待しています。商品ページ、ヘルプ記事、アカウント設定を閲覧している際、英語からスペイン語に切り替えると、同じページがスペイン語で表示されるべきです。しかし、多くの実装では言語選択をナビゲーションイベントとして扱い、ユーザーを新しい言語のホームページにリダイレクトし、元の場所に戻るよう強制します。これによりユーザーのフローが中断され、特にコンテンツが豊富なアプリケーションでナビゲーション階層の深い場所にいる場合、フラストレーションが生じます。

根本的な原因は、言語切り替え機能が現在のページに基づいてURLを動的に構築するのではなく、ハードコードされた宛先URLを使用することが多いためです。現在のURL構造を読み取って変換しなければ、言語変更時にアプリケーション内でのユーザーの位置を維持できません。

解決策

現在のURLを読み取り、アクティブなロケールパラメータと残りのパスセグメントの両方を抽出する言語切り替えコンポーネントを構築します。サポートされている各言語について、ロケールセグメントのみを置き換え、他のすべてのパスセグメントとクエリパラメータをそのまま保持して新しいURLを生成します。これらのURLをリンクとしてレンダリングすることで、ユーザーはアプリケーション内での位置を失うことなく言語を切り替えることができます。

このアプローチでは、ロケールをナビゲーション先ではなくURL構造内の置き換え可能なパラメータとして扱い、/en/products/shoesから/es/products/shoesへの切り替え時にユーザーのコンテキストが保持されることを保証します。

手順

1. ロケール対応URLを構築するヘルパー関数を作成する

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

export function buildLocalePath(
  currentPath: string,
  newLocale: string,
): string {
  const segments = currentPath.split("/").filter(Boolean);

  if (segments.length === 0) {
    return `/${newLocale}`;
  }

  segments[0] = newLocale;
  return `/${segments.join("/")}`;
}

この関数はパス名をセグメントに分割し、最初のセグメントをターゲットロケールに置き換えてパスを再構築します。ルートパスなどのエッジケースを処理し、ロケールが常に最初のセグメントになるようにします。

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

アプリケーションがサポートするすべての言語をリストした設定オブジェクトを作成します。

export const locales = [
  { code: "en", label: "English" },
  { code: "es", label: "Español" },
  { code: "fr", label: "Français" },
  { code: "de", label: "Deutsch" },
];

この設定は、スイッチャーに表示する言語の信頼できる情報源として機能し、各ロケールにユーザーフレンドリーなラベルを提供します。

3. 言語スイッチャーコンポーネントを構築する

現在の場所を読み取り、アクティブなロケールを判定し、サポートされている他のすべての言語のリンクをレンダリングするコンポーネントを作成します。

import { Link, useLocation, useParams } from "react-router";
import { locales, buildLocalePath } from "./i18n-config";

export function LanguageSwitcher() {
  const location = useLocation();
  const params = useParams();
  const currentLocale = params.locale || "en";

  return (
    <nav aria-label="Language switcher">
      <ul>
        {locales.map((locale) => {
          const isActive = locale.code === currentLocale;
          const newPath = buildLocalePath(location.pathname, locale.code);

          return (
            <li key={locale.code}>
              {isActive ? (
                <span aria-current="true">{locale.label}</span>
              ) : (
                <Link to={newPath}>{locale.label}</Link>
              )}
            </li>
          );
        })}
      </ul>
    </nav>
  );
}

このコンポーネントはuseLocationを使用して現在のパス名にアクセスし、useParamsを使用してURLからアクティブなロケールを抽出します。サポートされている各ロケールに対して、ヘルパー関数を使用して新しいパスを生成し、現在の言語にはリンクまたは非インタラクティブな要素をレンダリングします。

4. クエリパラメータとハッシュフラグメントを保持する

言語を切り替える際にクエリ文字列とURLフラグメントを維持するようにヘルパー関数を拡張します。

export function buildLocalePath(
  currentPath: string,
  search: string,
  hash: string,
  newLocale: string,
): string {
  const segments = currentPath.split("/").filter(Boolean);

  if (segments.length === 0) {
    return `/${newLocale}${search}${hash}`;
  }

  segments[0] = newLocale;
  return `/${segments.join("/")}${search}${hash}`;
}

この更新されたバージョンは、locationオブジェクトからsearchプロパティとhashプロパティを受け取り、生成されたパスに追加することで、フィルター、ソートパラメータ、アンカーリンクが言語切り替え後も維持されるようにします。

5. 拡張されたヘルパーを使用するようにコンポーネントを更新する

完全なlocation情報をヘルパー関数に渡すようにスイッチャーを変更します。

import { Link, useLocation, useParams } from "react-router";
import { locales, buildLocalePath } from "./i18n-config";

export function LanguageSwitcher() {
  const location = useLocation();
  const params = useParams();
  const currentLocale = params.locale || "en";

  return (
    <nav aria-label="Language switcher">
      <ul>
        {locales.map((locale) => {
          const isActive = locale.code === currentLocale;
          const newPath = buildLocalePath(
            location.pathname,
            location.search,
            location.hash,
            locale.code,
          );

          return (
            <li key={locale.code}>
              {isActive ? (
                <span aria-current="true">{locale.label}</span>
              ) : (
                <Link to={newPath}>{locale.label}</Link>
              )}
            </li>
          );
        })}
      </ul>
    </nav>
  );
}

このコンポーネントは、location.searchlocation.hashをヘルパーに渡すことで、スペイン語に切り替えた際に/en/products?category=shoes#reviewsのようなURLが/es/products?category=shoes#reviewsになることを保証します。