Як додати 13 мов UI у pet-проект без OpenAI/Google Translate API: Argos Translate + GitHub Actions на практиці

Контекст: у попередньому пості я розповідав про SelenaCore — офлайн-голосовий асистент для розумного будинку на Raspberry Pi / Jetson. Одне з питань, яке постало майже одразу — мультимовність. Українська та англійська були від початку, але self-hosted-проекту, який позиціонується як «локальний і приватний», обмежуватися двома мовами — це compromise. Довелося шукати спосіб додати ще десяток мов без жодного хмарного сервісу та без бюджету на перекладацькі API. Ділюся підходом — знадобиться будь-кому, хто пише self-hosted софт і не хоче платити $20/місяць за DeepL API.

Проблема

До v0.4.0 в SelenaCore було 2 мови UI: українська та англійська. Обидві ручні, src/i18n/locales/{en,uk}.ts, стандартний react-i18next. Додати 10+ мов «по-чесному» — це:

  • Платно: DeepL Pro $20/міс, Google Translate API ~$20 за мільйон символів. Помножте на N релізів на рік + регенерацію при зміні строк.
  • Компроміс приватності: хмарний переклад бачить усі рядки вашого інтерфейсу, включно з назвами кнопок типу «Активувати камеру спостереження».
  • Онлайн-залежність у build-pipeline: немає інтернету → немає релізу.

Для проекту з філософією «все локально, жодного cloud» це неприйнятно. Треба було інше рішення.

Рішення: Argos Translate + CI-генерація локалей

Argos Translate — open-source переклад на базі OpenNMT/CTranslate2. Поставив pip-пакет, скачав моделі en→{target_lang} один раз (~300MB кожна), далі працює офлайн.

Ключова ідея: переклад НЕ в рантаймі на девайсі користувача. Генеруємо всі локалі один раз на CI під час релізу, комітимо результат як статичні JSON-файли в репо. Користувач після git clone отримує вже готові мови без жодного запиту кудись.

# scripts/generate_auto_locales.py (спрощено)
from argostranslate import translate

def generate_locale(source_lang: str, target_lang: str, keys: dict) -> dict:
    tl = translate.get_translation_from_codes(source_lang, target_lang)
    result = {}
    for key, value in keys.items():
        result[key] = apply_glossary(tl.translate(value), target_lang)
    return result

Усе. 13 цільових мов (pl, cs, de, nl, es, fr, it, pt, tr, ja, zh, ko, hi) за ~3-5 хвилин генерації.

Три проблеми, які знадобилось вирішувати

1. Технічні терміни перекладаються кумедно

Argos, як і будь-який NMT, перекладатиме Wake word як «Слово прокидання», а Provider як «Постачальник». Для UI це ламає UX — користувач не впізнає терміни з документації.

Рішення — glossary перед перекладом. Замінюємо терміни на токени, перекладаємо, повертаємо токени назад. Плюс per-language overrides для випадків, коли хочемо все-таки локалізувати:

{
  "keep_original": ["Wake word", "Provider", "SelenaCore", "STT", "TTS", "LLM"],
  "per_language_overrides": {
    "de": {"Provider": "Anbieter"}
  }
}

2. Plural forms для слов’янських мов

Слов’янські мови мають 3-4 форми множини (1 пристрій / 2 пристрої / 5 пристроїв). Argos цього не знає — переклад одного "{{count}} devices" дасть одну форму.

Трюк: підставляємо реальні числа з різних плюральних діапазонів (1, 2, 5), перекладаємо кожне окремо, потім замінюємо число назад на {{count}}. babel.plural каже які форми потрібні для кожної мови. Працює на ~90% випадків, решта — community-override’и.

3. Bootstrap-проблема: що робити, поки моделі Argos не завантажені

Моделі Argos важать ~300MB на мовну пару × багато мов = кілька GB. На Raspberry Pi це неприйнятно. Але на девайсі моделі й не потрібні — вони потрібні тільки на CI, коли генеруються файли.

Це ключове архітектурне рішення: переклад — build-time, не runtime. Результат — статичні JSON’и в src/i18n/locales/auto/*.auto.json, які лежать у git і приходять до користувача разом з кодом. Інтерфейс перемикається моментально, бо i18next просто читає локальний файл через lazy-import.

Автоматизація через GitHub Actions

Workflow тригериться на зміну src/i18n/locales/en.ts у main:

on:
  push:
    branches: [main]
    paths:
      - 'src/i18n/locales/en.ts'

Далі — встановити Argos (~1 хв), кешувати моделі через actions/cache@v4 (потім вже миттєво), згенерувати, створити auto-PR через peter-evans/create-pull-request@v6. Підписав — з’їжджа у main → нові локалі активні в наступному релізі.

Для публічного OSS-репо GitHub Actions безкоштовні без лімітів, тож цей pipeline нічого не коштує.

Що вийшло в v0.4.0-rc

  • 15 мов UI: 2 ручних (uk, en) + 13 авто (pl, cs, de, nl, es, fr, it, pt, tr, ja, zh, ko, hi)
  • 3-tier resolution: manual > community > auto. Якщо хтось відправить PR з {lang}.community.json — його правки перекривають машинний переклад автоматично.
  • Lazy-load: з 2 мов до 15 initial bundle виріс лише на ~40KB gzip. Кожна мова — окремий async-chunk, вантажиться при зміні мови.
  • Safe-harbor recovery: якщо хтось випадково обрав 日本語 і заблукав у японському UI, long-press на логотипі (1.5 сек) відкриває reset-to-EN модалку. Крім того, вкладка Language сама по собі завжди показує дублювання native + English.

Крім i18n зайшли: редизайн нав-бару (Devices/Automations/Voice тепер у топ-левелі навігації, а не в підменю Settings), PIN-gate для Edit-mode дашборда, нова палітра з нейтральним градієнтом замість етнічних шпалер за замовчуванням, та тюнінг intent-pipeline.

Обмеження про які чесно

  • Якість машинного перекладу — не 95%. Ближче до 85%. Для UI-лейблів це ок, для емоційного copy (welcome screens, error messages) — гірше. Community PRs від носіїв мови — єдиний спосіб довести до production-quality.
  • Arabic (RTL) поки не включений — потребує окремого CSS-рефакторингу, відклав на v0.4.1.

Посилання

Буду радий фідбеку, особливо від тих, хто робив подібне. Якщо хтось має досвід з Helsinki-NLP чи NLLB-200 як альтернатив Argos — цікаво порівняти якість для слов’янських мов. І якщо побачите криві переклади у своїй мові — welcome PRs, src/i18n/locales/{lang}.community.json ідеальне місце.

Тестую v0.4.0-rc зараз на Pi 5 8GB + Jetson Orin Nano. Якщо хтось захоче приєднатися до testers — пишіть, дам доступ до RC-білда перед публічним релізом.

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось4
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Прикольно! Треба буде спробувати.
Часом лінь бігати додавати переклад до кожної мови, коли додається нове слово чи кнопка чи ще щось в шаблоні.
Раніше думав через LLM-ку генерити локально. Цей варіант теж локально можна оновлювати на рівні з тестами.

Підписатись на коментарі