Saltar al contenido

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

EstrategiaLo que ve el visitanteCambiar de idioma es
En la dirección/docs en inglés, /es/docs en españolNavegar a esa dirección
Fuera de la dirección/docs en todos los idiomasEl 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.

verbaly.config.mjs
export default {
  sourceLocale: 'en',
  locales: ['en', 'es', 'pt'],
  routing: 'prefix-all', // only when you disagree with what it read
};
ModoLa direcciónCuándo
prefix-except-source/docs y /es/docsTodo lo que la gente encuentra buscando, que es casi cualquier sitio público
prefix-all/en/docs y /es/docsCuando ningún idioma es el de la casa y todos son invitados
no-prefix/docs en todos los idiomasDetrá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.

import { switchLocale } from 'virtual:verbaly';

await switchLocale('es');

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:

await switchLocale('es', { navigate: (path) => router.push(path) });

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.

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.

head.html
<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.

RenderizasEn la direcciónFuera de la dirección
En el navegador, una SPA con router de clientelocaleFromPath lee el idioma de la dirección en cada cambio de rutaresolveLocale + persistLocale. Un clic nunca recarga, y un F5 vuelve a leer la elección guardada
En un servidor, SvelteKit, Nuxt o Next.jsEl router de tu framework es dueño del segmento, Verbaly de los catálogosresolveRequestLocale 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 SSGverbaly render escribe tu sitio en cada idioma, con hreflang y un sitemapNada 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:

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.

src/i18n.ts
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á.

// the same decision, made before the html exists
const locale = resolveRequestLocale({ supported, cookie, header });

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.

↑↓para moverteEnterpara abrir
Copiado en el portapapeles