<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

Um blog em 7 idiomas com URLs traduzidos: como este site está construído

Este blog nasceu em italiano. Quando acrescentei os outros seis idiomas — inglês, albanês, português, espanhol, francês e alemão — traduzir os textos foi a parte fácil. A parte difícil foi tudo o resto: URLs, links já partilhados, motores de pesquisa e uma tradução automática que parecia funcionar mas estragava o conteúdo em silêncio.

O problema: um URL italiano para sete idiomas

No início cada artigo tinha um único slug, o italiano. O resultado eram endereços como /de/blog/perche-il-blog-non-viene-indicizzato: uma página em alemão com um URL em italiano. Feio de partilhar, pouco claro para quem lê e um desperdício para a SEO, porque as palavras no URL contam.

O modelo de dados: um artigo, sete idiomas

Optei por manter um único documento por artigo, com campos por idioma: title_en, content_de, slug_fr e assim por diante. A alternativa seria um documento por tradução, mas teria de manter sincronizados etiquetas, visualizações e estado de publicação em sete cópias. Com um só documento, o artigo continua a ser uma coisa só.

Slugs traduzidos que nunca mudam

Cada slug por idioma é gerado uma única vez, a partir do título traduzido, e nunca mais é alterado, nem que eu corrija o título: um URL já partilhado tem de continuar a funcionar. Além disso, cada slug tem de ser único entre todos os campos de todos os artigos, para que um endereço leve sempre a um só artigo, seja de que idioma for:

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

Os links antigos continuam a funcionar

Antes dos slugs traduzidos, muitos links já tinham sido partilhados no formato /sq/blog/slug-italiano. Parti-los significaria páginas 404 e sinais negativos para o Google. Por isso a build gera cada artigo também com o slug italiano sob cada prefixo de idioma, com o canonical a apontar para a versão traduzida: quem chega pelo link antigo vê a página certa, e o Google sabe qual é o endereço oficial.

SEO para cada idioma

  • hreflang em cada página, a ligar entre si as sete versões do mesmo artigo.
  • og:locale dinâmico, para que as pré-visualizações nas redes sociais mostrem o idioma, o título e a descrição corretos.
  • Sitemap com todas as versões, atualizada automaticamente a cada publicação.
  • Páginas pré-renderizadas por idioma: sem elas, o Google só via a casca da aplicação, como contei neste artigo.

A tradução automática que estragava o código

Para traduzir sem mexer no HTML, substituía etiquetas e blocos de código por marcadores, enviava o texto ao tradutor e depois repunha tudo no lugar. Funcionava quase sempre. Mas quando vários marcadores estavam próximos — típico dos artigos com muitos blocos de código — o tradutor às vezes duplicava um e perdia outro. Não era um erro aleatório: ao repetir, voltava a acontecer igual, sobretudo em albanês.

A solução foi deixar de enviar os marcadores ao tradutor: divido o texto nos marcadores e traduzo apenas os pedaços de prosa. Mais chamadas, mas nenhuma hipótese de um marcador ser alterado:

// 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 desde então, depois de cada tradução, comparo a estrutura de cada idioma com a italiana — o mesmo número de títulos, parágrafos, tabelas e blocos de código — sem confiar no "nenhum erro" devolvido pelo script.


Em resumo

Um blog multilingue não é um blog com um tradutor acoplado: são precisos um modelo de dados claro, URLs estáveis para cada idioma, links antigos que não se partem e verificações automáticas da tradução. São as mesmas escolhas que proponho quando um cliente quer um site em vários idiomas. Se é o teu caso, escreve-me.

💬 Notas dos leitores

0 notas

Escreva uma nota

Partilhe a sua opinião, uma sugestão ou um elogio

Notas recentes

Ainda não há notas. Seja o primeiro a comentar!