Как создать компонент переключения языка в React Router v7

Меняйте язык, оставаясь на той же странице

Проблема

Пользователи ожидают, что переключатель языка сохранит их текущий контекст. Например, если они просматривают страницу товара, справочную статью или настройки аккаунта, переход с английского на испанский должен показать ту же страницу на испанском. Однако во многих реализациях выбор языка воспринимается как переход на главную страницу на новом языке, и пользователю приходится возвращаться туда, где он был. Это сбивает с толку и раздражает, особенно в приложениях с большим количеством контента, где пользователь может находиться глубоко в структуре навигации.

Корень проблемы в том, что переключатели языка часто используют жёстко заданные URL-адреса вместо динамического формирования ссылок на основе текущей страницы. Если не анализировать и не преобразовывать текущую структуру URL, переключатель не сможет сохранить положение пользователя в приложении при смене языка.

Решение

Создайте компонент переключения языка, который читает текущий URL и извлекает как активный параметр локали, так и остальные сегменты пути. Для каждого поддерживаемого языка генерируйте новый URL, заменяя только сегмент локали, сохраняя все остальные части пути и параметры запроса без изменений. Отображайте эти URL как ссылки, чтобы пользователь мог сменить язык, не теряя своё место в приложении.

Такой подход рассматривает локаль как заменяемый параметр в структуре URL, а не как отдельный маршрут, что позволяет при переключении с /en/products/shoes на /es/products/shoes сохранять контекст пользователя.

Шаги

1. Создайте вспомогательную функцию для построения URL с учётом локали

Определите функцию, которая принимает текущий путь и целевую локаль, а затем формирует новый путь, заменяя сегмент локали.

export function buildLocalePath(
  currentPath: string,
  newLocale: string,
): string {
  const segments = currentPath.split("/").filter(Boolean);

  if (segments.length === 0) {
    return `/${newLocale}`;
  }

  segments[0] = newLocale;
  return `/${segments.join("/")}`;
}

Эта функция разбивает путь на сегменты, заменяет первый сегмент на целевую локаль и собирает путь обратно. Она обрабатывает особые случаи, такие как корневой путь, и гарантирует, что локаль всегда будет первым сегментом.

2. Определите поддерживаемые локали

Создайте объект конфигурации, в котором перечислены все языки, поддерживаемые вашим приложением.

export const locales = [
  { code: "en", label: "English" },
  { code: "es", label: "Español" },
  { code: "fr", label: "Français" },
  { code: "de", label: "Deutsch" },
];

Эта конфигурация служит источником правды для языков, которые отображаются в переключателе, и содержит удобные для пользователя подписи для каждой локали.

3. Создайте компонент переключения языка

Создайте компонент, который читает текущий адрес, определяет активную локаль и отображает ссылки для всех других поддерживаемых языков.

import { Link, useLocation, useParams } from "react-router";
import { locales, buildLocalePath } from "./i18n-config";

export function LanguageSwitcher() {
  const location = useLocation();
  const params = useParams();
  const currentLocale = params.locale || "en";

  return (
    <nav aria-label="Language switcher">
      <ul>
        {locales.map((locale) => {
          const isActive = locale.code === currentLocale;
          const newPath = buildLocalePath(location.pathname, locale.code);

          return (
            <li key={locale.code}>
              {isActive ? (
                <span aria-current="true">{locale.label}</span>
              ) : (
                <Link to={newPath}>{locale.label}</Link>
              )}
            </li>
          );
        })}
      </ul>
    </nav>
  );
}

Компонент использует useLocation для доступа к текущему пути и useParams для извлечения активной локали из URL. Для каждой поддерживаемой локали он генерирует новый путь с помощью вспомогательной функции и отображает либо ссылку, либо неактивный элемент для текущего языка.

4. Сохраняйте параметры запроса и фрагменты якорей

Расширьте вспомогательную функцию, чтобы сохранять строки запроса и фрагменты URL при переключении языка.

export function buildLocalePath(
  currentPath: string,
  search: string,
  hash: string,
  newLocale: string,
): string {
  const segments = currentPath.split("/").filter(Boolean);

  if (segments.length === 0) {
    return `/${newLocale}${search}${hash}`;
  }

  segments[0] = newLocale;
  return `/${segments.join("/")}${search}${hash}`;
}

В этой обновлённой версии принимаются свойства search и hash из объекта location и добавляются к сгенерированному пути, чтобы фильтры, параметры сортировки и якорные ссылки сохранялись при смене языка.

5. Обновите компонент для использования улучшённой функции

Измените переключатель, чтобы он передавал полную информацию о местоположении во вспомогательную функцию.

import { Link, useLocation, useParams } from "react-router";
import { locales, buildLocalePath } from "./i18n-config";

export function LanguageSwitcher() {
  const location = useLocation();
  const params = useParams();
  const currentLocale = params.locale || "en";

  return (
    <nav aria-label="Language switcher">
      <ul>
        {locales.map((locale) => {
          const isActive = locale.code === currentLocale;
          const newPath = buildLocalePath(
            location.pathname,
            location.search,
            location.hash,
            locale.code,
          );

          return (
            <li key={locale.code}>
              {isActive ? (
                <span aria-current="true">{locale.label}</span>
              ) : (
                <Link to={newPath}>{locale.label}</Link>
              )}
            </li>
          );
        })}
      </ul>
    </nav>
  );
}

Теперь компонент передаёт location.search и location.hash в helper, чтобы при переключении на испанский такие URL, как /en/products?category=shoes#reviews, становились /es/products?category=shoes#reviews.