Steven
Steven8 min leestijd

Dertig talen uitbrengen zonder vangnet

GeekBye v1.8.3 koppelde react-i18next en vertaalde de hele app naar 30 talen. Het bibliotheekdeel was routine. Het interessante is wat ontbrak: geen sleutelpariteitscheck en geen string-extractor — waardoor hardcoded Engels erin bleef staan, een Deense typefout werd uitgebracht, en de changelog het er niet over eens was of de app 28, 29 of 30 talen sprak.

Engineering
i18n
Desktop
GeekBye Releases
Dertig talen uitbrengen zonder vangnet

Sommige releases zijn een feature. GeekBye v1.8.3 lijkt er zo een — "de app spreekt nu 30 talen" — maar de feature was het makkelijke deel, en daar gaat dit stuk niet over. Tussen 17 en 19 februari 2026 koppelden twee pull requests react-i18next in de app, zetten ze elke hardcoded Engelse string om naar een vertaalcall, en bundelden ze dertig locale-bestanden. Dat is echt werk, maar het is het soort werk dat een bibliotheek juist routine wil maken. De interessante engineering is alles wat de release niet bouwde — het vangnet dat zou hebben opgevangen wat kapot werd uitgebracht.

De bedrading was de makkelijke 80%

De infrastructuurcommit voegde react-i18next toe bovenop i18next en deed het conventionele: een i18n/index.ts die initReactI18next aanroept, alle dertig locale-JSON's statisch importeert en ze onder één resources-object registreert. Geen lazy loading — elke taal zit in de bundle. Vertaalstrings staan in src/i18n/locales/*.json als platte, met punten genamespacete maps: sidebar.meetings, startMeeting.start, één sleutel naar één string.

De migratie van hardcoded Engels naar t('key') gebeurde op de eerlijke manier, componentgroep voor componentgroep, en je kunt de veegbeweging teruglezen in de commit-log: eerst sidebar, navigatie en chat; dan settings, login en onboarding; dan meetings, dashboard, toasts en navbar-tooltips. Elke commit verving een batch letterlijke strings door vertaalcalls. Negenentwintig niet-Engelse bestanden landden in één enkele commit — een drop van negenduizend regels die de Engelse bron vertaalde naar alles van Bulgaars tot Vietnamees.

Niets daarvan is moeilijk om goed te doen, want het fout doen is luidruchtig: een ontbrekende provider crasht, een malformed JSON parset niet, een verkeerde import breekt de build. De bibliotheek geeft je vangrails voor de mechanische fouten. Wat ze je niet geeft, is een vangrail voor de fout die daadwerkelijk gebeurde.

Drie taalinstellingen, en waarom dat klopt

Voordat we bij het kapotte deel komen: één ding deed v1.8.3 helemaal goed, want het is een onderscheid dat veel apps platslaan en niet zouden moeten. Na deze release heeft GeekBye drie onafhankelijke taalinstellingen:

  • AI-antwoordtaal (output_language) — de taal waarin de assistent antwoordt. De oudste van de drie, uit november 2025.
  • Transcripttaal (transcriptLanguage) — de taal waarin de spraakengine je microfoon transcribeert. Een paar releases eerder ingebouwd, in v1.7.6.
  • App-taal (app_language) — de taal van de knoppen, menu's en onboarding. Nieuw in v1.8.3.

Dit zijn echt verschillende assen. Een ontwikkelaar in Berlijn wil misschien een Duitse interface, Engelse AI-antwoorden (want dat is de taal van de docs die ze plakken) en Engelse transcriptie (want de meeting is in het Engels). Die samenvouwen tot één "taal"-instelling zou voor die persoon verkeerd zijn, en de onboardingstap die in deze release werd toegevoegd, presenteert alle drie als aparte dropdowns om precies die reden.

Het mooie detail is het upgradepad. Een eenmalige migratie kopieert bij de eerste start na het updaten de output_language van elke bestaande gebruiker naar de nieuwe app_language-sleutel, en de provider bepaalt de taal via een fallbackketen — app_language, dan output_language, dan 'en'. Zo werden gebruikers die de app al hadden verteld dat ze Spaanse AI-antwoorden wilden, wakker met een Spaanse interface, in plaats van teruggezet te worden naar het Engels en de instelling te moeten opzoeken. Een nieuwe voorkeur vullen vanuit een verwante oude is een kleine vriendelijkheid die één migratie kost en elke upgrader een kleine ergernis bespaart.

De onboarding-re-render die niet wilde

Er is één echte bug die het waard is om uit het i18n-werk te lichten, want het is een React-val waar iedereen die runtime van taal wisselt tegenaan loopt. De onboarding-wizard bouwt sommige van zijn optielijsten binnen een useMemo. Als je je taal wijzigt op de taalstap zelf, hoort de UI meteen opnieuw te tekenen in de nieuwe taal — en dat gebeurde niet, omdat t niet in de dependency-array van de memo stond. De vertaalfunctie krijgt een andere identiteit als de taal verandert; als je gememoïseerde waarde is opgebouwd uit t maar er niet van afhangt, geeft React je vrolijk de verouderde versie in de vorige taal. De fix komt twee keer voor in de log — één keer voor de wizard, één keer voor de dropdownlijst in de settings — wat je vertelt dat het het soort fout is dat je maakt op elke plek waar je vertaalde content memoïseert, totdat je leert altijd t op te nemen.

Onboarding had ook een plooi die de changelog niet noemt: omdat dit een white-label codebase is, zijn er twee onboardingbomen om te vertalen — een GeekBye-wizard en een Pavleur-wizard — die leven onder parallelle onboarding.*- en onboarding.pavleur.*-sleutelnamespaces. Elke onboardingstring werd twee keer vertaald, in twee merkstemmen, over alle negenentwintig talen.

Wat de build niet wist dat het miste

Hier komt het deel dat dit een les maakt in plaats van een checklist. v1.8.3 had geen geautomatiseerde manier om hardcoded strings te extraheren en geen CI-check voor sleutelpariteit over de locales. De definitie van "klaar" was dus "iemand bezocht de componenten die hij zich herinnerde en wikkelde de strings die hij zag." Die definitie laat gaten vallen.

Twee weken na de release landde een opschooncommit met de boodschap "translate all hardcoded English strings across 10 components into 30 languages." Tien componenten — het issue-rapportformulier, de tabbladen voor samenvatting en metrics, het rapporttabblad, transcript- en chatinhoud, de prompt voor de gebruikslimiet, chatberichten, de copy-code-knop, de floating action buttons — waren in v1.8.3 uitgebracht terwijl ze nog steeds tegen iedereen Engels spraken, in elke taal. Niemands build faalde. Geen enkele test werd rood. Een Duitse gebruiker zag gewoon Engels in het rapporttabblad, en de enige detector was een mens die er uiteindelijk naar keek.

Dezelfde afwezigheid produceerde de kleinere gênes. Een Deense vertaling werd uitgebracht met het verkeerde woord — Luk application helt in plaats van Luk applikationen helt — en werd in een latere release gepatcht, omdat er geen native reviewer-gate was en geen manier voor de pipeline om het te weten. "Quit app"-sleutels ontbraken in de eerste batch en werden dagen later nagevuld. En elke feature die daarna werd uitgebracht — een modifier-key-preset, mic mute, recap-chips, overlay-opacity — sleepte zijn eigen "vertaal de nieuwe sleutels naar 29 locales"-commit met zich mee, voor altijd, omdat niets de invariant automatisch afdwong.

Je kunt de wanorde zelfs aflezen in de commitberichten. Dezelfde dertig locale-bestanden worden, release na release, beschreven als "30 talen," "29 talen," en — in één commit — "28 locales." Er werd niets toegevoegd of verwijderd; er zijn dertig bestanden bij elke tag van v1.8.3 tot en met v2. Het getal dwaalt af omdat er geen single source of truth was voor "hoeveel talen leveren we," dus elke commit-auteur telde met de hand, soms met Engels als taal en soms als bron. Een changelog die zijn eigen aantal niet stabiel kan houden, is een klein, eerlijk signaal dat het ding dat hij beschrijft niet door een machine wordt afgedwongen.

De pluralisatie-valstrik

Nog één ding om te markeren, niet omdat het luidruchtig kapotging maar omdat het het soort bug is dat deze opzet uitlokt en waar hij je niet voor waarschuwt. Pluralisatie in deze locale-bestanden gebeurt handmatig, met gepaarde sleutels: meetings.meetingCount en meetings.meetingCountPlural, profiles.filesAttached en filesAttachedPlural. Dat is een nette Engelse enkelvoud/meervoud-splitsing. Het is ook fout voor een deel van de talen die de app nu beweert te ondersteunen. Russisch, Pools en Tsjechisch hebben drie of vier meervoudsvormen afhankelijk van het getal — "1 file," "2 files," "5 files," en "21 files" kunnen verschillende woorduitgangen krijgen. Een schema met twee sleutels voor enkelvoud/meervoud kan dat niet uitdrukken, dus die aantallen worden grammaticaal scheef weergegeven. i18next heeft echte meervoudsmachinerie voor precies dit; de handmatige paren omzeilen die, en geen enkele commit erkent het gat. Het is het pluralisatie-equivalent van het hardcoded-strings-gat: onzichtbaar voor de build, alleen zichtbaar voor een moedertaalspreker.

Drie dingen die deze release ons leerde

  1. De bibliotheek is de makkelijke 80%; de tooling is de moeilijke 20%. react-i18next koppelen en strings in bulk vertalen is een opgelost probleem. Dertig bestanden voor altijd in pariteit houden niet — en dat vergt een extractor voor hardcoded strings en een CI-check voor ontbrekende sleutels, waarvan een bibliotheek er geen van beide voor je levert. Bouw die voordat je "volledige i18n" claimt, anders heb je een invariant geclaimd die je op geen enkele manier kunt vasthouden.
  2. Splits de taalinstellingen die echt verschillen. UI-taal, transcripttaal en AI-antwoordtaal zijn drie assen, en v1.8.3 had gelijk om elk een eigen sleutel te geven — en nog meer gelijk om de nieuwe te vullen vanuit een verwante oude via migratie, zodat upgraden gebruikers niets kostte.
  3. Het aantal in je changelog is een discipline-metriek. Als dezelfde set bestanden over drie commits 28, 29 en 30 talen is, is het getal niet het probleem — de ontbrekende source of truth wel. Als een machine je talen niet telt, telt niemand ze twee keer op dezelfde manier.

Voor het vorige hoofdstuk in het v1-verhaal, het dashboard opnieuw ontworpen rond één golfgrafiek (v1.8.2); en voor de hele boog, de anatomie van software tot in de perfectie uitbrengen.