Selector de Idioma
Esta guía muestra cómo construir un selector de idioma que permite a los usuarios navegar entre idiomas manteniéndose en la página equivalente.
La función switchLocalePath
switchLocalePath convierte una ruta de un idioma a otro, traduciendo slugs en ambas direcciones:
import { switchLocalePath } from 'virtual:i18n';
switchLocalePath('/es/sobre/', 'en'); // "/about/"switchLocalePath('/about/', 'es'); // "/es/sobre/"switchLocalePath('/es/saunas/modelo-165/', 'en'); // "/saunas/model-165/"Componente selector de idioma
Aquí tienes un selector de idioma completo usando las exportaciones de virtual:i18n:
---import { locales, localeLabels, switchLocalePath } from 'virtual:i18n';
const currentPath = Astro.url.pathname;const currentLocale = Astro.locals.locale;---
<nav aria-label="Idioma"> <ul> {locales.map(locale => ( <li> {locale === currentLocale ? ( <strong>{localeLabels[locale]}</strong> ) : ( <a href={switchLocalePath(currentPath, locale)}> {localeLabels[locale]} </a> )} </li> ))} </ul></nav>Agregando enlaces hreflang alternativos
Para SEO, agrega enlaces hreflang en el <head> de tu layout:
---import { locales, localeHtmlLang, switchLocalePath } from 'virtual:i18n';
const currentPath = Astro.url.pathname;---
<head> {locales.map(locale => ( <link rel="alternate" hreflang={localeHtmlLang[locale]} href={switchLocalePath(currentPath, locale)} /> ))} <link rel="alternate" hreflang="x-default" href={currentPath} /></head>Esto le indica a los motores de búsqueda sobre todas las versiones de idioma disponibles de cada página.
Banner de detección de idioma
Puedes ir más allá y detectar el idioma del navegador del usuario para sugerir un cambio. Este ejemplo usa i18next-browser-languagedetector para detectar el idioma preferido del lado del cliente y muestra un banner si no coincide con el idioma actual de la página.
Claves de traducción
Agrega los textos del banner a tus archivos de traducción:
{ "languageBanner": { "message": "This page is also available in your language.", "switch": "Switch", "dismiss": "Stay" }}{ "languageBanner": { "message": "Esta página también está disponible en tu idioma.", "switch": "Cambiar", "dismiss": "Quedarme" }}El componente
---import { locales, localeLabels } from 'virtual:i18n';import en from '../i18n/en.json';import es from '../i18n/es.json';
const bannerStrings = { en: en.languageBanner, es: es.languageBanner,};---
<div id="language-banner" class="language-banner hidden" role="alert" data-locales={JSON.stringify(locales)} data-locale-labels={JSON.stringify(localeLabels)} data-banner-strings={JSON.stringify(bannerStrings)}> <p id="banner-message"></p> <div> <button id="banner-dismiss"></button> <a id="banner-switch" href="#"></a> </div></div>
<script> import LanguageDetector from 'i18next-browser-languagedetector';
const banner = document.getElementById('language-banner')!; const locales: string[] = JSON.parse(banner.dataset.locales!); const bannerStrings: Record< string, { message: string; switch: string; dismiss: string } > = JSON.parse(banner.dataset.bannerStrings!);
// Detectar idioma del navegador (síncrono, sin necesidad de inicializar i18next) const detector = new LanguageDetector(null, { order: ['navigator'], caches: [], }); const rawDetected = detector.detect();
// Normalizar "en-US" → "en", "es-419" → "es" function toLocaleCode(lang: string | undefined): string | undefined { if (!lang) return undefined; const base = lang.split('-')[0].toLowerCase(); return locales.find((l) => l === base); }
const detectedLangs = Array.isArray(rawDetected) ? rawDetected : [rawDetected]; const detectedCode = detectedLangs.map(toLocaleCode).find(Boolean); const currentCode = toLocaleCode(document.documentElement.lang);
// Mostrar banner si el idioma detectado difiere y el usuario no ha elegido if (detectedCode && currentCode && detectedCode !== currentCode) { const storedPreference = localStorage.getItem('preferred-locale');
if (!storedPreference) { // Encontrar la URL alternativa desde los enlaces hreflang en <head> const alternateLink = document.querySelector( `link[rel="alternate"][hreflang="${detectedCode}"]` ) as HTMLLinkElement | null;
if (alternateLink) { const strings = bannerStrings[detectedCode]; document.getElementById('banner-message')!.textContent = strings.message;
const switchEl = document.getElementById( 'banner-switch' ) as HTMLAnchorElement; switchEl.textContent = strings.switch; switchEl.href = alternateLink.href;
document.getElementById('banner-dismiss')!.textContent = strings.dismiss;
// Mostrar el banner banner.classList.remove('hidden');
// "Cambiar" — guardar preferencia y navegar switchEl.addEventListener('click', () => { localStorage.setItem('preferred-locale', detectedCode); });
// "Quedarme" — guardar idioma actual como preferencia y ocultar document .getElementById('banner-dismiss')! .addEventListener('click', () => { localStorage.setItem('preferred-locale', currentCode); banner.classList.add('hidden'); }); } } }</script>Cómo funciona
- Detecta el idioma del navegador usando
i18next-browser-languagedetector(solo navigator, sin cookies) - Compara el idioma detectado con el
<html lang="...">de la página actual - Encuentra la URL alternativa desde las etiquetas
<link rel="alternate" hreflang="...">que agregaste en la sección hreflang anterior - Muestra el banner en el idioma detectado para que el usuario pueda leerlo
- Recuerda la elección en
localStoragepara que el banner no vuelva a aparecer