Steven
Steven7 min läsning

Att skeppa trettio språk utan skyddsnät

GeekBye v1.8.3 kopplade in react-i18next och översatte hela appen till 30 språk. Biblioteksdelen var rutin. Det intressanta är vad som saknades: ingen kontroll av nyckelparitet och ingen strängextraktor — så hårdkodad engelska slank igenom, ett danskt stavfel skeppades och ändringsloggen kunde inte enas om appen talade 28, 29 eller 30 språk.

Utveckling
i18n
Desktop
GeekBye-releaser
Att skeppa trettio språk utan skyddsnät

Vissa releaser är en funktion. GeekBye v1.8.3 ser ut som en — "appen talar nu 30 språk" — men funktionen var den enkla delen, och det är inte vad det här inlägget handlar om. Mellan 17 och 19 februari 2026 kopplade två pull requests in react-i18next i appen, gjorde om varje hårdkodad engelsk sträng till ett översättningsanrop och buntade ihop trettio lokalfiler. Det är riktigt arbete, men det är den sortens arbete ett bibliotek är designat för att göra till rutin. Den intressanta ingenjörskonsten är allt releasen inte byggde — skyddsnätet som skulle ha fångat det den skeppade trasigt.

Inkopplingen var de enkla 80 %

Infrastruktur-commiten lade react-i18next ovanpå i18next och gjorde det konventionella: en i18n/index.ts som anropar initReactI18next, statiskt importerar alla trettio lokal-JSON:er och registrerar dem under ett enda resources-objekt. Ingen lat inläsning — varje språk skeppas i bundlen. Översättningssträngarna bor i src/i18n/locales/*.json som platta, punktnamnrymdade kartor: sidebar.meetings, startMeeting.start, en nyckel till en sträng.

Migreringen från hårdkodad engelska till t('key') gjordes på det hederliga sättet, komponentgrupp för komponentgrupp, och du kan läsa svepet i commit-loggen: sidofält, navigering och chatt först; sedan inställningar, inloggning och onboarding; sedan möten, dashboard, toaster och navbar-tooltips. Varje commit ersatte en sats bokstavliga strängar med översättningsanrop. Tjugonio icke-engelska filer landade i en enda commit — ett niotusenradersdropp som översatte den engelska källan till allt från bulgariska till vietnamesiska.

Inget av det är svårt att få rätt, eftersom att få det fel är högljutt: en saknad provider kraschar, en felformad JSON parsar inte, en dålig import bryter bygget. Biblioteket ger dig skyddsräcken för de mekaniska misstagen. Vad det inte ger dig är ett skyddsräcke för misstaget som faktiskt hände.

Tre språkinställningar, och varför det är rätt

Innan vi kommer till vad som gick sönder, en sak som v1.8.3 fick exakt rätt, eftersom det är en distinktion många appar plattar till och inte borde. Efter den här releasen har GeekBye tre oberoende språkinställningar:

  • AI-svarsspråk (output_language) — vilket språk assistenten svarar på. Det äldsta av de tre, från november 2025.
  • Transkriptspråk (transcriptLanguage) — vad taldriven transkriberar din mikrofon som. Draget några releaser tidigare, i v1.7.6.
  • Appspråk (app_language) — språket i knappar, menyer och onboarding. Nytt i v1.8.3.

Det här är genuint olika axlar. En utvecklare i Berlin kanske vill ha ett tyskt gränssnitt, engelska AI-svar (eftersom det är språket i dokumentationen de klistrar in) och engelsk transkription (eftersom mötet är på engelska). Att slå ihop dem till en enda "språk"-inställning vore fel för den personen, och onboarding-steget som lades till i den här releasen presenterar alla tre som separata rullgardinsmenyer av exakt det skälet.

Den fina detaljen är uppgraderingsvägen. En engångsmigrering kopierar varje befintlig användares output_language till den nya app_language-nyckeln vid första starten efter uppdatering, och providern löser upp språket genom en fallback-kedja — app_language, sedan output_language, sedan 'en'. Så användare som redan hade sagt till appen att de ville ha spanska AI-svar vaknade upp till ett spanskt gränssnitt, i stället för att nollställas till engelska och tvingas leta efter inställningen. Att så en ny preferens från en besläktad gammal är en liten vänlighet som kostar en migrering och besparar varje uppgraderare ett litet irritationsmoment.

Onboarding-omritningen som inte ville

Det finns en genuin bugg värd att lyfta ut ur i18n-arbetet, för det är en React-fälla som alla som gör språkbyte i körtid kommer att träffa. Onboarding-guiden bygger några av sina alternativlistor inuti en useMemo. När du byter språk på själva språksteget ska gränssnittet ritas om på det nya språket direkt — och det gjorde det inte, eftersom t inte fanns i memons beroendearray. Översättningsfunktionen byter identitet när språket ändras; om ditt memoiserade värde är byggt från t men inte beror på det, räcker React glatt över den inaktuella versionen på föregående språk. Fixen dyker upp två gånger i loggen — en gång för guiden, en gång för inställningarnas rullgardinslista — vilket säger dig att det är den sortens misstag du gör på varje ställe du memoiserar översatt innehåll tills du lär dig att alltid lista t.

Onboarding bar också på en krångel som ändringsloggen inte nämner: eftersom det här är en white-label-kodbas finns det två onboarding-träd att översätta — en GeekBye-guide och en Pavleur-guide — som bor under parallella nyckelnamnrymder onboarding.* och onboarding.pavleur.*. Varje onboarding-sträng översattes två gånger, till två varumärkesröster, över alla tjugonio språk.

Vad bygget inte visste att det saknade

Här är delen som gör det här till en lärdom i stället för en checklista. v1.8.3 hade inget automatiskt sätt att extrahera hårdkodade strängar och ingen CI-kontroll för nyckelparitet mellan lokaler. Så definitionen av "klart" var "någon besökte de komponenter de kom ihåg och lindade in strängarna de såg." Den definitionen läcker.

Två veckor efter releasen landade en städ-commit med meddelandet "translate all hardcoded English strings across 10 components into 30 languages." Tio komponenter — felrapportsformuläret, samman­fattnings- och mätvärdesflikarna, rapportfliken, transkript- och chattinnehåll, användningsgränsprompten, chattmeddelanden, kopiera-kod-knappen, de flytande åtgärdsknapparna — hade skeppats i v1.8.3 fortfarande talandes engelska till alla, på varje språk. Ingens bygge misslyckades. Inget test blev rött. En tysk användare såg bara engelska i rapportfliken, och den enda detektorn var en människa som till slut tittade på det.

Samma frånvaro producerade de mindre pinsamheterna. En dansk översättning skeppades med fel ord — Luk application helt i stället för Luk applikationen helt — och lappades i en senare release, eftersom det inte fanns någon grind med modersmålsgranskare och inget sätt för pipelinen att veta. "Quit app"-nycklar saknades i den första omgången och fylldes på dagar senare. Och varenda funktion som skeppades därefter — en förval för modifierartangent, mikrofonmute, sammanfattningschips, overlay-opacitet — släpade med sig sin egen "översätt de nya nycklarna till 29 lokaler"-commit, för alltid, eftersom inget upprätthöll invarianten automatiskt.

Man kan till och med mäta oordningen i commit-meddelandena. Samma trettio lokalfiler beskrivs, release till release, som "30 språk", "29 språk" och — i en commit — "28 lokaler". Inget lades till eller togs bort; det finns trettio filer vid varje tagg från v1.8.3 till v2. Antalet driver eftersom det inte fanns någon enda sanningskälla för "hur många språk skeppar vi", så varje commit-författare räknade för hand, ibland med engelska som ett språk och ibland som källan. En ändringslogg som inte kan hålla sitt eget antal stadigt är en liten, ärlig signal om att det den beskriver inte upprätthålls av en maskin.

Pluralhanteringens fälla

En sak till värd att flagga, inte för att den gick sönder högljutt utan för att det är den sortens bugg det här upplägget bjuder in och inte varnar dig om. Pluralhanteringen i de här lokalfilerna görs manuellt, med parade nycklar: meetings.meetingCount och meetings.meetingCountPlural, profiles.filesAttached och filesAttachedPlural. Det är en ren engelsk singular/plural-uppdelning. Den är också fel för en del av de språk appen nu påstår sig stödja. Ryska, polska och tjeckiska har tre eller fyra pluralformer beroende på antalet — "1 fil", "2 filer", "5 filer" och "21 filer" kan ta olika ordändelser. Ett tvånyckels singular/plural-schema kan inte uttrycka det, så de antalen renderas grammatiskt fel. i18next har riktigt pluralmaskineri för exakt det här; de manuella paren går runt det, och ingen commit erkänner glappet. Det är pluralmotsvarigheten till läckan med hårdkodade strängar: osynlig för bygget, synlig bara för en som har språket som modersmål.

Tre saker den här releasen lärde oss

  1. Biblioteket är de enkla 80 %; verktygen är de svåra 20 %. Att koppla in react-i18next och massöversätta strängar är ett löst problem. Att hålla trettio filer i paritet för alltid är det inte — och det behöver en extraktor för hårdkodade strängar och en CI-kontroll för saknade nycklar, varav ett bibliotek inte skeppar någotdera åt dig. Bygg dem innan du påstår "full i18n", annars har du påstått en invariant du inte har något sätt att hålla.
  2. Dela upp språkinställningarna som faktiskt är olika. Gränssnittsspråk, transkriptspråk och AI-svarsspråk är tre axlar, och v1.8.3 gjorde rätt i att ge var och en sin egen nyckel — och ännu rättare i att så den nya från en besläktad gammal via migrering, så att uppgradering inte kostade användarna något.
  3. Din ändringsloggs antal är ett disciplinmått. När samma filuppsättning är 28, 29 och 30 språk över tre commits är antalet inte problemet — den saknade sanningskällan är det. Om en maskin inte räknar dina språk är det ingen som räknar dem likadant två gånger.

För det föregående kapitlet i v1-historien, att designa om dashboarden kring ett enda vågdiagram (v1.8.2); och för hela bågen, anatomin i att skeppa mjukvara till perfektion.