Cómo mantener el idioma en los enlaces de navegación en React Router v7

Mantén el idioma durante la navegación interna

Problema

Cuando la información de localización está codificada en la ruta URL, cada enlace de navegación debe preservar esa localización para mantener una experiencia de usuario consistente. Si un usuario navega por la versión francesa de tu sitio y hace clic en un enlace a /about, espera permanecer en francés y navegar a /fr/about. Sin enlaces adaptados a la localización, los usuarios son dirigidos al idioma predeterminado en medio de la sesión, interrumpiendo su contexto de navegación y obligándolos a cambiar manualmente de idioma otra vez. Esto crea fricción y socava la experiencia localizada.

Codificar manualmente el prefijo de localización en cada enlace es propenso a errores y hace que la base de código sea frágil. A medida que los usuarios navegan por la aplicación, la localización activa puede cambiar, y actualizar manualmente cientos de enlaces se vuelve insostenible.

Solución

Crea un componente Link personalizado que automáticamente lea la localización actual desde la URL y la anteponga a todas las rutas de navegación internas. Al envolver el componente Link de React Router, centralizas el manejo de la localización en un solo lugar. El wrapper extrae el parámetro de localización de la ruta actual y asegura que cada ruta de destino lo incluya, de modo que la navegación preserve la elección de idioma del usuario sin intervención manual.

Este enfoque mantiene las definiciones de enlaces limpias y agnósticas respecto a la localización en toda tu aplicación, garantizando que el contexto de localización viaje con cada clic.

Pasos

Construye un componente personalizado que utilice useParams para extraer la localización actual de la URL y envuelva el componente Link de React Router para anteponer la localización a la ruta de destino.

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} />;
}

Este componente lee el parámetro de localización de la ruta actual y automáticamente lo prefija a cualquier ruta que pases a la prop to, manejando tanto formas de cadena como de objeto.

Reemplaza los componentes Link estándar con LocaleLink siempre que necesites una navegación que preserve el idioma.

import { LocaleLink } from "./LocaleLink";

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

Cuando un usuario en /fr/products hace clic en el enlace About, navega a /fr/about. El prefijo de idioma se añade automáticamente sin sobrecargar la definición del enlace.

3. Maneja casos especiales para rutas absolutas y enlaces externos

Amplía el wrapper para detectar cuando una ruta ya incluye el idioma o apunta a una URL externa, evitando dobles prefijos o rompiendo la navegación externa.

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} />;
}

Esto protege contra la doble prefijación si una ruta ya comienza con el idioma y pasa las URLs externas sin cambios, asegurando que el componente funcione de manera confiable en todos los escenarios de navegación.