TanStack Start v1でユーザーの言語設定を検出する方法

ブラウザの設定に基づいた自動リダイレクト

問題

Webブラウザは、すべてのHTTPリクエストでAccept-Languageヘッダーを送信し、ユーザーが優先する言語を優先順位順に示します。このヘッダーは、ユーザーが最も使いやすい言語に関する貴重な情報を提供しますが、多くのアプリケーションはこれを完全に無視しています。代わりに、実際の設定に関係なく、すべての訪問者にデフォルト言語(通常は英語)を表示します。その後、ユーザーは手動で言語切り替え機能を探す必要があり、アプリケーションがより適切な初期選択を行うために必要な情報をすでに持っているにもかかわらず、不要な摩擦が生じます。

このパーソナライゼーションの機会損失は、ブラウザの言語設定を明示的に構成した非英語話者にとって特に不満です。その結果、第一印象が悪化し、ユーザーが希望する言語でコンテンツを利用する前に追加の手順が必要になります。

解決策

ルートパスへのリクエストをインターセプトし、Accept-Languageヘッダーを調べてユーザーの優先言語を判断します。ヘッダーを解析して、アプリケーションがサポートする最優先の言語を抽出します。一致するものが見つかった場合は、適切なローカライズされたルートにユーザーをリダイレクトします。ヘッダーにサポートされている言語が見つからない場合は、デフォルトの言語フォールバックにリダイレクトします。

このアプローチは、各言語が独自のパスプレフィックスを持つ明確なURL構造を維持しながら、ユーザーの設定を自動的に尊重します。リダイレクトはページがレンダリングされる前にサーバー側で行われるため、ユーザーは間違った言語のフラッシュを見ることなく、希望する言語のコンテンツに直接アクセスできます。

手順

1. Accept-Languageヘッダーを解析するヘルパーを作成する

Accept-Languageヘッダーには、優先順位を示すオプションの品質値を持つ言語コードが含まれています。これらの言語を抽出し、優先度順にソートし、サポートされている言語リストから最初の一致を見つけるパーサーを構築します。

export function parseAcceptLanguage(
  header: string | null,
  supportedLocales: string[],
): string | null {
  if (!header) {
    return null;
  }

  const languages = header
    .split(",")
    .map((lang) => {
      const [code, qValue] = lang.trim().split(";q=");
      const quality = qValue ? parseFloat(qValue) : 1.0;
      return { code: code.toLowerCase(), quality };
    })
    .sort((a, b) => b.quality - a.quality);

  for (const { code } of languages) {
    const exactMatch = supportedLocales.find(
      (locale) => locale.toLowerCase() === code,
    );
    if (exactMatch) {
      return exactMatch;
    }

    const baseCode = code.split("-")[0];
    const baseMatch = supportedLocales.find((locale) =>
      locale.toLowerCase().startsWith(baseCode),
    );
    if (baseMatch) {
      return baseMatch;
    }
  }

  return null;
}

この関数は、ヘッダーをカンマで分割し、品質値を抽出し、優先度順にソートし、サポートされているロケールに対して完全一致と基本言語一致の両方をチェックします。

2. サポートされているロケールを定義する

アプリケーションがサポートするすべての言語をリストし、デフォルトのフォールバックとして使用する言語を指定する設定を作成します。

export const SUPPORTED_LOCALES = ["en", "fr", "de", "es", "ja"];
export const DEFAULT_LOCALE = "en";

これらの定数は言語設定を一元化し、アプリケーションの成長に合わせてサポートされている言語を簡単に追加または削除できるようにします。

3. ルートパス用のサーバールートハンドラーを作成する

インデックスルートにサーバーサイドのGETハンドラーを追加し、Accept-Languageヘッダーを読み取り、最適なロケールを決定し、ユーザーを適切な言語固有のパスにリダイレクトします。

import { createFileRoute, redirect } from "@tanstack/react-router";
import { getRequestHeaders } from "@tanstack/react-start/server";

export const Route = createFileRoute("/")({
  server: {
    handlers: {
      GET: async () => {
        const headers = getRequestHeaders();
        const acceptLanguage = headers.get("accept-language");

        const preferredLocale = parseAcceptLanguage(
          acceptLanguage,
          SUPPORTED_LOCALES,
        );

        const targetLocale = preferredLocale || DEFAULT_LOCALE;

        throw redirect({
          to: `/${targetLocale}`,
          statusCode: 302,
        });
      },
    },
  },
});

このハンドラーは、ユーザーがルートパスにアクセスしたときにサーバー上で実行され、言語設定を確認し、クライアントサイドのコードが実行される前に、最適に一致するロケールルートに即座にリダイレクトします。

4. ヘルパー関数をインポートする

ロケール設定と一緒にファイルの先頭でインポートすることで、ルートファイルでparseヘルパーが利用できるようにします。

import { createFileRoute, redirect } from "@tanstack/react-router";
import { getRequestHeaders } from "@tanstack/react-start/server";
import {
  parseAcceptLanguage,
  SUPPORTED_LOCALES,
  DEFAULT_LOCALE,
} from "../lib/locale";

parseAcceptLanguage関数とロケール定数を共有モジュールに配置し、必要に応じてアプリケーション全体で再利用できるようにします。