Guías
Estrategia de URL
Dos formas de llevar el idioma: en la dirección, o en ninguna parte. Esta página va sobre elegir una a conciencia, porque esa elección decide tu SEO y cambiarla después es caro.
Las dos estrategias
| Estrategia | Lo que ve el visitante | Cambiar de idioma es |
|---|---|---|
| En la dirección | /docs en inglés, /es/docs en español | Navegar a esa dirección |
| Fuera de la dirección | /docs en todos los idiomas | El texto cambiando donde está |
Verbaly admite las dos. Este sitio pone el idioma en la dirección en todas sus páginas, porque a la documentación se llega buscando. Una app detrás de un login es el otro caso: cambia de idioma con una llamada y sin navegar, desde una sola dirección. La estrategia la manda la superficie, no tu gusto.
Ponle nombre al modo
Normalmente no escribes nada. Verbaly lee tu montaje: si construyes una versión de tu sitio por idioma tienes direcciones por idioma, y si no, una sola dirección sirve para todos. Solo lo dices en voz alta para llevar la contraria.
export default {
sourceLocale: 'en',
locales: ['en', 'es', 'pt'],
routing: 'prefix-all', // only when you disagree with what it read
};| Modo | La dirección | Cuándo |
|---|---|---|
prefix-except-source | /docs y /es/docs | Todo lo que la gente encuentra buscando, que es casi cualquier sitio público |
prefix-all | /en/docs y /es/docs | Cuando ningún idioma es el de la casa y todos son invitados |
no-prefix | /docs en todos los idiomas | Detrás de un login, donde nadie llega desde un resultado de búsqueda |
Cambiar de idioma es una línea, y es la misma línea en todos los modos. Impórtala de virtual:verbaly, donde ya conoce tus idiomas, tu fuente y tu modo.
Con el idioma en la dirección va a esa dirección, con su query y su ancla, y esa página ya está escrita en español, así que no hay espera ni parpadeo. Sin él, el texto cambia donde está y la dirección no se mueve. En los dos casos recuerda la elección y ajusta el idioma de la página. Dale el router de tu framework y tu aplicación sobrevive al cambio:
npx verbaly doctor te dice en qué modo estás y si eso lo leyó de tu configuración o lo dedujo, para que un modo que nadie escribió no parezca una decisión que alguien tomó. También te avisa cuando tu configuración pide dos cosas que no pueden ser ciertas a la vez.
La cabecera es la mitad de lo que enseña un resultado de búsqueda
Una página puede estar traducida en todo lo que se ve y aun así decirle a un buscador que está en inglés. El título y la descripción viven en el <head>, donde nadie los ve en la página, así que es fácil olvidarlos. Bindéalos como cualquier otro texto: son casi todo lo que enseña un resultado de búsqueda.
<title data-verbaly="page.title">URL strategy</title>
<meta name="description" content="…" data-verbaly-attr='{"content":"page.desc"}'>verbaly render los rellena por idioma como el resto de la página, y cuenta las páginas cuyo título nunca cambia para que te enteres una vez y no por un resultado de búsqueda. Nunca tumba un build: el nombre de un producto puede no traducirse.
Cuando la dirección lleva el idioma, manda ella
En cuanto el idioma está en la dirección, esa es la respuesta y nada la sobrescribe. Alguien abre /es/pricing y le sale español, aunque ayer eligiera inglés aquí. Una dirección se comparte, se enlaza y se cachea; una elección guardada es de una sola persona. La otra mitad importa igual: una página sin prefijo es tu idioma fuente, así que quien un día eligió español nunca se encuentra tus páginas en inglés traducidas sin avisar.
La elección guardada y el idioma del navegador siguen decidiendo una cosa, y solo una: a dónde mandar a quien llega sin idioma en la dirección. Ver Servidor para el cableado por framework.
La única diferencia que no se puede esquivar
Los buscadores indexan direcciones, no idiomas. Si tu español y tu inglés comparten una dirección, solo uno de los dos puede indexarse, y nada que hagas en runtime cambia eso. hreflang es como le dices a un buscador que dos direcciones son la misma página en idiomas distintos, así que con una sola dirección no hay nada que conectar. Google dice lo mismo: una URL por idioma, ni una cookie ni un ajuste del navegador.
Qué te da Verbaly, según cómo renderizas
Mantener el idioma estable entre un clic y un F5 es un problema distinto según quién construye el HTML. Estos son los tres casos y lo que pide cada uno.
| Renderizas | En la dirección | Fuera de la dirección |
|---|---|---|
| En el navegador, una SPA con router de cliente | localeFromPath lee el idioma de la dirección en cada cambio de ruta | resolveLocale + persistLocale. Un clic nunca recarga, y un F5 vuelve a leer la elección guardada |
| En un servidor, SvelteKit, Nuxt o Next.js | El router de tu framework es dueño del segmento, Verbaly de los catálogos | resolveRequestLocale lee la cookie en cada petición, así que el documento llega ya traducido. Cableado por ti en las tres integraciones |
| En el build, Astro, Eleventy o cualquier SSG | verbaly render escribe tu sitio en cada idioma, con hreflang y un sitemap | Nada lo hace bien. Se sirve un solo documento a todo el mundo, así que el idioma solo puede cambiar después de que llegue |
Por qué esa última casilla está vacía en todas partes, no solo aquí
Un hosting estático manda el mismo archivo a todos. Para mostrar otro idioma sin cambiar la dirección algo tiene que correr antes de que se envíe el HTML, y en un sitio estático no corre nada. Las librerías que ofrecen este modo lo resuelven todas con un servidor leyendo una cookie: next-intl reescribe en su middleware, y dice claramente que con el export estático de Next.js ese middleware no corre. Verbaly no va a fingir lo contrario. Tus opciones, en el orden en que las probaríamos:
- Ponerlo en la dirección.
verbaly render, yrender.redirectpara que el visitante aterrice en su idioma sin ver nunca cómo se decide. Es lo que hace este sitio. - Un subdominio por idioma.
es.example.comno lleva segmento en la ruta y aun así tiene dirección propia, así que el buscador sigue funcionando. Esta es la opción que todo el mundo olvida. - Reescribir en el edge. Tu hosting puede servir el archivo pre-renderizado de
/es/en la dirección pelada según una cookie. Funciona, y te cuesta: un solo idioma indexado, y una entrada de caché por valor de cookie en tu CDN. - Aceptar el cambiazo. Sirve el idioma fuente y deja que
bindDomreemplace el texto cuando llegue el catálogo. Bien detrás de un login, visible en una carga en frío.
La dirección te compra una cosa más. Con render.inlineCatalog, cada página lleva solo los mensajes que enseña, así que quien lee una página en un idioma traducido deja de bajarse ese idioma entero.
Algo que conviene saber si haces routing en el cliente: cada página lleva sus propios mensajes, así que tu router tiene que entregarlos al cambiar de documento, con adoptPage de virtual:verbaly. El <ClientRouter /> de Astro ya viene cableado.
El prefijo llega a todos los enlaces, incluidos los que van escritos dentro de una frase. Un mensaje que nombra un enlace (<docs>la guía</docs>) se trata igual que un <a> que escribiste tú. Así, quien hace clic a mitad de párrafo se queda en su idioma en vez de aterrizar en el tuyo.
Quedarse con una sola dirección, en la práctica
En el navegador son dos llamadas. resolveLocale elige el idioma de quien llega sin una dirección de la que leerlo, y persistLocale recuerda el cambio para que el siguiente F5 esté de acuerdo.
import { createVerbaly, persistLocale, resolveLocale } from 'verbaly';
const supported = ['en', 'es', 'pt'];
export const i18n = createVerbaly({
locale: resolveLocale({ supported, path: false }), // no language in the address
fallback: 'en',
loaders: { es: () => import('./locales/es.json') },
});
export async function switchTo(locale) {
await i18n.loadLocale(locale); // catalog first, so the text never flashes
i18n.setLocale(locale);
persistLocale(locale); // storage + <html lang> + <html dir>
}Lo que conviene saber es path: false. Por defecto resolveLocale lee el primer segmento de la dirección antes que nada, porque en un sitio que sí pone ahí el idioma la dirección es la única fuente que no puede contradecir a la página. Apágalo cuando tus direcciones no sean idiomas, y una ruta llamada /es no se podrá confundir nunca con español.
En un servidor la misma idea ocurre un paso antes. resolveRequestLocale lee la cookie, cae a Accept-Language y le pasa el idioma a la instancia que renderiza la respuesta, así que nadie recibe nunca una página en el que no es. @verbaly/sveltekit, @verbaly/nuxt y @verbaly/next ya lo hacen por ti. switchLocale es la otra mitad, en el navegador: escribe la cookie que el servidor leerá.
Cambiar de opinión más tarde
Pasar de una dirección a muchas es barato: activas render y cada idioma gana su dirección más su hreflang. Al revés te cuesta las direcciones que ya publicaste, y los buscadores las recuerdan mucho tiempo, así que redirige en vez de borrar. Ninguna de las dos direcciones toca tus mensajes, tus keys ni tus catálogos: esto es una decisión de rutas, no de traducción.