Почему создана
При аудите области shared-game (ветка develop) найдено, что BattleEngine строит готовые англоязычные пользовательские строки и кладёт их в поле BattleEvent.message shared-контракта. Клиент (pokemon-battle-arena.facade.ts:153) проталкивает их в лог под ключом key: 'raw', а шаблон (pokemon-battle-arena.component.html:97) рендерит log.params?.['message'] напрямую — в обход Transloco.
Проблематика
Нарушены два правила проекта сразу:
- i18n. CLAUDE.md (раздел «i18n — Transloco»): «Любой пользовательский текст ... через Transloco. Хардкод строк в шаблонах запрещён.» Проект поддерживает
ru/en, но зашитые строки не переключаются языком — лог боя всегда на английском.
- theme/locale-agnostic контракт. CLAUDE.md («Сервер держим pokemon/theme-agnostic»): презентация не должна протекать в
shared-game — для frenzy это уже сделано (движок эмитит структурные события, текст резолвит клиент). pokemon-battle идёт против этого: BattleEvent.payload уже несёт ВСЕ структурные данные (attackerId, moveName, targetId, damage, hpBefore/After, winner), а поле message дублирует их готовой презентацией и делает локализацию структурно невозможной — фасад вынужден прокидывать key:'raw' вместо реального ключа перевода.
Вред: лог боя неинтернационализируем; презентация в shared-слое (нарушение границ); шов key:'raw' существует только из-за этого поля.
Где
Место рендера-обхода (для контекста, фикс — в shared-game): src/app/features/pokemon-battle/data/facades/pokemon-battle-arena.facade.ts:153 (key:'raw') + .../pokemon-battle-arena/pokemon-battle-arena.component.html:97.
Варианты решения
- Убрать
message из контракта, резолвить текст на клиенте по type + payload — фасад строит ключ Transloco из event.type (battle.log.useMove, battle.log.damage, battle.log.faint, battle.log.battleOver) и подставляет параметры из payload. Плюсы: полноценная локализация, контракт снова theme/locale-agnostic, исчезает шов key:'raw'; ровно тот подход, что уже принят для frenzy. Минусы: нужно завести i18n-ключи (en/ru) под лог боя и переписать сборку лога в фасаде.
- Слать
i18nKey рядом с message — оставить message как fallback, но добавить ключ перевода. Плюсы: меньше ломает текущий код. Минусы: дублирование презентации в контракте остаётся; полумера, key:'raw' живёт дальше.
Рекомендация
Вариант 1. Это выравнивает pokemon-battle с уже реализованным theme-agnostic подходом frenzy (движок — структурные события, клиент — текст через Transloco) и закрывает нарушение Transloco-правила разом. Объём — M (i18n-ключи en/ru + сборка ключа в фасаде из type/payload + удаление message из контракта и движка).
Связано с #120 (хардкод текста мимо Transloco), но это отдельный класс: там — строки в .html-шаблонах (фикс = обернуть в t(...)), здесь — презентация, зашитая в shared-контракт движка (фикс = реструктурировать контракт события). Слой и способ исправления разные, поэтому отдельный issue, а не пункт в #120.
[CLAUDE.md — «i18n — Transloco», «Сервер держим pokemon/theme-agnostic»; shared-game/frenzy + src/app/features/frenzy как образец: движок без презентации, текст на клиенте через Transloco]
Почему создана
При аудите области
shared-game(веткаdevelop) найдено, чтоBattleEngineстроит готовые англоязычные пользовательские строки и кладёт их в полеBattleEvent.messageshared-контракта. Клиент (pokemon-battle-arena.facade.ts:153) проталкивает их в лог под ключомkey: 'raw', а шаблон (pokemon-battle-arena.component.html:97) рендеритlog.params?.['message']напрямую — в обход Transloco.Проблематика
Нарушены два правила проекта сразу:
ru/en, но зашитые строки не переключаются языком — лог боя всегда на английском.shared-game— дляfrenzyэто уже сделано (движок эмитит структурные события, текст резолвит клиент).pokemon-battleидёт против этого:BattleEvent.payloadуже несёт ВСЕ структурные данные (attackerId,moveName,targetId,damage,hpBefore/After,winner), а полеmessageдублирует их готовой презентацией и делает локализацию структурно невозможной — фасад вынужден прокидыватьkey:'raw'вместо реального ключа перевода.Вред: лог боя неинтернационализируем; презентация в shared-слое (нарушение границ); шов
key:'raw'существует только из-за этого поля.Где
shared-game/pokemon-battle/types.ts:69— полеmessage: stringвBattleEvent(презентация в контракте)shared-game/pokemon-battle/battle-engine.ts:114—`${attacker.name} used ${cmd.moveName}!`shared-game/pokemon-battle/battle-engine.ts:139—`${defender.name} took ${damage} damage!`shared-game/pokemon-battle/battle-engine.ts:152—`${defender.name} fainted!`shared-game/pokemon-battle/battle-engine.ts:171—`Battle over! Winner is ${winner}!`Место рендера-обхода (для контекста, фикс — в shared-game):
src/app/features/pokemon-battle/data/facades/pokemon-battle-arena.facade.ts:153(key:'raw') +.../pokemon-battle-arena/pokemon-battle-arena.component.html:97.Варианты решения
messageиз контракта, резолвить текст на клиенте поtype+payload— фасад строит ключ Transloco изevent.type(battle.log.useMove,battle.log.damage,battle.log.faint,battle.log.battleOver) и подставляет параметры изpayload. Плюсы: полноценная локализация, контракт снова theme/locale-agnostic, исчезает шовkey:'raw'; ровно тот подход, что уже принят дляfrenzy. Минусы: нужно завести i18n-ключи (en/ru) под лог боя и переписать сборку лога в фасаде.i18nKeyрядом сmessage— оставитьmessageкак fallback, но добавить ключ перевода. Плюсы: меньше ломает текущий код. Минусы: дублирование презентации в контракте остаётся; полумера,key:'raw'живёт дальше.Рекомендация
Вариант 1. Это выравнивает
pokemon-battleс уже реализованным theme-agnostic подходомfrenzy(движок — структурные события, клиент — текст через Transloco) и закрывает нарушение Transloco-правила разом. Объём — M (i18n-ключи en/ru + сборка ключа в фасаде изtype/payload+ удалениеmessageиз контракта и движка).Связано с #120 (хардкод текста мимо Transloco), но это отдельный класс: там — строки в
.html-шаблонах (фикс = обернуть вt(...)), здесь — презентация, зашитая в shared-контракт движка (фикс = реструктурировать контракт события). Слой и способ исправления разные, поэтому отдельный issue, а не пункт в #120.[CLAUDE.md — «i18n — Transloco», «Сервер держим pokemon/theme-agnostic»;
shared-game/frenzy+src/app/features/frenzyкак образец: движок без презентации, текст на клиенте через Transloco]