Aller au contenu

Blog ·

Seize mille clés, neuf langues

Traduire un panneau n’est pas un problème de texte. Les défaillances qui comptent sont muettes : un argument perdu, un pluriel aplati, un garde-fou vide.

Par Provibr

Notre catalogue de messages anglais contient actuellement un peu plus de seize mille chaînes. Il y en a neuf, un par langue, et ils sont tenus d’avoir exactement les mêmes clés. Cela fait un peu plus de cent quarante mille morceaux de texte qui doivent tous rester fidèles les uns aux autres pendant qu’une douzaine de personnes modifient le produit en dessous.

Ce qui est intéressant dans ce problème, ce n’est pas le volume. C’est que presque aucune des défaillances ne s’annonce. Une traduction avec une faute de frappe est signalée par un client en moins d’une semaine. Une traduction qui a discrètement perdu un argument affiche une phrase avec un trou dedans, pour toujours, et personne n’ouvre de ticket au sujet d’une phrase qui se lit bien.

Voici l’ensemble de garde-fous auquel nous avons abouti, et le raisonnement derrière chacun. Ils ne sont propres à aucun cadriciel : les modes de défaillance sont une propriété du fait d’avoir plus d’une langue.

La classe de bogue qui compte

Commençons par celle qui nous a fait prendre l’affaire au sérieux.

Une chaîne anglaise dit Welcome back, {name}. Quelqu’un la traduit, et la traduction est Welcome back. C’est grammatical, c’est une phrase raisonnable, et cela s’affiche sans erreur dans n’importe quel système de gabarits existant. Le paramètre n’apparaît tout simplement pas. Il n’y a aucune exception, aucune ligne de journal, aucun avertissement de clé manquante.

Ce bogue est invisible pour tous les tests qui vérifient si les clés existent, c’est-à-dire le test que presque tout le monde écrit en premier. La seule chose qui le trouve est une comparaison : extraire l’ensemble des noms de paramètres de la chaîne de référence, les extraire de la traduction, et exiger qu’ils correspondent.

Une fois cette comparaison construite, trois raffinements se révèlent nécessaires, et chacun d’eux vient d’une chaîne réelle qui était passée entre les mailles.

Comparez les types d’arguments, pas seulement les noms. Un {count} nu ne se met pas en forme. Il imprime le nombre brut, sans séparateur de milliers, dans toutes les langues. Écrivez {count, number} et il se met en forme. Si le type est présent en anglais et absent dans la traduction, le nom correspond, la structure des branches correspond, et une langue affiche discrètement un entier brut là où les autres en affichent un mis en forme. Il en va de même pour les dates : un argument de date qui a perdu son type affiche un horodatage ISO au visage du lecteur.

Ne comparez pas les styles d’arguments. Un style monétaire contre un style numérique simple est un choix de présentation qui diffère légitimement d’une langue à l’autre, et un garde-fou qui le signale apprend aux gens à ignorer le garde-fou. Le type est structurel, le style est une affaire de goût, et un seul des deux a sa place dans un test.

Comparez les noms de branches, pas leur texte. Un pluriel que le traducteur a aplati conserve son argument : une comparaison de paramètres le laisse donc passer volontiers, tandis que l’écran finit par montrer un nombre nu sans nom derrière. Nous avions exactement cela dans notre catalogue néerlandais, et c’est ainsi qu’il a été trouvé.

Les pluriels ne sont pas un problème résolu que l’on recopie

L’instinct est d’exiger qu’une traduction ait les mêmes branches de pluriel que l’anglais. L’anglais en a deux : one et other. Cette exigence est correcte pour les langues qui en ont deux aussi, et elle est précisément fausse pour celles qui n’en ont pas deux, car elle accepte volontiers un catalogue auquel manquent des catégories dont sa propre langue a besoin.

Quelques mesures, toutes issues des règles standard livrées avec n’importe quel moteur d’exécution moderne :

LangueCe qui surprend
FrançaisZéro prend la branche du singulier. « 0 machine », et non « 0 machines ».
UkrainienQuatre catégories. Zéro tombe dans many, avec 5 à 20 ; 21 est one et 22 est few.
Roumain2 à 19 est few, 20 est other, et 101 est de nouveau few.
ArabeSix catégories, séparées à 0, 1, 2, 3 à 10, et 11 à 99.

La ligne du roumain a une conséquence qui se manifeste en volume brut. Le roumain place une préposition devant le nom au-delà de dix-neuf : une tournure qui n’a besoin d’aucun pluriel en anglais en a donc besoin d’un en roumain. En comptant les messages de nos catalogues qui contiennent un pluriel, l’anglais en a 319 et le roumain 498. 179 messages ont besoin en roumain d’une construction plurielle dont l’anglais n’a aucun besoin. L’ukrainien en ajoute 13 qui lui sont propres, le français 4.

En voici un, et c’est de l’anglais parfaitement ordinaire :

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

Rien dans la phrase anglaise ne laisse deviner qu’il s’agit d’un problème de pluriel. Un garde-fou qui tire ses attentes de l’anglais ne peut absolument pas voir cela.

Le deuxième garde-fou tourne donc par langue, contre son propre catalogue : pour chaque pluriel qui s’y trouve, est-ce que toutes les catégories que cette langue peut réellement produire sont traitées, soit par leur nom, soit par une branche à valeur exacte ?

Ce garde-fou a besoin d’une limite délibérée, sans quoi il devient inutile. Le français et l’espagnol ont une catégorie many qui ne s’applique qu’aux multiples exacts d’un million. L’exiger partout ajouterait des centaines de branches qu’aucun humain ne verra jamais, dans un produit qui compte des serveurs, des adresses, des membres d’équipe et des jours. La vérification est donc pilotée par des échantillons : elle demande quelles catégories sont atteignables pour les nombres qu’une clé rencontre de façon réaliste, et il existe une petite liste explicite de clés qui comptent plus haut, si bien que la machinerie est en place le jour où quelque chose le fera.

Cette liste est actuellement vide, et c’est une mesure plutôt qu’un oubli. Les deux candidates qui semblent pouvoir devenir grandes comptent des octets, et la règle française veut un multiple exact d’un million ; un fichier de précisément 1 000 000 d’octets est une coïncidence, pas un cas d’usage.

Le garde-fou qui ne faisait discrètement rien

Voici maintenant celle qui met mal à l’aise, car c’est la défaillance dont parle tout cet article, arrivée à l’outillage lui-même.

Nous avons une vérification qui exige que chaque appel de traduction dans le code renvoie à une clé existante. Elle analyse la source, suit quel objet traducteur appartient à quel espace de noms, et cherche chaque clé. Elle annonçait couvrir plusieurs milliers de clés. Elle était verte.

Elle sautait aussi tout appel issu d’un motif de déstructuration, parce que le code qui liait un traducteur à un espace de noms ne traitait qu’un identifiant simple et ignorait silencieusement tout le reste. Pas de liaison, donc pas de clé à vérifier, donc pas d’échec. Le motif qu’elle sautait se trouvait être celui qu’utilise la majeure partie de notre application.

Nous l’avons trouvé parce que quelqu’un a deviné un nom de clé, l’a écrit, et le test est resté vert.

Deux règles en sont sorties, et nous les appliquons désormais toutes les deux à chaque analyse statique que nous écrivons.

Un analyseur qui rencontre une forme qu’il ne comprend pas doit échouer bruyamment, pas passer son chemin en silence. Une forme non reconnue est un constat, pas une absence d’opération. Toute la valeur de l’outil tient à ce qu’il a tout regardé.

Prouvez le garde-fou rouge avant de lui faire confiance quand il est vert. Cassez quelque chose exprès et regardez le test échouer. Une vérification qui n’a jamais échoué n’a pas été testée : elle a seulement été exécutée.

Une proche parente de cette leçon : un nom de marque s’écrivait de quatre façons différentes dans nos catalogues, et le seul endroit de toute la base de code qui connaissait l’orthographe correcte la connaissait comme une exception dans une liste d’autorisations. Le seul composant qui avait la bonne réponse était configuré pour détourner le regard. Une exception n’est pas un garde-fou, et la correction consiste d’ordinaire à inverser la chose : énoncer la règle, et laisser les exceptions être des données accrochées à elle.

Analyser son propre format de messages est plus difficile qu’il n’y paraît

L’extraction des paramètres par expression régulière fonctionne jusqu’à ce qu’elle ne fonctionne plus, et sa façon d’échouer est instructive, car elle échoue sur vos meilleures chaînes.

Une branche de sélection dont le texte tient en un seul mot ressemble exactement à un argument pour un moteur de motifs. Notre ancien scanneur levait de fausses alertes sur les messages les plus soigneusement écrits du catalogue, et manquait en même temps de vrais arguments à l’intérieur des branches, parce qu’une expression régulière ne sait pas compter les accolades. Il se trompait dans les deux sens à la fois.

Le remplaçant parcourt correctement les accolades et descend récursivement dans le corps des branches. Il fait trente lignes au lieu d’une, et c’est la différence entre un garde-fou auquel les gens font confiance et un garde-fou que les gens contournent à coups d’annotations.

La typographie est un garde-fou par langue, pas un garde-fou partagé

La dernière catégorie ne peut pas du tout être vérifiée par comparaison, car elle est précisément ce qui diffère d’une langue à l’autre. Le français veut une espace insécable devant les deux-points, le point-virgule, le point d’exclamation et le point d’interrogation. L’ukrainien veut un caractère d’apostrophe précis qui ressemble aux deux autres apostrophes que vous avez probablement sur votre clavier. L’espagnol ouvre une question par un signe inversé, à l’endroit où la question commence plutôt qu’au début de la phrase. Le roumain utilise ses propres guillemets et des diacritiques à virgule souscrite.

Rien de tout cela n’est comparable d’une langue à l’autre : chaque langue qui a des règles reçoit donc sa propre vérification. Cela ressemble à de la duplication et n’en est pas : l’autre solution serait un unique garde-fou générique qui ne peut affirmer que l’intersection de neuf jeux de règles, laquelle ne vaut pratiquement rien.

Un avertissement tiré de cette pratique dans un grand dépôt : une espace insécable est invisible, et plusieurs outils la convertiront serviablement en espace ordinaire au passage. Nous les mettons en place avec un script pour exactement cette raison, et nous vérifions ensuite qu’elles sont toujours là.

Ce que nous construirions en premier, et dans quel ordre

Si vous partez d’une seule langue en vous attendant à en ajouter d’autres, voici l’ordre qui nous aurait le plus épargné.

  1. Les mêmes clés dans tous les catalogues, vérifié en intégration continue. Bon marché, et c’est la fondation sur laquelle repose le reste.
  2. La comparaison des paramètres et des types d’arguments avec la langue de référence. C’est celle qui attrape les bogues muets.
  3. La comparaison des noms de branches pour les pluriels et les sélections, pour qu’un pluriel aplati soit un échec.
  4. La couverture des pluriels par langue, pilotée par les règles propres à la langue, avant l’arrivée de la première langue qui a besoin de plus de catégories que votre référence.
  5. La typographie par langue, une petite vérification par langue qui a des conventions.
  6. Une vérification statique que chaque clé référencée dans le code existe et, plus important encore, qu’elle est honnête sur les appels qu’elle n’a pas su comprendre.

Le thème qui traverse ces six points, c’est que les bogues de traduction ne lèvent pas d’exception. Ils s’affichent. Chaque garde-fou présenté ici existe parce que quelque chose s’est affiché parfaitement, dans une langue que personne dans la pièce ne lit, et a dit quelque chose de légèrement différent de ce que nous voulions dire.

Commencez avec un hôte et un agent

Créez un compte, installez l’agent avec une seule commande et connectez votre premier hôte Proxmox. La période d’essai dure 14 jours et ne demande aucune coordonnée de paiement.