Cómo construir un componente de selector de idioma en Next.js (Pages Router) v16

Cambia de idioma sin salir de la misma página

Problema

Los usuarios esperan que los selectores de idioma conserven su ubicación actual. Cuando alguien visualiza una página de producto en inglés y cambia al español, quiere ver esa misma página de producto en español, no ser redirigido a la página de inicio. Romper esta expectativa crea fricción y obliga a los usuarios a navegar de vuelta a donde estaban, degradando la experiencia y potencialmente causando que abandonen la tarea por completo.

Muchas implementaciones de selectores de idioma fallan porque tratan la selección de idioma como una simple navegación en lugar de como una transformación de la vista actual. Sin acceso a la información de enrutamiento de la página actual, un selector solo puede enlazar a destinos fijos, perdiendo el contexto del usuario en el proceso.

Solución

Construir un selector de idioma que lea la ruta actual y los parámetros de consulta del enrutador, y luego genere enlaces para cada idioma soportado que preserven esta información de enrutamiento mientras cambia solo el idioma. Al pasar la ruta y la consulta a la API de navegación junto con el idioma de destino, el selector asegura que los usuarios permanezcan en la página equivalente en el nuevo idioma.

Pasos

1. Crear un componente selector de idioma que lea la ruta actual

El objeto router proporciona las propiedades pathname, asPath, query, locale y locales que contienen toda la información necesaria para construir enlaces adaptados al idioma.

import { useRouter } from "next/router";
import Link from "next/link";

export default function LanguageSwitcher() {
  const router = useRouter();
  const { locale, locales, pathname, asPath, query } = router;

  return (
    <nav>
      {locales?.map((loc) => (
        <Link key={loc} href={{ pathname, query }} as={asPath} locale={loc}>
          {loc.toUpperCase()}
        </Link>
      ))}
    </nav>
  );
}

El componente Link acepta una propiedad locale para transicionar a un idioma diferente del actualmente activo. Pasar pathname y query como un objeto a href preserva toda la información de enrutamiento, incluidos los valores de consulta de rutas dinámicas.

2. Estilizar el idioma activo para proporcionar retroalimentación visual

Resalta el idioma actual para que los usuarios sepan qué configuración regional están visualizando.

import { useRouter } from "next/router";
import Link from "next/link";

export default function LanguageSwitcher() {
  const router = useRouter();
  const { locale, locales, pathname, asPath, query } = router;

  return (
    <nav>
      {locales?.map((loc) => {
        const isActive = loc === locale;
        return (
          <Link
            key={loc}
            href={{ pathname, query }}
            as={asPath}
            locale={loc}
            style={{
              fontWeight: isActive ? "bold" : "normal",
              textDecoration: isActive ? "none" : "underline",
              marginRight: "1rem",
            }}
          >
            {loc.toUpperCase()}
          </Link>
        );
      })}
    </nav>
  );
}

Comparar cada configuración regional con el valor actual de locale identifica el idioma activo y aplica un estilo distintivo para diferenciarlo de las alternativas disponibles.

3. Añadir etiquetas accesibles usando react-intl

Reemplaza los códigos de idioma con nombres de idiomas legibles para mejorar la usabilidad.

import { useRouter } from "next/router";
import { useIntl } from "react-intl";
import Link from "next/link";

const localeNames: Record<string, string> = {
  en: "English",
  es: "Español",
  fr: "Français",
  de: "Deutsch",
};

export default function LanguageSwitcher() {
  const router = useRouter();
  const intl = useIntl();
  const { locale, locales, pathname, asPath, query } = router;

  return (
    <nav
      aria-label={intl.formatMessage({
        id: "languageSwitcher.label",
        defaultMessage: "Select language",
      })}
    >
      {locales?.map((loc) => {
        const isActive = loc === locale;
        return (
          <Link
            key={loc}
            href={{ pathname, query }}
            as={asPath}
            locale={loc}
            aria-current={isActive ? "true" : undefined}
            style={{
              fontWeight: isActive ? "bold" : "normal",
              textDecoration: isActive ? "none" : "underline",
              marginRight: "1rem",
            }}
          >
            {localeNames[loc] || loc}
          </Link>
        );
      })}
    </nav>
  );
}

El hook useIntl proporciona acceso a funciones de formateo para traducir etiquetas de interfaz. Los atributos aria-label y aria-current mejoran la accesibilidad para usuarios de lectores de pantalla.