Ir al contenido

Blog ·

Dieciséis mil claves, nueve idiomas

Traducir un panel no es un problema de texto. Los fallos que importan son silenciosos: un argumento perdido, un plural aplanado, una protección que no mira.

Por Provibr

Nuestro catálogo de mensajes en inglés contiene ahora mismo algo más de dieciséis mil cadenas. Hay nueve catálogos, uno por idioma, y están obligados a tener exactamente las mismas claves. Eso es algo por encima de ciento cuarenta mil piezas de texto que tienen que seguir siendo fieles entre sí mientras una docena de personas cambia el producto que hay debajo.

Lo interesante de ese problema no es el volumen. Es que casi ningún fallo se anuncia. Una traducción con una errata la comunica un cliente en una semana. Una traducción que perdió un argumento en silencio renderiza para siempre una frase con un agujero, y nadie abre un ticket por una frase que se lee bien.

Este es el conjunto de protecciones con el que acabamos, y el razonamiento de cada una. No son específicas de ningún framework; los modos de fallo son una propiedad de tener más de un idioma.

La clase de fallo que importa

Empecemos por el que nos hizo tomarnos esto en serio.

Una cadena inglesa dice Welcome back, {name}. Alguien la traduce, y la traducción es Welcome back. Es gramatical, es una frase razonable, y se renderiza sin error en cualquier sistema de plantillas que exista. El parámetro simplemente no aparece. No hay excepción, no hay línea de registro, no hay aviso de clave ausente.

Ese fallo es invisible para toda prueba que compruebe si las claves existen, que es la prueba que casi todo el mundo escribe primero. Lo único que lo encuentra es una comparación: extraer el conjunto de nombres de marcador de la cadena de referencia, extraerlos de la traducción y exigir que coincidan.

Una vez construida esa comparación, resultan necesarios tres refinamientos, y cada uno vino de una cadena real que se coló.

Comparar tipos de argumento, no solo nombres. Un {count} desnudo no formatea. Imprime el número crudo, sin separador de millares, en cualquier idioma. Escrito como {count, number} sí formatea. Si el tipo está presente en inglés y ausente en la traducción, el nombre coincide, la estructura de ramas coincide, y un idioma renderiza en silencio un entero crudo donde los demás renderizan uno formateado. Lo mismo vale para las fechas: un argumento de fecha que perdió su tipo le renderiza al lector una marca de tiempo ISO.

No comparar estilos de argumento. El estilo de moneda frente al estilo de número simple es una decisión de presentación que difiere legítimamente por idioma, y una protección que lo señala enseña a la gente a ignorar la protección. El tipo es estructural, el estilo es gusto, y solo uno de los dos pertenece a una prueba.

Comparar nombres de rama, no texto de rama. Un plural que el traductor aplanó conserva su argumento, así que una comparación de marcadores lo deja pasar tan contenta, mientras la pantalla acaba mostrando un número desnudo sin sustantivo detrás. Teníamos exactamente eso en nuestro catálogo neerlandés, y así se encontró.

Los plurales no son un problema resuelto que se pueda copiar

El instinto es exigir que una traducción tenga las mismas ramas de plural que el inglés. El inglés tiene dos: one y other. Ese requisito es correcto para los idiomas que también tienen dos, y es exactamente erróneo para los que no, porque acepta tan contento un catálogo al que le faltan categorías que su propio idioma necesita.

Algunas mediciones, todas ellas de las reglas estándar que trae cualquier entorno de ejecución moderno:

IdiomaLo que sorprende a la gente
FrancésEl cero toma la rama singular. «0 machine», no «0 machines».
UcranianoCuatro categorías. El cero cae en many, junto con 5 a 20; 21 es one y 22 es few.
RumanoDe 2 a 19 es few, 20 es other, y 101 vuelve a ser few.
ÁrabeSeis categorías, separadas en 0, 1, 2, 3 a 10, y 11 a 99.

La fila del rumano tiene una consecuencia que aparece como volumen puro. El rumano pone una preposición delante del sustantivo por encima de diecinueve, así que una frase que en inglés no necesita plural alguno sí lo necesita en rumano. Contando los mensajes de nuestros catálogos que contienen un plural: el inglés tiene 319 y el rumano tiene 498. 179 mensajes necesitan en rumano una construcción de plural que el inglés no necesita en absoluto. El ucraniano añade 13 propios, y el francés 4.

Aquí va uno de ellos, y es inglés completamente corriente:

text
en: Start {days}-day trial
uk: Почати пробний період на {days, plural, one {# день} few {# дні} many {# днів} other {# дня}}

Nada en la frase inglesa insinúa que sea un problema de plural. Una protección que deriva sus expectativas del inglés no puede verlo de ninguna manera.

Así que la segunda protección se ejecuta por idioma contra su propio catálogo: para cada plural que hay en él, ¿está atendida toda categoría que ese idioma puede producir realmente, sea por nombre o por una rama de valor exacto?

Esa protección necesita un límite deliberado, o se vuelve inútil. El francés y el español tienen una categoría many que se aplica solo a múltiplos exactos de un millón. Exigirla en todas partes añadiría cientos de ramas que ningún humano verá jamás, en un producto que cuenta servidores, direcciones, miembros de equipo y días. Así que la comprobación se guía por muestras: pregunta qué categorías son alcanzables para las cuentas que una clave ve de forma realista, y hay una pequeña lista explícita de claves que cuentan más alto, de modo que la maquinaria está lista para el día en que algo lo necesite.

Esa lista está vacía ahora mismo, y eso es una medición y no un descuido. Las dos candidatas que parecen poder crecer mucho cuentan bytes, y la regla francesa quiere un múltiplo exacto de un millón; un fichero de exactamente 1.000.000 de bytes es una casualidad, no un caso.

La protección que en silencio no hacía nada

Ahora la incómoda, porque es el fallo del que trata todo este artículo, ocurriéndole a las propias herramientas.

Tenemos una comprobación de que toda llamada de traducción del código se refiere a una clave que existe. Analiza el fuente, sigue qué objeto traductor pertenece a qué espacio de nombres y busca cada clave. Informaba de que cubría varios miles de claves. Estaba en verde.

También se saltaba toda llamada que venía de un patrón de desestructuración, porque el código que ataba un traductor a un espacio de nombres solo trataba un identificador simple e ignoraba en silencio cualquier otra cosa. Sin atadura, por tanto sin clave que comprobar, por tanto sin fallo. El patrón que se saltaba resultaba ser el que se usa en casi toda nuestra aplicación.

Lo encontramos porque alguien adivinó un nombre de clave, lo escribió, y la prueba siguió en verde.

De ahí salieron dos reglas, y ahora las aplicamos a todo análisis estático que escribimos.

Un analizador que encuentra una forma que no entiende tiene que fallar a gritos, no saltarla en silencio. Lo no reconocido es un hallazgo, no una operación vacía. Todo el valor de la herramienta está en que miró todo.

Hay que ver la protección en rojo antes de confiar en su verde. Romper algo a propósito y ver fallar la prueba. Una comprobación que nunca ha fallado no ha sido probada; solo ha sido ejecutada.

Un pariente cercano de esa lección: un nombre de marca de nuestros catálogos estaba escrito de cuatro maneras distintas, y el único sitio de todo el código que conocía la grafía correcta la conocía como una excepción en una lista de permitidos. El único componente que tenía la respuesta correcta estaba configurado para apartar la vista de ella. Una excepción no es una protección, y el arreglo suele ser invertir la cosa: enunciar la regla, y dejar que las excepciones sean datos que cuelgan de ella.

Analizar el propio formato de mensajes es más difícil de lo que parece

La extracción de marcadores con una expresión regular funciona hasta que deja de funcionar, y la forma en que falla es instructiva porque falla con las mejores cadenas.

Una rama de select cuyo texto es una sola palabra se parece exactamente a un argumento para un buscador de patrones. Nuestro antiguo escáner daba falsas alarmas en los mensajes escritos con más cuidado del catálogo y, a la vez, se perdía argumentos reales dentro de las ramas, porque una expresión regular no sabe contar llaves. Se equivocaba en las dos direcciones a la vez.

El sustituto recorre las llaves como es debido y entra recursivamente en los cuerpos de las ramas. Son treinta líneas en lugar de una, y es la diferencia entre una protección en la que la gente confía y una protección que la gente rodea a base de anotaciones.

La tipografía es una protección por idioma, no una compartida

La última categoría no se puede comprobar por comparación en absoluto, porque es justo lo que difiere entre idiomas. El francés quiere un espacio irrompible antes de los dos puntos, del punto y coma, del signo de exclamación y del de interrogación. El ucraniano quiere un carácter de apóstrofo concreto que se parece a los otros dos apóstrofos que probablemente hay en el teclado. El español abre una pregunta con un signo invertido, en el punto donde la pregunta empieza y no al principio de la frase. El rumano usa sus propias comillas y diacríticos con coma debajo.

Nada de eso es comparable entre idiomas, así que cada idioma que tenga reglas recibe su propia comprobación. Eso suena a duplicación y no lo es: la alternativa es una única protección genérica que solo puede afirmar la intersección de nueve conjuntos de reglas, que a efectos prácticos es nada.

Un aviso por hacer esto en un repositorio grande: un espacio irrompible es invisible, y varias herramientas lo convertirán solícitamente en un espacio normal por el camino. Los ponemos con un script exactamente por ese motivo, y comprobamos después que siguen ahí.

Lo que construiríamos primero, y en qué orden

Si se parte de un solo idioma y se espera añadir más, este es el orden que más nos habría ahorrado.

  1. Las mismas claves en todos los catálogos, comprobado en integración continua. Barato, y es el cimiento sobre el que se apoya el resto.
  2. Comparación de marcadores y de tipos de argumento contra el idioma de referencia. Esta es la que atrapa los fallos silenciosos.
  3. Comparación de nombres de rama para plurales y selects, de modo que un plural aplanado sea un fallo.
  4. Cobertura de plurales por idioma, guiada por las reglas del propio idioma, antes de que llegue el primer idioma que necesita más categorías que el de referencia.
  5. Tipografía por idioma, una comprobación pequeña por cada idioma que tenga convenciones.
  6. Una comprobación estática de que toda clave referida en el código existe y, más importante, de que es honesta sobre las llamadas que no pudo entender.

El tema que atraviesa las seis es que los fallos de traducción no lanzan excepciones. Renderizan. Cada protección de aquí existe porque algo se renderizó a la perfección, en un idioma que nadie de la sala lee, y dijo algo ligeramente distinto de lo que queríamos decir.

Empiece con un host y un agente

Cree una cuenta, instale el agente con un solo comando y conecte su primer host de Proxmox. El periodo de prueba dura 14 días y no pide ningún dato de pago.