i18n
Cada campo es multilingüe por defecto. Cómo los locales dan forma a las URL, a cuál se resuelve una petición y qué pasa cuando falta una traducción.
En cmssy no modelas traducciones. Cada campo ya es multilingüe: el contenido se guarda indexado por código de idioma, así que una página lleva todos sus idiomas y una instancia de bloque todos los suyos.
Eso elimina el problema habitual de duplicación -no hay "página en inglés" y "página en alemán" que mantener sincronizadas- y lo sustituye por dos preguntas más pequeñas. ¿A qué locale se resuelve una petición, y qué se muestra cuando un campo aún no está traducido?
El conjunto de locales
Los locales vienen de la configuración del sitio, no de tu código:
const { defaultLocale, locales } = await resolveSiteLocales();Añadir un idioma es un ajuste del workspace. Tu app lo recoge en la siguiente petición, lo que significa que un locale nuevo no necesita despliegue, solo traducciones.
URL: el locale por defecto no lleva prefijo
Esta es la regla de la que se deriva todo lo demás:
localizedPath("/pricing", "es", "es"); // "/pricing" por defecto: desnuda
localizedPath("/pricing", "de", "es"); // "/de/pricing" el resto: con prefijo
localizedPath("/", "de", "es"); // "/de"Mantener el locale por defecto sin prefijo significa que tus URL canónicas no cambian cuando añades un segundo idioma: enlaces existentes, comparticiones y posiciones sobreviven.
Resolver una petición
La dirección inversa lee el primer segmento de la ruta, y es estricta a propósito:
splitLocaleFromPath(["de", "pricing"], { defaultLocale: "es", locales: ["es", "de"] });
// -> { locale: "de", path: ["pricing"] }
splitLocaleFromPath(["pricing"], { defaultLocale: "es", locales: ["es", "de"] });
// -> { locale: "es", path: ["pricing"] }Un primer segmento cuenta como locale solo si está en locales y no es el por defecto. Así que /es/pricing no es la página de precios en español: es una página cuyo slug empieza por casualidad con es. Una URL canónica por página e idioma, sin duplicados.
La ruta catch-all hace esto antes de buscar un slug:
const { path: strippedPath } = splitLocaleFromPath(path, await resolveSiteLocales());
const slug = "/" + (strippedPath ?? []).join("/");Traducciones ausentes
Un campo cae a un respaldo en vez de desaparecer:
pickLocalized(value, locale, defaultLocale);
// value[locale] ?? value[defaultLocale] ?? primer valor disponibleTres pasos: el idioma pedido, luego el por defecto, luego lo que exista. El último importa: un campo traducido solo al francés se sigue renderizando en una página alemana en vez de dejar un hueco.
La consecuencia para quien escribe bloques: una página parcialmente traducida es un estado normal, no un error. Escribe componentes que rendericen con sensatez cuando un campo vuelva en el idioma equivocado, y prueba con un locale que no hayas traducido: es lo primero con lo que se toparán tus visitantes en un idioma nuevo.
Middleware
export const proxy = createCmssyProxy(cmssy, {
// Solo si tus URL llevan el idioma Y tus rutas son caminos estáticos
// en vez de una catch-all.
stripLocalePrefix: true,
});El SDK lo dice explícitamente: déjalo apagado para una app catch-all que lee el idioma de la ruta por sí misma. Actívalo igualmente y la ruta recibe un camino sin prefijo, así que splitLocaleFromPath no encuentra nada que recortar y cae al locale por defecto: todos los idiomas se renderizan como el predeterminado.
El middleware sigue resolviendo el idioma y pasándolo en una cabecera, así que una app puede leerlo de ahí. Pero si tu ruta deriva el locale de la ruta, como suele hacer una catch-all, esas dos fuentes le están diciendo ahora cosas distintas.
Declara el idioma en el HTML
Fija <html lang> desde el locale resuelto. Es lo que leen los lectores de pantalla y las herramientas de traducción, y es un contrato: por eso la prueba de humo del editor se apoya en él en vez de buscar una palabra en tu copia. La copia es contenido; un editor puede reescribirla en cualquier momento, y entonces la prueba miente.
Siguientes pasos
- SEO: alternativas hreflang construidas desde el mismo conjunto de locales.
- Pruebas: la afirmación sobre
<html lang>. - Modelos de datos: los registros también son multilingües.