Слогер Создать блог
Разработка

Локализация игры: как добавить второй язык и не потерять смысл

На примере «Мокши»: правила перевода, архитектура i18n, ошибки и уроки.

Игра «Мокша» была доступна только на хинди. В README прямо было написано: «For now its only for hindi users who deeply understand hindu philosophy». Но это ограничение, а не фича. Игроки, которые не читают на хинди, не могли прикоснуться к философии, заложенной в механике: прарабдха, вайрагья, самарпан. Добавление английского языка превратилось в целый квест — с архитектурными решениями, десятком багов и неожиданными открытиями.

Правила перевода: что нельзя переводить

Самый первый вопрос был не техническим, а смысловым: что вообще переводить? Если перевести всё подряд, философия рассыплется. Поэтому установили три правила.

Шлоки не переводятся

Стихи на санскрите — не просто текст, они несут силу сами по себе. В английской версии шлока остаётся в деванагари, а внизу добавляется только пояснение. Пример:

नाम जपत मंगल दिसि दसहूँ॥
By chanting the Nāma, auspiciousness arises in all ten directions.
— Rāmacaritamānasa

Сначала переводчик написал «Auspiciousness in all ten directions» — и это неправильно. Пропал субъект действия: смысл в том, что именно от повторения имени наступает благость, а не просто «благость есть». Пришлось переделывать.

Термины — транслитерация, а не перевод

«Папа» — это не «бад карма». Это не моральное зло, а загрязнённость, долг. Если перевести «папа» как «грех», смысл исказится. Поэтому все ключевые понятия оставили в транслитерации — с пояснением в скобках.

ХиндиНеправильный переводПравильно
पापBad KarmaPāpa
पुण्यGood KarmaPuṇya
प्रारब्धPast-life karmaPrārabdha
कृपाGraceKṛpā (grace)
Omॐ (не переводится никогда)

Слова «grace» недостаточно — оно несёт христианский подтекст.

Архитектура i18n: как устроить переключение языка без боли

Весь код разделили на три части.

Модуль i18n.js отвечает за языковые ключи. Он не зависит от DOM, поэтому его можно безопасно импортировать из движка и рендера. Методы простые: initLang() читает язык из localStorage, setLang() меняет и сохраняет, t(key, params) возвращает строку с подстановкой параметров.

Важная деталь — fallback chain: текущий язык → хинди → ключ. Если какого-то ключа нет, игра не падает, а показывает хинди. Английский режим остаётся читаемым.

Late binding — ключевое решение

В очереди оповещений игры хранятся не переведённые строки, а ключи. Перевод выполняется в момент отрисовки кадра. Это значит: если игрок переключит язык во время игры, старые уведомления тоже рендерятся на новом языке. Никакого смешения языков.

// В очереди алертов: { titleKey: 'alert.chetana.title', params: null, icon: '👁️' } // В render.js при каждом кадре: const { icon, title, subtitle } = resolveAlert(a);

Побочный эффект: эмодзи теперь хранятся в коде, а не в строках. Поэтому слой, который раньше вырезал эмодзи из текста регулярками, стал не нужен.

Сакральные тексты — отдельно

Для текстов шактры создали отдельный модуль shastra.js. Эти тексты не похожи на UI-строки: они длинные, содержат разметку и видны только в оверлее, а не каждый кадр. Шлоки хранятся в словаре и подключаются по идентификатору — один раз написаны, используются в обеих языковых версиях.

UI-строкиШактра
ФорматКороткие, разбросанные, с плейсхолдерамиДлинный связный текст с разметкой
РазметкаОтсутствует<strong>, <em>, шлоки
ИспользованиеКаждый кадрОдин раз при открытии оверлея

Баги, которые нашлись благодаря локализации

Перевод вскрыл кучу давних проблем. Некоторые видны сразу, другие давно молчали.

Видимые баги

  • laguage-toggle — опечатка в id: переключение языка просто не работало, а опциональная цепочка ?. молча глотала ошибку.
  • last.Time = ... — после нажатия клавиши «R» весь обработчик клавиш умирал.
  • _tutorialClickRead — туториал не закрывался по клику.
  • info.alert вызывался дважды — при сборе каждой раковины появлялось два алерта, один пустой.

Молчаливые ошибки

Эти проявились бы только при локализации. aria-describedby="language-choicce-status" — два «c» в значении атрибута. Screen reader никогда не узнавал о смене языка. Опечатка в CSS: padding:10px, 28px — запятая делает всю декларацию недействительной. letter-spacing:0.5pxl — несуществующая единица. Тихие поломки.

Отдельная история — шрифт. Orbitron не содержит деванагари. Когда игрок видел хинди, браузер подставлял системный шрифт, из-за чего строки выглядели криво. Плюс CARD_H был фиксированным (290px). Английский текст длиннее хинди на 25%, и туториал вылезал за пределы карточки. Пришлось вычислять высоту по содержимому.

Философский баг

В index.html было написано «сарáтхи (ум)», а в коде спрайта — cachedBuddhiSprite. Код знал правильный ответ, а документация — нет. В Катхопанишаде (1.3.3) сказано: буддхи — возница, ум — вожжи. Исправили и добавили этот стих на страницу. Игра стала честнее.

Уроки, которые вынесем с собой

  • Считайте ключи не строками, а фактом: grep -c считает строки, а не ключи. Пришлось написать скрипт, который считает вхождения по регулярке и находит несбалансированность.
  • Синтаксическая проверка не спасает от ошибок имён: node --check прошёл, а ReferenceError появился только в рантайме.
  • Самый лучший предохранитель — git diff | grep "^-". Именно так поймали два регресса, включая удалённую декларацию tutorialBtn.
  • Заглядывайте в git status. Два новых файла — i18n.js и shastra.js — оказались untracked. Если бы закоммитили, все, кто клонирует репозиторий, получили бы сломанную игру, а на локальной машине всё работало бы.

Итоговые цифры

134 ключа на два языка, полностью сбалансированы. 40+ мест, где вызывались алерты, переведены на ключи. Два новых модуля. И 25+ дополнительных багов, многие из которых были невидимы для пользователя.

Что дальше

В планах — углубление философской части на обоих языках, унификация транслитерации IAST, решить, как отображать деванагари-цифры. И третья языковая версия: архитектура уже готова.

Локализация — это не перевод слов. Это перевод смысла. И, как показала «Мокша», она способна вскрыть такие ошибки, о которых не знал даже автор.

По материалам: dev. Текст переработан редакцией Слогера.

← На главную

Рекламное место — Конец поста
Реклама · Слогер

Комментарии (0)

Войдите, чтобы комментировать.

Пока нет комментариев. Будьте первым.