Topics

Cómo dejamos de usar la API de Google Translate y rediseñamos la traducción con LLM: internacionalización con Claude API y caché diferencial en R2

  • column

Hace un tiempo escribí un artículo sobre cómo hicimos multilingüe un sitio Astro SSG con la API de Google Translate. Utilizaba Google Cloud Translation API para traducir el HTML estático generado tras la compilación y producir una versión para cada idioma. Como forma sencilla y económica de tener un sitio multilingüe, fue perfectamente práctico.

Sin embargo, tras un tiempo en funcionamiento, surgieron dudas tanto en lo referente al SEO como a la calidad de la traducción. Si quiere saber por qué dejamos Google Translate, consulte el artículo aparte.

Artículo: Migración de la traducción automática de Google Translate a Claude API

Este artículo trata sobre cómo lo rehicimos en la práctica. Cambiamos el motor de Google Translate por un LLM (Claude API) y, de paso, eliminamos el trabajo manual de operación que había quedado pendiente desde la vez anterior.

Traducción con LLM durante 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 (en el lado del servidor). No llamamos a Claude API desde el navegador. Lo que cambia es el motor de traducción y el lugar donde se almacena la caché.

El flujo de compilación es el siguiente.

npm run build
  ├── fetch-microcms   (microCMSから記事データ取得)
  ├── astro build      (日本語HTMLを生成 → dist/)
  ├── translate        (各ロケールのHTMLを生成 → dist/{locale}/)
  └── update-xml       (sitemap更新)

Lo que hace translate internamente (translate-html-llm.mjs) sigue, a grandes rasgos, este orden.

  1. Descargar desde Cloudflare R2 la caché de traducciones (un único archivo JSON)
  2. Extraer el texto a traducir de cada HTML en dist/ usando cheerio
  3. Si existe caché, usarla; solo se envían a la API de Claude los textos que no estén en caché
  4. Reemplazar el texto con los resultados de la traducción y escribirlo en dist/{locale}/
  5. Combinar las traducciones nuevas con la caché y subirla a R2

Los dos puntos clave son la traducción diferencial, que solo traduce lo que ha cambiado, y guardar esa caché en R2.

① Cómo traducir de forma natural las frases que atraviesan etiquetas inline

Esto era lo que más queríamos mejorar.

Por ejemplo, supongamos que el contenido tiene este HTML.

<p>私たちは<strong>ウェブアクセシビリティ</strong>を重視しています</p>

Si se procesa la traducción de forma normal, se traducirían por separado tres fragmentos: 私たちは / ウェブアクセシビリティ / を重視しています. Como el japonés y el español tienen orden de palabras distinto, al volver a colocar los fragmentos traducidos en su posición original, <strong> puede quedar en el lugar equivocado o la frase puede sonar poco natural. Cuanto más etiquetas inline tiene una frase, peor se desordena. Este era el principal problema del sistema anterior.

La mejora se basa en dos pasos.

1. Dividir en fragmentos por elementos de bloque. La unidad mínima de traducción no son los "huecos entre etiquetas", sino el contenido completo (innerHTML) de elementos de bloque como p, h1 a h6, li, td o blockquote. No se fragmentan las oraciones.

2. Sustituir las etiquetas en línea por marcadores para pasar la oración completa como una única unidad de traducción. Dentro de cada fragmento, las <strong> y <a> se sustituyen primero por marcadores como ....

私たちは[[T1]]ウェブアクセシビリティ[[/T1]]を重視しています

Se indica al LLM que "esto es una sola oración. Tradúzcala de forma natural y, si es necesario, vuelva a asignar los mismos marcadores a las palabras que deban destacarse. Los marcadores pueden moverse para adaptarse al orden de palabras de la traducción". Cuando se recibe la traducción, los marcadores se restauran a sus <strong> y <a href="..."> originales. Los atributos (href y class) se conservan tal cual.

Así, <strong> queda asociado a las palabras correctas también en la versión en inglés, y la oración resulta natural.

2. Diseño de traducción diferencial y caché en R2

Si se tradujera todo en cada ejecución, el coste sería inasumible. La versión anterior ya contaba con una caché llamada translate-cache.json, pero esta vez se revisaron la forma de generar las claves y el lugar de almacenamiento.

Un mecanismo que vuelve a generar las traducciones al modificar el prompt

La clave de la caché combina el "texto japonés original + la configuración regional + el contenido del prompt de traducción". Así, si cambia el japonés original, solo se retraduce esa entrada, y si cambian las instrucciones de traducción o la política terminológica (el prompt), todas las entradas se tratan automáticamente como "no presentes en caché" y se vuelven a traducir.

El objetivo es evitar el problema de que, tras mejorar el prompt, las traducciones antiguas sigan presentes en la caché. En la práctica, estos datos se convierten en una cadena con sha256 y esa cadena se usa como clave.

La estructura del contenido de la caché es la siguiente.

{"<sha256のキー>":{"value":"翻訳結果(マーカー込み)","locale":"en","model":"claude-haiku-4-5-20251001","translatedAt":"2026-06-12T..."}}

Se movió la caché a R2 y se eliminó el trabajo manual

Aquí se resuelven los pendientes de la vez anterior.

Anteriormente, la caché se gestionaba mediante un archivo JSON dentro del repositorio. Por eso, al añadir un artículo en el CMS y ejecutar la compilación a través del deploy hook, el servidor solo podía acceder a la caché antigua que estaba en Git. Como solución provisional, se seguía la regla de operación de "compilar en local después de añadir un artículo y hacer push del archivo de caché actualizado a Git".

Ahora la caché se almacena como un único blob JSON en Cloudflare R2. En cada compilación se obtiene desde R2 y, al terminar, se vuelve a escribir allí. De este modo, la caché persiste también en las compilaciones lanzadas mediante webhook, y se eliminó por completo el trabajo manual de compilar en local y hacer push. Al añadir un artículo y hacer push, solo se traduce el contenido nuevo y la caché se actualiza automáticamente.

Prioridad de traducción

Las variaciones en la terminología (como las inconsistencias en la escritura del nombre de la empresa, Liberogic) también fueron un problema en la vez anterior. Esta vez, se gestiona en cuatro niveles.

  1. Anulación manual (override) (data-i18n-key) ― Los elementos del HTML que tienen data-i18n-key se fijan a una traducción escrita a mano y preparada de antemano. Permite aplicar una regla de "esto debe traducirse siempre así" sin depender del LLM ni del diccionario.
  2. Glosario terminológico ― Las etiquetas fijas que se repiten, como las de la navegación o los títulos de página, se fijan mediante un diccionario en JSON. Al corregir el diccionario y volver a compilar, los cambios se aplican de inmediato.
  3. Caché de R2 ― Si no se encuentra en ninguno de los anteriores, se consulta la caché.
  4. Claude API ― Solo para los casos que no están en ninguno de los anteriores, como último recurso se recurre al LLM.

La unificación de la terminología que aparece en el texto no puede cubrirse con un glosario de coincidencias exactas (no admite coincidencias parciales), por lo que la alineamos de forma flexible mediante pistas terminológicas en el prompt del sistema.

Convivir con las "manías" de los LLM

Que la traducción automática produzca resultados extraños no es nuevo, pero los LLM tienen sus propias manías. A continuación presentamos algunas que hemos identificado en el uso real.

  • Frases técnicas que se quedan completamente en inglés. En ocasiones, al aplicar en exceso la lista de términos que no se traducen (como API, React o Vue), el modelo devuelve la frase entera en inglés. Lo solucionamos reforzando en el prompt de usuario la instrucción de responder en el idioma de destino.
  • Las posiciones de los marcadores se invierten en el significado. El modelo interpreta mal qué palabras deben destacarse, y <strong> acaba abarcando un rango incorrecto. Basta con eliminar esa entrada concreta y volver a traducirla para corregirlo.
  • La respuesta en idiomas CJK se corta a mitad. Los caracteres kanji consumen más tokens por carácter, así que al enviar todo junto la salida alcanza el límite y el JSON se rompe. Lo resolvemos aumentando max_tokens y reduciendo el número de elementos por lote.
  • Secuencias literales y fechas CJK rotas. Saltos de línea que se cuelan como cadenas , o espacios innecesarios como en 2026 年 05 月 22 日. Estos casos se limpian en bloque con un script de posprocesado.

Un aprendizaje práctico: corregir las entradas problemáticas una a una y volver a traducirlas es lo que mejor funciona. Si se intentan arreglar varias a la vez, el LLM tiende a clasificar de forma errónea la misma estructura en todas ellas. Más vale ir despacio.

Sobre los costes

Usamos Claude Haiku 4.5 como motor único. Priorizamos el coste y, si aparecen problemas de calidad, primero intentamos mejorarlos con el prompt y el glosario. En el prompt del sistema activamos el prompt caching para comprimir el coste de la parte fija que se repite en cada llamada.

Estas son las estimaciones orientativas de costes reales.

Contenido

Coste

Traducción completa inicial de un idioma

Aprox. $2,5-3

Traducción completa inicial de todos los idiomas

Aprox. $20

Despliegue habitual (solo con caché)

Casi $0

Añadir 1 artículo (traducción de unas pocas entradas)

Aprox. $0,01

Los despliegues diarios son prácticamente gratuitos, y añadir artículos cuesta entre 1 y unos pocos céntimos. La reconstrucción inicial sí requiere una inversión considerable, pero una vez superada, el costo de operación es incluso más bajo que antes.

Conclusión

Al migrar de Google Translate a la traducción con LLM, el costo operativo resultó ser bajo y la calidad de la traducción mejoró notablemente.

Por ser un LLM, no todo queda perfecto de forma automática. Seguir detectando y corrigiendo los patrones inadecuados requiere un ajuste constante y meticuloso. Sin embargo, como los puntos a modificar se concentran en lugares claros como el prompt y el diccionario, el ciclo de mejora es ahora mucho más fácil de llevar a cabo.

Autor de este artículo

Pasó del DTP al mundo web y, sin darse cuenta, domina el maquetado, el frontend, la dirección de proyectos y la accesibilidad: un auténtico "maestro de todas las técnicas". Desde los inicios de Liberogic ha desempeñado múltiples funciones y hoy es una auténtica enciclopedia viviente dentro de la empresa. Últimamente se ha obsesionado con la optimización mediante prompts, preguntándose: «¿No podríamos apoyarnos más en la IA para la accesibilidad?». Su tecnología y su forma de pensar siguen evolucionando.

Ayumu Futamata

Especialista en Accesibilidad Web certificado por IAAP (WAS) / Ingeniero de marcado / Ingeniero frontend / Director web

Ver los artículos de este miembro del equipo

Equipo de confianza y una respuesta ágil son nuestras fortalezas

En Liberogic, nuestro personal veterano impulsa los proyectos de forma proactiva, por lo que nuestros clientes nos valoran muy positivamente.
Nos esforzamos por asignar de manera adecuada a un gestor de proyectos y a un director, para que el proyecto avance sin contratiempos en su conjunto. Nuestro estilo consiste en distribuir los recursos de forma adecuada a cada tarea, evitando el aumento de costes por dedicaciones completas innecesarias. También somos reconocidos por la rapidez con la que comprendemos el alcance del trabajo y preparamos y presentamos los presupuestos.

Nota: Ten en cuenta que Liberogic no se dedica activamente a los servicios de desarrollo en las instalaciones del cliente (tipo SES).

Puede utilizar prácticamente todas las principales herramientas de gestión de proyectos y de chat, como Slack, Teams, Redmine, Backlog, Asana, Jira, Notion, Google Workspace, Zoom, Webex y más.

Cuéntenos sus desafíos web.

Casos de Estudio