Este blog nació en italiano. Cuando añadí los otros seis idiomas — inglés, albanés, portugués, español, francés y alemán — traducir los textos fue la parte fácil. La difícil fue todo lo demás: URLs, enlaces ya compartidos, buscadores y una traducción automática que parecía funcionar pero rompía el contenido en silencio.
El problema: una URL en italiano para siete idiomas
Al principio cada artículo tenía un único slug, el italiano. El resultado eran direcciones como /de/blog/perche-il-blog-non-viene-indicizzato: una página en alemán con una URL en italiano. Fea de compartir, poco clara para el lector y un desperdicio para el SEO, porque las palabras de la URL cuentan.
El modelo de datos: un artículo, siete idiomas
Decidí mantener un único documento por artículo, con campos por idioma: title_en, content_de, slug_fr, etc. La alternativa era un documento por traducción, pero habría tenido que mantener sincronizados etiquetas, visitas y estado de publicación en siete copias. Con un solo documento, el artículo sigue siendo una sola cosa.
Slugs traducidos que nunca cambian
Cada slug por idioma se genera una sola vez, a partir del título traducido, y no se vuelve a tocar, ni siquiera si corrijo el título: una URL ya compartida tiene que seguir funcionando. Además, cada slug debe ser único entre todos los campos de todos los artículos, para que una dirección lleve siempre a un solo artículo, venga del idioma que venga:
// Fill slug_xx from the translated title, only where it's missing.
// Existing slugs are never touched: they're in URLs people have shared.
for (const lang of ['en', 'sq', 'pt', 'es', 'fr', 'de']) {
if (post[`slug_${lang}`]) continue;
const title = post[`title_${lang}`];
if (title?.trim()) {
post[`slug_${lang}`] = await ensureUniqueSlug(title); // unique across ALL slug fields
}
}
Los enlaces antiguos siguen funcionando
Antes de los slugs traducidos, muchos enlaces ya se habían compartido con la forma /sq/blog/slug-italiano. Romperlos habría supuesto páginas 404 y señales negativas para Google. Por eso la build genera cada artículo también con el slug italiano bajo cada prefijo de idioma, con el canonical apuntando a la versión traducida: quien llega desde el enlace antiguo ve la página correcta, y Google sabe qué dirección es la oficial.
SEO para cada idioma
- hreflang en cada página, que enlaza entre sí las siete versiones del mismo artículo.
- og:locale dinámico, para que las vistas previas en redes sociales muestren el idioma, el título y la descripción correctos.
- Sitemap con todas las versiones, actualizado automáticamente en cada publicación.
- Páginas prerenderizadas por idioma: sin ellas, Google solo veía el cascarón de la aplicación, como conté en este artículo.
La traducción automática que rompía el código
Para traducir sin tocar el HTML, sustituía etiquetas y bloques de código por marcadores, enviaba el texto al traductor y después lo volvía a colocar todo. Funcionaba casi siempre. Pero cuando varios marcadores estaban cerca — algo típico de los artículos con muchos bloques de código — el traductor a veces duplicaba uno y perdía otro. No era un error aleatorio: al repetir, se reproducía idéntico, sobre todo en albanés.
La solución fue dejar de enviar los marcadores al traductor: divido el texto por los marcadores y traduzco solo los fragmentos de prosa. Más llamadas, pero ninguna posibilidad de que un marcador se altere:
// Split on the placeholders: the translator never sees them
const parts = text.split(/(✦\d+✦)/);
const out = [];
for (const part of parts) {
out.push(/^✦\d+✦$/.test(part) ? part : await translate(part, lang));
}
return out.join('');
Y desde entonces, después de cada traducción, comparo la estructura de cada idioma con la italiana — mismo número de títulos, párrafos, tablas y bloques de código — sin fiarme del "ningún error" que devuelve el script.
En resumen
Un blog multilingüe no es un blog con un traductor pegado: hacen falta un modelo de datos claro, URLs estables para cada idioma, enlaces antiguos que no se rompan y controles automáticos sobre la traducción. Son las mismas decisiones que propongo cuando un cliente quiere una web en varios idiomas. Si es tu caso, escríbeme.