
Доставяне на трийсет езика без предпазна мрежа
GeekBye v1.8.3 закачи react-i18next и преведе цялото приложение на 30 езика. Частта с библиотеката беше рутинна. Интересната част е това, което липсваше: никаква проверка за паритет на ключовете и никакъв екстрактор на стрингове — така hardcoded английски се промъкна, датска печатна грешка стигна до производство, а changelog-ът не можеше да реши дали приложението говори 28, 29 или 30 езика.
Някои издания са функция. GeekBye v1.8.3 изглежда като такава — „приложението вече говори 30 езика" — но функцията беше лесната част и не за нея е този текст. Между 17 и 19 февруари 2026 два pull request-а закачиха react-i18next в приложението, рефакторираха всеки hardcoded английски стринг в извикване за превод и опаковаха трийсет locale файла. Това е истинска работа, но е от вида работа, който библиотеката е проектирана да направи рутинна. Интересното инженерство е всичко, което изданието не построи — предпазната мрежа, която би уловила това, което достави счупено.
Закачането беше лесните 80%
Инфраструктурният комит добави react-i18next върху i18next и направи конвенционалното: i18n/index.ts, който извиква initReactI18next, статично импортира всичките трийсет locale JSON-а и ги регистрира под един resources обект. Никакво lazy loading — всеки език пътува в bundle-а. Стринговете за превод живеят в src/i18n/locales/*.json като плоски карти с namespace през точки: sidebar.meetings, startMeeting.start, един ключ към един стринг.
Миграцията от hardcoded английски към t('key') беше направена по честния начин, група компоненти след група компоненти, и можеш да прочетеш обхождането в лога на комитите: първо sidebar, навигация и chat; после настройки, login и onboarding; после meetings, dashboard, toast-ове и tooltip-ите на navbar-а. Всеки комит замени партида буквални стрингове с извиквания за превод. Двайсет и девет неанглийски файла кацнаха в един-единствен комит — спускане от девет хиляди реда, което преведе английския източник във всичко от български до виетнамски.
Нищо от това не е трудно да се направи правилно, защото да го направиш грешно е шумно: липсващ provider дава crash, зле форматиран JSON не се парсва, лош импорт чупи build-а. Библиотеката ти дава перила за механичните грешки. Това, което не ти дава, е перило за грешката, която всъщност се случи.
Три настройки за език и защо това е правилно
Преди да стигнем до това, което се счупи, едно нещо v1.8.3 улучи точно, защото е различие, което много приложения смачкват, а не бива. След това издание GeekBye има три независими настройки за език:
- Език на AI отговорите (
output_language) — на какъв език отговаря асистентът. Най-старата от трите, от ноември 2025. - Език на транскрипцията (
transcriptLanguage) — на какъв език speech engine-ът транскрибира микрофона ти. Прокаран няколко издания по-рано, във v1.7.6. - Език на приложението (
app_language) — езикът на бутоните, менютата и onboarding-а. Нов във v1.8.3.
Това са наистина различни оси. Разработчик в Берлин може да иска немски интерфейс, английски AI отговори (защото това е езикът на документацията, която поставя) и английска транскрипция (защото срещата е на английски). Свеждането им до една настройка за „език" би било грешно за този човек, а стъпката на onboarding, добавена в това издание, представя и трите като отделни dropdown-и точно поради тази причина.
Приятният детайл е пътят на обновяване. Еднократна миграция копира output_language на всеки съществуващ потребител в новия app_language ключ при първото стартиране след ъпдейта, а provider-ът разрешава езика през верига от fallback-ове — app_language, после output_language, после 'en'. Така потребителите, които вече бяха казали на приложението, че искат испански AI отговори, се събудиха с испански интерфейс, вместо да бъдат нулирани на английски и накарани да търсят настройката. Посаждането на нова предпочитана настройка от свързана стара е малка любезност, която струва една миграция и спестява на всеки обновяващ се дребно раздразнение.
Пре-рендерът на onboarding, който отказваше
Има един истински бъг, който заслужава да се извади от работата по i18n, защото е React капан, който всеки, правещ смяна на език по време на изпълнение, ще удари. Onboarding wizard-ът строи някои от списъците си с опции вътре в useMemo. Когато смениш езика си точно на стъпката за език, интерфейсът би трябвало да се преначертае веднага на новия език — и не го направи, защото t не беше в масива от зависимости на memo-то. Функцията за превод сменя идентичността си, когато езикът се смени; ако мемоизираната ти стойност е построена от t, но не зависи от него, React с удоволствие ти връчва остарялата версия на предишния език. Fix-ът се появява два пъти в лога — веднъж за wizard-а, веднъж за dropdown списъка в настройките — което ти казва, че е от вида грешки, които правиш на всяко място, където мемоизираш преведено съдържание, докато не се научиш винаги да изброяваш t.
Onboarding-ът носеше и една гънка, която changelog-ът не споменава: понеже това е white-label codebase, има две onboarding дървета за превеждане — GeekBye wizard и Pavleur — живеещи под паралелните namespace-и на ключове onboarding.* и onboarding.pavleur.*. Всеки onboarding стринг беше преведен два пъти, в два гласа на бранда, през всичките двайсет и девет езика.
Това, което build-ът не знаеше, че му липсва
Ето частта, която прави от това урок, а не чеклист. v1.8.3 нямаше автоматичен начин да извлича hardcoded стрингове и нямаше CI проверка за паритет на ключовете между locale-ите. Така че дефиницията на „готово" беше „някой посети компонентите, които си спомни, и опакова стринговете, които видя". Тази дефиниция пропуска.
Две седмици след изданието кацна комит за почистване със съобщението "translate all hardcoded English strings across 10 components into 30 languages" (преведи всички hardcoded английски стрингове в 10 компонента на 30 езика). Десет компонента — формулярът за докладване на проблеми, табовете за резюме и метрики, табът за отчет, съдържанието на транскрипцията и chat-а, промптът за лимит на употреба, chat съобщенията, бутонът за копиране на код, плаващите бутони за действие — бяха излезли във v1.8.3, все още говорейки английски на всички, на всеки език. Ничий build не се провали. Никой тест не почервеня. Немски потребител просто виждаше английски в отчетния таб, а единственият детектор беше човек, който в крайна сметка го погледна.
Същата липса произведе по-малките неудобства. Датски превод излезе с грешната дума — Luk application helt вместо Luk applikationen helt — и беше закърпен в по-късно издание, защото нямаше праг с native ревюър и нямаше как pipeline-ът да разбере. Ключовете „quit app" липсваха от първоначалната партида и бяха допълнени дни по-късно. И всяка отделна функция, която излезе след това — preset за модификаторни клавиши, заглушаване на микрофона, recap чипове, прозрачност на overlay-а — влачеше след себе си собствен комит „преведи новите ключове в 29 locale-а", завинаги, защото нищо не налагаше инварианта автоматично.
Можеш дори да измериш безпорядъка в съобщенията на комитите. Същите трийсет locale файла са описани, издание след издание, като „30 езика", „29 езика" и — в един комит — „28 locale-а". Нищо не беше добавено или премахнато; има трийсет файла на всеки таг от v1.8.3 до v2. Числото се лута, защото нямаше един-единствен източник на истина за „колко езика доставяме", така че всеки автор на комит броеше на ръка, понякога включвайки английския като език, а понякога като източник. Changelog, който не може да задържи собствения си брой стабилен, е малък, честен сигнал, че нещото, което описва, не се налага от машина.
Капанът на плурализацията
Още едно нещо, което заслужава да се отбележи, не защото се счупи шумно, а защото е от вида бъгове, които тази постановка кани и за които няма да те предупреди. Плурализацията в тези locale файлове се прави ръчно, със сдвоени ключове: meetings.meetingCount и meetings.meetingCountPlural, profiles.filesAttached и filesAttachedPlural. Това е чисто английско разделяне единствено/множествено число. То е и грешно за част от езиците, които приложението сега твърди, че поддържа. Руският, полският и чешкият имат три или четири форми за множествено число в зависимост от числото — „1 файл", „2 файла", „5 файла" и „21 файла" могат да приемат различни окончания. Схема с два ключа единствено/множествено не може да изрази това, така че тези бройки се изобразяват граматически невярно. i18next има истинска plural машинария точно за това; ръчните двойки я заобикалят, а никой комит не признава пропастта. Това е плурализационният еквивалент на пропуска с hardcoded стрингове: невидим за build-а, видим само за носител на езика.
Три неща, на които ни научи това издание
- Библиотеката е лесните 80%; инструментите са трудните 20%. Закачането на
react-i18nextи масовото превеждане на стрингове е решен проблем. Държането на трийсет файла в паритет завинаги не е — и то се нуждае от екстрактор за hardcoded стрингове и CI проверка за липсващи ключове, нито един от които библиотеката не доставя вместо теб. Построй ги преди да заявиш „пълен i18n", иначе си заявил инвариант, който нямаш как да задържиш. - Раздели настройките за език, които наистина са различни. Езикът на интерфейса, езикът на транскрипцията и езикът на AI отговорите са три оси, и v1.8.3 беше прав да даде на всяка собствен ключ — и още по-прав да посади новия от свързан стар чрез миграция, така че обновяването не струваше на потребителите нищо.
- Броят в твоя changelog е метрика за дисциплина. Когато същият набор файлове е 28, 29 и 30 езика през три комита, числото не е проблемът — липсващият източник на истина е. Ако машина не брои езиците ти, никой не ги брои по един и същи начин два пъти.
За предишната глава от историята на v1 — предизайн на dashboard-а около една-единствена вълнова диаграма (v1.8.2); а за цялата дъга — анатомията на доставянето на софтуер до съвършенство.