Questo blog è nato in italiano. Quando ho aggiunto le altre sei lingue — inglese, albanese, portoghese, spagnolo, francese e tedesco — tradurre i testi è stata la parte facile. La parte difficile è stata tutto il resto: URL, link già condivisi, motori di ricerca e una traduzione automatica che sembrava funzionare e invece rompeva il contenuto in modo silenzioso.
Il problema: un URL italiano per sette lingue
All'inizio ogni articolo aveva un solo slug, quello italiano. Il risultato erano indirizzi come /de/blog/perche-il-blog-non-viene-indicizzato: una pagina in tedesco con un URL in italiano. Brutto da condividere, poco chiaro per chi lo legge e uno spreco per la SEO, perché le parole nell'URL contano.
Il modello dei dati: un articolo, sette lingue
Ho scelto di tenere un solo documento per articolo, con campi per lingua: title_en, content_de, slug_fr e così via. In alternativa avrei potuto avere un documento per ogni traduzione, ma avrei dovuto tenere sincronizzati tag, visualizzazioni e stato di pubblicazione tra sette copie. Con un documento solo, l'articolo resta una cosa sola.
Slug tradotti che non cambiano mai
Ogni slug per lingua viene generato una volta sola, dal titolo tradotto, e poi non viene più toccato, nemmeno se correggo il titolo: un URL già condiviso deve continuare a funzionare. In più ogni slug deve essere unico tra tutti i campi di tutti gli articoli, così un indirizzo porta sempre a un solo articolo, qualunque sia la lingua da cui proviene:
// 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
}
}
I link vecchi continuano a funzionare
Prima degli slug tradotti, molti link erano già stati condivisi nella forma /sq/blog/slug-italiano. Romperli avrebbe significato pagine 404 e segnali negativi per Google. Per questo la build genera ogni articolo anche con lo slug italiano sotto ciascun prefisso di lingua, con il canonical che punta alla versione tradotta: chi arriva dal vecchio link vede la pagina giusta, e Google sa quale indirizzo considerare quello ufficiale.
SEO per ogni lingua
- hreflang su ogni pagina, che collega tra loro le sette versioni dello stesso articolo.
- og:locale dinamico, così le anteprime sui social mostrano lingua, titolo e descrizione corretti.
- Sitemap con tutte le versioni, aggiornata automaticamente a ogni pubblicazione.
- Pagine prerenderizzate per lingua: senza, Google vedeva solo la shell dell'applicazione, come ho raccontato in questo articolo.
La traduzione automatica che rompeva il codice
Per tradurre senza toccare l'HTML sostituivo tag e blocchi di codice con dei segnaposto, mandavo il testo al traduttore e poi rimettevo tutto al suo posto. Funzionava quasi sempre. Quando però diversi segnaposto erano vicini — tipico degli articoli con molti blocchi di codice — il traduttore a volte ne duplicava uno e ne perdeva un altro. Non era un errore casuale: rilanciando, si ripeteva identico, soprattutto in albanese.
La soluzione è stata smettere di mandare i segnaposto al traduttore: divido il testo sui segnaposto e traduco solo i pezzi di prosa. Più chiamate, ma nessuna possibilità che un segnaposto venga alterato:
// 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('');
E da allora, dopo ogni traduzione, confronto la struttura di ogni lingua con quella italiana — stesso numero di titoli, paragrafi, tabelle, blocchi di codice — senza fidarmi del "nessun errore" restituito dallo script.
In sintesi
Un blog multilingua non è un blog con un traduttore attaccato: servono un modello dei dati chiaro, URL stabili per ogni lingua, link vecchi che non si rompono e controlli automatici sulla traduzione. Sono le stesse scelte che propongo quando un cliente vuole un sito in più lingue. Se è il tuo caso, scrivimi.