From 1da6fa7a2b6b613b0a31bd4a868b7488ea69dedd Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Tue, 23 Dec 2025 15:51:56 +0900 Subject: [PATCH] =?UTF-8?q?feat(l10n):=20=EA=B2=8C=EC=9E=84=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=20=ED=99=95=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - game_text_l10n.dart: 스탯/UI 텍스트 추가 (+61 라인) - 한국어/일본어 번역 업데이트 - game_data_l10n.dart: 텍스트 접근자 추가 - equipment_stats_panel: l10n 적용 및 레이아웃 개선 - active_buff_panel, potion_inventory_panel: 코드 정리 - new_character_screen: 코드 정리 - progress_service: 마이너 개선 --- lib/data/game_text_l10n.dart | 61 +++++++++++++++++++ lib/data/game_translations_ja.dart | 11 ++++ lib/data/game_translations_ko.dart | 11 ++++ lib/src/core/engine/progress_service.dart | 6 +- lib/src/core/l10n/game_data_l10n.dart | 11 ++++ lib/src/features/game/game_play_screen.dart | 25 ++++---- .../game/widgets/active_buff_panel.dart | 8 +-- .../game/widgets/equipment_stats_panel.dart | 22 ++++++- .../game/widgets/potion_inventory_panel.dart | 4 +- .../new_character/new_character_screen.dart | 4 +- 10 files changed, 137 insertions(+), 26 deletions(-) diff --git a/lib/data/game_text_l10n.dart b/lib/data/game_text_l10n.dart index f30d2a9..6372494 100644 --- a/lib/data/game_text_l10n.dart +++ b/lib/data/game_text_l10n.dart @@ -1129,6 +1129,17 @@ String translateFaction(String englishFaction) { return englishFaction; } +/// 스킬/주문 이름 번역 (전투 로그용) +String translateSpell(String englishName) { + if (isKoreanLocale) { + return spellTranslationsKo[englishName] ?? englishName; + } + if (isJapaneseLocale) { + return spellTranslationsJa[englishName] ?? englishName; + } + return englishName; +} + // ============================================================================ // 프론트 화면 텍스트 // ============================================================================ @@ -1418,3 +1429,53 @@ String get uiDot { if (isJapaneseLocale) return 'DOT'; return 'DOT'; } + +// ============================================================================ +// 아이템 희귀도 +// ============================================================================ + +String get rarityCommon { + if (isKoreanLocale) return '일반'; + if (isJapaneseLocale) return 'コモン'; + return 'COMMON'; +} + +String get rarityUncommon { + if (isKoreanLocale) return '고급'; + if (isJapaneseLocale) return 'アンコモン'; + return 'UNCOMMON'; +} + +String get rarityRare { + if (isKoreanLocale) return '희귀'; + if (isJapaneseLocale) return 'レア'; + return 'RARE'; +} + +String get rarityEpic { + if (isKoreanLocale) return '영웅'; + if (isJapaneseLocale) return 'エピック'; + return 'EPIC'; +} + +String get rarityLegendary { + if (isKoreanLocale) return '전설'; + if (isJapaneseLocale) return 'レジェンダリー'; + return 'LEGENDARY'; +} + +// ============================================================================ +// 캐릭터 생성 화면 텍스트 +// ============================================================================ + +String uiRollHistory(int count) { + if (isKoreanLocale) return '리롤 기록: $count회'; + if (isJapaneseLocale) return 'リロール履歴: $count回'; + return '$count roll(s) in history'; +} + +String get uiEnterName { + if (isKoreanLocale) return '이름을 입력해주세요.'; + if (isJapaneseLocale) return '名前を入力してください。'; + return 'Please enter a name.'; +} diff --git a/lib/data/game_translations_ja.dart b/lib/data/game_translations_ja.dart index cbffd5e..3c32e11 100644 --- a/lib/data/game_translations_ja.dart +++ b/lib/data/game_translations_ja.dart @@ -119,6 +119,17 @@ const Map spellTranslationsJa = { 'Deploy': 'デプロイ', 'Scale Up': 'スケールアップ', 'Failover': 'フェイルオーバー', + // ポーション (Potions) + 'Minor Health Patch': 'マイナーHPパッチ', + 'Health Patch': 'HPパッチ', + 'Major Health Patch': 'メジャーHPパッチ', + 'Super Health Patch': 'スーパーHPパッチ', + 'Ultra Health Patch': 'ウルトラHPパッチ', + 'Minor Mana Cache': 'マイナーMPキャッシュ', + 'Mana Cache': 'MPキャッシュ', + 'Major Mana Cache': 'メジャーMPキャッシュ', + 'Super Mana Cache': 'スーパーMPキャッシュ', + 'Ultra Mana Cache': 'ウルトラMPキャッシュ', }; /// モンスター名日本語翻訳 (主要モンスター) diff --git a/lib/data/game_translations_ko.dart b/lib/data/game_translations_ko.dart index 1ae0d90..f3fd0dc 100644 --- a/lib/data/game_translations_ko.dart +++ b/lib/data/game_translations_ko.dart @@ -119,6 +119,17 @@ const Map spellTranslationsKo = { 'Deploy': '배포', 'Scale Up': '스케일 업', 'Failover': '페일오버', + // 포션 (Potions) + 'Minor Health Patch': '소형 HP 패치', + 'Health Patch': 'HP 패치', + 'Major Health Patch': '대형 HP 패치', + 'Super Health Patch': '초대형 HP 패치', + 'Ultra Health Patch': '최고급 HP 패치', + 'Minor Mana Cache': '소형 MP 캐시', + 'Mana Cache': 'MP 캐시', + 'Major Mana Cache': '대형 MP 캐시', + 'Super Mana Cache': '초대형 MP 캐시', + 'Ultra Mana Cache': '최고급 MP 캐시', }; /// 몬스터 이름 한국어 번역 (주요 몬스터만) diff --git a/lib/src/core/engine/progress_service.dart b/lib/src/core/engine/progress_service.dart index 78f9df4..579f692 100644 --- a/lib/src/core/engine/progress_service.dart +++ b/lib/src/core/engine/progress_service.dart @@ -1000,11 +1000,13 @@ class ProgressService { final damage = dot.damagePerTick * ticksTriggered; dotDamageThisTick += damage; - // DOT 데미지 이벤트 생성 + // DOT 데미지 이벤트 생성 (skillId → name 변환) + final dotSkillName = + SkillData.getSkillById(dot.skillId)?.name ?? dot.skillId; newEvents.add( CombatEvent.dotTick( timestamp: timestamp, - skillName: dot.skillId, + skillName: dotSkillName, damage: damage, targetName: monsterStats.name, ), diff --git a/lib/src/core/l10n/game_data_l10n.dart b/lib/src/core/l10n/game_data_l10n.dart index 6bf6846..7d8ba6f 100644 --- a/lib/src/core/l10n/game_data_l10n.dart +++ b/lib/src/core/l10n/game_data_l10n.dart @@ -459,11 +459,22 @@ class GameDataL10n { } /// 각 단어의 첫 글자를 대문자로 (Title Case) + /// 하이픈으로 연결된 단어도 처리 (예: "off-by-one" → "Off-by-One") static String _toTitleCase(String s) { return s .split(' ') .map((word) { if (word.isEmpty) return word; + // 하이픈 포함 단어 처리 + if (word.contains('-')) { + return word + .split('-') + .map((part) { + if (part.isEmpty) return part; + return part[0].toUpperCase() + part.substring(1); + }) + .join('-'); + } return word[0].toUpperCase() + word.substring(1); }) .join(' '); diff --git a/lib/src/features/game/game_play_screen.dart b/lib/src/features/game/game_play_screen.dart index ac04125..e641e3e 100644 --- a/lib/src/features/game/game_play_screen.dart +++ b/lib/src/features/game/game_play_screen.dart @@ -174,6 +174,10 @@ class _GamePlayScreenState extends State /// 전투 이벤트를 메시지와 타입으로 변환 (String, CombatLogType) _formatCombatEvent(CombatEvent event) { final target = event.targetName ?? ''; + // 스킬/포션 이름 번역 (전역 로케일 사용) + final skillName = event.skillName != null + ? game_l10n.translateSpell(event.skillName!) + : ''; return switch (event.type) { CombatEventType.playerAttack => event.isCritical @@ -208,41 +212,34 @@ class _GamePlayScreenState extends State CombatEventType.playerSkill => event.isCritical ? ( - game_l10n.combatSkillCritical( - event.skillName ?? '', - event.damage, - ), + game_l10n.combatSkillCritical(skillName, event.damage), CombatLogType.critical, ) : ( - game_l10n.combatSkillDamage(event.skillName ?? '', event.damage), + game_l10n.combatSkillDamage(skillName, event.damage), CombatLogType.spell, ), CombatEventType.playerHeal => ( game_l10n.combatSkillHeal( - event.skillName ?? game_l10n.uiHeal, + skillName.isNotEmpty ? skillName : game_l10n.uiHeal, event.healAmount, ), CombatLogType.heal, ), CombatEventType.playerBuff => ( - game_l10n.combatBuffActivated(event.skillName ?? ''), + game_l10n.combatBuffActivated(skillName), CombatLogType.buff, ), CombatEventType.dotTick => ( - game_l10n.combatDotTick(event.skillName ?? '', event.damage), + game_l10n.combatDotTick(skillName, event.damage), CombatLogType.dotTick, ), CombatEventType.playerPotion => ( - game_l10n.combatPotionUsed( - event.skillName ?? '', - event.healAmount, - target, - ), + game_l10n.combatPotionUsed(skillName, event.healAmount, target), CombatLogType.potion, ), CombatEventType.potionDrop => ( - game_l10n.combatPotionDrop(event.skillName ?? ''), + game_l10n.combatPotionDrop(skillName), CombatLogType.potionDrop, ), }; diff --git a/lib/src/features/game/widgets/active_buff_panel.dart b/lib/src/features/game/widgets/active_buff_panel.dart index 77582f3..91e96b0 100644 --- a/lib/src/features/game/widgets/active_buff_panel.dart +++ b/lib/src/features/game/widgets/active_buff_panel.dart @@ -127,7 +127,7 @@ class _BuffRow extends StatelessWidget { if (effect.atkModifier != 0) { modifiers.add( _ModifierChip( - label: 'ATK', + label: l10n.statAtk, value: effect.atkModifier, isPositive: effect.atkModifier > 0, ), @@ -137,7 +137,7 @@ class _BuffRow extends StatelessWidget { if (effect.defModifier != 0) { modifiers.add( _ModifierChip( - label: 'DEF', + label: l10n.statDef, value: effect.defModifier, isPositive: effect.defModifier > 0, ), @@ -147,7 +147,7 @@ class _BuffRow extends StatelessWidget { if (effect.criRateModifier != 0) { modifiers.add( _ModifierChip( - label: 'CRI', + label: l10n.statCri, value: effect.criRateModifier, isPositive: effect.criRateModifier > 0, ), @@ -157,7 +157,7 @@ class _BuffRow extends StatelessWidget { if (effect.evasionModifier != 0) { modifiers.add( _ModifierChip( - label: 'EVA', + label: l10n.statEva, value: effect.evasionModifier, isPositive: effect.evasionModifier > 0, ), diff --git a/lib/src/features/game/widgets/equipment_stats_panel.dart b/lib/src/features/game/widgets/equipment_stats_panel.dart index 15e61cb..3a8d178 100644 --- a/lib/src/features/game/widgets/equipment_stats_panel.dart +++ b/lib/src/features/game/widgets/equipment_stats_panel.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:askiineverdie/data/game_text_l10n.dart' as l10n; import 'package:askiineverdie/src/core/engine/item_service.dart'; +import 'package:askiineverdie/src/core/l10n/game_data_l10n.dart'; import 'package:askiineverdie/src/core/model/equipment_item.dart'; import 'package:askiineverdie/src/core/model/equipment_slot.dart'; import 'package:askiineverdie/src/core/model/game_state.dart'; @@ -80,6 +81,12 @@ class _EquipmentSlotTile extends StatelessWidget { final score = ItemService.calculateEquipmentScore(item); final rarityColor = _getRarityColor(item.rarity); + // 슬롯 인덱스로 아이템 이름 번역 (0: weapon, 1: shield, 2+: armor) + final translatedName = GameDataL10n.translateEquipString( + context, + item.name, + item.slot.index, + ); return ExpansionTile( initiallyExpanded: initiallyExpanded, @@ -92,7 +99,7 @@ class _EquipmentSlotTile extends StatelessWidget { const SizedBox(width: 4), Expanded( child: Text( - item.name, + translatedName, style: TextStyle( fontSize: 11, color: rarityColor, @@ -426,7 +433,7 @@ class _ItemMetaRow extends StatelessWidget { @override Widget build(BuildContext context) { - final rarityName = item.rarity.name.toUpperCase(); + final rarityName = _getTranslatedRarity(item.rarity); return Row( children: [ @@ -479,3 +486,14 @@ String _getSlotName(EquipmentSlot slot) { EquipmentSlot.sollerets => l10n.slotSollerets, }; } + +/// 희귀도 번역 반환 +String _getTranslatedRarity(ItemRarity rarity) { + return switch (rarity) { + ItemRarity.common => l10n.rarityCommon, + ItemRarity.uncommon => l10n.rarityUncommon, + ItemRarity.rare => l10n.rarityRare, + ItemRarity.epic => l10n.rarityEpic, + ItemRarity.legendary => l10n.rarityLegendary, + }; +} diff --git a/lib/src/features/game/widgets/potion_inventory_panel.dart b/lib/src/features/game/widgets/potion_inventory_panel.dart index 036ff4c..e5e0587 100644 --- a/lib/src/features/game/widgets/potion_inventory_panel.dart +++ b/lib/src/features/game/widgets/potion_inventory_panel.dart @@ -110,10 +110,10 @@ class _PotionRow extends StatelessWidget { _PotionIcon(type: potion.type, tier: potion.tier), const SizedBox(width: 4), - // 물약 이름 + // 물약 이름 (번역 적용) Expanded( child: Text( - potion.name, + l10n.translateSpell(potion.name), style: TextStyle( fontSize: 11, color: color, diff --git a/lib/src/features/new_character/new_character_screen.dart b/lib/src/features/new_character/new_character_screen.dart index aac8f00..04da1c4 100644 --- a/lib/src/features/new_character/new_character_screen.dart +++ b/lib/src/features/new_character/new_character_screen.dart @@ -145,7 +145,7 @@ class _NewCharacterScreenState extends State { if (name.isEmpty) { ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('이름을 입력해주세요.'))); + ).showSnackBar(SnackBar(content: Text(game_l10n.uiEnterName))); return; } @@ -359,7 +359,7 @@ class _NewCharacterScreenState extends State { Padding( padding: const EdgeInsets.only(top: 8), child: Text( - '${_rollHistory.length} roll(s) in history', + game_l10n.uiRollHistory(_rollHistory.length), style: Theme.of(context).textTheme.bodySmall, textAlign: TextAlign.center, ),