React Router v7でセッション間の言語選択を記憶する方法

ユーザーの明示的な言語選択を保存する

問題

ユーザーが明示的に言語を選択した場合、その選択は彼らの好みを反映しており、自動検出よりも優先されるべきです。永続化がない場合、この選択はブラウザを閉じたりセッションが終了したりすると消失します。次回の訪問時には、アプリケーションは初期状態から始まり、ユーザーは再度言語を選択することを強いられます。この繰り返しは、アプリケーションがユーザーの好みを尊重していないことを示し、摩擦を生み出し、信頼を損ないます。

解決策

ユーザーが選択を行った際に、Cookieなどの永続的な場所にユーザーの言語選択を保存します。その後の訪問時には、ブラウザヘッダーや他の検出方法にフォールバックする前に、この保存された設定を確認します。有効な保存済み言語が見つかった場合、ユーザーを自動的にその言語のルートにリダイレクトします。これにより、ユーザーの明示的な選択が優先され、セッション間で永続化されます。

手順

1. 言語設定を保存するためのCookieを作成する

長い有効期限を持つ、ユーザーが選択した言語を保持するCookieを定義します。

import { createCookie } from "react-router";

export const languagePreference = createCookie("language-preference", {
  maxAge: 31536000,
  httpOnly: false,
  secure: process.env.NODE_ENV === "production",
  sameSite: "lax",
});

このCookieは1年間保持され、クライアント側のコードから設定を読み取ることができます。

2. 言語選択を保存するアクションを追加する

言語選択フォームの送信を処理し、選択をCookieに保存するアクションを作成します。

import { redirect } from "react-router";
import type { Route } from "./+types/root";
import { languagePreference } from "./cookies";

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const selectedLanguage = formData.get("language");

  if (typeof selectedLanguage === "string") {
    return redirect(`/${selectedLanguage}`, {
      headers: {
        "Set-Cookie": await languagePreference.serialize(selectedLanguage),
      },
    });
  }

  return redirect("/");
}

ユーザーが言語選択を送信すると、このアクションはそれをCookieに保存し、適切な言語ルートにリダイレクトします。

3. 言語選択コンポーネントを作成する

ユーザーが希望する言語を選択できるフォームコンポーネントを構築します。

import { Form } from "react-router";

export function LanguageSelector({
  currentLanguage,
}: {
  currentLanguage: string;
}) {
  return (
    <Form method="post">
      <select
        name="language"
        defaultValue={currentLanguage}
        onChange={(e) => e.currentTarget.form?.requestSubmit()}
      >
        <option value="en">English</option>
        <option value="es">Español</option>
        <option value="fr">Français</option>
        <option value="de">Deutsch</option>
      </select>
    </Form>
  );
}

このコンポーネントは、ユーザーが選択を変更すると自動的に送信され、設定を保存するアクションをトリガーします。

4. ルートローダーで保存された設定を確認する

保存された言語設定を確認し、それに応じてリダイレクトするロジックをルートルートローダーに追加します。

import { redirect } from "react-router";
import type { Route } from "./+types/root";
import { languagePreference } from "./cookies";

export async function loader({ request }: Route.LoaderArgs) {
  const url = new URL(request.url);
  const cookieHeader = request.headers.get("Cookie");
  const storedLanguage = await languagePreference.parse(cookieHeader);

  if (url.pathname === "/" && storedLanguage) {
    return redirect(`/${storedLanguage}`);
  }

  return null;
}

ユーザーがルートパスにアクセスすると、このローダーは保存された言語設定を確認し、設定が存在する場合は選択された言語ルートにリダイレクトします。

5. 保存された言語をサポート対象ロケールと照合する

リダイレクトに使用する前に、保存された設定が有効であることを確認します。

import { redirect } from "react-router";
import type { Route } from "./+types/root";
import { languagePreference } from "./cookies";

const SUPPORTED_LANGUAGES = ["en", "es", "fr", "de"];

export async function loader({ request }: Route.LoaderArgs) {
  const url = new URL(request.url);
  const cookieHeader = request.headers.get("Cookie");
  const storedLanguage = await languagePreference.parse(cookieHeader);

  if (
    url.pathname === "/" &&
    storedLanguage &&
    SUPPORTED_LANGUAGES.includes(storedLanguage)
  ) {
    return redirect(`/${storedLanguage}`);
  }

  return null;
}

この検証により、Cookie値が改ざんされた場合や、設定が保存されてからサポート対象言語が変更された場合に、無効またはサポート対象外の言語ルートへのリダイレクトを防ぎます。