Как сохранять локаль в навигационных ссылках в React Router v7

Сохраняйте локаль при внутренней навигации

Проблема

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

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

Решение

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

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

Шаги

Создайте собственный компонент, который использует useParams для извлечения текущей локали из URL и оборачивает компонент Link из React Router, чтобы добавлять локаль к целевому пути.

import { Link, useParams } from "react-router";
import type { LinkProps } from "react-router";

export function LocaleLink({ to, ...props }: LinkProps) {
  const { locale } = useParams<{ locale: string }>();

  const localizedTo =
    typeof to === "string"
      ? `/${locale}${to.startsWith("/") ? to : `/${to}`}`
      : {
          ...to,
          pathname: `/${locale}${to.pathname?.startsWith("/") ? to.pathname : `/${to.pathname}`}`,
        };

  return <Link to={localizedTo} {...props} />;
}

Этот компонент считывает параметр locale из текущего маршрута и автоматически добавляет его в начало любого пути, который вы передаёте в prop to, поддерживая как строковые, так и объектные формы.

Заменяйте стандартные компоненты Link на LocaleLink везде, где нужно сохранять локаль при навигации.

import { LocaleLink } from "./LocaleLink";

export function Navigation() {
  return (
    <nav>
      <LocaleLink to="/">Home</LocaleLink>
      <LocaleLink to="/about">About</LocaleLink>
      <LocaleLink to="/products">Products</LocaleLink>
    </nav>
  );
}

Когда пользователь на /fr/products кликает по ссылке About, он переходит на /fr/about. Префикс локали добавляется автоматически, не усложняя определение ссылки.

3. Обрабатывайте крайние случаи для абсолютных путей и внешних ссылок

Расширьте обёртку, чтобы определять, когда путь уже содержит локаль или ведёт на внешний URL, чтобы избежать двойного префикса или ошибок при переходе на внешние ресурсы.

import { Link, useParams } from "react-router";
import type { LinkProps } from "react-router";

export function LocaleLink({ to, ...props }: LinkProps) {
  const { locale } = useParams<{ locale: string }>();

  if (!locale) {
    return <Link to={to} {...props} />;
  }

  const isExternal =
    typeof to === "string" &&
    (to.startsWith("http://") || to.startsWith("https://"));
  const alreadyLocalized =
    typeof to === "string" && to.startsWith(`/${locale}/`);

  if (isExternal || alreadyLocalized) {
    return <Link to={to} {...props} />;
  }

  const localizedTo =
    typeof to === "string"
      ? `/${locale}${to.startsWith("/") ? to : `/${to}`}`
      : {
          ...to,
          pathname: `/${locale}${to.pathname?.startsWith("/") ? to.pathname : `/${to.pathname}`}`,
        };

  return <Link to={localizedTo} {...props} />;
}

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