Next.js(Pages Router) v16에서 언어 전환 컴포넌트를 구축하는 방법

같은 페이지에 머물면서 언어 전환하기

문제

사용자들은 언어 전환기가 현재 위치를 유지해주기를 기대합니다. 누군가 영어로 된 제품 페이지를 보다가 스페인어로 전환할 때, 홈페이지로 리디렉션되는 것이 아니라 동일한 제품 페이지를 스페인어로 보고 싶어합니다. 이러한 기대를 충족시키지 못하면 마찰이 생기고 사용자가 이전 위치로 다시 이동해야 하므로 경험이 저하되고 결국 작업을 완전히 포기하게 될 수 있습니다.

많은 언어 전환기 구현이 실패하는 이유는 언어 선택을 현재 뷰의 변환이 아닌 단순한 네비게이션으로 취급하기 때문입니다. 현재 페이지의 라우팅 정보에 접근할 수 없으면, 전환기는 고정된 목적지로만 연결할 수 있어 사용자의 컨텍스트를 잃게 됩니다.

해결책

라우터에서 현재 경로의 pathname과 쿼리 매개변수를 읽고, 지원되는 각 언어에 대해 이 라우팅 정보를 유지하면서 로케일만 변경하는 링크를 생성하는 언어 전환기를 구축합니다. pathname과 쿼리를 대상 로케일과 함께 네비게이션 API에 전달함으로써, 전환기는 사용자가 새 언어에서도 동등한 페이지에 머물도록 보장합니다.

단계

1. 현재 경로를 읽는 언어 전환기 컴포넌트 생성

라우터 객체는 로케일 인식 링크를 구축하는 데 필요한 모든 정보를 포함하는 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 속성을 받습니다. 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-labelaria-current 속성은 스크린 리더 사용자를 위한 접근성을 향상시킵니다.