feat(i18n): 다국어 번역 확장 및 UI 개선

- 영어/일본어/한국어/중국어 번역 추가
- VictoryOverlay 레이아웃 개선
- NewCharacterScreen 기능 추가
This commit is contained in:
JiWoong Sul
2026-01-05 15:44:54 +09:00
parent afbd4e6853
commit 9ecf9d1692
11 changed files with 437 additions and 25 deletions

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:asciineverdie/l10n/app_localizations.dart';
import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/shared/retro_colors.dart';
@@ -92,7 +93,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
child: TextButton(
onPressed: widget.onComplete,
child: Text(
'SKIP',
L10n.of(context).endingSkip,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 10,
@@ -108,7 +109,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
left: 0,
right: 0,
child: Text(
'TAP TO SKIP',
L10n.of(context).endingTapToSkip,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 8,
@@ -148,6 +149,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
}
Widget _buildCreditContent(BuildContext context) {
final l10n = L10n.of(context);
final gold = RetroColors.goldOf(context);
final textPrimary = RetroColors.textPrimaryOf(context);
@@ -168,7 +170,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
// CONGRATULATIONS
// ═══════════════════════════════════
Text(
'★ CONGRATULATIONS ★',
l10n.endingCongratulations,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 14,
@@ -178,7 +180,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
),
const SizedBox(height: 16),
Text(
'You have completed the game!',
l10n.endingGameComplete,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 14,
@@ -190,7 +192,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
// ═══════════════════════════════════
// THE HERO
// ═══════════════════════════════════
_buildSectionTitle('THE HERO', gold),
_buildSectionTitle(l10n.endingTheHero, gold),
const SizedBox(height: 20),
_buildHeroInfo(context),
const SizedBox(height: 80),
@@ -198,7 +200,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
// ═══════════════════════════════════
// JOURNEY STATISTICS
// ═══════════════════════════════════
_buildSectionTitle('JOURNEY STATISTICS', gold),
_buildSectionTitle(l10n.endingJourneyStats, gold),
const SizedBox(height: 20),
_buildStatistics(context),
const SizedBox(height: 80),
@@ -206,7 +208,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
// ═══════════════════════════════════
// FINAL STATS
// ═══════════════════════════════════
_buildSectionTitle('FINAL STATS', gold),
_buildSectionTitle(l10n.endingFinalStats, gold),
const SizedBox(height: 20),
_buildFinalStats(context),
const SizedBox(height: 100),
@@ -220,7 +222,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
// ═══════════════════════════════════
// CREDITS
// ═══════════════════════════════════
_buildSectionTitle('CREDITS', gold),
_buildSectionTitle(l10n.endingCredits, gold),
const SizedBox(height: 20),
_buildCredits(context),
const SizedBox(height: 100),
@@ -228,7 +230,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
// ═══════════════════════════════════
// THE END
// ═══════════════════════════════════
_buildTheEnd(gold),
_buildTheEnd(context, gold),
const SizedBox(height: 200), // 여백 (스크롤 끝)
],
),
@@ -303,6 +305,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
}
Widget _buildHeroInfo(BuildContext context) {
final l10n = L10n.of(context);
final gold = RetroColors.goldOf(context);
final textPrimary = RetroColors.textPrimaryOf(context);
final textMuted = RetroColors.textMutedOf(context);
@@ -321,7 +324,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
const SizedBox(height: 12),
// 레벨, 종족, 직업
Text(
'Level ${widget.traits.level}',
l10n.endingLevelFormat(widget.traits.level),
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 10,
@@ -343,6 +346,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
}
Widget _buildStatistics(BuildContext context) {
final l10n = L10n.of(context);
final textPrimary = RetroColors.textPrimaryOf(context);
final exp = RetroColors.expOf(context);
@@ -357,13 +361,14 @@ class _VictoryOverlayState extends State<VictoryOverlay>
return Column(
children: [
_buildStatLine('Monsters Slain', '${widget.progress.monstersKilled}',
textPrimary, exp),
_buildStatLine(l10n.endingMonstersSlain,
'${widget.progress.monstersKilled}', textPrimary, exp),
const SizedBox(height: 8),
_buildStatLine('Quests Completed', '${widget.progress.questCount}',
textPrimary, exp),
_buildStatLine(l10n.endingQuestsCompleted,
'${widget.progress.questCount}', textPrimary, exp),
const SizedBox(height: 8),
_buildStatLine('Play Time', playTimeStr, textPrimary, textPrimary),
_buildStatLine(
l10n.endingPlayTime, playTimeStr, textPrimary, textPrimary),
],
);
}
@@ -474,6 +479,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
}
Widget _buildCredits(BuildContext context) {
final l10n = L10n.of(context);
final textPrimary = RetroColors.textPrimaryOf(context);
final textMuted = RetroColors.textMutedOf(context);
final gold = RetroColors.goldOf(context);
@@ -481,7 +487,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
return Column(
children: [
Text(
'ASCII NEVER DIE',
l10n.appTitle,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 12,
@@ -490,7 +496,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
),
const SizedBox(height: 16),
Text(
'Thank you for playing!',
l10n.endingThankYou,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 12,
@@ -499,7 +505,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
),
const SizedBox(height: 8),
Text(
'Your legend lives on...',
l10n.endingLegendLivesOn,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 10,
@@ -511,7 +517,8 @@ class _VictoryOverlayState extends State<VictoryOverlay>
);
}
Widget _buildTheEnd(Color gold) {
Widget _buildTheEnd(BuildContext context, Color gold) {
final l10n = L10n.of(context);
const theEnd = '''
████████╗██╗ ██╗███████╗ ███████╗███╗ ██╗██████╗
╚══██╔══╝██║ ██║██╔════╝ ██╔════╝████╗ ██║██╔══██╗
@@ -534,7 +541,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
),
const SizedBox(height: 24),
Text(
'Your heroic deeds will be',
l10n.endingHallOfFameLine1,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 12,
@@ -543,7 +550,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
),
const SizedBox(height: 4),
Text(
'remembered in the Hall of Fame',
l10n.endingHallOfFameLine2,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 12,

View File

@@ -2,6 +2,7 @@ import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show FilteringTextInputFormatter;
import 'package:asciineverdie/data/class_data.dart';
import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
@@ -398,6 +399,10 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
fontSize: 10,
color: RetroColors.textLight,
),
// 영문 알파벳만 허용 (공백 불가)
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z]')),
],
decoration: InputDecoration(
labelText: l10n.name,
labelStyle: const TextStyle(