Steven
Steven9 min citire

Livrarea a treizeci de limbi fără plasă de siguranță

GeekBye v1.8.3 a conectat react-i18next și a tradus întreaga aplicație în 30 de limbi. Partea cu librăria a fost de rutină. Partea interesantă e ce lipsea: nicio verificare de paritate a cheilor și niciun extractor de stringuri — așa că engleza hardcodată a scăpat, o greșeală daneză a ajuns în producție, iar changelog-ul nu se putea hotărî dacă aplicația vorbea 28, 29 sau 30 de limbi.

Inginerie
i18n
Desktop
Lansări GeekBye
Livrarea a treizeci de limbi fără plasă de siguranță

Unele lansări sunt o funcționalitate. GeekBye v1.8.3 pare una — „aplicația vorbește acum 30 de limbi" — dar funcționalitatea a fost partea ușoară și nu despre ea e vorba în acest text. Între 17 și 19 februarie 2026, două pull request-uri au conectat react-i18next în aplicație, au refactorizat fiecare string englezesc hardcodat într-un apel de traducere și au împachetat treizeci de fișiere de localizare. E muncă reală, dar e genul de muncă pe care o librărie e concepută s-o facă de rutină. Ingineria interesantă e tot ce lansarea nu a construit — plasa de siguranță care ar fi prins ce a livrat stricat.

Conectarea a fost cei 80% ușori

Commit-ul de infrastructură a adăugat react-i18next peste i18next și a făcut lucrul convențional: un i18n/index.ts care apelează initReactI18next, importă static toate cele treizeci de JSON-uri de localizare și le înregistrează sub un singur obiect resources. Fără lazy loading — fiecare limbă ajunge în bundle. Stringurile de traducere stau în src/i18n/locales/*.json ca hărți plate, cu namespace pe puncte: sidebar.meetings, startMeeting.start, o cheie pentru un string.

Migrarea de la engleza hardcodată la t('key') s-a făcut pe bune, grup de componente după grup de componente, și poți citi baleiajul în log-ul de commit-uri: mai întâi sidebar, navigație și chat; apoi setări, login și onboarding; apoi meetings, dashboard, toast-uri și tooltip-urile din navbar. Fiecare commit a înlocuit un lot de stringuri literale cu apeluri de traducere. Douăzeci și nouă de fișiere non-engleze au aterizat într-un singur commit — o cădere de nouă mii de linii care a tradus sursa engleză în tot, de la bulgară la vietnameză.

Nimic din toate astea nu e greu de nimerit, pentru că a le greși e zgomotos: un provider lipsă dă crash, un JSON malformat nu se parsează, un import greșit strică build-ul. Librăria îți dă balustrade pentru greșelile mecanice. Ce nu-ți dă e o balustradă pentru greșeala care chiar s-a întâmplat.

Trei setări de limbă și de ce e corect așa

Înainte să ajungem la ce s-a stricat, un lucru pe care v1.8.3 l-a nimerit exact, pentru că e o distincție pe care multe aplicații o aplatizează, deși n-ar trebui. După această lansare, GeekBye are trei setări de limbă independente:

  • Limba răspunsurilor AI (output_language) — în ce limbă răspunde asistentul. Cea mai veche dintre cele trei, din noiembrie 2025.
  • Limba transcrierii (transcriptLanguage) — în ce limbă transcrie motorul de voce microfonul tău. Instalată cu câteva lansări mai devreme, în v1.7.6.
  • Limba aplicației (app_language) — limba butoanelor, meniurilor și a onboarding-ului. Nouă în v1.8.3.

Acestea sunt cu adevărat axe diferite. Un developer din Berlin ar putea vrea o interfață în germană, răspunsuri AI în engleză (pentru că aceea e limba documentației pe care o lipește) și transcriere în engleză (pentru că ședința e în engleză). Contopirea lor într-o singură setare de „limbă" ar fi greșită pentru acea persoană, iar pasul de onboarding adăugat în această lansare prezintă toate cele trei ca dropdown-uri separate exact din acest motiv.

Atingerea plăcută e calea de upgrade. O migrare unică copiază output_language al fiecărui utilizator existent în noua cheie app_language la prima pornire după actualizare, iar provider-ul rezolvă limba printr-un lanț de fallback — app_language, apoi output_language, apoi 'en'. Așa că utilizatorii care spuseseră deja aplicației că vor răspunsuri AI în spaniolă s-au trezit cu o interfață în spaniolă, în loc să fie resetați la engleză și puși să caute setarea. Inițializarea unei preferințe noi dintr-una veche înrudită e o mică bunătate care costă o migrare și scutește fiecare utilizator care face upgrade de o mică bătaie de cap.

Re-randarea de onboarding care refuza

Există un bug autentic care merită scos din munca de i18n, pentru că e o capcană React pe care o va lovi oricine face schimbare de limbă la runtime. Wizard-ul de onboarding construiește unele dintre listele sale de opțiuni într-un useMemo. Când îți schimbi limba chiar pe pasul de limbă, interfața ar trebui să se redeseneze imediat în noua limbă — și nu a făcut-o, pentru că t nu era în array-ul de dependențe al memo-ului. Funcția de traducere își schimbă identitatea când se schimbă limba; dacă valoarea ta memoizată e construită din t, dar nu depinde de el, React îți înmânează voios versiunea învechită, în limba anterioară. Fix-ul apare de două ori în log — o dată pentru wizard, o dată pentru lista de dropdown din setări — ceea ce îți spune că e genul de greșeală pe care o faci în fiecare loc unde memoizezi conținut tradus, până înveți să listezi mereu t.

Onboarding-ul a purtat și o cută pe care changelog-ul n-o menționează: pentru că acesta e un codebase white-label, există doi arbori de onboarding de tradus — un wizard GeekBye și unul Pavleur — care trăiesc sub namespace-urile paralele de chei onboarding.* și onboarding.pavleur.*. Fiecare string de onboarding a fost tradus de două ori, în două voci de brand, în toate cele douăzeci și nouă de limbi.

Ce nu știa build-ul că-i lipsește

Iată partea care face din asta o lecție, nu o listă de bifat. v1.8.3 nu avea nicio modalitate automată de a extrage stringuri hardcodate și nicio verificare CI de paritate a cheilor între localizări. Așa că definiția lui „gata" era „cineva a vizitat componentele de care și-a amintit și a împachetat stringurile pe care le-a văzut". Definiția aceea lasă lucruri să scape.

La două săptămâni după lansare, un commit de curățare a aterizat cu mesajul "translate all hardcoded English strings across 10 components into 30 languages" (tradu toate stringurile englezești hardcodate din 10 componente în 30 de limbi). Zece componente — formularul de raportare a problemelor, tab-urile de sumar și metrici, tab-ul de raport, conținutul de transcriere și chat, prompt-ul de limită de utilizare, mesajele de chat, butonul de copiere a codului, butoanele flotante de acțiune — ajunseseră în v1.8.3 vorbind în continuare engleză cu toată lumea, în orice limbă. Build-ul nimănui n-a picat. Niciun test n-a devenit roșu. Un utilizator german pur și simplu vedea engleză în tab-ul de raport, iar singurul detector a fost un om care s-a uitat într-un final la el.

Aceeași absență a produs și jenele mai mici. O traducere daneză a ajuns cu cuvântul greșit — Luk application helt în loc de Luk applikationen helt — și a fost peticită într-o lansare ulterioară, pentru că nu exista niciun filtru de revizor nativ și nicio cale prin care pipeline-ul să afle. Cheile „quit app" lipseau din lotul inițial și au fost completate zile mai târziu. Iar fiecare funcționalitate care a ieșit după aceea — un preset de taste modificatoare, mute la microfon, chip-uri de recapitulare, opacitatea overlay-ului — și-a târât în urmă propriul commit „tradu cheile noi în 29 de localizări", la nesfârșit, pentru că nimic nu impunea invariantul automat.

Poți chiar măsura dezordinea în mesajele de commit. Aceleași treizeci de fișiere de localizare sunt descrise, de la o lansare la alta, drept „30 de limbi", „29 de limbi" și — într-un commit — „28 de localizări". Nimic n-a fost adăugat sau eliminat; există treizeci de fișiere la fiecare tag, de la v1.8.3 până la v2. Numărul se abate pentru că nu exista o singură sursă de adevăr pentru „câte limbi livrăm", așa că fiecare autor de commit a numărat de mână, uneori incluzând engleza ca limbă și alteori ca sursă. Un changelog care nu-și poate ține propriul număr stabil e un semnal mic și onest că lucrul pe care-l descrie nu e impus de o mașină.

Capcana pluralizării

Încă un lucru care merită semnalat, nu pentru că s-a stricat zgomotos, ci pentru că e genul de bug pe care această configurație îl invită și despre care nu te va avertiza. Pluralizarea în aceste fișiere de localizare se face manual, cu chei perechi: meetings.meetingCount și meetings.meetingCountPlural, profiles.filesAttached și filesAttachedPlural. E o împărțire curată singular/plural englezească. E și greșită pentru o bună parte din limbile pe care aplicația pretinde acum că le suportă. Rusa, poloneza și ceha au trei sau patru forme de plural în funcție de număr — „1 fișier", „2 fișiere", „5 fișiere" și „21 de fișiere" pot lua terminații diferite. O schemă de două chei singular/plural nu poate exprima asta, așa că acele numere se redau greșit gramatical. i18next are un mecanism real de plural exact pentru asta; perechile manuale îl ocolesc, iar niciun commit nu recunoaște lacuna. E echivalentul la pluralizare al scăpării de stringuri hardcodate: invizibil pentru build, vizibil doar pentru un vorbitor nativ.

Trei lucruri pe care ni le-a predat această lansare

  1. Librăria e cei 80% ușori; unealta e cei 20% grei. Conectarea react-i18next și traducerea în masă a stringurilor sunt o problemă rezolvată. Menținerea a treizeci de fișiere în paritate pentru totdeauna nu e — și are nevoie de un extractor pentru stringuri hardcodate și de o verificare CI pentru chei lipsă, niciuna livrată de o librărie în locul tău. Construiește-le înainte să pretinzi „i18n complet", altfel ai pretins un invariant pe care n-ai cum să-l ții.
  2. Desparte setările de limbă care chiar sunt diferite. Limba interfeței, limba transcrierii și limba răspunsurilor AI sunt trei axe, iar v1.8.3 a avut dreptate să dea fiecăreia propria cheie — și încă mai multă dreptate să inițializeze noua cheie dintr-una veche înrudită prin migrare, așa că upgrade-ul nu i-a costat pe utilizatori nimic.
  3. Numărul din changelog-ul tău e o metrică de disciplină. Când același set de fișiere e 28, 29 și 30 de limbi în trei commit-uri, numărul nu e problema — sursa de adevăr lipsă e. Dacă o mașină nu-ți numără limbile, nimeni nu le numără la fel de două ori.

Pentru capitolul anterior al poveștii v1, redesignul dashboard-ului în jurul unui singur grafic-val (v1.8.2); iar pentru întregul arc, anatomia livrării de software până la perfecțiune.