feat(l10n): 국제화(L10n) 시스템 도입 및 하드코딩 텍스트 변환
- flutter_localizations 및 intl 패키지 추가 - l10n.yaml 설정 파일 및 app_ko.arb 메시지 파일 생성 - 모든 화면(app, front, game_play, new_character, save_picker)의 하드코딩 텍스트를 L10n 키로 변환 - 테스트 파일에 localizationsDelegates 추가하여 L10n 지원
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/l10n/app_localizations.dart';
|
||||
import 'package:askiineverdie/src/core/animation/ascii_animation_data.dart';
|
||||
import 'package:askiineverdie/src/core/animation/ascii_animation_type.dart';
|
||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||
@@ -129,21 +130,22 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
/// 뒤로가기 시 저장 확인 다이얼로그
|
||||
Future<bool> _onPopInvoked() async {
|
||||
final l10n = L10n.of(context);
|
||||
final shouldPop = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Exit Game'),
|
||||
content: const Text('Save your progress before leaving?'),
|
||||
title: Text(l10n.exitGame),
|
||||
content: Text(l10n.saveProgressQuestion),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Cancel'),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: const Text('Exit without saving'),
|
||||
child: Text(l10n.exitWithoutSaving),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
@@ -152,7 +154,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
},
|
||||
child: const Text('Save and Exit'),
|
||||
child: Text(l10n.saveAndExit),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -189,23 +191,23 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Progress Quest - ${state.traits.name}'),
|
||||
title: Text(L10n.of(context).progressQuestTitle(state.traits.name)),
|
||||
actions: [
|
||||
// 치트 버튼 (디버그용)
|
||||
if (widget.controller.cheatsEnabled) ...[
|
||||
IconButton(
|
||||
icon: const Text('L+1'),
|
||||
tooltip: 'Level Up',
|
||||
tooltip: L10n.of(context).levelUp,
|
||||
onPressed: () => widget.controller.loop?.cheatCompleteTask(),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Text('Q!'),
|
||||
tooltip: 'Complete Quest',
|
||||
tooltip: L10n.of(context).completeQuest,
|
||||
onPressed: () => widget.controller.loop?.cheatCompleteQuest(),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Text('P!'),
|
||||
tooltip: 'Complete Plot',
|
||||
tooltip: L10n.of(context).completePlot,
|
||||
onPressed: () => widget.controller.loop?.cheatCompletePlot(),
|
||||
),
|
||||
],
|
||||
@@ -250,34 +252,35 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
/// 좌측 패널: Character Sheet (Traits, Stats, Experience, Spells)
|
||||
Widget _buildCharacterPanel(GameState state) {
|
||||
final l10n = L10n.of(context);
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildPanelHeader('Character Sheet'),
|
||||
_buildPanelHeader(l10n.characterSheet),
|
||||
|
||||
// Traits 목록
|
||||
_buildSectionHeader('Traits'),
|
||||
_buildSectionHeader(l10n.traits),
|
||||
_buildTraitsList(state),
|
||||
|
||||
// Stats 목록
|
||||
_buildSectionHeader('Stats'),
|
||||
_buildSectionHeader(l10n.stats),
|
||||
Expanded(flex: 2, child: _buildStatsList(state)),
|
||||
|
||||
// Experience 바
|
||||
_buildSectionHeader('Experience'),
|
||||
_buildSectionHeader(l10n.experience),
|
||||
_buildProgressBar(
|
||||
state.progress.exp.position,
|
||||
state.progress.exp.max,
|
||||
Colors.blue,
|
||||
tooltip:
|
||||
'${state.progress.exp.max - state.progress.exp.position} '
|
||||
'XP needed for next level',
|
||||
'${l10n.xpNeededForNextLevel}',
|
||||
),
|
||||
|
||||
// Spell Book
|
||||
_buildSectionHeader('Spell Book'),
|
||||
_buildSectionHeader(l10n.spellBook),
|
||||
Expanded(flex: 2, child: _buildSpellsList(state)),
|
||||
],
|
||||
),
|
||||
@@ -286,22 +289,23 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
/// 중앙 패널: Equipment/Inventory
|
||||
Widget _buildEquipmentPanel(GameState state) {
|
||||
final l10n = L10n.of(context);
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildPanelHeader('Equipment'),
|
||||
_buildPanelHeader(l10n.equipment),
|
||||
|
||||
// Equipment 목록
|
||||
Expanded(flex: 2, child: _buildEquipmentList(state)),
|
||||
|
||||
// Inventory
|
||||
_buildPanelHeader('Inventory'),
|
||||
_buildPanelHeader(l10n.inventory),
|
||||
Expanded(flex: 3, child: _buildInventoryList(state)),
|
||||
|
||||
// Encumbrance 바
|
||||
_buildSectionHeader('Encumbrance'),
|
||||
_buildSectionHeader(l10n.encumbrance),
|
||||
_buildProgressBar(
|
||||
state.progress.encumbrance.position,
|
||||
state.progress.encumbrance.max,
|
||||
@@ -314,12 +318,13 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
/// 우측 패널: Plot/Quest
|
||||
Widget _buildQuestPanel(GameState state) {
|
||||
final l10n = L10n.of(context);
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildPanelHeader('Plot Development'),
|
||||
_buildPanelHeader(l10n.plotDevelopment),
|
||||
|
||||
// Plot 목록
|
||||
Expanded(child: _buildPlotList(state)),
|
||||
@@ -334,7 +339,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
: null,
|
||||
),
|
||||
|
||||
_buildPanelHeader('Quests'),
|
||||
_buildPanelHeader(l10n.quests),
|
||||
|
||||
// Quest 목록
|
||||
Expanded(child: _buildQuestList(state)),
|
||||
@@ -345,7 +350,10 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
state.progress.quest.max,
|
||||
Colors.green,
|
||||
tooltip: state.progress.quest.max > 0
|
||||
? '${(100 * state.progress.quest.position ~/ state.progress.quest.max)}% complete'
|
||||
? l10n.percentComplete(
|
||||
100 * state.progress.quest.position ~/
|
||||
state.progress.quest.max,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
@@ -398,11 +406,12 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
}
|
||||
|
||||
Widget _buildTraitsList(GameState state) {
|
||||
final l10n = L10n.of(context);
|
||||
final traits = [
|
||||
('Name', state.traits.name),
|
||||
('Race', state.traits.race),
|
||||
('Class', state.traits.klass),
|
||||
('Level', '${state.traits.level}'),
|
||||
(l10n.traitName, state.traits.name),
|
||||
(l10n.traitRace, state.traits.race),
|
||||
(l10n.traitClass, state.traits.klass),
|
||||
(l10n.traitLevel, '${state.traits.level}'),
|
||||
];
|
||||
|
||||
return Padding(
|
||||
@@ -433,15 +442,16 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
}
|
||||
|
||||
Widget _buildStatsList(GameState state) {
|
||||
final l10n = L10n.of(context);
|
||||
final stats = [
|
||||
('STR', state.stats.str),
|
||||
('CON', state.stats.con),
|
||||
('DEX', state.stats.dex),
|
||||
('INT', state.stats.intelligence),
|
||||
('WIS', state.stats.wis),
|
||||
('CHA', state.stats.cha),
|
||||
('HP Max', state.stats.hpMax),
|
||||
('MP Max', state.stats.mpMax),
|
||||
(l10n.statStr, state.stats.str),
|
||||
(l10n.statCon, state.stats.con),
|
||||
(l10n.statDex, state.stats.dex),
|
||||
(l10n.statInt, state.stats.intelligence),
|
||||
(l10n.statWis, state.stats.wis),
|
||||
(l10n.statCha, state.stats.cha),
|
||||
(l10n.statHpMax, state.stats.hpMax),
|
||||
(l10n.statMpMax, state.stats.mpMax),
|
||||
];
|
||||
|
||||
return ListView.builder(
|
||||
@@ -467,8 +477,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
Widget _buildSpellsList(GameState state) {
|
||||
if (state.spellBook.spells.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('No spells yet', style: TextStyle(fontSize: 11)),
|
||||
return Center(
|
||||
child: Text(L10n.of(context).noSpellsYet, style: const TextStyle(fontSize: 11)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -498,18 +508,19 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
Widget _buildEquipmentList(GameState state) {
|
||||
// 원본 Main.dfm Equips ListView - 11개 슬롯
|
||||
final l10n = L10n.of(context);
|
||||
final equipment = [
|
||||
('Weapon', state.equipment.weapon),
|
||||
('Shield', state.equipment.shield),
|
||||
('Helm', state.equipment.helm),
|
||||
('Hauberk', state.equipment.hauberk),
|
||||
('Brassairts', state.equipment.brassairts),
|
||||
('Vambraces', state.equipment.vambraces),
|
||||
('Gauntlets', state.equipment.gauntlets),
|
||||
('Gambeson', state.equipment.gambeson),
|
||||
('Cuisses', state.equipment.cuisses),
|
||||
('Greaves', state.equipment.greaves),
|
||||
('Sollerets', state.equipment.sollerets),
|
||||
(l10n.equipWeapon, state.equipment.weapon),
|
||||
(l10n.equipShield, state.equipment.shield),
|
||||
(l10n.equipHelm, state.equipment.helm),
|
||||
(l10n.equipHauberk, state.equipment.hauberk),
|
||||
(l10n.equipBrassairts, state.equipment.brassairts),
|
||||
(l10n.equipVambraces, state.equipment.vambraces),
|
||||
(l10n.equipGauntlets, state.equipment.gauntlets),
|
||||
(l10n.equipGambeson, state.equipment.gambeson),
|
||||
(l10n.equipCuisses, state.equipment.cuisses),
|
||||
(l10n.equipGreaves, state.equipment.greaves),
|
||||
(l10n.equipSollerets, state.equipment.sollerets),
|
||||
];
|
||||
|
||||
return ListView.builder(
|
||||
@@ -543,10 +554,11 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
}
|
||||
|
||||
Widget _buildInventoryList(GameState state) {
|
||||
final l10n = L10n.of(context);
|
||||
if (state.inventory.items.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'Gold: ${state.inventory.gold}',
|
||||
l10n.goldAmount(state.inventory.gold),
|
||||
style: const TextStyle(fontSize: 11),
|
||||
),
|
||||
);
|
||||
@@ -559,8 +571,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
if (index == 0) {
|
||||
return Row(
|
||||
children: [
|
||||
const Expanded(
|
||||
child: Text('Gold', style: TextStyle(fontSize: 11)),
|
||||
Expanded(
|
||||
child: Text(l10n.gold, style: const TextStyle(fontSize: 11)),
|
||||
),
|
||||
Text(
|
||||
'${state.inventory.gold}',
|
||||
@@ -594,10 +606,11 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
Widget _buildPlotList(GameState state) {
|
||||
// 플롯 단계를 표시 (Act I, Act II, ...)
|
||||
final l10n = L10n.of(context);
|
||||
final plotCount = state.progress.plotStageCount;
|
||||
if (plotCount == 0) {
|
||||
return const Center(
|
||||
child: Text('Prologue', style: TextStyle(fontSize: 11)),
|
||||
return Center(
|
||||
child: Text(l10n.prologue, style: const TextStyle(fontSize: 11)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -615,7 +628,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
index == 0 ? 'Prologue' : 'Act ${_toRoman(index)}',
|
||||
index == 0 ? l10n.prologue : l10n.actNumber(_toRoman(index)),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
decoration: isCompleted
|
||||
@@ -630,10 +643,11 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
}
|
||||
|
||||
Widget _buildQuestList(GameState state) {
|
||||
final l10n = L10n.of(context);
|
||||
final questCount = state.progress.questCount;
|
||||
if (questCount == 0) {
|
||||
return const Center(
|
||||
child: Text('No active quests', style: TextStyle(fontSize: 11)),
|
||||
return Center(
|
||||
child: Text(l10n.noActiveQuests, style: const TextStyle(fontSize: 11)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -649,7 +663,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
child: Text(
|
||||
currentTask.caption.isNotEmpty
|
||||
? currentTask.caption
|
||||
: 'Quest #$questCount',
|
||||
: l10n.questNumber(questCount),
|
||||
style: const TextStyle(fontSize: 11),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user