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, que 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 un visitante mira y aun así decirle a un buscador que está en inglés, porque el título y la descripción viven en el <head> y nadie los lee en la página. Esa es casi toda la razón de darle a un idioma su propia dirección, así que bindéalos como todo lo demás.

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.

La única diferencia que no se puede esquivar

Un buscador indexa direcciones, no idiomas. Si tu español y tu inglés viven en la misma dirección, solo se indexa uno, y no hay truco de runtime que lo cambie: las anotaciones hreflang existen justo para conectar direcciones distintas, así que sin ellas no hay nada que conectar. La propia guía de Google es usar URLs distintas por idioma en vez de cookies o ajustes 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:

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 se mueve un paso antes: resolveRequestLocale lee la cookie, cae al Accept-Language y le pasa el locale a la instancia que renderiza la respuesta, así que el visitante nunca recibe un documento en el idioma equivocado. @verbaly/sveltekit, @verbaly/nuxt y @verbaly/next ya lo hacen; switchLocale es la mitad del cliente y escribe la cookie que leerá el servidor.

// 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.

Copiado en el portapapeles