Решили: язык первого запуска берётся у СИСТЕМЫ, а не из дефолта проекта, и тут же записывается в prefs (ISettingsService.LanguageCode / SetLanguage). Применяет его новый entry point LocaleStartup; LocalizationService остался тонким и про настройки игрока не знает.

Почему: до этой правки LocalizationSettings в рантайме не трогал НИКТО — игра всегда стартовала в локали, стоящей в настройках проекта, то есть половина аудитории видела чужой язык, пока не найдёт настройки (а переключателя языка там до сих пор нет). Развилок было три, и все решены в пользу «пустота — это факт»:

  • дефолт против пустоты. Поле Language в prefs не имеет дефолта: «поля нет» означает «игрок не выбирал». Подставь мы "en" — русский получил бы английский, а файл после первого Save заявил бы, что он его сам выбрал.
  • запоминать подобранное или каждый раз спрашивать систему. Запоминаем: иначе смена языка ОС переключала бы игру под человеком, который уже привык к её языку.
  • чем отвечать на язык, которого у нас нет. Английским, а не «ближайшим»: японцу русский не ближе английского.

Сказано: «Язык по системному» — выбор Макса из списка находок по старту игры (2026-07-31, сессия 4d35c7ed).

Грабли: порядок IStartable в VContainer — это порядок регистрации, поэтому применять локаль только в Start() было бы миной: перестановка строк в скоупе, и LocaleStartup прочитал бы настройки до их загрузки. Поэтому он ещё и слушает ISettingsService.Changed — заодно бесплатно подхватит будущий переключатель языка. Второе: Start() сделан публичным, а не явной реализацией IStartable, чтобы тест не тащил VContainer в тестовую сборку — правка references в asmdef всё равно не видна compile-check.ps1, пока редактор не пересоберёт .rsp.

Владелец правды: тест LocaleStartupTests (пять инвариантов: первый запуск, приоритет выбора игрока, фолбэк на английский, en-GBen, применение без перезапуска), Assets/_Project/Scripts/Game/Services/LocaleStartup.cs.