Cómo crear un componente selector de idioma en React Router v7

Cambia de idioma sin salir de la página actual

Problema

Los usuarios esperan que los selectores de idioma preserven su contexto actual. Al navegar por una página de producto, artículo de ayuda o configuración de cuenta, cambiar de inglés a español debería mostrar esa misma página en español. En cambio, muchas implementaciones tratan la selección de idioma como un evento de navegación que redirige a los usuarios a la página de inicio en el nuevo idioma, obligándolos a navegar de vuelta a donde estaban. Esto interrumpe el flujo del usuario y genera frustración, especialmente en aplicaciones con mucho contenido donde los usuarios pueden estar en lo profundo de una jerarquía de navegación.

La causa raíz es que los selectores de idioma a menudo utilizan URLs de destino codificadas en lugar de construir URLs dinámicamente basándose en la página actual. Sin leer y transformar la estructura de la URL actual, el selector no puede mantener la posición del usuario en la aplicación a través de los cambios de idioma.

Solución

Crea un componente selector de idioma que lea la URL actual y extraiga tanto el parámetro de locale activo como los segmentos de ruta restantes. Para cada idioma soportado, genera una nueva URL reemplazando únicamente el segmento de locale mientras mantienes intactos todos los demás segmentos de ruta y parámetros de consulta. Renderiza estas URLs como enlaces para que los usuarios puedan cambiar de idioma sin perder su ubicación en la aplicación.

Este enfoque trata el locale como un parámetro reemplazable en la estructura de la URL en lugar de un destino de navegación, asegurando que cambiar de /en/products/shoes a /es/products/shoes preserve el contexto del usuario.

Pasos

1. Crea una función auxiliar para construir URLs con locale

Define una función que tome la ruta actual y una configuración regional de destino, luego construya una nueva ruta reemplazando el segmento de configuración regional.

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("/")}`;
}

Esta función divide la ruta en segmentos, reemplaza el primer segmento con la configuración regional de destino y reconstruye la ruta. Maneja casos especiales como la ruta raíz y garantiza que la configuración regional sea siempre el primer segmento.

2. Define tus configuraciones regionales compatibles

Crea un objeto de configuración que enumere todos los idiomas que tu aplicación admite.

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

Esta configuración sirve como fuente de verdad para determinar qué idiomas mostrar en el selector y proporciona etiquetas fáciles de usar para cada configuración regional.

3. Construye el componente selector de idioma

Crea un componente que lea la ubicación actual, determine la configuración regional activa y renderice enlaces para todos los demás idiomas compatibles.

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

El componente utiliza useLocation para acceder a la ruta actual y useParams para extraer la configuración regional activa de la URL. Para cada configuración regional compatible, genera una nueva ruta usando la función auxiliar y renderiza un enlace o un elemento no interactivo para el idioma actual.

4. Preserva los parámetros de consulta y fragmentos hash

Extiende la función auxiliar para mantener las cadenas de consulta y fragmentos de URL al cambiar de idioma.

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

Esta versión actualizada acepta las propiedades search y hash del objeto location y las añade a la ruta generada, garantizando que los filtros, parámetros de ordenación y enlaces de anclaje sobrevivan al cambio de idioma.

5. Actualiza el componente para usar la función auxiliar mejorada

Modifica el selector para pasar la información completa de ubicación a la función auxiliar.

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

El componente ahora pasa location.search y location.hash al helper, asegurando que las URL como /en/products?category=shoes#reviews se conviertan en /es/products?category=shoes#reviews al cambiar a español.