Steven
Steven8 min læsning

At shippe tredive sprog uden sikkerhedsnet

GeekBye v1.8.3 koblede react-i18next på og oversatte hele appen til 30 sprog. Biblioteksdelen var rutine. Det interessante er, hvad der manglede: ingen kontrol af nøgleparitet og ingen strengekstraktor — så hårdkodet engelsk slap igennem, en dansk stavefejl blev shippet, og ændringsloggen kunne ikke blive enig om, hvorvidt appen talte 28, 29 eller 30 sprog.

Udvikling
i18n
Desktop
GeekBye-releaser
At shippe tredive sprog uden sikkerhedsnet

Nogle releaser er en feature. GeekBye v1.8.3 ligner én — "appen taler nu 30 sprog" — men featuren var den nemme del, og det er ikke det, dette indlæg handler om. Mellem 17. og 19. februar 2026 koblede to pull requests react-i18next på appen, lavede hver hårdkodet engelsk streng om til et oversættelseskald og buntede tredive lokalfiler sammen. Det er rigtigt arbejde, men det er den slags arbejde, et bibliotek er designet til at gøre til rutine. Den interessante ingeniørkunst er alt det, releasen ikke byggede — sikkerhedsnettet, der ville have fanget det, den shippede i stykker.

Tilkoblingen var de nemme 80 %

Infrastruktur-commiten lagde react-i18next oven på i18next og gjorde det konventionelle: en i18n/index.ts, der kalder initReactI18next, statisk importerer alle tredive lokal-JSON'er og registrerer dem under ét enkelt resources-objekt. Ingen doven indlæsning — hvert sprog shippes i bundtet. Oversættelsesstrengene bor i src/i18n/locales/*.json som flade, punktnavnrums-inddelte kort: sidebar.meetings, startMeeting.start, én nøgle til én streng.

Migreringen fra hårdkodet engelsk til t('key') blev gjort på den ærlige måde, komponentgruppe for komponentgruppe, og du kan læse forløbet i commit-loggen: sidebjælke, navigation og chat først; så indstillinger, login og onboarding; så møder, dashboard, toasts og navbar-tooltips. Hver commit erstattede en portion bogstavelige strenge med oversættelseskald. Niogtyve ikke-engelske filer landede i én enkelt commit — et nitusind-linjers drop, der oversatte den engelske kilde til alt fra bulgarsk til vietnamesisk.

Intet af det er svært at få rigtigt, fordi det er højlydt at få det forkert: en manglende provider crasher, en misdannet JSON parser ikke, et dårligt import ødelægger buildet. Biblioteket giver dig værn mod de mekaniske fejl. Hvad det ikke giver dig, er et værn mod den fejl, der rent faktisk skete.

Tre sprogindstillinger, og hvorfor det er rigtigt

Før vi kommer til, hvad der gik i stykker, én ting, v1.8.3 fik helt rigtigt, fordi det er en skelnen, mange apps flader ud, selvom de ikke burde. Efter denne release har GeekBye tre uafhængige sprogindstillinger:

  • AI-svarsprog (output_language) — hvilket sprog assistenten svarer på. Det ældste af de tre, fra november 2025.
  • Transskriptionssprog (transcriptLanguage) — hvad talemotoren transskriberer din mikrofon som. Trukket ind et par releaser tidligere, i v1.7.6.
  • App-sprog (app_language) — sproget i knapper, menuer og onboarding. Nyt i v1.8.3.

Det her er reelt forskellige akser. En udvikler i Berlin vil måske have en tysk grænseflade, engelske AI-svar (fordi det er sproget i den dokumentation, de indsætter) og engelsk transskription (fordi mødet er på engelsk). At slå dem sammen til én enkelt "sprog"-indstilling ville være forkert for den person, og onboarding-trinnet, der blev tilføjet i denne release, præsenterer alle tre som separate dropdowns af netop den grund.

Den fine detalje er opgraderingsvejen. En engangsmigrering kopierer hver eksisterende brugers output_language ind i den nye app_language-nøgle ved første start efter opdatering, og provideren finder sproget via en fallback-kæde — app_language, så output_language, så 'en'. Så brugere, der allerede havde fortalt appen, at de ville have spanske AI-svar, vågnede op til en spansk grænseflade i stedet for at blive nulstillet til engelsk og sat til at lede efter indstillingen. At så en ny præference fra en beslægtet gammel er en lille venlighed, der koster én migrering og sparer hver opgraderer for et lille irritationsmoment.

Onboarding-genrenderingen, der ikke ville

Der er én ægte bug værd at trække ud af i18n-arbejdet, for det er en React-fælde, som alle, der laver sprogskift i runtime, vil ramme. Onboarding-guiden bygger nogle af sine valglister inde i et useMemo. Når du skifter sprog på selve sprogtrinnet, skal grænsefladen tegnes om på det nye sprog med det samme — og det gjorde den ikke, fordi t ikke var i memoens dependency-array. Oversættelsesfunktionen skifter identitet, når sproget ændres; hvis din memoiserede værdi er bygget fra t, men ikke afhænger af den, rækker React glad den forældede version på det forrige sprog frem til dig. Fixet dukker op to gange i loggen — én gang for guiden, én gang for indstillingernes dropdown-liste — hvilket fortæller dig, at det er den slags fejl, du laver hvert sted, du memoiserer oversat indhold, indtil du lærer altid at anføre t.

Onboarding bar også på en krølle, ændringsloggen ikke nævner: fordi det her er en white-label-kodebase, er der to onboarding-træer at oversætte — en GeekBye-guide og en Pavleur-guide — som bor under parallelle nøglenavnrum onboarding.* og onboarding.pavleur.*. Hver onboarding-streng blev oversat to gange, til to brandstemmer, på tværs af alle niogtyve sprog.

Hvad buildet ikke vidste, det manglede

Her er den del, der gør det her til en lektie i stedet for en tjekliste. v1.8.3 havde ingen automatisk måde at udtrække hårdkodede strenge på og ingen CI-kontrol af nøgleparitet på tværs af lokaler. Så definitionen af "færdig" var "nogen besøgte de komponenter, de huskede, og pakkede de strenge ind, de så." Den definition er utæt.

To uger efter releasen landede en oprydnings-commit med beskeden "translate all hardcoded English strings across 10 components into 30 languages." Ti komponenter — fejlrapportformularen, sammendrags- og metrikfanerne, rapportfanen, transskriptions- og chatindhold, forbrugsgrænseprompten, chatbeskeder, kopiér-kode-knappen, de flydende handlingsknapper — var blevet shippet i v1.8.3 og talte stadig engelsk til alle, på hvert sprog. Ingens build fejlede. Ingen test blev rød. En tysk bruger så bare engelsk i rapportfanen, og den eneste detektor var et menneske, der til sidst kiggede på det.

Det samme fravær producerede de mindre pinligheder. En dansk oversættelse blev shippet med et forkert ord — den lød Luk application helt, hvor den skulle have lydt Luk applikationen helt — og blev rettet i en senere release, fordi der ikke var nogen gate med en modersmålsanmelder og ingen måde, hvorpå pipelinen kunne vide det. "Quit app"-nøgler manglede i den første portion og blev fyldt på dage senere. Og hver eneste feature, der blev shippet derefter — en forudindstilling til modifikatortast, mikrofon-mute, opsummeringschips, overlay-opacitet — slæbte sin egen "oversæt de nye nøgler til 29 lokaler"-commit med sig, for altid, fordi intet håndhævede invarianten automatisk.

Man kan endda måle uordenen i commit-beskederne. De samme tredive lokalfiler beskrives, release til release, som "30 sprog", "29 sprog" og — i én commit — "28 lokaler". Intet blev tilføjet eller fjernet; der er tredive filer ved hvert tag fra v1.8.3 til v2. Tallet driver, fordi der ikke var én enkelt kilde til sandhed for "hvor mange sprog shipper vi", så hver commit-forfatter talte i hånden, nogle gange med engelsk som et sprog og nogle gange som kilden. En ændringslog, der ikke kan holde sit eget tal stabilt, er et lille, ærligt signal om, at det, den beskriver, ikke bliver håndhævet af en maskine.

Pluralhåndteringens fælde

Én ting mere værd at markere, ikke fordi den gik højlydt i stykker, men fordi det er den slags bug, dette setup inviterer til og ikke advarer dig om. Pluralhåndtering i disse lokalfiler laves manuelt, med parrede nøgler: meetings.meetingCount og meetings.meetingCountPlural, profiles.filesAttached og filesAttachedPlural. Det er en ren engelsk singular/plural-opdeling. Den er også forkert for en del af de sprog, appen nu hævder at understøtte. Russisk, polsk og tjekkisk har tre eller fire pluralformer afhængigt af tallet — "1 fil", "2 filer", "5 filer" og "21 filer" kan tage forskellige ordendelser. Et to-nøgle singular/plural-skema kan ikke udtrykke det, så de antal renderes grammatisk skævt. i18next har rigtigt pluralmaskineri til præcis det her; de manuelle par går uden om det, og ingen commit anerkender hullet. Det er pluralpendanten til lækagen med hårdkodede strenge: usynlig for buildet, kun synlig for en, der har sproget som modersmål.

Tre ting denne release lærte os

  1. Biblioteket er de nemme 80 %; værktøjerne er de svære 20 %. At koble react-i18next på og masseoversætte strenge er et løst problem. At holde tredive filer i paritet for altid er det ikke — og det kræver en ekstraktor til hårdkodede strenge og en CI-kontrol for manglende nøgler, hvoraf et bibliotek ikke shipper nogen af delene til dig. Byg dem, før du hævder "fuld i18n", ellers har du hævdet en invariant, du ikke har nogen måde at holde.
  2. Del de sprogindstillinger op, der faktisk er forskellige. Grænsefladesprog, transskriptionssprog og AI-svarsprog er tre akser, og v1.8.3 gjorde ret i at give hver sin egen nøgle — og endnu mere ret i at så den nye fra en beslægtet gammel via migrering, så opgradering ikke kostede brugerne noget.
  3. Tallet i din ændringslog er en disciplinmåling. Når det samme filsæt er 28, 29 og 30 sprog på tværs af tre commits, er tallet ikke problemet — den manglende kilde til sandhed er. Hvis en maskine ikke tæller dine sprog, er der ingen, der tæller dem ens to gange.

For det forrige kapitel i v1-historien, at redesigne dashboardet omkring ét enkelt bølgediagram (v1.8.2); og for hele buen, anatomien i at shippe software til perfektion.