Sistema de Tradução
Este site é publicado em 17 idiomas. Não por uma equipa de tradutores, mas por um script Python, um modelo Claude Opus e uma estrutura de ficheiros concebida para que tudo encaixe no sítio certo. Eis como todo o processo funciona.
A Árvore de Idiomas
Seção intitulada “A Árvore de Idiomas”A documentação reside em src/content/docs/. O inglês é a raiz — todos os outros idiomas espelham-no de forma exata:
src/content/docs/├── index.mdx ← English (root)├── getting-started.md├── features/│ ├── play-chess.md│ ├── multiplayer.md│ └── ...├── setup/│ ├── tts-overview.md│ └── ...├── under-the-hood/│ ├── architecture.md│ └── ...│├── fr/ ← French│ ├── index.mdx│ ├── getting-started.md│ ├── features/│ │ ├── play-chess.md│ │ └── ...│ └── ...│├── ja/ ← Japanese│ ├── index.mdx│ ├── getting-started.md│ └── ...│└── ... (16 language directories total)Cada ficheiro traduzido é um espelho estrutural da sua fonte em inglês. O mesmo nome de ficheiro, o mesmo caminho de subdiretório, as mesmas chaves de frontmatter. A única diferença é que a prosa está noutro idioma.
Porquê a importância dos espelhos
Seção intitulada “Porquê a importância dos espelhos”O Starlight (a framework de documentação) depende desta simetria. Quando um utilizador muda de idioma, o Starlight troca /docs/getting-started/ por /fr/docs/getting-started/ — o mesmo caminho, um prefixo de locale diferente. Se o ficheiro em francês não existir exatamente em fr/getting-started.md, o seletor de idioma falha ou recai silenciosamente para inglês.
Os 17 Idiomas
Seção intitulada “Os 17 Idiomas”Os idiomas estão ordenados pela população global de jogadores de xadrez, com base em dados do Lichess, Chess.com e registos da FIDE. O inglês surge em primeiro como idioma de origem; tudo o resto segue a classificação do xadrez:
| Posição | Código | Idioma | Estilo |
|---|---|---|---|
| 1 | en | Inglês | Fonte de verdade |
| 2 | es | Espanhol | Formal padrão |
| 3 | hi | Hindi | Escrita Devanagari |
| 4 | ru | Russo | Formal padrão |
| 5 | de | Alemão | Formal padrão |
| 6 | fr | Francês | Formal padrão |
| 7 | pt | Português | Português europeu |
| 11 | pl | Polaco | Formal padrão |
| 12 | it | Italiano | Formal padrão |
| 13 | uk | Ucraniano | Formal padrão |
| 14 | tr | Turco | Formal padrão |
| 17 | ko | Coreano | Forma 합니다/습니다 |
| 18 | zh | Chinês (Simplificado) | Caracteres simplificados |
| — | zh-tw | Chinês (Tradicional) | Caracteres tradicionais |
| 23 | nb | Norueguês Bokmål | Bokmål padrão |
| — | be | Bielorrusso | Bielorrusso padrão |
| 34 | ja | Japonês | Forma です/ます |
A coluna “estilo” é importante. O japonês e o coreano têm escolhas de registo formal que afetam cada frase. O prompt de tradução inclui estas instruções para que o modelo produza prosa natural e polida — não uma tradução automática rígida.
Esta ordenação também controla o menu pendente de idiomas no cabeçalho do site. Os idiomas mais falados na comunidade de xadrez aparecem primeiro, para que os utilizadores tenham mais probabilidade de encontrar o seu sem precisar de deslocar a lista.
O Pipeline de Tradução
Seção intitulada “O Pipeline de Tradução”Passo 1: Escrever em inglês
Seção intitulada “Passo 1: Escrever em inglês”Toda a documentação começa como markdown em inglês em src/content/docs/. O frontmatter contém um title e uma description:
---title: "Getting Started"description: "Install En Parlant~ and play your first game."---
Download the latest release...Passo 2: Executar o script
Seção intitulada “Passo 2: Executar o script”Um script Python (scripts/translate-docs.py) lê cada ficheiro fonte em inglês, envia-o para a API do Claude e escreve o markdown traduzido:
python3 scripts/translate-docs.py \ --anthropic-key $ANTHROPIC_API_KEY \ --model claude-opus-4-6 \ --workers 5O script demora cerca de 60–70 minutos a traduzir todos os 28 ficheiros fonte para os 16 idiomas de destino (448 ficheiros no total). Executa 5 chamadas API em paralelo para respeitar os limites de taxa.
Para um único idioma novo, demora cerca de 4 minutos.
Passo 3: Compilar
Seção intitulada “Passo 3: Compilar”pnpm buildO Astro lê o markdown fonte, renderiza-o através dos modelos do Starlight e produz HTML estático em dist/. A compilação demora cerca de 30 segundos para as ~500 páginas.
Passo 4: Publicar
Seção intitulada “Passo 4: Publicar”pnpm run deployEnvia o site compilado para o Cloudflare Workers.
Como o Script Funciona
Seção intitulada “Como o Script Funciona”O script de tradução é intencionalmente simples — cerca de 300 linhas de Python. Eis o fluxo:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐│ Read English │────▶│ Claude API │────▶│ Write target ││ source file │ │ (Opus 4.6) │ │ language file ││ │ │ │ │ ││ getting- │ │ "Translate into │ │ fr/getting- ││ started.md │ │ French..." │ │ started.md │└─────────────────┘ └──────────────────┘ └─────────────────┘ × 5 parallel × 16 languages × 28 filesO prompt indica ao Claude exatamente o que traduzir e o que manter inalterado:
- Traduzir: prosa, títulos, etiquetas de tabelas, título e descrição do frontmatter
- Manter em inglês: blocos de código, código inline, exemplos de comandos, caminhos de ficheiros, URLs, nomes de produtos, nomes de pessoas, diagramas ASCII
Esta separação é crítica. Um comando como pnpm build deve permanecer pnpm build em todos os idiomas. Um nome de produto como “En Parlant~” ou “Stockfish” não é traduzido. Mas “Getting Started” torna-se “はじめに” em japonês e “Начало работы” em russo.
Porquê o Opus?
Seção intitulada “Porquê o Opus?”O Claude Opus 4.6 produz traduções visivelmente melhores do que modelos mais rápidos. A diferença manifesta-se em:
- Fraseado natural — O Opus escreve como um falante nativo, não como um tradutor. Reestrutura frases quando a ordem de palavras do inglês soaria estranha no idioma de destino.
- Precisão técnica — A terminologia de xadrez, o jargão de TTS e os conceitos de software são traduzidos usando os termos corretos do domínio específico.
- Consistência — O registo formal mantém-se consistente ao longo de todo o texto. O japonês usa a forma です/ます em todo o lado, sem alternar entre casual e polido a meio de um parágrafo.
- Tratamento de MDX — O Opus preserva corretamente as tags de componentes JSX (
<Card>,<CardGrid>) e as instruçõesimportem ficheiros.mdxsem os corromper.
A diferença de custo é real — cerca de $28 para uma tradução completa do site com o Opus contra $5 com o Sonnet — mas para 448 ficheiros que os utilizadores vão efetivamente ler, a qualidade compensa.
A Arquitetura de Encaminhamento de Locales
Seção intitulada “A Arquitetura de Encaminhamento de Locales”Esta foi a parte que mais tempo levou a acertar.
O problema
Seção intitulada “O problema”O Starlight deteta o idioma de uma página verificando o primeiro segmento do seu slug de conteúdo. Quando vê fr/docs/getting-started, sabe que é francês porque fr é o primeiro segmento. Mas a implementação inicial produzia slugs como docs/fr/getting-started — o locale enterrado sob docs/. O Starlight via docs como primeiro segmento, tratava tudo como inglês e gerava mais de 7.000 páginas duplicadas em vez de ~500.
A solução
Seção intitulada “A solução”Uma função generateId personalizada em src/content.config.ts controla como os caminhos de ficheiros se tornam slugs de conteúdo:
generateId({ entry }) { const slug = entry.replace(/\.[^.]+$/, ""); const firstSeg = slug.split("/")[0]; if (firstSeg && localeKeys.includes(firstSeg)) { return `${firstSeg}/docs/${slug.slice(firstSeg.length + 1)}`; } return `docs/${slug}`;}Isto coloca o prefixo de locale antes de docs/:
| Caminho do ficheiro | Slug | URL |
|---|---|---|
getting-started.md | docs/getting-started | /docs/getting-started/ |
fr/getting-started.md | fr/docs/getting-started | /fr/docs/getting-started/ |
ja/features/puzzles.md | ja/docs/features/puzzles | /ja/docs/features/puzzles/ |
O inglês usa defaultLocale: "root", o que significa que não tem prefixo — vive diretamente em /docs/, não em /en/docs/.
O array localeKeys
Seção intitulada “O array localeKeys”Um array localeKeys no mesmo ficheiro deve listar todos os locales que não sejam inglês. Se um locale existe na configuração do Astro mas não neste array, o seu conteúdo traduzido é tratado como conteúdo em inglês — o seletor de idioma falha e a contagem de páginas dispara.
Cache do data store
Seção intitulada “Cache do data store”O Astro armazena em cache os mapeamentos de slugs em .astro/data-store.json. Após qualquer alteração na configuração de locales, este ficheiro deve ser eliminado antes de recompilar, caso contrário a compilação é bem-sucedida com encaminhamento obsoleto (errado).
A Estrutura do Menu
Seção intitulada “A Estrutura do Menu”A barra lateral é definida em astro.config.mjs. Os itens que apontam para uma página (propriedade slug) usam automaticamente o título do frontmatter da página traduzida — sem necessidade de tradução manual:
{ slug: "docs/getting-started" }// English: "Getting Started" (from English frontmatter)// French: "Premiers pas" (from French frontmatter)// Japanese: "はじめに" (from Japanese frontmatter)Mas as etiquetas de grupo e os links externos necessitam de traduções explícitas:
{ label: "Features", translations: { fr: "Fonctionnalités", es: "Características", de: "Funktionen", ja: "機能", // ... all 16 languages }, items: [ { slug: "docs/features/play-chess" }, { slug: "docs/features/multiplayer" }, // ... ],}Sete elementos da barra lateral necessitam de traduções manuais: Welcome, Features, App Menus, Setup Guides, Under the Hood, Credits e Accessibility. Adicionar um novo idioma significa acrescentar uma entrada a cada um destes sete blocos.
A Ligação com a Aplicação
Seção intitulada “A Ligação com a Aplicação”O En Parlant~ liga a esta documentação a partir do seu menu Ajuda. O link é sensível ao locale — se estiver a usar a aplicação em francês, abre a documentação em francês:
const docsLocalePrefix = useMemo(() => { const lang = i18n.language; // e.g. "fr_FR", "zh_TW" if (!lang || lang.startsWith("en")) return ""; if (lang === "zh_TW") return "/zh-tw"; return `/${lang.slice(0, 2)}`;}, [i18n.language]);Todos os idiomas disponíveis na aplicação têm uma tradução correspondente no site. O mapeamento é automático — fr_FR mapeia para /fr/docs/, ja_JP mapeia para /ja/docs/. O único caso especial é o chinês tradicional: zh_TW mapeia para /zh-tw/docs/ (com hífen).
Porquê Agora É Simples
Seção intitulada “Porquê Agora É Simples”As partes difíceis estão resolvidas:
-
A arquitetura de encaminhamento está resolvida. A função
generateId, o arraylocaleKeyse a configuraçãodefaultLocale: "root"trabalham em conjunto para que o Starlight gere a estrutura de URLs correta. Este foi o maior ponto problemático — exigiu rastrear mais de 6 ficheiros fonte no Starlight e no Astro para encontrar e corrigir. -
O script de tradução trata de tudo. Retradução completa do site, adição de um único idioma, atualização de ficheiros individuais — tudo o mesmo script com flags diferentes. Faz novas tentativas em caso de limites de taxa, paraleliza entre workers e reporta erros de forma clara.
-
Adicionar um novo idioma são quatro edições de configuração e um comando. Adicionar o locale a
astro.config.mjs,content.config.tsetranslate-docs.py, acrescentar as traduções da barra lateral, executar o script. Cerca de 10 minutos de trabalho mais 4 minutos de tempo de tradução. -
Atualizar conteúdo é ainda mais simples. Editar o ficheiro fonte em inglês, executar o script com
--filese--overwriteapenas para os ficheiros alterados, recompilar. Ou, para pequenas correções de texto, editar diretamente os ficheiros traduzidos. -
Todo o pipeline está capturado numa skill. Executar
/translate_docsguia-nos por todo o processo — qual modo usar, que flags passar, verificações pré-voo, verificação pós-tradução. Sem necessidade de conhecimento institucional.
A tradução total de 448 ficheiros em 16 idiomas custou cerca de $28 e demorou aproximadamente 70 minutos. Um único idioma novo custa cerca de $1,70. Um único ficheiro retraduzido em todos os idiomas custa cerca de $1. Estes são os custos correntes de manter a documentação atualizada em 17 idiomas.