Como Localizámos o Nosso Site em 9 Idiomas com o Astro
Lançámos o encaminhamento por idioma para 9 línguas neste sprint. Eis a versão honesta de como isso aconteceu — incluindo o bug que estava silenciosamente a prejudicar-nos em produção antes de o notarmos.
A Descoberta
Ainda no Sprint 1, construímos a estrutura de i18n que seria de esperar: ficheiros JSON de idioma para todas as 9 línguas (inglês, espanhol, árabe, alemão, francês, japonês, português, russo, chinês), um auxiliar getTranslation(), getLocalePath() para construir URLs sensíveis ao idioma, e etiquetas hreflang em cada página apontando para todas as 9 variantes de idioma.
O que não construímos: páginas reais nesses URLs de idioma.
Cada página em src/pages/ tinha const locale = "en" as const fixo no código. A canalização de tradução existia e funcionava — mas não havia nada por trás dela. Isso significava que o BaseLayout.astro estava a emitir etiquetas hreflang como https://proman365.com/es/pricing/ em todas as páginas, para URLs que devolviam um 404 em produção.
Isto não é um bug cosmético. Os motores de busca usam etiquetas hreflang para perceber que URLs são variantes de idioma umas das outras. Anunciar 404s em grande escala é um sinal de SEO ativamente negativo — diz aos crawlers que os metadados do seu site não correspondem à realidade, o que corrói a confiança em todos os seus metadados, não apenas na parte quebrada.
A Correção: Extração de Templates, Não Duplicação
A correção ingénua é copiar cada página 8 vezes, uma por cada idioma não inglês, e fixar uma constante de idioma diferente em cada cópia. Rejeitámos isso imediatamente — 8x os ficheiros significa 8x a degradação no momento em que alguém mexe numa página e se esquece das outras 7 cópias.
A abordagem que seguimos foi outra: extrair o conteúdo de cada página para um componente de template que recebe locale como prop, e depois renderizar esse template a partir de dois tipos de rotas.
Para as 12 páginas de Nível 1 (página inicial, preços, contacto, parceiros, ajuda e as seis páginas de funcionalidades), cada uma foi movida de src/pages/*.astro para src/page-templates/*.astro, com a constante de idioma fixa substituída por:
interface Props {
locale: Locale;
}
const { locale } = Astro.props;
A rota original em inglês torna-se um wrapper simples — algumas linhas que apenas importam o template e o renderizam com locale="en". Uma nova rota espelho em src/pages/[locale]/ renderiza o mesmo template para os 8 idiomas não ingleses, usando getStaticPaths() para gerar uma página por idioma.
Isto funcionou de forma limpa porque a lógica de cada página de Nível 1 — getTranslation(locale), construtores de meta tags, marcação de esquema — já estava parametrizada nessa única constante locale desde a estrutura do Sprint 1. Não estávamos a adaptar retroativamente o i18n a estas páginas; estávamos finalmente a ligar um i18n que estava por usar.
O resultado: 96 novas páginas estáticas (12 páginas × 8 idiomas) do lançamento de Nível 1, seguidas de uma segunda fase que estendeu o mesmo padrão às 64 páginas de comparação (8 concorrentes × 8 idiomas).
Controlar o hreflang com uma Prop localized
Corrigir a lacuna de encaminhamento não corrige automaticamente o problema do hreflang — ainda precisa que as etiquetas afirmem apenas o que é realmente verdade. Adicionámos uma prop localized?: boolean ao BaseLayout e ao PageLayout, com o valor predefinido false.
Quando localized é false, uma página emite exatamente duas etiquetas hreflang: en e x-default. Quando é true, emite o conjunto completo de 10 (todos os 9 idiomas mais x-default). Só os templates de Nível 1 e as páginas de comparação passam localized={true}.
Ter false como predefinição foi uma escolha de segurança deliberada. Uma página tem de optar ativamente por afirmar que tem 9 variantes de idioma. Se alguém adicionar uma nova página amanhã e se esquecer de pensar nos idiomas, o sistema falha em segurança — metadados apenas em inglês, não outra ronda de URLs fantasma.
O Que Deliberadamente Não Localizámos
Nem tudo recebeu o tratamento de localização, e isso foi uma decisão, não um esquecimento:
- O blog mantém-se apenas em inglês indefinidamente. Esta é a prática habitual em blogs de marketing SaaS, e traduzir conteúdo longo por máquina produz um texto pior do que simplesmente não o traduzir. Se as análises algum dia justificarem conteúdo dedicado por idioma, reconsideraremos.
- As páginas legais (privacidade, termos, política de cookies) mantêm-se em inglês. Esse texto é conteúdo jurídico gerado pela Termly; traduzi-lo não é uma decisão que estejamos equipados para tomar unilateralmente.
- A página 404 mantém-se em inglês — o alojamento estático serve um único 404 independentemente do caminho pedido, por isso não há forma limpa de o localizar sem um modelo de alojamento diferente.
- O RSS já era apenas em inglês e corretamente excluído do hreflang; não foram precisas alterações aí.
A Lista de Verificação Final
Se está a fazer algo semelhante num site estático Astro:
- Audite antes de construir. Verifique se as suas etiquetas hreflang apontam para páginas que realmente existem. Se a sua estrutura de i18n é anterior ao seu encaminhamento, pode já ter este bug.
- Extraia templates, não duplique páginas. Um template por página, rotas wrapper finas específicas de idioma por cima. A degradação acontece na camada de wrapper, se acontecer, e os wrappers são baratos de manter sincronizados.
- Defina as afirmações de metadados para a declaração verdadeira mais restrita. Uma flag
localizedque assume “não” por predefinição significa que novas páginas não podem afirmar demasiado por acidente. - Decida o que fica por traduzir, de propósito, e registe isso. Blog, páginas legais e páginas de erro são candidatos razoáveis a excluir — mas torne isso uma decisão documentada, não um silêncio.
- Tradução e encaminhamento são problemas separados. Lançar as rotas não exige terminar as traduções — texto de recurso em inglês numa rota de idioma real ainda é mais honesto do que uma chave de tradução funcional sem rota por trás.
Ainda temos trabalho manual de tradução pela frente — muitas strings nos ficheiros de idioma não ingleses são placeholders em inglês à espera de um tradutor humano. Mas os URLs são reais agora, e os metadados deixaram de mentir. Esse era o problema mais urgente.
Alex Rivera
Gerente de projetos e consultor de fluxo de trabalho. Ajuda equipes a encontrar ferramentas que combinem com a forma como realmente trabalham.