Next.js(Pages Router)v16で言語切り替えコンポーネントを構築する方法
同じページに留まりながら言語を切り替える
問題
ユーザーは言語切り替え機能が現在の位置を保持することを期待しています。英語で商品ページを閲覧している人がスペイン語に切り替えた場合、ホームページにリダイレクトされるのではなく、同じ商品ページをスペイン語で表示したいと考えます。この期待を裏切ると摩擦が生じ、ユーザーは元の場所に戻るために再度ナビゲートする必要があり、エクスペリエンスが低下し、タスクを完全に放棄する可能性があります。
多くの言語切り替え実装が失敗するのは、言語選択を現在のビューの変換としてではなく、単純なナビゲーションとして扱うためです。現在のページのルーティング情報にアクセスできない場合、切り替え機能は固定された宛先にのみリンクでき、ユーザーのコンテキストが失われます。
解決策
ルーターから現在のルートのパス名とクエリパラメータを読み取り、ロケールのみを変更しながらこのルーティング情報を保持する各サポート言語のリンクを生成する言語切り替え機能を構築します。ターゲットロケールと共にパス名とクエリをナビゲーションAPIに渡すことで、切り替え機能はユーザーが新しい言語の同等のページに留まることを保証します。
手順
1. 現在のルートを読み取る言語切り替えコンポーネントを作成する
routerオブジェクトは、pathname、asPath、query、locale、localesプロパティを提供し、ロケール対応リンクを構築するために必要なすべての情報を含んでいます。
import { useRouter } from "next/router";
import Link from "next/link";
export default function LanguageSwitcher() {
const router = useRouter();
const { locale, locales, pathname, asPath, query } = router;
return (
<nav>
{locales?.map((loc) => (
<Link key={loc} href={{ pathname, query }} as={asPath} locale={loc}>
{loc.toUpperCase()}
</Link>
))}
</nav>
);
}
Linkコンポーネントは、現在アクティブなロケールから別のロケールに移行するためのlocaleプロパティを受け入れます。pathnameとqueryをオブジェクトとしてhrefに渡すことで、動的ルートのクエリ値を含むすべてのルーティング情報が保持されます。
2. アクティブなロケールをスタイル設定して視覚的なフィードバックを提供する
ユーザーがどのロケールを表示しているかがわかるように、現在の言語を強調表示します。
import { useRouter } from "next/router";
import Link from "next/link";
export default function LanguageSwitcher() {
const router = useRouter();
const { locale, locales, pathname, asPath, query } = router;
return (
<nav>
{locales?.map((loc) => {
const isActive = loc === locale;
return (
<Link
key={loc}
href={{ pathname, query }}
as={asPath}
locale={loc}
style={{
fontWeight: isActive ? "bold" : "normal",
textDecoration: isActive ? "none" : "underline",
marginRight: "1rem",
}}
>
{loc.toUpperCase()}
</Link>
);
})}
</nav>
);
}
各ロケールを現在のlocaleの値と比較することで、アクティブな言語を識別し、利用可能な代替言語と区別するための独自のスタイルを適用します。
3. react-intlを使用してアクセシブルなラベルを追加する
ロケールコードを人間が読みやすい言語名に置き換えて、使いやすさを向上させます。
import { useRouter } from "next/router";
import { useIntl } from "react-intl";
import Link from "next/link";
const localeNames: Record<string, string> = {
en: "English",
es: "Español",
fr: "Français",
de: "Deutsch",
};
export default function LanguageSwitcher() {
const router = useRouter();
const intl = useIntl();
const { locale, locales, pathname, asPath, query } = router;
return (
<nav
aria-label={intl.formatMessage({
id: "languageSwitcher.label",
defaultMessage: "Select language",
})}
>
{locales?.map((loc) => {
const isActive = loc === locale;
return (
<Link
key={loc}
href={{ pathname, query }}
as={asPath}
locale={loc}
aria-current={isActive ? "true" : undefined}
style={{
fontWeight: isActive ? "bold" : "normal",
textDecoration: isActive ? "none" : "underline",
marginRight: "1rem",
}}
>
{localeNames[loc] || loc}
</Link>
);
})}
</nav>
);
}
useIntlフックは、UIラベルを翻訳するためのフォーマット関数へのアクセスを提供します。aria-label属性とaria-current属性は、スクリーンリーダーユーザーのアクセシビリティを向上させます。