diff --git a/lib/game/config/app_strings.dart b/lib/game/config/app_strings.dart new file mode 100644 index 0000000..90cedd9 --- /dev/null +++ b/lib/game/config/app_strings.dart @@ -0,0 +1,64 @@ +class AppStrings { + // Main Menu + static const String gameTitle = "Colosseum's Choice"; + static const String startGame = "Start Game"; + static const String continueGame = "Continue"; + static const String exitGame = "Exit Game"; + static const String credits = "Credits"; + + // Common Actions + static const String confirm = "Confirm"; + static const String cancel = "Cancel"; + static const String back = "Back"; + static const String close = "Close"; + static const String equip = "Equip"; + static const String unequip = "Unequip"; + static const String discard = "Discard"; + static const String sell = "Sell"; + static const String buy = "Buy"; + + // Stats + static const String hp = "HP"; + static const String atk = "ATK"; + static const String def = "DEF"; + static const String armor = "Armor"; + static const String luck = "Luck"; + static const String gold = "Gold"; + + // Battle + static const String attack = "Attack"; + static const String defend = "Defend"; + static const String turn = "Turn"; + static const String playerTurn = "Player's Turn"; + static const String enemyTurn = "Enemy's Turn"; + static const String victory = "Victory!"; + static const String defeat = "Defeat"; + static const String reward = "Reward"; + static const String chooseReward = "Choose a Reward"; + static const String skip = "Skip"; + static const String nextStage = "Next Stage"; + static const String returnToMenu = "Return to Menu"; + static const String restart = "Restart"; + + // Inventory + static const String inventory = "Inventory"; + static const String equipment = "Equipment"; + static const String bag = "Bag"; + static const String emptySlot = "Empty"; + static const String noItems = "No items in inventory"; + + // Shop + static const String shopTitle = "Merchant"; + static const String shopWelcome = "Welcome, traveler!"; + static const String refreshShop = "Restock"; + static const String notEnoughGold = "Not enough gold!"; + static const String inventoryFull = "Inventory is full!"; + + // Risk Levels + static const String riskSafe = "Safe"; + static const String riskNormal = "Normal"; + static const String riskRisky = "Risky"; + + // Settings + static const String settings = "Settings"; +} diff --git a/lib/screens/battle_screen.dart b/lib/screens/battle_screen.dart index 7ed57ae..c0c1cc3 100644 --- a/lib/screens/battle_screen.dart +++ b/lib/screens/battle_screen.dart @@ -21,6 +21,7 @@ import '../widgets/battle/explosion_widget.dart'; import 'main_menu_screen.dart'; import '../game/config/battle_config.dart'; import '../game/config/theme_config.dart'; +import '../game/config/app_strings.dart'; class BattleScreen extends StatefulWidget { const BattleScreen({super.key}); @@ -43,7 +44,7 @@ class _BattleScreenState extends State { GlobalKey(); final GlobalKey _explosionKey = GlobalKey(); - bool _showLogs = true; + bool _showLogs = false; bool _isPlayerAttacking = false; // Player Attack Animation State @override @@ -421,7 +422,7 @@ class _BattleScreenState extends State { child: FittedBox( fit: BoxFit.scaleDown, child: Text( - "Turn ${battleProvider.turnCount}", + "${AppStrings.turn} ${battleProvider.turnCount}", style: const TextStyle( color: ThemeConfig.textColorWhite, fontSize: ThemeConfig.fontSizeHeader, @@ -540,12 +541,16 @@ class _BattleScreenState extends State { child: SimpleDialog( title: Row( children: [ - const Text("Victory! Choose a Reward"), + const Text("${AppStrings.victory} ${AppStrings.chooseReward}"), const Spacer(), Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.monetization_on, color: ThemeConfig.statGoldColor, size: 18), + Icon( + Icons.monetization_on, + color: ThemeConfig.statGoldColor, + size: 18, + ), const SizedBox(width: 4), Text( "${battleProvider.lastGoldReward} G", @@ -568,7 +573,7 @@ class _BattleScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( - "Inventory is full! Cannot take item.", + "${AppStrings.inventoryFull} Cannot take item.", ), backgroundColor: Colors.red, ), @@ -586,10 +591,11 @@ class _BattleScreenState extends State { decoration: BoxDecoration( color: Colors.blueGrey[700], borderRadius: BorderRadius.circular( - 4), + 4, + ), border: Border.all( - color: item.rarity != - ItemRarity.magic + color: + item.rarity != ItemRarity.magic ? ItemUtils.getRarityColor( item.rarity, ) @@ -613,7 +619,8 @@ class _BattleScreenState extends State { color: isSkip ? ThemeConfig.textColorGrey : ItemUtils.getRarityColor( - item.rarity), + item.rarity, + ), ), ), ], @@ -651,7 +658,7 @@ class _BattleScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ const Text( - "DEFEAT", + AppStrings.defeat, style: TextStyle( color: ThemeConfig.statHpColor, fontSize: ThemeConfig.fontSizeHuge, @@ -677,7 +684,7 @@ class _BattleScreenState extends State { ); }, child: const Text( - "Return to Main Menu", + AppStrings.returnToMenu, style: TextStyle( color: ThemeConfig.textColorWhite, fontSize: ThemeConfig.fontSizeHeader, @@ -698,10 +705,10 @@ class _BattleScreenState extends State { Widget _buildItemStatText(Item item) { List stats = []; - if (item.atkBonus > 0) stats.add("+${item.atkBonus} ATK"); - if (item.hpBonus > 0) stats.add("+${item.hpBonus} HP"); - if (item.armorBonus > 0) stats.add("+${item.armorBonus} DEF"); - if (item.luck > 0) stats.add("+${item.luck} Luck"); + if (item.atkBonus > 0) stats.add("+${item.atkBonus} ${AppStrings.atk}"); + if (item.hpBonus > 0) stats.add("+${item.hpBonus} ${AppStrings.hp}"); + if (item.armorBonus > 0) stats.add("+${item.armorBonus} ${AppStrings.def}"); + if (item.luck > 0) stats.add("+${item.luck} ${AppStrings.luck}"); List effectTexts = item.effects.map((e) => e.description).toList(); @@ -715,7 +722,10 @@ class _BattleScreenState extends State { padding: const EdgeInsets.only(top: 4.0, bottom: 4.0), child: Text( stats.join(", "), - style: const TextStyle(fontSize: ThemeConfig.fontSizeMedium, color: ThemeConfig.statAtkColor), + style: const TextStyle( + fontSize: ThemeConfig.fontSizeMedium, + color: ThemeConfig.statAtkColor, + ), ), ), if (effectTexts.isNotEmpty) @@ -723,7 +733,10 @@ class _BattleScreenState extends State { padding: const EdgeInsets.only(bottom: 4.0), child: Text( effectTexts.join(", "), - style: const TextStyle(fontSize: 11, color: ThemeConfig.rarityLegendary), // 11 is custom, keep or change? Let's use Small + style: const TextStyle( + fontSize: 11, + color: ThemeConfig.rarityLegendary, + ), // 11 is custom, keep or change? Let's use Small ), ), ], diff --git a/lib/screens/character_selection_screen.dart b/lib/screens/character_selection_screen.dart index 2caeb4f..6bec011 100644 --- a/lib/screens/character_selection_screen.dart +++ b/lib/screens/character_selection_screen.dart @@ -5,6 +5,7 @@ import '../game/data/player_table.dart'; import 'main_wrapper.dart'; import '../widgets/responsive_container.dart'; import '../game/config/theme_config.dart'; +import '../game/config/app_strings.dart'; class CharacterSelectionScreen extends StatelessWidget { const CharacterSelectionScreen({super.key}); @@ -83,19 +84,19 @@ class CharacterSelectionScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text( - "HP: ${warrior.baseHp}", + "${AppStrings.hp}: ${warrior.baseHp}", style: const TextStyle( fontWeight: ThemeConfig.fontWeightBold, ), ), Text( - "ATK: ${warrior.baseAtk}", + "${AppStrings.atk}: ${warrior.baseAtk}", style: const TextStyle( fontWeight: ThemeConfig.fontWeightBold, ), ), Text( - "DEF: ${warrior.baseDefense}", + "${AppStrings.def}: ${warrior.baseDefense}", style: const TextStyle( fontWeight: ThemeConfig.fontWeightBold, ), diff --git a/lib/screens/inventory_screen.dart b/lib/screens/inventory_screen.dart index 47c522d..ef09d65 100644 --- a/lib/screens/inventory_screen.dart +++ b/lib/screens/inventory_screen.dart @@ -5,6 +5,7 @@ import '../game/model/item.dart'; import '../game/enums.dart'; import '../utils/item_utils.dart'; import '../game/config/theme_config.dart'; +import '../game/config/app_strings.dart'; class InventoryScreen extends StatelessWidget { const InventoryScreen({super.key}); @@ -37,28 +38,28 @@ class InventoryScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildStatItem( - "HP", + AppStrings.hp, "${player.hp}/${player.totalMaxHp}", color: ThemeConfig.statHpColor, ), _buildStatItem( - "ATK", + AppStrings.atk, "${player.totalAtk}", color: ThemeConfig.statAtkColor, ), _buildStatItem( - "DEF", + AppStrings.def, "${player.totalDefense}", color: ThemeConfig.statDefColor, ), - _buildStatItem("Shield", "${player.armor}"), + _buildStatItem(AppStrings.armor, "${player.armor}"), _buildStatItem( - "Luck", + AppStrings.luck, "${player.totalLuck}", color: ThemeConfig.statLuckColor, ), _buildStatItem( - "Gold", + AppStrings.gold, "${player.gold} G", color: ThemeConfig.statGoldColor, ), @@ -162,7 +163,7 @@ class InventoryScreen extends StatelessWidget { FittedBox( fit: BoxFit.scaleDown, child: Text( - item?.name ?? "Empty", + item?.name ?? AppStrings.emptySlot, textAlign: TextAlign.center, style: TextStyle( fontSize: @@ -208,7 +209,7 @@ class InventoryScreen extends StatelessWidget { child: Align( alignment: Alignment.centerLeft, child: Text( - "Bag (${player.inventory.length}/${player.maxInventorySize})", + "${AppStrings.bag} (${player.inventory.length}/${player.maxInventorySize})", style: const TextStyle( fontSize: ThemeConfig.fontSizeHeader, fontWeight: ThemeConfig.fontWeightBold, @@ -371,7 +372,7 @@ class InventoryScreen extends StatelessWidget { children: [ Icon(Icons.shield, color: ThemeConfig.btnDefendActive), SizedBox(width: 10), - Text("Equip"), + Text(AppStrings.equip), ], ), ), @@ -391,7 +392,7 @@ class InventoryScreen extends StatelessWidget { color: ThemeConfig.statGoldColor, ), const SizedBox(width: 10), - Text("Sell (${item.price} G)"), + Text("${AppStrings.sell} (${item.price} G)"), ], ), ), @@ -407,7 +408,7 @@ class InventoryScreen extends StatelessWidget { children: [ Icon(Icons.delete, color: ThemeConfig.btnActionActive), SizedBox(width: 10), - Text("Discard"), + Text(AppStrings.discard), ], ), ), @@ -430,7 +431,7 @@ class InventoryScreen extends StatelessWidget { actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text("Cancel"), + child: const Text(AppStrings.cancel), ), ElevatedButton( style: ElevatedButton.styleFrom( @@ -440,7 +441,7 @@ class InventoryScreen extends StatelessWidget { provider.sellItem(item); Navigator.pop(ctx); }, - child: const Text("Sell", style: TextStyle(color: Colors.black)), + child: const Text(AppStrings.sell, style: TextStyle(color: Colors.black)), ), ], ), @@ -460,7 +461,7 @@ class InventoryScreen extends StatelessWidget { actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text("Cancel"), + child: const Text(AppStrings.cancel), ), ElevatedButton( style: ElevatedButton.styleFrom( @@ -470,7 +471,7 @@ class InventoryScreen extends StatelessWidget { provider.discardItem(item); Navigator.pop(ctx); }, - child: const Text("Discard"), + child: const Text(AppStrings.discard), ), ], ), @@ -510,7 +511,7 @@ class InventoryScreen extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - "Equip ${newItem.name}?", + "${AppStrings.equip} ${newItem.name}?", style: const TextStyle(fontWeight: ThemeConfig.fontWeightBold), ), if (oldItem != null) @@ -524,8 +525,8 @@ class InventoryScreen extends StatelessWidget { const SizedBox(height: 16), _buildStatChangeRow("Max HP", currentMaxHp, newMaxHp), _buildStatChangeRow("Current HP", currentHp, newHp), - _buildStatChangeRow("ATK", currentAtk, newAtk), - _buildStatChangeRow("DEF", currentDef, newDef), + _buildStatChangeRow(AppStrings.atk, currentAtk, newAtk), + _buildStatChangeRow(AppStrings.def, currentDef, newDef), _buildStatChangeRow( "LUCK", player.totalLuck, @@ -536,14 +537,14 @@ class InventoryScreen extends StatelessWidget { actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text("Cancel"), + child: const Text(AppStrings.cancel), ), ElevatedButton( onPressed: () { provider.equipItem(newItem); Navigator.pop(ctx); }, - child: const Text("Confirm"), + child: const Text(AppStrings.confirm), ), ], ), @@ -582,27 +583,27 @@ class InventoryScreen extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - "Unequip ${itemToUnequip.name}?", + "${AppStrings.unequip} ${itemToUnequip.name}?", style: const TextStyle(fontWeight: ThemeConfig.fontWeightBold), ), const SizedBox(height: 16), _buildStatChangeRow("Max HP", currentMaxHp, newMaxHp), _buildStatChangeRow("Current HP", currentHp, newHp), - _buildStatChangeRow("ATK", currentAtk, newAtk), - _buildStatChangeRow("DEF", currentDef, newDef), + _buildStatChangeRow(AppStrings.atk, currentAtk, newAtk), + _buildStatChangeRow(AppStrings.def, currentDef, newDef), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text("Cancel"), + child: const Text(AppStrings.cancel), ), ElevatedButton( onPressed: () { provider.unequipItem(itemToUnequip); Navigator.pop(ctx); }, - child: const Text("Confirm"), + child: const Text(AppStrings.confirm), ), ], ), @@ -657,10 +658,10 @@ class InventoryScreen extends StatelessWidget { Widget _buildItemStatText(Item item) { List stats = []; - if (item.atkBonus > 0) stats.add("+${item.atkBonus} ATK"); - if (item.hpBonus > 0) stats.add("+${item.hpBonus} HP"); - if (item.armorBonus > 0) stats.add("+${item.armorBonus} DEF"); - if (item.luck > 0) stats.add("+${item.luck} Luck"); + if (item.atkBonus > 0) stats.add("+${item.atkBonus} ${AppStrings.atk}"); + if (item.hpBonus > 0) stats.add("+${item.hpBonus} ${AppStrings.hp}"); + if (item.armorBonus > 0) stats.add("+${item.armorBonus} ${AppStrings.def}"); + if (item.luck > 0) stats.add("+${item.luck} ${AppStrings.luck}"); // Include effects List effectTexts = item.effects.map((e) => e.description).toList(); diff --git a/lib/screens/main_menu_screen.dart b/lib/screens/main_menu_screen.dart index 37a22bf..7e844fa 100644 --- a/lib/screens/main_menu_screen.dart +++ b/lib/screens/main_menu_screen.dart @@ -6,6 +6,7 @@ import '../widgets/responsive_container.dart'; import '../game/save_manager.dart'; import '../providers/battle_provider.dart'; import '../game/config/theme_config.dart'; +import '../game/config/app_strings.dart'; class MainMenuScreen extends StatefulWidget { const MainMenuScreen({super.key}); @@ -79,7 +80,7 @@ class _MainMenuScreenState extends State { ), const SizedBox(height: 20), const Text( - "COLOSSEUM'S CHOICE", + AppStrings.gameTitle, style: TextStyle( fontSize: ThemeConfig.fontSizeHero, fontWeight: ThemeConfig.fontWeightBold, @@ -112,7 +113,7 @@ class _MainMenuScreenState extends State { fontWeight: ThemeConfig.fontWeightBold, ), ), - child: const Text("CONTINUE"), + child: const Text(AppStrings.continueGame), ), const SizedBox(height: 20), ], @@ -139,7 +140,7 @@ class _MainMenuScreenState extends State { fontWeight: ThemeConfig.fontWeightBold, ), ), - child: const Text("NEW GAME"), + child: const Text(AppStrings.startGame), ), ], ), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index c00175d..05889f3 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import '../providers/battle_provider.dart'; import 'main_menu_screen.dart'; import '../game/config/theme_config.dart'; +import '../game/config/app_strings.dart'; class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key}); @@ -14,7 +15,7 @@ class SettingsScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( - 'Settings', + AppStrings.settings, style: TextStyle( fontSize: ThemeConfig.fontSizeTitle, fontWeight: ThemeConfig.fontWeightBold, @@ -43,7 +44,7 @@ class SettingsScreen extends StatelessWidget { onPressed: () { _showConfirmationDialog( context, - title: 'Restart Game?', + title: '${AppStrings.restart} Game?', content: 'All progress will be lost. Are you sure?', onConfirm: () { context.read().initializeBattle(); @@ -70,7 +71,7 @@ class SettingsScreen extends StatelessWidget { onPressed: () { _showConfirmationDialog( context, - title: 'Return to Main Menu?', + title: '${AppStrings.returnToMenu}?', content: 'Unsaved progress may be lost. (Progress is saved automatically after each stage)', onConfirm: () { Navigator.of(context).pushAndRemoveUntil( @@ -80,7 +81,7 @@ class SettingsScreen extends StatelessWidget { }, ); }, - child: const Text('Return to Main Menu'), + child: const Text(AppStrings.returnToMenu), ), ], ), @@ -96,14 +97,14 @@ class SettingsScreen extends StatelessWidget { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: const Text(AppStrings.cancel), ), TextButton( onPressed: () { Navigator.pop(context); onConfirm(); }, - child: const Text('Confirm', style: TextStyle(color: Colors.red)), + child: const Text(AppStrings.confirm, style: TextStyle(color: Colors.red)), ), ], ), diff --git a/prompt/60_introduce_app_strings.md b/prompt/60_introduce_app_strings.md new file mode 100644 index 0000000..3d9cbef --- /dev/null +++ b/prompt/60_introduce_app_strings.md @@ -0,0 +1,16 @@ +# 60. Introduce AppStrings for Soft i18n + +## 1. 목표 (Goal) +- UI 텍스트 하드코딩을 방지하고 추후 본격적인 i18n 적용을 대비하기 위해 `AppStrings` 상수 클래스를 도입합니다. +- 자주 사용되는 UI 텍스트(메뉴, 스탯, 행동 등)를 한 곳에서 관리합니다. + +## 2. 구현 계획 (Implementation Plan) +1. **`lib/game/config/app_strings.dart` 생성:** + - `actionAttack`, `actionDefend`, `statHp`, `statAtk` 등 카테고리별로 정적 상수를 정의합니다. +2. **UI 코드 수정:** + - `BattleScreen`, `InventoryScreen`, `MainMenuScreen` 등에서 하드코딩된 문자열을 `AppStrings.xxx`로 교체합니다. + - *Note:* 전투 로그와 같이 동적으로 생성되는 복잡한 문장은 이번 단계에서 제외합니다. + +## 3. 기대 효과 (Expected Outcome) +- 텍스트 변경 시 `AppStrings`만 수정하면 되므로 유지보수성 향상. +- 추후 다국어 지원 라이브러리 도입 시 마이그레이션이 매우 쉬워짐.