Next.js(Pages Router)v16で言語切替コンポーネントを構築する方法

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

問題

ユーザーは言語切り替え時に現在の位置が保持されることを期待しています。英語の商品ページを閲覧している人がスペイン語に切り替えた場合、ホームページにリダイレクトされるのではなく、同じ商品ページをスペイン語で表示してほしいと考えています。この期待を裏切ると摩擦が生じ、ユーザーは元の場所に戻るために再度ナビゲーションを強いられ、エクスペリエンスが低下し、最終的にはタスクを放棄する可能性があります。

多くの言語切り替え実装は、言語選択を現在のビューの変換としてではなく、単純なナビゲーションとして扱うため失敗します。現在のページのルーティング情報にアクセスできなければ、切り替え機能は固定された宛先にしかリンクできず、プロセス中にユーザーのコンテキストが失われてしまいます。

解決策

ルーターから現在のルートのパス名とクエリパラメータを読み取り、このルーティング情報を保持しながらロケールのみを変更する各サポート言語のリンクを生成する言語切り替え機能を構築します。パス名とクエリをターゲットロケールと共にナビゲーションAPIに渡すことで、切り替え機能はユーザーが新しい言語でも同等のページに留まることを保証します。

ステップ

1. 現在のルートを読み取る言語切り替えコンポーネントを作成する

ルーターオブジェクトは、ロケール対応リンクを構築するために必要なすべての情報を含むpathnameasPathquerylocale、および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プロパティを受け入れます。pathnamequeryをオブジェクトとして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属性は、スクリーンリーダーユーザーのアクセシビリティを向上させます。