Sitio multilenguaje: un solo árbol de contenido

Como implementar um site multilíngue em Hugo usando uma única árvore de conteúdo e data files

Depois de mudar o site para HUGO no ano passado e entender melhor esta tecnologia/framework, quis resolver alguns problemas e ver se é possível servir conteúdo multilíngue de forma simplificada, ou seja, a partir de uma única árvore de arquivos, sem multiplicar os ramos de conteúdo.

A Forma Convencional

Por padrão, o HUGO propõe fazer uma árvore de conteúdo para cada idioma. Isso implica criar uma estrutura similar para cada um:

content/
├── es/
│   ├── posts/
│   └── series/
├── en/
│   ├── posts/
│   └── series/
└── pt-br/
    ├── posts/
    └── series/

Nos casos em que o conteúdo é muito diferente entre idiomas, essa abordagem é adequada. Não é o meu caso.

A maior penalidade vem de manter 3 versões de cada arquivo.

As Séries

Outra coisa que eu queria fazer é criar séries simples de artigos, algo que ajude a manter uma navegação e fluxo de informação coerente entre um conjunto de artigos relacionados a um tema. Este post faz parte de uma série, e vou falar sobre isso em outro artigo mais adiante.

Data Files + Templates Inteligentes

Para resolver o conteúdo multilíngue, finalmente implementei um sistema híbrido que usa arquivos de dados YAML para conteúdo multilíngue e templates Hugo que selecionam automaticamente o idioma correto.

Estrutura Final

content/
└── 20250810_meu_post.md         # Apenas metadados

data/
└── 20250810_meu_post.yaml       # Conteúdo em 3 idiomas

layouts/
├── _default/
│   ├── single.html              # Renderiza a partir de data files
│   └── summary.html             # Trechos multilíngues
└── partials/
    └── content-renderer.html    # Lógica de seleção de idioma

Arquivo de Conteúdo (.md)

O arquivo .md contém apenas metadados e uma referência:

---
title: 'Antena Moxon para 6m'
date: 2025-08-10
categories: ["Radio"]
tags: ["Antena", "VHF"]
---

Este post usa data files para mostrar conteúdo diferente de acordo com o idioma do usuário.

Data File (.yaml)

Todo o conteúdo real está no arquivo YAML:

title:
  es: 'Antena Moxon para 6 metros'
  en: 'Moxon Antenna for 6 meters'
  pt: 'Antena Moxon para 6 metros'

content:
  es: |
    La Moxon es una direccional simple de dos elementos...
    
  en: |
    The Moxon is a simple two-element directional antenna...
    
  pt: |
    A Moxon é uma antena direcional simples de dois elementos...

Não estou convencido desta estrutura. Logo de cara, penso que não é necessário, e não sei se é conveniente, usar o arquivo em /data para o conteúdo traduzido, provavelmente voltarei a ter todo o conteúdo em todos os idiomas no arquivo .md.

Template de Renderização

O template detecta automaticamente o idioma e renderiza o conteúdo apropriado:

{{ $currentLang := .Language.Lang }}
{{ $contentKey := .File.BaseFileName }}
{{ $dataFile := index .Site.Data $contentKey }}

{{ if $dataFile }}
  {{ $title := index $dataFile.title $currentLang }}
  {{ $content := index $dataFile.content $currentLang }}
  
  <h1>{{ $title }}</h1>
  <div class="content">
    {{ $content | markdownify }}
  </div>
{{ end }}

Benefícios do Sistema

Manutenção Simplificada

  • Um único arquivo por post (o .yaml)
  • Uma única URL servindo 3 idiomas
  • Sem duplicação de estrutura de diretórios

SEO Otimizado

  • URLs limpas: /es/post/, /en/post/, /pt-br/post/
  • Hreflang automático entre versões
  • Conteúdo único por idioma (não duplicado)

Experiência do Usuário

  • Troca instantânea de idioma (mesma URL)
  • Consistência na navegação
  • Fallback inteligente se faltar tradução

Fluxo de Trabalho Eficiente

  • Escrevo em espanhol
  • Sistema traduz automaticamente para EN/PT-BR
  • Uma única publicação para os 3 idiomas

Implementação Técnica

O coração do sistema está na lógica de templates que:

  1. Detecta o idioma atual ({{ .Language.Lang }})
  2. Busca o data file correspondente
  3. Extrai conteúdo no idioma apropriado
  4. Renderiza com fallback se faltar tradução
{{/* Buscar conteúdo no idioma atual */}}
{{ $content := index $dataFile.content $currentLang }}

{{/* Fallback para espanhol se não existir tradução */}}
{{ if not $content }}
  {{ $content = index $dataFile.content "es" }}
{{ end }}

{{ $content | markdownify }}

Resultados

O site agora serve automaticamente conteúdo em espanhol, inglês e português a partir de uma única base de código. Os leitores podem acessar os artigos em seu idioma preferido, mas eu só preciso manter um conjunto de arquivos.

git hooks

Como a principal vantagem disso é que publicar um post é muito simplificado (novo arquivo, escrever, git add, git commit, git push), fazer as traduções é definitivamente um ponto de fricção adicional e significativo. Por isso vou implementar um script que use a API de alguma IA para fazer as traduções, veremos como isso funciona.

URLs do site:

O sistema funciona de forma transparente para o usuário final, mas simplifica enormemente o trabalho de manutenção.