GameDev Guide
Глава 1413 мин чтенияПроверено: 5 сентября 2026 г.

Локализация: автоопределение языка через SDK

Реализуем требование 2.14 — автоопределение языка через ysdk.environment.i18n.lang, соберём словарную систему без библиотек, разберём запасной язык, вёрстку под длинные строки и проверку на debug-панели.

Содержание главы

Локализация в Яндекс Играх — редкий случай, когда требование платформы не зависит от того, сколько языков вы поддерживаете. Пункт 2.14 обязателен даже для игры, в которой один язык и вообще нет текстов: автоопределение языка через SDK должно быть подключено, и модератор проверяет его отдельным индикатором на debug-панели. «Автоопределение языка через SDK не реализовано» стоит в официальной таблице частых причин отказа — рядом с отсутствующим Game Ready.

Хорошая новость: код здесь на десять строк, никакие библиотеки не нужны, а вся сложность — в вёрстке и в дисциплине.

Что именно требует пункт 2.14

Формулировка требования короткая: в игру встроено автоопределение языка через SDK. Пояснения к нему добавляют две вещи, которые и ломают большинство реализаций:

  • определение должно происходить во время запуска, а не в процессе игры;
  • оно должно быть реализовано у всех игр, даже если заявлен только один язык или текстов в игре нет.

Первое означает, что нельзя читать язык при открытии меню настроек или после нажатия «Играть». Модерация смотрит на индикатор на debug-панели: он должен позеленеть на загрузке. Допустима небольшая задержка — пока подгружается файл перевода, на экране может успеть мелькнуть загрузочный текст на другом языке.

Ручное переключение языка внутри игры разрешено: требование запрещает не переключатель, а подмену автоопределения ручным выбором. Если игрок один раз выбрал язык и вы сохранили выбор, при следующем запуске автоопределение может не сработать — документация прямо оговаривает это как допустимый случай.

Частая ошибка

Определять язык через navigator.language — типовая ошибка перенесённой с других платформ игры. Язык браузера и язык портала Яндекс Игр — разные вещи: игрок с английской системой может открыть каталог на yandex.ru и ждать русского интерфейса. Модерация проверяет именно SDK, а ysdk.environment.browser.lang в актуальной документации отсутствует.

Откуда берётся язык

Одно поле объекта окружения:

const ysdk = await YaGames.init();
const lang = ysdk.environment.i18n.lang; // 'ru', 'en', 'tr', ...

Код языка приходит в формате ISO 639-1 — две буквы. Это язык интерфейса Яндекс Игр, на котором пользователь открыл каталог, а не язык его операционной системы.

Платформа поддерживает 36 языков. Полный список кодов:

ar — арабскийaz — азербайджанскийbe — белорусскийbg — болгарский
ca — каталанскийcs — чешскийde — немецкийen — английский
es — испанскийfa — персидскийfr — французскийhe — иврит
hi — хиндиhu — венгерскийhy — армянскийid — индонезийский
it — итальянскийja — японскийka — грузинскийkk — казахский
nl — голландскийpl — польскийpt — португальскийro — румынский
ru — русскийsk — словацкийsr — сербскийth — тайский
tk — туркменскийtr — турецкийuk — украинскийuz — узбекский
vi — вьетнамскийzh — китайский

Ваша игра почти наверняка не будет переведена на все 36. Поэтому нужен запасной язык.

Резервный набор языков

Документация задаёт его явно:

Язык пользователяЗапасной язык
be, kk, uk, uzru
Все остальныеen

Логика понятная: для языков постсоветского пространства русский ближе, чем английский, для всех прочих — наоборот. Эта таблица — единственная строчка «бизнес-логики» в вашем модуле локализации:

// i18n.js
const RU_FALLBACK = ['be', 'kk', 'uk', 'uz'];

function resolveLang(sdkLang, supported) {
  if (supported.includes(sdkLang)) return sdkLang;
  if (RU_FALLBACK.includes(sdkLang) && supported.includes('ru')) return 'ru';
  if (supported.includes('en')) return 'en';
  return supported[0]; // на случай игры только на русском
}

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

Словари без библиотек

Для игры на несколько экранов полноценный i18n-фреймворк — лишний вес. Достаточно объекта со строками и функции t().

// i18n.js (продолжение)
const DICTS = {
  ru: {
    'menu.play': 'Играть',
    'menu.records': 'Рекорды',
    'hud.score': 'Очки: {score}',
    'result.win': 'Уровень пройден!',
    'ad.reward': 'Получить {coins} монет за просмотр рекламы',
  },
  en: {
    'menu.play': 'Play',
    'menu.records': 'Leaderboard',
    'hud.score': 'Score: {score}',
    'result.win': 'Level complete!',
    'ad.reward': 'Watch an ad for {coins} coins',
  },
};

const SUPPORTED = Object.keys(DICTS);
let current = 'ru';

export function initI18n(ysdk) {
  const sdkLang = ysdk?.environment?.i18n?.lang || 'ru';
  current = resolveLang(sdkLang, SUPPORTED);
  document.documentElement.lang = current;
  return current;
}

export function t(key, params) {
  const dict = DICTS[current] || {};
  let str = dict[key];

  if (str === undefined) {
    // Ключ забыли перевести — берём английский, потом сам ключ.
    str = DICTS.en?.[key] ?? key;
    console.warn('[i18n] нет перевода:', current, key);
  }

  if (params) {
    for (const name of Object.keys(params)) {
      str = str.split('{' + name + '}').join(String(params[name]));
    }
  }
  return str;
}

export function getLang() {
  return current;
}

Три решения здесь неслучайны. Ключи — плоские строки вида menu.play, а не вложенные объекты: их проще грепать и проще отдавать Claude Code («переведи все ключи из ru в tr, верни готовый объект»). Подстановка через {score} избавляет от конкатенации, которая в другом языке разваливается из-за порядка слов. И падение на английский вместо пустой строки: непереведённый ключ — это косметическая проблема, пустая кнопка — отказ по пункту 1.15.

Разметку удобно размечать атрибутом и обходить один раз после инициализации:

<button id="play" data-i18n="menu.play">Играть</button>
<h2 data-i18n="result.win">Уровень пройден!</h2>
export function applyTranslations(root = document) {
  for (const el of root.querySelectorAll('[data-i18n]')) {
    el.textContent = t(el.dataset.i18n);
  }
}

Порядок в жизненном цикле — тот же, что у остальных стартовых операций: YaGames.init()initI18n(ysdk) → загрузка ресурсов и применение переводов → ysdk.features.LoadingAPI?.ready(). Язык должен быть определён до Game Ready, иначе индикатор на debug-панели позеленеет позже, чем нужно.

Вёрстка под длинные строки

Русский текст в среднем длиннее английского примерно на четверть, немецкий и турецкий — ещё длиннее, а «Играть» и «Play» отличаются вдвое. Всё, что сломается, сломается именно на этом.

Практические правила:

  • Кнопки и панели — по содержимому, с min-width, а не с фиксированной шириной. padding: 0 16px вместо width: 120px.
  • Для длинных надписей — перенос по словам и уменьшение шрифта, а не обрезка многоточием. Обрезанный текст в интерфейсе модерация трактует как нечитаемую надпись.
  • В canvas измеряйте текст сами: ctx.measureText(str).width и уменьшайте кегль в цикле, пока строка не влезет в отведённую ширину.
  • Не собирайте фразы из кусков («Уровень » + n + « пройден») — в другом языке порядок слов другой. Один ключ на одну фразу.
function fitText(ctx, str, maxWidth, maxSize) {
  let size = maxSize;
  do {
    ctx.font = size + 'px sans-serif';
    if (ctx.measureText(str).width <= maxWidth) break;
    size -= 1;
  } while (size > 10);
  return size;
}

Отдельно про шрифты: если вы подключаете кастомный шрифт, проверьте, что в нём есть кириллица и нужные диакритики. Битая кодировка и квадраты вместо букв — прямой пример нарушения из пояснений к пункту 1.15.

Осторожно

Текст, зашитый в картинку («ИГРАТЬ» на спрайте кнопки), локализовать невозможно без перерисовки. Если вы планируете больше одного языка, выносите весь интерфейсный текст в DOM или рисуйте его на canvas программно — это решение принимается один раз и в начале, а переделывается неделю.

Что ещё локализуется, кроме интерфейса

Часть текстов живёт не в вашем коде, а в Консоли разработчика, и про них забывают.

ЧтоГде задаётсяНюанс
Названия и описания товаровКонсоль → покупки, поля на русском и английском (title_ru, title_en, description_ru, description_en)Описание — до 200 символов; в диалоге оплаты показывается именно оно
Отображаемое название лидербордаКонсоль → Лидерборды, локализованное название по языкамВ коде вы используете техническое название, игрок видит локализованное
Название игры, «Об игре», «Как играть»Вкладка «Черновик», отдельный набор полей на каждый языкЗаполняется для каждого выбранного языка отдельно

Витрину карточки можно не переводить руками: у платформы есть функция AI-описаний, которая делает переводы карточки на другие языки на основе английского. Но она переводит только карточку — все тексты внутри игры остаются на вас.

Поле «Игра переведена на» и как это проверяет модерация

В черновике есть обязательное поле Игра переведена на. Указывайте в нём ровно те языки, которые действительно поддержаны: модерация проверяет каждый заявленный язык и отклоняет игру, если хотя бы один не переключился полностью.

Методика проверки простая, и её стоит прогнать самому:

  1. Очистить кеш браузера.
  2. Открыть игру с debug-панелью — через Консоль (Открыть с debug-панелью) или добавив debug-mode=16 в адрес.
  3. Посмотреть на индикатор : I18N is used — автоопределение подключено, I18N is not used — нет.
  4. Нажать кнопку SDK mocks ⚒️, выбрать язык из выпадающего списка — игра откроется на нём в новой вкладке.
  5. Повторить для каждого языка из поля Игра переведена на.

Совет

Заявлять языки «на вырост» невыгодно вдвойне. Непереведённый язык — это отказ, а лишний язык в списке не даёт трафика сам по себе: видимость в других странах даёт заполненная карточка на этом языке. Начните с честного минимума и добавляйте языки обновлениями.

Какие языки подключать первыми

Документация называет минимальный рекомендуемый набор для максимального трафика: русский, турецкий, английский. Турецкий здесь не случайность — у платформы есть отдельный домен yandex.com.tr, и турецкая аудитория для Яндекс Игр большая.

Разумная последовательность для небольшой игры:

  1. Русский — основной рынок.
  2. Английский — покрывает всё, что не попадает в запасной набор ru.
  3. Турецкий — третий по объёму, требует минимум работы (латиница, тот же шрифт).
  4. Дальше — по метрикам: смотрите, из каких стран приходят игроки, и переводите под них.

Гарантий прироста аудитории от каждого нового языка никто не даёт — но пункт 2.14 обязателен независимо от того, сколько их у вас.

Что дальше

Локализация закрывает вопрос «понятно ли», а следующий большой блок — «комфортно ли». Игра может быть переведена идеально и всё равно получить отказ, если она подтормаживает на среднем телефоне: заметные фризы и просадки производительности стоят отдельной строкой в списке частых причин отказа. В следующей главе разберём бюджет кадра, профилирование в DevTools и типичные причины просадок в 2D-играх — с измерениями вместо догадок.

Источники

Официальная документация платформы — единственный источник истины по её требованиям.

Нужно подробнее?

Полное обучение разбирает те же темы глубже: уроки с примерами, готовые структуры проектов, развёрнутые чек-листы и файл инструкций для Claude Code — он входит в стоимость. Бесплатные главы при этом остаются открытыми и продолжают дополняться.

Что входит в полное обучение