Sistema de Traducción
Este sitio se publica en 17 idiomas. No gracias a un equipo de traductores, sino mediante un script de Python, un modelo Claude Opus y una estructura de archivos diseñada para que todo encaje en su lugar. Así es como funciona todo el sistema.
El Árbol de Idiomas
Sección titulada «El Árbol de Idiomas»La documentación reside en src/content/docs/. El inglés es la raíz — todos los demás idiomas lo replican exactamente:
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 archivo traducido es un reflejo estructural de su fuente en inglés. Mismo nombre de archivo, misma ruta de subdirectorio, mismas claves de frontmatter. La única diferencia es que la prosa está en otro idioma.
Por qué los reflejos importan
Sección titulada «Por qué los reflejos importan»Starlight (el framework de documentación) depende de esta simetría. Cuando un usuario cambia de idioma, Starlight intercambia /docs/getting-started/ por /fr/docs/getting-started/ — misma ruta, diferente prefijo de locale. Si el archivo en francés no existe exactamente en fr/getting-started.md, el selector de idioma falla o vuelve silenciosamente al inglés.
Los 17 Idiomas
Sección titulada «Los 17 Idiomas»Los idiomas están ordenados por población global de jugadores de ajedrez, basándose en datos de Lichess, Chess.com y registros de la FIDE. El inglés aparece primero como idioma fuente; todo lo demás sigue el ranking ajedrecístico:
| Posición | Código | Idioma | Estilo |
|---|---|---|---|
| 1 | en | Inglés | Fuente de referencia |
| 2 | es | Español | Formal estándar |
| 3 | hi | Hindi | Escritura devanagari |
| 4 | ru | Ruso | Formal estándar |
| 5 | de | Alemán | Formal estándar |
| 6 | fr | Francés | Formal estándar |
| 7 | pt | Portugués | Portugués europeo |
| 11 | pl | Polaco | Formal estándar |
| 12 | it | Italiano | Formal estándar |
| 13 | uk | Ucraniano | Formal estándar |
| 14 | tr | Turco | Formal estándar |
| 17 | ko | Coreano | Forma 합니다/습니다 |
| 18 | zh | Chino (Simplificado) | Caracteres simplificados |
| — | zh-tw | Chino (Tradicional) | Caracteres tradicionales |
| 23 | nb | Noruego Bokmål | Bokmål estándar |
| — | be | Bielorruso | Bielorruso estándar |
| 34 | ja | Japonés | Forma です/ます |
La columna de “estilo” es importante. El japonés y el coreano tienen opciones de registro formal que afectan a cada oración. El prompt de traducción incluye estas instrucciones para que el modelo produzca prosa natural y pulida — no una salida de máquina rígida.
Este orden también controla el menú desplegable de idiomas en la cabecera del sitio. Los idiomas con más hablantes que juegan al ajedrez aparecen primero, de modo que los usuarios tienen más probabilidades de encontrar el suyo sin tener que desplazarse.
El Pipeline de Traducción
Sección titulada «El Pipeline de Traducción»Paso 1: Escribir en inglés
Sección titulada «Paso 1: Escribir en inglés»Toda la documentación comienza como markdown en inglés en src/content/docs/. El frontmatter incluye un title y una description:
---title: "Getting Started"description: "Install En Parlant~ and play your first game."---
Download the latest release...Paso 2: Ejecutar el script
Sección titulada «Paso 2: Ejecutar el script»Un script de Python (scripts/translate-docs.py) lee cada archivo fuente en inglés, lo envía a la API de Claude y escribe el markdown traducido:
python3 scripts/translate-docs.py \ --anthropic-key $ANTHROPIC_API_KEY \ --model claude-opus-4-6 \ --workers 5El script tarda aproximadamente 60–70 minutos en traducir los 28 archivos fuente a los 16 idiomas de destino (448 archivos en total). Ejecuta 5 llamadas paralelas a la API para mantenerse dentro de los límites de velocidad.
Para un solo idioma nuevo, son aproximadamente 4 minutos.
Paso 3: Compilar
Sección titulada «Paso 3: Compilar»pnpm buildAstro lee el markdown fuente, lo renderiza a través de las plantillas de Starlight y genera HTML estático en dist/. La compilación tarda unos 30 segundos para las ~500 páginas.
Paso 4: Desplegar
Sección titulada «Paso 4: Desplegar»pnpm run deployPublica el sitio compilado en Cloudflare Workers.
Cómo Funciona el Script
Sección titulada «Cómo Funciona el Script»El script de traducción es intencionalmente simple — unas 300 líneas de Python. Este es el flujo:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐│ 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 filesEl prompt le indica a Claude exactamente qué traducir y qué dejar intacto:
- Traducir: prosa, encabezados, etiquetas de tablas, título y descripción del frontmatter
- Mantener en inglés: bloques de código, código en línea, ejemplos de comandos, rutas de archivos, URLs, nombres de productos, nombres de personas, diagramas ASCII
Esta separación es fundamental. Un comando como pnpm build debe permanecer como pnpm build en todos los idiomas. Un nombre de producto como “En Parlant~” o “Stockfish” no se traduce. Pero “Getting Started” se convierte en “はじめに” en japonés y “Начало работы” en ruso.
¿Por qué Opus?
Sección titulada «¿Por qué Opus?»Claude Opus 4.6 produce traducciones notablemente mejores que los modelos más rápidos. La diferencia se manifiesta en:
- Fraseo natural — Opus escribe como un hablante nativo, no como un traductor. Reestructura las oraciones cuando el orden de palabras del inglés sonaría extraño en el idioma de destino.
- Precisión técnica — La terminología de ajedrez, la jerga de TTS y los conceptos de software se traducen utilizando los términos correctos del dominio específico.
- Consistencia — El registro formal se mantiene coherente en todo momento. El japonés usa la forma です/ます en todas partes, sin alternar entre casual y cortés a mitad de párrafo.
- Manejo de MDX — Opus preserva correctamente las etiquetas de componentes JSX (
<Card>,<CardGrid>) y las sentenciasimporten archivos.mdxsin corromperlas.
La diferencia de coste es real — aproximadamente $28 por una traducción completa del sitio con Opus frente a $5 con Sonnet — pero para 448 archivos que los usuarios realmente leerán, la calidad lo vale.
La Arquitectura de Enrutamiento por Locale
Sección titulada «La Arquitectura de Enrutamiento por Locale»Esta es la parte que más tiempo llevó hacer funcionar correctamente.
El problema
Sección titulada «El problema»Starlight detecta el idioma de una página comprobando el primer segmento de su slug de contenido. Cuando ve fr/docs/getting-started, sabe que es francés porque fr es el primer segmento. Pero la implementación inicial producía slugs como docs/fr/getting-started — el locale enterrado debajo de docs/. Starlight veía docs como el primer segmento, trataba todo como inglés y generaba más de 7.000 páginas duplicadas en lugar de ~500.
La solución
Sección titulada «La solución»Una función personalizada generateId en src/content.config.ts controla cómo las rutas de archivos se convierten en slugs de contenido:
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}`;}Esto coloca el prefijo de locale antes de docs/:
| Ruta del archivo | 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/ |
El inglés usa defaultLocale: "root", lo que significa que no tiene prefijo — reside directamente en /docs/, no en /en/docs/.
El array localeKeys
Sección titulada «El array localeKeys»Un array localeKeys en el mismo archivo debe listar cada locale que no sea inglés. Si un locale existe en la configuración de Astro pero no en este array, su contenido traducido se trata como contenido en inglés — el selector de idioma falla y el recuento de páginas se dispara.
Caché del almacén de datos
Sección titulada «Caché del almacén de datos»Astro almacena en caché las asignaciones de slugs en .astro/data-store.json. Después de cualquier cambio en la configuración de locales, este archivo debe eliminarse antes de recompilar, o la compilación se completa con éxito pero con enrutamiento obsoleto (incorrecto).
La Estructura del Menú
Sección titulada «La Estructura del Menú»La barra lateral se define en astro.config.mjs. Los elementos que apuntan a una página (propiedad slug) utilizan automáticamente el título del frontmatter de la página traducida — no se necesita traducción manual:
{ slug: "docs/getting-started" }// English: "Getting Started" (from English frontmatter)// French: "Premiers pas" (from French frontmatter)// Japanese: "はじめに" (from Japanese frontmatter)Pero las etiquetas de grupo y los enlaces externos necesitan traducciones 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" }, // ... ],}Siete elementos de la barra lateral necesitan traducciones manuales: Welcome, Features, App Menus, Setup Guides, Under the Hood, Credits y Accessibility. Añadir un nuevo idioma significa agregar una entrada a cada uno de estos siete bloques.
La Conexión con la Aplicación
Sección titulada «La Conexión con la Aplicación»En Parlant~ enlaza a esta documentación desde su menú de Ayuda. El enlace es sensible al locale — si estás usando la aplicación en francés, se abre la documentación en 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]);Cada idioma disponible en la aplicación tiene una traducción correspondiente en el sitio. La asignación es automática — fr_FR se mapea a /fr/docs/, ja_JP se mapea a /ja/docs/. El único caso especial es el chino tradicional: zh_TW se mapea a /zh-tw/docs/ (con guion).
Por Qué Ahora Es Fácil
Sección titulada «Por Qué Ahora Es Fácil»Las partes difíciles ya están resueltas:
-
La arquitectura de enrutamiento está solucionada. La función
generateId, el arraylocaleKeysy la configuracióndefaultLocale: "root"trabajan en conjunto para que Starlight genere la estructura de URLs correcta. Este fue el mayor punto de dolor — requirió rastrear más de 6 archivos fuente en Starlight y Astro para encontrar y corregir el problema. -
El script de traducción se encarga de todo. Retraducción completa del sitio, adición de un solo idioma, actualizaciones de archivos individuales — todo es el mismo script con diferentes flags. Reintenta ante límites de velocidad, paraleliza entre workers e informa los errores con claridad.
-
Añadir un nuevo idioma son cuatro ediciones de configuración y un comando. Agregar el locale a
astro.config.mjs,content.config.tsytranslate-docs.py, añadir las traducciones de la barra lateral, ejecutar el script. Aproximadamente 10 minutos de trabajo más 4 minutos de tiempo de traducción. -
Actualizar contenido es aún más sencillo. Editar el fuente en inglés, ejecutar el script con
--filesy--overwritesolo para los archivos modificados, recompilar. O para correcciones menores de prosa, editar directamente los archivos traducidos. -
Todo el pipeline está capturado en un skill. Ejecutar
/translate_docsguía a través de todo el proceso — qué modo usar, qué flags pasar, comprobaciones previas, verificación posterior a la traducción. No se requiere conocimiento institucional.
La traducción total de 448 archivos en 16 idiomas costó aproximadamente $28 y tomó alrededor de 70 minutos. Un solo idioma nuevo cuesta aproximadamente $1,70. Un solo archivo retraducido a todos los idiomas cuesta aproximadamente $1. Estos son los costes continuos de mantener la documentación actualizada en 17 idiomas.