Anteriormente, escribí un artículo titulado Multilingüismo de sitios Astro SSG con la API de Google Translate. Se trata de un sistema que utiliza Google Cloud Translation API para traducir HTML estático después de la compilación y generar versiones en cada idioma. Como multilingüismo simplificado con costos reducidos, fue totalmente práctico.
Sin embargo, después de operar durante un tiempo, surgieron puntos que nos preocupaban en términos tanto de SEO como de calidad de traducción. Para más detalles sobre «por qué dejamos de usar Google Translate», consulta otro artículo.
Artículo: Migración de traducción automática de Google Translate a Claude API
Este artículo trata sobre «cómo lo rehicimos en realidad», es decir, la implementación. Cambiamos el motor de Google Translate a LLM (Claude API) y, de paso, eliminamos todo el trabajo manual de operación que había quedado pendiente la última vez.
Traducción con LLM en el momento de la compilación y caché diferencial en R2
La política básica es la misma que antes: la traducción se completa en el momento de la compilación (lado del servidor). No llamamos a la API de Claude desde el navegador. La diferencia está en el motor de traducción y la ubicación del caché.
El flujo de compilación es así:
npm run build
├── fetch-microcms (microCMSから記事データ取得)
├── astro build (日本語HTMLを生成 → dist/)
├── translate (各ロケールのHTMLを生成 → dist/{locale}/)
└── update-xml (sitemap更新)
Lo que hace el contenido de translate (archivo translate-html-llm.mjs) es, en general, en este orden:
- Descarga el caché de traducción (un único archivo JSON) desde Cloudflare R2
- Cargar cada HTML bajo
dist/con cheerio y extraer el texto que debe traducirse - Si está en caché, usarlo; solo enviar los elementos faltantes a la API de Claude
- Reemplazar el texto con los resultados de la traducción y escribir en
dist/{locale}/ - Fusionar las nuevas traducciones en el caché y subir a R2
Los dos puntos clave son la «traducción diferencial» (traducir solo lo que cambió) y almacenar ese caché en R2.
① Cómo hacer que las traducciones que atraviesan etiquetas en línea sean naturales
Este fue el aspecto que más queríamos mejorar esta vez.
Por ejemplo, supongamos que el cuerpo tiene este HTML.
<p>私たちは<strong>ウェブアクセシビリティ</strong>を重視しています</p>
Si se procesa la traducción normalmente, nosotros / accesibilidad web / lo valoramos se traducen como 3 fragmentos separados. Como el orden de las palabras difiere entre japonés e inglés, cuando devuelves los fragmentos traducidos a su posición original, <strong> termina aplicándose en el lugar incorrecto, o la oración resulta antinatural. Cuantas más etiquetas en línea hay en una oración, más se distorsiona. Este era el mayor problema del sistema anterior.
El método de mejora es un enfoque de dos pasos:
Primero: segmentar por elementos de bloque. La unidad mínima de traducción no es el «espacio entre etiquetas», sino el contenido completo (innerHTML) de elementos de bloque como p, h1–h6, li, td y blockquote. No se dividen las oraciones.
Segundo: reemplazar etiquetas en línea con marcadores y pasar la oración completa como una única unidad de traducción. Los elementos <strong> y <a> dentro del segmento se reemplazan temporalmente con marcadores como ....
私たちは[[T1]]ウェブアクセシビリティ[[/T1]]を重視しています
Le indicamos al LLM: «Esta es una oración. Tradúcela naturalmente y puedes volver a colocar los mismos marcadores en las palabras que deben enfatizarse. Los marcadores pueden moverse según el orden de las palabras en tu traducción». Cuando recuperamos la traducción, convertimos los marcadores nuevamente a <strong>, <a href="...">, etc. Los atributos (como href o class) se mantienen intactos.
De este modo, <strong>strong se aplica al término correcto también en inglés, y la oración resulta natural.
② Diseño de traducción incremental y caché de R2
Si tradujéramos todo cada vez, no habría presupuesto suficiente. En la iteración anterior teníamos un caché llamado translate-cache.json, pero esta vez revisamos cómo generábamos las claves y dónde las almacenábamos.
Cuando actualizamos el prompt, se regeneran las traducciones
La clave del caché se genera combinando «el texto original en japonés + el locale + el contenido del prompt de traducción». De esta manera, si el texto japonés cambia, solo se vuelve a traducir esa entrada; si modificamos las instrucciones o la política de términos (el prompt), todas las entradas se marcan automáticamente como «no están en caché» y se retraduce.
El objetivo es prevenir el accidente de que «mejoramos el prompt, pero las traducciones antiguas siguen en caché». En la práctica, convertimos estas cadenas a sha256 hash para usar como clave.
La estructura del contenido del caché es la siguiente.
{"<sha256のキー>":{"value":"翻訳結果(マーカー込み)","locale":"en","model":"claude-haiku-4-5-20251001","translatedAt":"2026-06-12T..."}}
Colocamos el caché en R2 y eliminamos el trabajo manual
Esta es la recuperación del trabajo pendiente de la vez anterior.
La vez anterior administrábamos el caché mediante un archivo JSON dentro del repositorio. Por eso, cuando se agregaba un artículo en el CMS y se compilaba a través del webhook de despliegue, el servidor solo podía ver el caché antiguo en Git. Así que nos vimos obligados a seguir una regla operativa: "cuando agregues un artículo, compila localmente, envía el archivo de caché actualizado a Git". Era un proceso manual que funcionaba pero resultaba engorroso.
Esta vez colocamos el caché en un único blob JSON en Cloudflare R2. Con cada compilación, lo recuperamos de R2 y al terminar lo escribimos nuevamente. Así el caché se persiste incluso en compilaciones mediante webhook, y desaparece completamente el trabajo manual de compilación local → envío. Ahora, al agregar un artículo y hacer push, solo se traducen las partes nuevas y el caché se actualiza automáticamente.
Prioridad de traducción
Las variaciones de términos de traducción (como las inconsistencias en la representación del nombre Liberogic) fueron un punto difícil también en la ocasión anterior. Esta vez lo procesamos en 4 niveles.
- Anulación manual (
data-i18n-key) ― Los elementos HTML que llevan el atributodata-i18n-keyse fijan con traducciones predefinidas que escribimos a mano. Sin confiar en el LLM ni en el diccionario, podemos aplicar traducciones específicas donde decimos "aquí sí o sí debe ser esta versión". - Diccionario de términos (glossary) ― Las etiquetas fijas que aparecen repetidamente, como navegación y títulos de página, fijamos sus traducciones en un diccionario JSON. Cuando actualizamos el diccionario y recompilamos, el cambio se refleja de inmediato.
- Caché en R2 ― Si no se encuentra en ninguna de las opciones anteriores, consultamos el caché.
- API de Claude ― Solo lo que no aparece en ninguno de los niveles anteriores se envía finalmente al LLM.
La unificación de términos que aparecen en el texto no se puede capturar completamente con un diccionario puntual (no es compatible con coincidencias parciales), así que los alineamos flexiblemente con pistas de términos en el lado del sistema prompt.
Convivir con los "hábitos" del LLM
La traducción automática produce traducciones extrañas, lo mismo que pasó anteriormente, pero los LLM tienen sus propios hábitos. Aquí presentamos algunos de los que hemos visto en la operación real.
- Textos técnicos enteros permanecen en inglés. Aplicamos excesivamente listas de "términos que no se traducen" como API, React, Vue, y a veces devuelve oraciones completas sin traducir al inglés. Lo manejamos reforzando "produce el resultado en el idioma de la región objetivo" en el prompt del usuario.
- La posición de los marcadores se invierte semánticamente. A veces juzgamos mal qué palabra debe enfatizarse y
<strong>cubre un rango extraño. Se corrige eliminando solo esa entrada y volviéndola a traducir. - La respuesta se corta a mitad de camino en idiomas CJK. Los caracteres chinos consumen más tokens por carácter, y cuando se envían en lote, la salida alcanza el límite y se daña el JSON.
max_tokensse incrementa y se reduce el número de lotes por solicitud. - Corrupción de literales
y fechas CJK. Las saltos de línea se mezclan como cadena de caracteres, o aparecen espacios innecesarios como en22 de mayo de 2026. Estos se limpian con scripts de post-procesamiento.
Un aprendizaje operacional que funcionó bien fue que la tasa de éxito es más alta cuando se elimina y se retraduce punto por punto lo que está mal. Cuando se intenta arreglarlo de una vez, el LLM tiende a hacer el mismo error de juicio en la misma estructura. Fue un caso de "despacio pero seguro".
Sobre el costo
Hemos unificado el motor en Claude Haiku 4.5. Priorizamos el costo, y si surge algún problema de calidad, primero lo mejoramos con el prompt y el diccionario. Aplicamos prompt caching al sistema prompt para comprimir el costo de la parte fija que se repite cada vez.
Aquí te mostramos una estimación de referencia.
Contenido | Costo |
|---|---|
Traducción completa inicial de una sola región | Aproximadamente $2,5-3 |
Traducción completa inicial de todas las regiones | Aproximadamente $20 |
Despliegue estándar (solo aciertos en caché) | Casi $0 |
Agregar un artículo (algunas traducciones) | Aproximadamente $0,01 |
Los despliegues diarios son prácticamente gratuitos, y agregar artículos cuesta entre 1 y unos pocos yenes. Aunque la reconstrucción inicial requirió una inversión significativa, después de eso los costos operativos se redujeron considerablemente.
Conclusión
Al migrar de Google Translate a traducción con LLM, los costos operativos se mantuvieron bajos y la calidad de la traducción mejoró significativamente.
No todo es automático y perfecto solo por usar LLM. Seguimos haciendo ajustes meticulosos para eliminar peculiaridades. Sin embargo, como los cambios se concentran en lugares claros como el prompt y el diccionario, es más fácil iterar y mejorar.
Desde que saltó del mundo del DTP a la web, ha dominado markups, frontend, dirección y accesibilidad, convirtiéndose en el "sabio técnico" de la empresa. Ha sido un pilar multifacético desde los inicios de Liberogic y es ahora una referencia indispensable dentro de la organización. Últimamente está explorando eficiencias basadas en prompts, preguntándose «¿podríamos delegar más trabajo de accesibilidad a la IA?». Tanto su tecnología como su pensamiento siguen evolucionando.
Ayumi Futamata
Especialista en web accesibilidad certificado por IAAP (WAS) / Ingeniero de markups / Ingeniero frontend / Director web