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/engine/game_mutations.dart';
|
||||
import 'package:askiineverdie/src/core/engine/progress_service.dart';
|
||||
import 'package:askiineverdie/src/core/engine/reward_service.dart';
|
||||
@@ -51,6 +52,8 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
||||
return MaterialApp(
|
||||
title: 'Ascii Never Die',
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: L10n.localizationsDelegates,
|
||||
supportedLocales: L10n.supportedLocales,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF234361)),
|
||||
scaffoldBackgroundColor: const Color(0xFFF4F5F7),
|
||||
@@ -85,9 +88,9 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
||||
|
||||
if (saves.isEmpty) {
|
||||
// 저장 파일이 없으면 안내 메시지
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('저장된 게임이 없습니다.')));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(L10n.of(context).noSavedGames)),
|
||||
);
|
||||
return;
|
||||
} else if (saves.length == 1) {
|
||||
// 파일이 하나면 바로 선택
|
||||
@@ -114,7 +117,7 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'저장 파일을 불러올 수 없습니다: ${_controller.error ?? "알 수 없는 오류"}',
|
||||
L10n.of(context).loadError(_controller.error ?? 'Unknown error'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/l10n/app_localizations.dart';
|
||||
|
||||
class FrontScreen extends StatelessWidget {
|
||||
const FrontScreen({super.key, this.onNewCharacter, this.onLoadSave});
|
||||
|
||||
@@ -107,7 +109,7 @@ class _HeroHeader extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Ascii Never Die',
|
||||
L10n.of(context).appTitle,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
color: colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -126,14 +128,19 @@ class _HeroHeader extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: const [
|
||||
_Tag(icon: Icons.cloud_off_outlined, label: 'No network'),
|
||||
_Tag(icon: Icons.timer_outlined, label: 'Idle RPG loop'),
|
||||
_Tag(icon: Icons.storage_rounded, label: 'Local saves'),
|
||||
],
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final l10n = L10n.of(context);
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_Tag(icon: Icons.cloud_off_outlined, label: l10n.tagNoNetwork),
|
||||
_Tag(icon: Icons.timer_outlined, label: l10n.tagIdleRpg),
|
||||
_Tag(icon: Icons.storage_rounded, label: l10n.tagLocalSaves),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -151,6 +158,7 @@ class _ActionRow extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final l10n = L10n.of(context);
|
||||
|
||||
return Wrap(
|
||||
spacing: 12,
|
||||
@@ -159,7 +167,7 @@ class _ActionRow extends StatelessWidget {
|
||||
FilledButton.icon(
|
||||
onPressed: onNewCharacter,
|
||||
icon: const Icon(Icons.casino_outlined),
|
||||
label: const Text('New character'),
|
||||
label: Text(l10n.newCharacter),
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
|
||||
textStyle: theme.textTheme.titleMedium,
|
||||
@@ -168,7 +176,7 @@ class _ActionRow extends StatelessWidget {
|
||||
OutlinedButton.icon(
|
||||
onPressed: onLoadSave,
|
||||
icon: const Icon(Icons.folder_open),
|
||||
label: const Text('Load save'),
|
||||
label: Text(l10n.loadSave),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
|
||||
textStyle: theme.textTheme.titleMedium,
|
||||
@@ -177,7 +185,7 @@ class _ActionRow extends StatelessWidget {
|
||||
TextButton.icon(
|
||||
onPressed: () => _showPlaceholder(context),
|
||||
icon: const Icon(Icons.menu_book_outlined),
|
||||
label: const Text('View build plan'),
|
||||
label: Text(l10n.viewBuildPlan),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -189,11 +197,12 @@ class _StatusCards extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context);
|
||||
return Column(
|
||||
children: const [
|
||||
children: [
|
||||
_InfoCard(
|
||||
icon: Icons.route_outlined,
|
||||
title: 'Build roadmap',
|
||||
title: l10n.buildRoadmap,
|
||||
points: [
|
||||
'Port PQ 6.4 data set (Config.dfm) into Dart constants.',
|
||||
'Recreate quest/task loop with deterministic RNG + saves.',
|
||||
@@ -203,7 +212,7 @@ class _StatusCards extends StatelessWidget {
|
||||
SizedBox(height: 16),
|
||||
_InfoCard(
|
||||
icon: Icons.auto_fix_high_outlined,
|
||||
title: 'Tech stack',
|
||||
title: l10n.techStack,
|
||||
points: [
|
||||
'Flutter (Material 3) with multiplatform targets enabled.',
|
||||
'path_provider + shared_preferences for local storage hooks.',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:askiineverdie/l10n/app_localizations.dart';
|
||||
import 'package:askiineverdie/src/core/storage/save_service.dart'
|
||||
show SaveFileInfo;
|
||||
|
||||
@@ -20,7 +21,7 @@ class SavePickerDialog extends StatelessWidget {
|
||||
// 저장 파일이 없으면 안내 메시지
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('저장된 게임이 없습니다.')));
|
||||
).showSnackBar(SnackBar(content: Text(L10n.of(context).noSavedGames)));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -35,12 +36,13 @@ class SavePickerDialog extends StatelessWidget {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
final l10n = L10n.of(context);
|
||||
return AlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(Icons.folder_open, color: colorScheme.primary),
|
||||
const SizedBox(width: 12),
|
||||
const Text('Load Game'),
|
||||
Text(l10n.loadGame),
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
@@ -64,7 +66,7 @@ class SavePickerDialog extends StatelessWidget {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(null),
|
||||
child: const Text('Cancel'),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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';
|
||||
@@ -60,7 +61,7 @@ class TaskProgressPanel extends StatelessWidget {
|
||||
child: Text(
|
||||
progress.currentTask.caption.isNotEmpty
|
||||
? progress.currentTask.caption
|
||||
: 'Welcome to Progress Quest!',
|
||||
: L10n.of(context).welcomeMessage,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/l10n/app_localizations.dart';
|
||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||
import 'package:askiineverdie/src/core/model/pq_config.dart';
|
||||
import 'package:askiineverdie/src/core/util/deterministic_random.dart';
|
||||
@@ -241,6 +242,7 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
}
|
||||
|
||||
Widget _buildNameSection() {
|
||||
final l10n = L10n.of(context);
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -249,9 +251,9 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.name,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
maxLength: 30,
|
||||
),
|
||||
@@ -260,7 +262,7 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
IconButton.filled(
|
||||
onPressed: _onGenerateName,
|
||||
icon: const Icon(Icons.casino),
|
||||
tooltip: 'Generate Name',
|
||||
tooltip: l10n.generateName,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -269,29 +271,30 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
}
|
||||
|
||||
Widget _buildStatsSection() {
|
||||
final l10n = L10n.of(context);
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Stats', style: Theme.of(context).textTheme.titleMedium),
|
||||
Text(l10n.stats, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 스탯 그리드
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _buildStatTile('STR', _str)),
|
||||
Expanded(child: _buildStatTile('CON', _con)),
|
||||
Expanded(child: _buildStatTile('DEX', _dex)),
|
||||
Expanded(child: _buildStatTile(l10n.statStr, _str)),
|
||||
Expanded(child: _buildStatTile(l10n.statCon, _con)),
|
||||
Expanded(child: _buildStatTile(l10n.statDex, _dex)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _buildStatTile('INT', _int)),
|
||||
Expanded(child: _buildStatTile('WIS', _wis)),
|
||||
Expanded(child: _buildStatTile('CHA', _cha)),
|
||||
Expanded(child: _buildStatTile(l10n.statInt, _int)),
|
||||
Expanded(child: _buildStatTile(l10n.statWis, _wis)),
|
||||
Expanded(child: _buildStatTile(l10n.statCha, _cha)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -307,9 +310,9 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Total',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
Text(
|
||||
l10n.total,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
'$_total',
|
||||
@@ -333,7 +336,7 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
OutlinedButton.icon(
|
||||
onPressed: _onUnroll,
|
||||
icon: const Icon(Icons.undo),
|
||||
label: const Text('Unroll'),
|
||||
label: Text(l10n.unroll),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: _rollHistory.isEmpty ? Colors.grey : null,
|
||||
),
|
||||
@@ -342,7 +345,7 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
FilledButton.icon(
|
||||
onPressed: _onReroll,
|
||||
icon: const Icon(Icons.casino),
|
||||
label: const Text('Roll'),
|
||||
label: Text(l10n.roll),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -389,7 +392,7 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Race', style: Theme.of(context).textTheme.titleMedium),
|
||||
Text(L10n.of(context).race, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: 300,
|
||||
@@ -434,7 +437,7 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Class', style: Theme.of(context).textTheme.titleMedium),
|
||||
Text(L10n.of(context).classTitle, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: 300,
|
||||
|
||||
Reference in New Issue
Block a user