Blog ·
Zestienduizend sleutels, negen talen
Een controlepaneel vertalen is geen tekstprobleem. De fouten die tellen zijn stil: een verdwenen argument, een plat meervoud, een bewaking die niets ziet.
Door Provibr
Onze Engelse berichtencatalogus bevat op dit moment iets meer dan zestienduizend tekenreeksen. Er zijn er negen, één per taal, en ze moeten precies dezelfde sleutels hebben. Dat is ruim boven de honderdveertigduizend stukjes tekst die allemaal waar moeten blijven ten opzichte van elkaar terwijl een stuk of tien mensen het product eronder veranderen.
Het interessante aan dat probleem is niet de omvang. Het is dat vrijwel geen van de fouten zich aankondigt. Een vertaling met een tikfout wordt binnen een week door een klant gemeld. Een vertaling die stilletjes een argument liet vallen rendert voor altijd een zin met een gat erin, en niemand maakt een ticket aan over een zin die prima leest.
Dit is de verzameling bewakingen waarop we uitkwamen, en de redenering achter elke ervan. Ze zijn niet aan één framework gebonden; de faalvormen zijn een eigenschap van het hebben van meer dan één taal.
De klasse fouten die ertoe doet
Begin met degene die ons dit serieus deed nemen.
Een Engelse tekenreeks zegt Welcome back, {name}. Iemand vertaalt hem, en de vertaling luidt Welcome back. Hij is grammaticaal, hij is een redelijke zin, en hij rendert zonder fout in elk sjabloonsysteem dat bestaat. De parameter verschijnt eenvoudigweg niet. Er is geen uitzondering, geen logregel, geen waarschuwing over een ontbrekende sleutel.
Die fout is onzichtbaar voor elke test die controleert of sleutels bestaan, en dat is de test die vrijwel iedereen als eerste schrijft. Het enige dat hem vindt is een vergelijking: haal de verzameling plaatshoudernamen uit de referentietekst, haal ze uit de vertaling, en eis dat ze overeenkomen.
Zodra je die vergelijking gebouwd hebt blijken er drie verfijningen nodig, en elk daarvan kwam uit een echte tekenreeks die erdoorheen glipte.
Vergelijk argumenttypen, niet alleen namen. Een kaal {count} formatteert niet. Het drukt het rauwe getal af, zonder duizendtalscheiding, in elke taal. Schrijf {count, number} en het formatteert. Staat het type wel in het Engels en niet in de vertaling, dan komen de naam en de takstructuur netjes overeen en rendert één taal stilletjes een rauw geheel getal waar de andere een opgemaakt getal tonen. Hetzelfde geldt voor datums: een datumargument dat zijn type kwijtraakte rendert een ISO-tijdstempel richting de lezer.
Vergelijk geen argumentstijlen. Valutastijl tegenover gewone getalstijl is een presentatiekeuze die legitiem per taal verschilt, en een bewaking die daarop aanslaat leert mensen de bewaking te negeren. Het type is structureel, de stijl is smaak, en maar één van die twee hoort in een test.
Vergelijk taknamen, niet taktekst. Een meervoud dat de vertaler platsloeg houdt zijn argument, dus een plaatshoudervergelijking laat het opgewekt door, terwijl het scherm uiteindelijk een kaal getal toont zonder zelfstandig naamwoord erachter. Wij hadden precies dat in onze Nederlandse catalogus, en zo is het gevonden.
Meervoud is geen opgelost probleem dat je kunt overnemen
Het instinct is te eisen dat een vertaling dezelfde meervoudstakken heeft als het Engels. Het Engels heeft er twee: one en other. Die eis klopt voor de talen die er ook twee hebben, en hij is juist verkeerd voor de talen die dat niet hebben, want hij aanvaardt opgewekt een catalogus waarin categorieën ontbreken die zijn eigen taal nodig heeft.
Een paar metingen, allemaal uit de standaardregels die met elke moderne runtime meekomen:
| Taal | Wat mensen verrast |
|---|---|
| Frans | Nul neemt de enkelvoudstak. "0 machine", niet "0 machines". |
| Oekraïens | Vier categorieën. Nul valt onder many, samen met 5 tot en met 20; 21 is one en 22 is few. |
| Roemeens | 2 tot en met 19 is few, 20 is other, en 101 is weer few. |
| Arabisch | Zes categorieën, gesplitst bij 0, 1, 2, 3 tot en met 10, en 11 tot en met 99. |
De Roemeense rij heeft een gevolg dat zich als pure hoeveelheid laat zien. Het Roemeens zet boven negentien een voorzetsel voor het zelfstandig naamwoord, dus een zinsnede die in het Engels helemaal geen meervoud nodig heeft, heeft er in het Roemeens wel een nodig. Tellen we de berichten in onze catalogi die een meervoud bevatten: het Engels heeft er 319, het Roemeens 498. 179 berichten hebben in het Roemeens een meervoudsconstructie nodig die het Engels helemaal niet nodig heeft. Het Oekraïens voegt er 13 van zichzelf toe, het Frans 4.
Hier is er een van, en het is volstrekt gewoon Engels:
en: Start {days}-day trial
uk: Почати пробний період на {days, plural, one {# день} few {# дні} many {# днів} other {# дня}}Niets aan de Engelse zin wijst erop dat het een meervoudsprobleem is. Een bewaking die haar verwachtingen uit het Engels afleidt kan dit onmogelijk zien.
Dus draait de tweede bewaking per taal tegen zijn eigen catalogus: is voor elk meervoud daarin elke categorie afgehandeld die deze taal werkelijk kan voortbrengen, hetzij bij naam, hetzij met een tak op een exacte waarde?
Die bewaking heeft één bewuste grens nodig, anders wordt ze nutteloos. Het Frans en het Spaans hebben een many-categorie die uitsluitend geldt voor exacte veelvouden van een miljoen. Die overal eisen zou honderden takken toevoegen die geen mens ooit ziet, in een product dat servers, adressen, teamleden en dagen telt. De controle werkt daarom met steekproeven: ze vraagt welke categorieën bereikbaar zijn voor de aantallen die een sleutel realistisch tegenkomt, en er is een kleine expliciete lijst van sleutels die hoger tellen, zodat de machinerie er staat op de dag dat iets dat doet.
Die lijst is op dit moment leeg, en dat is een meting en geen nalatigheid. De twee kandidaten die eruitzien alsof ze groot kunnen worden tellen bytes, en de Franse regel wil een exact veelvoud van een miljoen; een bestand van precies 1.000.000 bytes is toeval, geen geval.
De bewaking die stilletjes niets deed
Nu de ongemakkelijke, want het is de mislukking waar dit hele artikel over gaat, die het gereedschap zelf overkomt.
Wij hebben een controle die eist dat elke vertaalaanroep in de code verwijst naar een sleutel die bestaat. Ze ontleedt de bron, houdt bij welk vertaalobject bij welke naamruimte hoort, en zoekt elke sleutel op. Ze meldde dat ze enkele duizenden sleutels dekte. Ze stond groen.
Ze sloeg ook elke aanroep over die uit een destructureringspatroon kwam, want de code die een vertaalobject aan een naamruimte bond kende alleen een kale identifier en negeerde stilzwijgend al het andere. Geen binding, dus geen sleutel om te controleren, dus geen fout. Het patroon dat ze oversloeg was toevallig het patroon dat in vrijwel onze hele applicatie gebruikt wordt.
We vonden het doordat iemand een sleutelnaam gokte, hem opschreef, en de test groen bleef.
Daar kwamen twee regels uit voort, en die passen we nu toe op elke statische analyse die we schrijven.
Een analysator die een vorm tegenkomt die hij niet begrijpt moet luidruchtig falen, niet stil overslaan. Onherkend is een bevinding, geen niets-doen. De hele waarde van het gereedschap is dat het naar alles gekeken heeft.
Bewijs de bewaking rood voordat je haar groen vertrouwt. Maak met opzet iets stuk en kijk of de test faalt. Een controle die nog nooit gefaald heeft is niet getest; ze is alleen gedraaid.
Een naaste verwant van die les: een merknaam in onze catalogi werd op vier verschillende manieren geschreven, en de ene plek in de hele codebase die de juiste schrijfwijze kende, kende die als uitzondering in een allowlist. Het enige onderdeel dat het goede antwoord had was zo ingesteld dat het ervan wegkeek. Een uitzondering is geen bewaking, en de oplossing is meestal het ding om te keren: formuleer de regel, en laat de uitzonderingen data zijn die eraan hangen.
Je eigen berichtformaat ontleden is lastiger dan het lijkt
Plaatshouders uithalen met een reguliere expressie werkt tot het niet meer werkt, en de manier waarop het faalt is leerzaam, want het faalt op je beste teksten.
Een select-tak waarvan de tekst één woord is ziet er voor een patroonherkenner precies uit als een argument. Onze oude scanner sloeg vals alarm op de zorgvuldigst geschreven berichten in de catalogus, en miste tegelijkertijd echte argumenten binnen takken, omdat een reguliere expressie geen accolades kan tellen. Hij had in beide richtingen tegelijk ongelijk.
De vervanger loopt accolades netjes af en daalt af in de inhoud van takken. Hij is dertig regels in plaats van één, en hij is het verschil tussen een bewaking die mensen vertrouwen en een bewaking waar mensen zich met annotaties omheen werken.
Typografie is een bewaking per taal, geen gedeelde
De laatste categorie is helemaal niet met een vergelijking te controleren, want het is nu juist datgene wat tussen talen verschilt. Het Frans wil een onbreekbare spatie vóór een dubbelepunt, een puntkomma, een uitroepteken en een vraagteken. Het Oekraïens wil een specifiek apostrofteken dat lijkt op de twee andere apostroffen die je waarschijnlijk op je toetsenbord hebt. Het Spaans opent een vraag met een omgekeerd teken, op het punt waar de vraag begint en niet aan het begin van de zin. Het Roemeens gebruikt zijn eigen aanhalingstekens en diacritieken met komma eronder.
Niets daarvan is tussen talen vergelijkbaar, dus krijgt elke taal met regels zijn eigen controle. Dat klinkt als dubbel werk en dat is het niet: het alternatief is één generieke bewaking die alleen de doorsnede van negen verzamelingen regels kan hard maken, en die is voor praktische doeleinden leeg.
Eén waarschuwing uit het doen hiervan in een grote repository: een onbreekbare spatie is onzichtbaar, en verschillende gereedschappen zetten hem onderweg behulpzaam om in een gewone spatie. Wij zetten ze er precies daarom met een script in, en controleren achteraf of ze er nog staan.
Wat we als eerste zouden bouwen, op volgorde
Begin je met één taal en verwacht je er meer toe te voegen, dan is dit de volgorde die ons het meeste bespaard zou hebben.
- Dezelfde sleutels in elke catalogus, gecontroleerd in CI. Goedkoop, en het is het fundament waar de rest op staat.
- Vergelijking van plaatshouders en argumenttypen tegen de referentietaal. Dit is degene die de stille fouten vangt.
- Vergelijking van taknamen voor meervouden en selects, zodat een platgeslagen meervoud een mislukking is.
- Meervoudsdekking per taal, gestuurd door de eigen regels van die taal, vóór de eerste taal arriveert die meer categorieën nodig heeft dan je referentie.
- Typografie per taal, één kleine controle per taal met eigen conventies.
- Een statische controle dat elke in code aangeroepen sleutel bestaat en, belangrijker nog, dat die controle eerlijk is over de aanroepen die ze niet kon begrijpen.
Het thema dat door alle zes heen loopt is dat vertaalfouten niet gooien. Ze renderen. Elke bewaking hier bestaat omdat er iets perfect rendeerde, in een taal die niemand in de kamer leest, en iets zei dat net iets anders was dan wat we bedoelden.