React Router v7でナビゲーションリンクのロケールを維持する方法
内部ナビゲーション間でロケールを維持する
問題
URLパスにロケール情報がエンコードされている場合、一貫したユーザー体験を維持するためにすべてのナビゲーションリンクはそのロケールを保持する必要があります。ユーザーがサイトのフランス語バージョンを閲覧し、/aboutへのリンクをクリックした場合、フランス語のままで/fr/aboutに移動することを期待します。ロケール対応のリンクがなければ、ユーザーはセッション途中でデフォルト言語に戻されてしまい、ブラウジングのコンテキストが崩れ、手動で再度言語を切り替える必要が生じます。これにより摩擦が生じ、ローカライズされた体験が損なわれます。
すべてのリンクにロケールプレフィックスをハードコーディングすることはエラーが発生しやすく、コードベースを脆弱にします。ユーザーがアプリケーション内を移動するにつれて、アクティブなロケールが変更される可能性があり、何百ものリンクを手動で更新することは持続不可能になります。
解決策
URLから現在のロケールを自動的に読み取り、すべての内部ナビゲーションパスの前にそれを付加するカスタムLinkコンポーネントを作成します。React RouterのLinkコンポーネントをラップすることで、ロケール処理を一箇所に集中させます。このラッパーは現在のルートからロケールパラメータを抽出し、すべての宛先パスにそれを含めることで、手動介入なしでユーザーの言語選択を保持します。
このアプローチにより、アプリケーション全体でリンク定義をクリーンでロケールに依存しないままにしながら、ロケールコンテキストがクリックごとに引き継がれることを保証します。
ステップ
1. ロケール対応のLinkラッパーコンポーネントを作成する
useParamsを使用してURLから現在のロケールを抽出し、React RouterのLinkコンポーネントをラップして宛先パスの前にロケールを付加するカスタムコンポーネントを構築します。
import { Link, useParams } from "react-router";
import type { LinkProps } from "react-router";
export function LocaleLink({ to, ...props }: LinkProps) {
const { locale } = useParams<{ locale: string }>();
const localizedTo =
typeof to === "string"
? `/${locale}${to.startsWith("/") ? to : `/${to}`}`
: {
...to,
pathname: `/${locale}${to.pathname?.startsWith("/") ? to.pathname : `/${to.pathname}`}`,
};
return <Link to={localizedTo} {...props} />;
}
このコンポーネントは現在のルートからロケールパラメータを読み取り、toプロパティに渡すパスの前に自動的にそれを付加し、文字列形式とオブジェクト形式の両方を処理します。
2. アプリケーション全体でLocaleLinkコンポーネントを使用する
ロケールを保持するナビゲーションが必要な場所では、標準のLinkコンポーネントをLocaleLinkに置き換えてください。
import { LocaleLink } from "./LocaleLink";
export function Navigation() {
return (
<nav>
<LocaleLink to="/">Home</LocaleLink>
<LocaleLink to="/about">About</LocaleLink>
<LocaleLink to="/products">Products</LocaleLink>
</nav>
);
}
/fr/productsページにいるユーザーがAboutリンクをクリックすると、/fr/aboutに移動します。リンク定義を複雑にすることなく、ロケールプレフィックスが自動的に追加されます。
3. 絶対パスと外部リンクのエッジケースを処理する
パスにすでにロケールが含まれている場合や外部URLを指している場合を検出するようにラッパーを拡張し、二重プレフィックスや外部ナビゲーションの破損を回避します。
import { Link, useParams } from "react-router";
import type { LinkProps } from "react-router";
export function LocaleLink({ to, ...props }: LinkProps) {
const { locale } = useParams<{ locale: string }>();
if (!locale) {
return <Link to={to} {...props} />;
}
const isExternal =
typeof to === "string" &&
(to.startsWith("http://") || to.startsWith("https://"));
const alreadyLocalized =
typeof to === "string" && to.startsWith(`/${locale}/`);
if (isExternal || alreadyLocalized) {
return <Link to={to} {...props} />;
}
const localizedTo =
typeof to === "string"
? `/${locale}${to.startsWith("/") ? to : `/${to}`}`
: {
...to,
pathname: `/${locale}${to.pathname?.startsWith("/") ? to.pathname : `/${to.pathname}`}`,
};
return <Link to={localizedTo} {...props} />;
}
これにより、パスがすでにロケールで始まる場合の二重プレフィックスを防ぎ、外部URLを変更せずに渡すことで、コンポーネントがすべてのナビゲーションシナリオで確実に機能するようになります。