Как создать компонент переключения языка в Next.js (Pages Router) v16
Меняйте язык, оставаясь на той же странице
Проблема
Пользователи ожидают, что переключатель языка сохранит их текущее местоположение. Если кто-то просматривает страницу товара на английском и переключается на испанский, он хочет увидеть ту же страницу товара, только на испанском, а не быть перенаправлённым на главную. Нарушение этого ожидания вызывает неудобства и заставляет пользователя возвращаться обратно, что ухудшает опыт и может привести к тому, что пользователь вообще уйдёт.
Многие реализации переключателей языка не работают, потому что воспринимают выбор языка как обычную навигацию, а не как преобразование текущего представления. Без доступа к информации о текущем маршруте переключатель может только вести на фиксированные страницы, теряя контекст пользователя.
Решение
Создайте переключатель языка, который читает pathname и query-параметры текущего маршрута из роутера, а затем генерирует ссылки для каждого поддерживаемого языка, сохраняя эти параметры и меняя только локаль. Передавая pathname и query в навигационный 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 для перехода на другую локаль из текущей. Передача pathname и query как объекта в href сохраняет всю информацию о маршруте, включая значения динамических query-параметров.
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 улучшают доступность для пользователей скринридеров.