Publicado el 5 min de lectura

Comprueba los marcadores de traducciones de IA con Python

En esta página

Una notificación traducida por IA puede sonar natural y aun así romper tu aplicación. Si {order_id} se convierte en {id_pedido}, la aplicación sigue proporcionando la clave original. Si desaparece un marcador, el formato puede aplicarse sin errores, pero omitir información sin avisar.

Este tutorial crea una pequeña comprobación en Python para plantillas traducidas. Detecta cambios estructurales antes de renderizarlas, sin clave de API ni paquetes externos. No evalúa la calidad de la traducción. Para generar traducciones en un flujo de trabajo, consulta nuestro tutorial de traducción con LCEL.

Dos tarjetas de traducción conservan las mismas piezas ámbar y están unidas por un escudo verde de verificación

1. Define el contrato de la plantilla

Conserva una plantilla original aprobada junto a cada traducción propuesta, identificadas con el mismo ID de mensaje. Pide a tu herramienta de traducción que traduzca el texto circundante, conserve los nombres y las cantidades de los marcadores y devuelva solo la plantilla. Durante las pruebas utiliza nombres y números de pedido ficticios, no datos de clientes.

Nuestro contrato, deliberadamente estricto, permite campos con nombres ASCII simples, como {name} y {order_id}. Su orden puede cambiar; su escritura, sus mayúsculas y su número de apariciones no. Por tanto, si {name} aparece dos veces en el original, debe aparecer dos veces en la traducción. Esta regla de repetición es una política del proyecto, no un requisito lingüístico universal: algunos idiomas necesitan estructuras de frase diferentes.

Los campos con nombre permiten reorganizar la información, como explica la guía de Python de GNU gettext. Aquí quedan fuera del alcance los campos posicionales, el acceso a atributos o índices, las conversiones y los especificadores de formato no vacíos. También los mensajes ICU, las plantillas Jinja y las cadenas con %s; necesitan sus propios analizadores.

2. Analiza los campos en vez de adivinarlos con regex

Guarda lo siguiente como check_placeholders.py en UTF-8. Los casos son simulaciones escritas a mano de respuestas de IA, no mediciones de un modelo en vivo.

El analizador Formatter de Python separa los campos de sustitución del texto literal. Un Counter conserva las repeticiones que se perderían al comparar conjuntos. La comprobación nunca evalúa código generado.

from collections import Counter
from string import Formatter


def placeholders(template):
    if not isinstance(template, str):
        raise ValueError("expected text")
    counts = Counter()
    for _, field, spec, conversion in Formatter().parse(template):
        if field is None:
            continue
        if not field.isascii() or not field.isidentifier():
            raise ValueError("only simple named fields allowed")
        if spec or conversion is not None:
            raise ValueError("format options are outside this contract")
        counts[field] += 1
    return counts


def check(source, translated):
    expected = placeholders(source)
    try:
        actual = placeholders(translated)
    except ValueError:
        return "REJECT"
    return "ACCEPT" if actual == expected else "REJECT"


source = "Hi {name}; order {order_id} is ready, {name}."
cases = {
    "good": ("Hola {name}; pedido {order_id} listo, {name}.", "ACCEPT"),
    "reordered": ("Pedido {order_id} listo, {name}. Gracias, {name}.", "ACCEPT"),
    "renamed": ("Hola {nombre}; pedido {order_id} listo, {nombre}.", "REJECT"),
    "missing": ("Hola {name}; tu pedido está listo, {name}.", "REJECT"),
    "duplicated": ("Hola {name}; pedido {order_id} {order_id} listo, {name}.", "REJECT"),
    "broken": ("Hola {name; pedido {order_id} listo, {name}.", "REJECT"),
    "attribute": ("Hola {name.first}; pedido {order_id} listo, {name}.", "REJECT"),
    "conversion": ("Hola {name!r}; pedido {order_id} listo, {name}.", "REJECT"),
    "wrong-meaning": ("Hola {name}; pedido {order_id} cancelado, {name}.", "ACCEPT"),
}
for label, (translated, expected) in cases.items():
    result = check(source, translated)
    if result != expected:
        raise AssertionError(label)
    print(f"{label}: {result}")

approved = cases["good"][0]
if check(source, approved) == "ACCEPT":
    print(approved.format(name="Ana", order_id="A-17"))

El original aprobado se analiza fuera del manejador de excepciones a propósito: una plantilla de referencia inválida detiene la ejecución en lugar de hacerse pasar por una traducción rechazada. Un texto candidato inválido devuelve REJECT.

3. Ejecuta los casos e inspecciona el resultado

Con Python 3.10 o posterior, ejecuta:

python3 check_placeholders.py

Resultado esperado:

good: ACCEPT
reordered: ACCEPT
renamed: REJECT
missing: REJECT
duplicated: REJECT
broken: REJECT
attribute: REJECT
conversion: REJECT
wrong-meaning: ACCEPT
Hola Ana; pedido A-17 listo, Ana.

La frase reorganizada se acepta porque coinciden los nombres y las cantidades. Cambiar el nombre de una clave, omitirla, duplicarla o romper la sintaxis de las llaves provoca un rechazo. El acceso a atributos y las conversiones también se rechazan según este contrato. La última línea verifica que una plantilla aceptada se renderiza con valores concretos de prueba.

Fíjate en wrong-meaning: ACCEPT: la frase española dice que el pedido fue cancelado, no que está listo. Todos los marcadores siguen intactos, así que esta comprobación estructural la acepta correctamente. ACCEPT solo significa integridad de los marcadores. Una persona bilingüe todavía debe revisar el significado, el tono, los números y el contexto antes de publicar.

La ejecución mostrada termina sin errores porque cada caso produce su clasificación esperada, incluidos los rechazos intencionales. Si cambia una clasificación, se genera un error. Al integrar esta comprobación en un proceso por lotes, bloquea explícitamente el lote si algún candidato real devuelve REJECT; este ejecutor de casos no es, por sí solo, una barrera de despliegue.

4. Resuelve los problemas antes de ampliar la comprobación

  • Se rechaza una traducción válida: comprueba si el idioma necesita menos repeticiones. Aprueba un contrato específico para ese idioma en lugar de debilitar todas las comprobaciones sin avisar.
  • Las llaves literales causan problemas: Python utiliza llaves dobles, {{ y }}, para representar llaves literales. El analizador no las cuenta como campos; esta comprobación no verifica su contenido visible.
  • Necesitas opciones de formato: aquí se rechaza {amount:.2f}. Amplía el contrato y los casos deliberadamente o utiliza las comprobaciones de tu sistema de localización.
  • El resultado se acepta, pero se ve mal: inspecciona la notificación renderizada. No se validan HTML, URL, concordancia del plural ni veracidad del contenido. Mantén las URL fijas fuera del texto traducido o pásalas mediante campos con nombre, siguiendo la guía de preparación de GNU gettext.

Para otros formatos de entrada, compara nuestro tutorial de casos para regex y las comprobaciones estrictas de JSON de IA. Un analizador adecuado para un formato no sirve como validador de otro.

Conclusión práctica

Fija el original, valida la estructura de los marcadores, renderiza con valores de prueba y después revisa el significado. Mantén los candidatos rechazados fuera de la aplicación y añade cada fallo real a tus casos de regresión. Una pequeña comprobación determinista complementa la traducción con IA; no la certifica.

Artículos relacionados

Últimos artículos