Steven
Steven7 мин. чтения

Выпуск тридцати языков без страховочной сетки

GeekBye v1.8.3 подключил react-i18next и перевёл всё приложение на 30 языков. Часть с библиотекой была рутиной. Интересно то, чего не хватало: ни проверки паритета ключей, ни экстрактора строк — так что зашитый в код английский просочился, в продакшн уехала датская опечатка, а changelog не мог договориться, на скольких языках говорит приложение — 28, 29 или 30.

Инженерия
i18n
Десктоп
Релизы GeekBye
Выпуск тридцати языков без страховочной сетки

Некоторые релизы — это фича. 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, видимый только носителю языка.

Три вещи, которым нас научил этот релиз

  1. Библиотека — это лёгкие 80%; инструменты — трудные 20%. Подключить react-i18next и массово перевести строки — решённая задача. Вечно держать тридцать файлов в паритете — нет, и для этого нужен экстрактор зашитых в код строк и CI-проверка отсутствующих ключей, ни одного из которых библиотека тебе не поставляет. Построй их прежде чем заявлять «полный i18n», иначе ты заявил инвариант, который тебе нечем удержать.
  2. Раздели настройки языка, которые на самом деле разные. Язык интерфейса, язык транскрипции и язык ответов AI — три оси, и v1.8.3 был прав, дав каждой собственный ключ — и ещё правее, засеяв новый из родственного старого через миграцию, так что обновление ничего пользователям не стоило.
  3. Счёт в твоём changelog — метрика дисциплины. Когда один и тот же набор файлов — это 28, 29 и 30 языков в трёх коммитах, проблема не в числе — а в отсутствующем источнике истины. Если машина не считает твои языки, никто не считает их дважды одинаково.

Про предыдущую главу истории v1 — перепроектирование дашборда вокруг одного волнового графика (v1.8.2); а про весь путь — анатомию доведения софта до совершенства.

Похожие статьи

Релизы в CI, дважды
Steven
Steven7 мин. чтения

Релизы в CI, дважды

GeekBye v1.8.4 перенёс сборки релизов в CI для macOS и Windows. Чего changelog не говорит — что мы уже пробовали это однажды, удалили месяц спустя из-за стоимости раннеров и заставили прижиться только со второго раза, потому что к тому моменту скрипт релиза уже работал вручную.

Инженерия
CI/CD
Десктоп
Четыре релиза за двадцать шесть часов: языковая сантехника и «bridge release»
Steven
Steven5 мин. чтения

Четыре релиза за двадцать шесть часов: языковая сантехника и «bridge release»

GeekBye от v1.7.6 до v1.8.1: настройка языка, которая ни разу не покидала ноутбук, оверлей Cmd+/, показывающий твои настоящие горячие клавиши, версия без единого изменения в коде — и проблема курицы и яйца при миграции фида автообновлений.

Инженерия
Релизы
Десктоп
Встречи, которые снова можно найти: релиз с таймлайном
Steven
Steven6 мин. чтения

Встречи, которые снова можно найти: релиз с таймлайном

GeekBye v1.7.3 заменил дропдаун встреч настоящим таймлайном — человеческие даты, поиск, хлебные крошки, показывающие, в какой встрече ты находишься. А под капотом: гонка авто-выбора, скриншот старше собственной встречи и пять фиксов для Windows за двадцать восемь минут.

Инженерия
UX
Десктоп