🌐 También en: English · Deutsch · Français

Durante casi dos años, este blog habló exactamente un idioma. El inglés es la lingua franca de los sysadmins, así que parecía suficiente. Pero yo soy alemán, buena parte de mis lectores tampoco tiene el inglés como lengua materna, y cuando alguien busca «ZFS RAIDZ1 NAS Debian» en alemán, francés o español, obtiene resultados en alemán, francés o español. No los míos.
Así que esta semana el blog aprendió tres idiomas nuevos. Cada artículo publicado está ahora disponible en inglés, alemán, francés y español, con un selector de idioma en el menú y una pequeña línea «🌐 También disponible en» encima de cada artículo. Este artículo es el making-of: el plugin, el pipeline, el bug que mandó un idioma entero al 404 y lo que aprendí al poner a trabajar en paralelo a un pequeño ejército de agentes de IA. El mismo flujo de trabajo de siempre: yo tomo las decisiones, mi coadministrador de IA (Claude) hizo el trabajo pesado.
🎯 ¿Para qué tanto esfuerzo? Más lectores, ojalá
Seamos sinceros sobre la motivación: espero más visitas desde los buscadores. Google, Bing y DuckDuckGo emparejan las búsquedas con páginas en el idioma de quien busca. Un administrador alemán que busca una solución para fail2ban escribirá con mucha más probabilidad palabras clave en alemán, y una página que existe en alemán tiene una oportunidad real de aparecer ahí. Con etiquetas hreflang correctas, el buscador sabe además que las cuatro versiones van juntas, así que puede mostrarle a cada persona la de su idioma en lugar de tratarlas como contenido duplicado.
¿Funcionará? Ni idea todavía. Hace un tiempo conté a mis lectores reales, humanos (spoiler: muchos menos de lo que sugieren los logs de peticiones), y volveré a contarlos dentro de unos meses. Si las páginas traducidas atraen a gente que nunca habría encontrado el original en inglés, el experimento habrá valido la pena. Si no, también escribiré ese artículo.
🧩 El plugin: Polylang, y por qué
Elegí Polylang, en su versión gratuita. Mis criterios para la preselección eran sencillos: gratis, código abierto (GPLv3), sin cuenta, sin servicio en la nube y sin traducción automática que envíe mi contenido a ningún sitio. Polylang no traduce nada por sí mismo. Solo sabe qué artículo está en qué idioma y qué artículos van juntos, y lo hace guardando el idioma como una taxonomía de WordPress de lo más normal. Sin tablas adicionales, nada exótico en la base de datos.
Los ajustes que elegí, y por qué:
- El inglés sigue siendo el idioma predeterminado, sin
/en/en la URL. Todos los enlaces, marcadores y backlinks existentes siguen funcionando. Los idiomas nuevos viven en/de/,/fr/y/es/. - Sin detección del idioma del navegador. Los rastreadores de los buscadores no envían un idioma preferido, y no quiero que a un visitante que ha hecho clic en un enlace en inglés lo manden a otra parte. Recibes lo que has pulsado; el selector está justo ahí si quieres otro idioma.
- Los medios no se traducen. Una captura de pantalla es una captura de pantalla. Los textos alternativos y los pies de foto de los artículos se traducen; los archivos de imagen se comparten.
- Las categorías y etiquetas existen por idioma («IT Security» pasa a ser «IT-Sicherheit», «Sécurité informatique» y «Seguridad informática»), para que las páginas de archivo también funcionen en todos los idiomas.
La fontanería SEO vino de regalo: Polylang añade las alternativas hreflang a cada página, y el sitemap XML de Yoast recoge las traducciones automáticamente. El menú recibió cuatro banderitas como selector de idioma, y un pequeño plugin must-use (desplegado por mi rol de Ansible, como todo lo demás en este servidor) muestra la línea «También disponible en», pero solo para los idiomas en los que el artículo existe de verdad.

🤖 Con total transparencia sobre la IA
Todas las traducciones están hechas con IA. Lo digo en la página AI Usage Notice, que ahora tiene una sección «Translations»: el inglés es el original, los demás idiomas son traducciones de IA revisadas por una segunda pasada de IA independiente. Si algo suena raro en alemán, francés o español, la versión inglesa es la referencia, y me encantará que me lo cuentes.
🧪 El piloto, y el 404 que se tragó un idioma entero
Antes de tocar cien artículos, traduje exactamente uno: mi artículo sobre la portada en el móvil. Menos mal, porque el primer resultado fue espectacular, pero en el mal sentido: todas las páginas en alemán devolvían un 404. Los artículos existían, Polylang los conocía, el selector enlazaba a ellos y WordPress decía «ni idea de qué me hablas».
El culpable: las reglas de reescritura. Había configurado Polylang con wp-cli y regenerado las reglas de reescritura desde la línea de comandos. En la CLI, el filtro de prefijo de idioma de Polylang no está del todo activo, así que WordPress generó tan tranquilo un juego nuevo de reglas que no sabía nada de /de/. El arreglo fue casi vergonzoso: borrar las reglas en caché y dejar que la siguiente petición normal al front-end las reconstruyera, esta vez con Polylang completamente cargado.
wp eval "delete_option('rewrite_rules');"
# then just load any page once – WordPress rebuilds the rules with the language prefixesLección apuntada: todo lo que depende del contexto de la petición (idiomas, temas, algunos plugins de caché) debería regenerar su estado en una petición real, no en una sesión de CLI.
🏭 El pipeline: 111 artículos × 3 idiomas
111 artículos publicados por tres idiomas son 333 traducciones. Hacerlo a mano en el editor nunca fue una opción, así que se convirtió en un pipeline. Cada artículo pasa por cinco pasos:
- Exportación. Un script de wp-cli vuelca cada artículo como HTML de Gutenberg. Los bloques de código se recortan y se sustituyen por marcadores como
<!--PLLCODE 3-->; el código original va a un archivo JSON aparte. Un traductor no puede «mejorar» un comando de shell que nunca ve. - Traducción. Un agente de IA traduce el HTML al alemán, francés y español siguiendo una guía de estilo (más sobre eso abajo), además del título, el extracto y la metadescripción de cada idioma.
- Comprobación, mecánica. Un pequeño script de Python compara cada traducción con el original: la misma secuencia de comentarios de bloque de Gutenberg, los mismos marcadores, el mismo conjunto de destinos de enlace y fuentes de imagen, el mismo número de filas de tabla y elementos de lista. Si algo no cuadra, la traducción se devuelve.
- Revisión, independiente. Un segundo agente, que nunca vio cómo se escribía la traducción, compara original y traducciones frase por frase y redacta sus correcciones como pares exactos de buscar y reemplazar, cada uno con su motivo. Cada cadena «antigua» debe aparecer exactamente una vez; si no, el cambio se rechaza.
- Importación. Primero un volcado de la base de datos; después, un script que se ejecuta como
www-datavuelve a insertar los bloques de código, publica las traducciones con la fecha original, asigna las categorías y etiquetas de cada idioma, reutiliza la imagen destacada, establece la metadescripción de Yoast y enlaza las cuatro versiones entre sí en Polylang.
El corazón de la comprobación mecánica son una docena de líneas. No entiende ni una sola palabra de ningún idioma, y de eso se trata:
def feats(h):
return {
'blocks': re.findall(r'<!-- (/?wp:[a-z0-9/-]+)', h),
'code': sorted(re.findall(r'<!--PLLCODE \d+-->', h)),
'href': sorted(re.findall(r'href="([^"]*)"', h)),
'src': sorted(re.findall(r'src="([^"]*)"', h)),
'tr': h.count('<tr'), 'li': h.count('<li'), 'img': h.count('<img'),
'inline_code': len(re.findall(r'<code>', h)),
}
# original and translation must produce identical features📏 Una guía de estilo, como en una agencia de traducción de verdad
La coherencia entre 333 textos no surge por casualidad. La guía de estilo fija el tono (du en alemán, vous en francés, tú en español), un glosario («hardening» es siempre «Härtung», «durcissement», «bastionado»), los formatos numéricos (2,6 s en lugar de 2.6 s), la tipografía francesa con espacios de no separación antes de : ; ? ! y una lista de cosas que nunca se tocan: comandos, rutas, nombres de host, números de versión, mensajes de error, nombres de productos. Los enlaces a artículos relacionados conservan su título en inglés, con una breve nota «(en inglés)», hasta que esos artículos también estén traducidos.
🔍 ¿Mereció la pena la revisión?
Totalmente. La mayoría de las traducciones eran buenas, pero «bueno» no es «correcto». En los primeros 26 artículos, el revisor encontró alrededor de un centenar de cosas que corregir, repartidas de forma más o menos uniforme entre los tres idiomas; solo dos artículos volvieron sin ninguna observación. La mayoría eran retoques de estilo, pero unas cuantas eran auténticos errores de sentido que un texto de lectura fluida esconde a la perfección:
- «a longer memory» (en el sentido de conservar los logs más tiempo) se había convertido en «más RAM» en español.
- «customer-facing service» (un servicio expuesto a los clientes) se había convertido en «servicio al cliente» en francés.
- Una columna de tabla «Focus» se había transformado en «Priority». Verosímil, pero incorrecto.
✏️ TODO (Raphael): final numbers when all 111 posts are through.
🐝 Subagentes: un despachador, muchos trabajadores
Aquí es donde la cosa se puso interesante. Traducir un artículo le cuesta a una IA mucho leer y escribir: la guía de estilo, el original, tres traducciones. Hacer 111 de esos en una sola conversación la habría sepultado bajo el texto mucho antes del final. Así que la sesión principal se convirtió en un despachador y apenas tradujo nada por sí misma:
- Lanza un agente traductor por artículo. Cada uno es una instancia nueva con un contexto limpio: lee la guía de estilo y exactamente un artículo, escribe los archivos, ejecuta la comprobación e informa con tres líneas.
- Cuando un traductor informa, el despachador lanza un agente revisor para ese artículo, que funciona con un modelo más pequeño y barato (Sonnet), y acto seguido el siguiente traductor.
- Cuando vuelve una revisión, el despachador aplica las correcciones, vuelve a ejecutar la comprobación y lo sube.
Funcionaban a la vez entre tres y cuatro traductores y un par de revisores. El contexto del propio despachador se mantuvo pequeño, porque lo único que veía eran informes breves. Separar autor y revisor tiene una segunda ventaja más allá del paralelismo: el revisor no tiene ningún apego a la traducción. No la escribió, no «recuerda» por qué una frase se formuló así; simplemente compara.
✅ Cuándo compensan los agentes en paralelo
- Tareas independientes. Al artículo 1370 le da igual el artículo 1373. Sin estado compartido, sin problemas de orden.
- Entrada clara, salida clara. «Lee estos archivos, escribe aquellos, ejecuta esta comprobación». Un agente con un encargo nítido no necesita idas y venidas.
- Un filtro mecánico. Como la comprobación en Python detecta cualquier estructura rota, no necesito fiarme de cada agente; me fío del filtro.
- Un contexto limpio ahorra presupuesto. Un trabajador que solo lee lo que necesita sale más barato que una conversación larga que arrastra todos los artículos anteriores.
❌ Cuándo no
- Pasos encadenados. Configurar Polylang, perseguir el 404, construir el script de importación: cada paso dependía del resultado del anterior. Eso fue una sola conversación, un solo cerebro, sin reparto.
- Cosas pequeñas. Cada agente arranca en frío y primero tiene que leer la guía de estilo. Para un arreglo de una línea, el arranque cuesta más que el trabajo.
- Instrucciones mezcladas. Un revisor recibió un encargo extra («cambia también este título en español de vosotros a tú»). Lo hizo, e informó con honestidad de que solo había echado un vistazo por encima al alemán y al francés. Una segunda revisión completa del mismo artículo encontró nueve errores reales. Lección: mantén limpio el encargo de cada agente y lee los informes, no solo las cifras.
🧱 El verdadero límite: la cuota
El paralelismo no abarata los tokens; solo te estampa antes contra la pared. Hacia el mediodía del primer día, tras unos diez artículos terminados y con cuatro traductores y tres revisores en marcha, mi suscripción alcanzó su límite de sesión. Los siete agentes murieron en el mismo segundo y dejaron archivos a medio escribir. Nada se rompió en línea, porque no se sube nada antes de haber superado la comprobación y la revisión. Tras el reinicio, los traductores relanzados recibieron la instrucción de reutilizar un archivo sobrante solo después de demostrar, comparándolo con el original, que estaba completo, y de reescribirlo en caso contrario. Desde entonces, menos agentes en paralelo y un ritmo más constante. Con un límite de uso por sesión y por semana, lo que decide la velocidad es ese presupuesto, no el número de agentes.
🚫 Lo que no hice a propósito
- Nada de chino (ni japonés, ni…). Tentador por el alcance, pero estaría publicando texto que ni siquiera puedo revisar por encima. Tres idiomas que al menos puedo seguir más o menos me parecieron el límite honesto.
- Nada de piloto automático sin supervisión. En algún momento surgió la idea de un temporizador de systemd que lanzara una sesión de IA headless cada pocas horas y siguiera traduciendo sin que nadie mirase. Las comprobaciones de seguridad de mis herramientas de IA se negaron a configurarlo y, visto en retrospectiva, con razón: una IA sin supervisión con acceso SSH de escritura a un servidor de producción es algo que un humano debería activar deliberadamente, no algo que se cuele como una comodidad. Así que el pipeline funciona mientras yo estoy presente.
📝 Pendiente en la lista
- Terminar los artículos restantes. ✏️ TODO (Raphael): current state.
- Hacer que los enlaces internos y las listas «Related posts» apunten a las versiones traducidas y quitar las notas «(en inglés)».
- Traducir las páginas estáticas (About, aviso legal, política de privacidad, AI Usage Notice) y darle a cada idioma su propio menú.
- Mirar las estadísticas de búsqueda dentro de unos meses y ver si de verdad ha venido alguien.
Si estás leyendo esto en alemán, francés o español: willkommen, bienvenue, bienvenido. Y si alguna frase suena como si la hubiera escrito un robot, es porque así fue. Dímelo y un humano lo arreglará.





