Как поддерживать локаль в навигационных ссылках в 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} />;
}

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

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

import { LocaleLink } from "./LocaleLink";

export function Navigation() {
  return (
    <nav>
      <LocaleLink to="/">Главная</LocaleLink>
      <LocaleLink to="/about">О нас</LocaleLink>
      <LocaleLink to="/products">Продукты</LocaleLink>
    </nav>
  );
}

Когда пользователь на странице /fr/products нажимает на ссылку "О нас", он переходит на /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 без изменений, обеспечивая надежную работу компонента во всех сценариях навигации.