
Випуск тридцяти мов без страхувальної сітки
GeekBye v1.8.3 підключив react-i18next і переклав увесь застосунок на 30 мов. Частина з бібліотекою була рутиною. Цікаве те, чого бракувало: ані перевірки паритету ключів, ані екстрактора рядків — тож зашитий у код англійський текст просочився, у продакшн поїхала данська друкарська помилка, а changelog не міг дійти згоди, скількома мовами говорить застосунок — 28, 29 чи 30.
Деякі релізи — це фіча. GeekBye v1.8.3 виглядає однією з них — «застосунок тепер говорить 30 мовами» — але фіча була легкою частиною, і пост не про неї. Між 17 і 19 лютого 2026 два пул-реквести підключили react-i18next до застосунку, відрефакторили кожен зашитий у код англійський рядок у виклик перекладу й упакували тридцять файлів локалізації. Це справжня робота, але це той сорт роботи, який бібліотека й покликана робити рутиною. Цікава інженерія — це все, чого реліз не побудував: страхувальна сітка, яка спіймала б те, що він випустив зламаним.
Обвʼязка була легкими 80%
Інфраструктурний коміт додав react-i18next поверх i18next і зробив конвенційну річ: i18n/index.ts, який викликає initReactI18next, статично імпортує всі тридцять JSON-ів локалізації й реєструє їх під одним об'єктом resources. Жодного лінивого завантаження — кожна мова їде в бандлі. Рядки перекладів живуть у src/i18n/locales/*.json як пласкі мапи з крапковими просторами назв: sidebar.meetings, startMeeting.start, один ключ на один рядок.
Міграція з зашитого в код англійського на t('key') була зроблена чесно, група компонентів за групою, і весь цей прохід читається з логу комітів: спершу бічна панель, навігація й чат; потім налаштування, логін і онбординг; потім зустрічі, дашборд, тости й підказки навбара. Кожен коміт заміняв пачку буквальних рядків викликами перекладів. Двадцять дев'ять неанглійських файлів приземлилися в одному коміті — дев'ятитисячний скид рядків, що переклав англійський вихідник у все, від болгарської до в'єтнамської.
Ніщо з цього не важко зробити правильно, бо зробити це неправильно гучно: відсутній провайдер падає, кривий JSON не парситься, поганий імпорт ламає build. Бібліотека дає тобі огорожі для механічних помилок. Чого вона не дає — це огорожа для помилки, яка насправді сталася.
Три налаштування мови, і чому це правильно
Перш ніж перейти до того, що зламалося, одна річ, яку v1.8.3 зробив рівно правильно, бо це розрізнення, яке багато застосунків схлопує, а не мало б. Після цього релізу GeekBye має три незалежні налаштування мови:
- Мова відповідей AI (
output_language) — якою мовою відповідає асистент. Найстарша з трьох, з листопада 2025. - Мова транскрипції (
transcriptLanguage) — якою мовою рушій мовлення транскрибує твій мікрофон. Прокладена кількома релізами раніше, у v1.7.6. - Мова застосунку (
app_language) — мова кнопок, меню й онбордингу. Новинка у v1.8.3.
Це по-справжньому різні осі. Розробник у Берліні може хотіти німецький інтерфейс, англійські відповіді AI (бо це мова документації, яку він вставляє) і англійську транскрипцію (бо зустріч англійською). Схлопування цього в одне налаштування «мова» було б для цієї людини хибним, а крок онбордингу, доданий у цьому релізі, подає всі три як окремі випадні списки саме з цієї причини.
Приємний штрих — шлях оновлення. Разова міграція копіює output_language кожного наявного користувача в новий ключ app_language при першому запуску після оновлення, а провайдер розв'язує мову через ланцюг фолбеків — app_language, потім output_language, потім 'en'. Тож користувачі, які вже сказали застосунку, що хочуть іспанські відповіді AI, прокинулися з іспанським інтерфейсом, замість того щоб бути скинутими на англійську й змушеними полювати на це налаштування. Засіяти нове вподобання зі спорідненого старого — маленька люб'язність, яка коштує однієї міграції й позбавляє кожного, хто оновлюється, дрібної скалки.
Перемальовування онбордингу, яке не хотіло
Є один справжній баг, який варто витягти з роботи над i18n, бо це пастка React, у яку втрапить кожен, хто робить перемикання мови під час роботи. Майстер онбордингу будує частину своїх списків опцій усередині useMemo. Коли ти змінюєш мову на самому кроці вибору мови, інтерфейс має негайно перемалюватися новою мовою — а він цього не робив, бо t не було в масиві залежностей memo. Функція перекладу змінює ідентичність, коли змінюється мова; якщо твоє мемоїзоване значення побудоване з t, але від нього не залежить, React радо вручає тобі застарілу версію попередньою мовою. Фікс з'являється в лозі двічі — раз для майстра, раз для випадного списку налаштувань — що каже тобі, що це той сорт помилки, який ти робиш у кожному місці, де мемоїзуєш перекладений контент, поки не навчишся завжди вказувати t.
Онбординг ніс ще й зморшку, про яку changelog не згадує: оскільки це white-label кодова база, є два дерева онбордингу для перекладу — майстер GeekBye і майстер Pavleur — що живуть під паралельними просторами назв ключів onboarding.* і onboarding.pavleur.*. Кожен рядок онбордингу було перекладено двічі, у два голоси бренду, усіма двадцятьма дев'ятьма мовами.
Чого build не знав, що йому бракує
Ось частина, яка робить це уроком, а не чек-листом. У v1.8.3 не було ані автоматичного способу витягти зашиті в код рядки, ані CI-перевірки паритету ключів між локалізаціями. Тож визначенням «готово» було «хтось зайшов у компоненти, які пам'ятав, і обгорнув рядки, які побачив». Це визначення тече.
За два тижні після релізу приземлився прибиральний коміт із повідомленням «translate all hardcoded English strings across 10 components into 30 languages.» Десять компонентів — форма звіту про проблему, вкладки зведення й метрик, вкладка звіту, вміст транскрипту й чату, промпт про ліміт використання, повідомлення чату, кнопка копіювання коду, плавучі кнопки дій — поїхали у v1.8.3, усе ще говорячи всім англійською, кожною мовою. Ні в кого build не впав. Жоден тест не почервонів. Німецький користувач просто бачив англійську у вкладці звіту, а єдиним детектором була людина, яка врешті на це подивилася.
Та сама відсутність спродукувала менші конфузи. Данський переклад поїхав із хибним словом — Luk application helt замість Luk applikationen helt — і був пропатчений у пізнішому релізі, бо не було ані воріт носія-рецензента, ані способу для пайплайна про це знати. Ключі «закрити застосунок» були відсутні в початковій партії й дозаповнені днями пізніше. І кожна до єдиної фіча, що поїхала потім — пресет клавіш-модифікаторів, мьют мікрофона, чипи підсумку, прозорість оверлея — тягла за собою власний коміт «перекласти нові ключі на 29 локалей», назавжди, бо ніщо не забезпечувало інваріант автоматично.
Безлад можна навіть виміряти в повідомленнях комітів. Ті самі тридцять файлів локалізації описуються, від релізу до релізу, як «30 мов», «29 мов» і — в одному коміті — «28 локалей». Нічого не додали й не прибрали; тридцять файлів при кожному тезі від v1.8.3 до v2. Число дрейфує, бо не було єдиного джерела істини про те, «скільки мов ми відвантажуємо», тож кожен автор коміта рахував вручну, іноді включаючи англійську як мову, а іноді як вихідник. Changelog, який не може втримати власний лік стабільним, — маленький, чесний сигнал, що річ, яку він описує, не забезпечується машиною.
Міна плюралізації
Ще одна річ, варта позначення, не тому що вона зламалася гучно, а тому що це той сорт багу, який ця конфігурація запрошує й про який не попередить. Плюралізація в цих файлах локалізації зроблена вручну, парними ключами: meetings.meetingCount і meetings.meetingCountPlural, profiles.filesAttached і filesAttachedPlural. Це чистий англійський поділ однина/множина. Він же хибний для шматка мов, які застосунок тепер заявляє, що підтримує. У російській, польській та чеській три чи чотири форми множини залежно від числа — «1 файл», «2 файли», «5 файлів» і «21 файл» можуть брати різні закінчення слів. Схема з двох ключів однина/множина не може цього виразити, тож ці лічильники рендеряться граматично криво. У i18next є справжній механізм множини саме для цього; ручні пари його обходять, і жоден коміт не визнає цю прогалину. Це плюралізаційний еквівалент витоку зашитих у код рядків: невидимий для build, видимий лише носієві мови.
Три речі, яких нас навчив цей реліз
- Бібліотека — це легкі 80%; інструменти — важкі 20%. Підключити
react-i18nextі масово перекласти рядки — розв'язана задача. Вічно тримати тридцять файлів у паритеті — ні, і для цього потрібен екстрактор зашитих у код рядків і CI-перевірка відсутніх ключів, жодного з яких бібліотека тобі не постачає. Побудуй їх перш ніж заявляти «повний i18n», інакше ти заявив інваріант, який тобі нічим утримати. - Розділи налаштування мови, які насправді різні. Мова інтерфейсу, мова транскрипції та мова відповідей AI — три осі, і v1.8.3 мав рацію, давши кожній власний ключ — і ще більше рацію, засіявши новий зі спорідненого старого через міграцію, тож оновлення нічого користувачам не коштувало.
- Лік у твоєму changelog — метрика дисципліни. Коли той самий набір файлів — це 28, 29 і 30 мов у трьох комітах, проблема не в числі — а у відсутньому джерелі істини. Якщо машина не рахує твої мови, ніхто не рахує їх двічі однаково.
Про попередній розділ історії v1 — перепроєктування дашборда навколо одного хвильового графіка (v1.8.2); а про всю дугу — анатомію доставки програмного забезпечення до досконалості.