feat(l10n): 게임 텍스트 다국어 지원 확장
- 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: 마이너 개선
This commit is contained in:
@@ -1129,6 +1129,17 @@ String translateFaction(String englishFaction) {
|
|||||||
return 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';
|
if (isJapaneseLocale) return 'DOT';
|
||||||
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.';
|
||||||
|
}
|
||||||
|
|||||||
@@ -119,6 +119,17 @@ const Map<String, String> spellTranslationsJa = {
|
|||||||
'Deploy': 'デプロイ',
|
'Deploy': 'デプロイ',
|
||||||
'Scale Up': 'スケールアップ',
|
'Scale Up': 'スケールアップ',
|
||||||
'Failover': 'フェイルオーバー',
|
'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キャッシュ',
|
||||||
};
|
};
|
||||||
|
|
||||||
/// モンスター名日本語翻訳 (主要モンスター)
|
/// モンスター名日本語翻訳 (主要モンスター)
|
||||||
|
|||||||
@@ -119,6 +119,17 @@ const Map<String, String> spellTranslationsKo = {
|
|||||||
'Deploy': '배포',
|
'Deploy': '배포',
|
||||||
'Scale Up': '스케일 업',
|
'Scale Up': '스케일 업',
|
||||||
'Failover': '페일오버',
|
'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 캐시',
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 몬스터 이름 한국어 번역 (주요 몬스터만)
|
/// 몬스터 이름 한국어 번역 (주요 몬스터만)
|
||||||
|
|||||||
@@ -1000,11 +1000,13 @@ class ProgressService {
|
|||||||
final damage = dot.damagePerTick * ticksTriggered;
|
final damage = dot.damagePerTick * ticksTriggered;
|
||||||
dotDamageThisTick += damage;
|
dotDamageThisTick += damage;
|
||||||
|
|
||||||
// DOT 데미지 이벤트 생성
|
// DOT 데미지 이벤트 생성 (skillId → name 변환)
|
||||||
|
final dotSkillName =
|
||||||
|
SkillData.getSkillById(dot.skillId)?.name ?? dot.skillId;
|
||||||
newEvents.add(
|
newEvents.add(
|
||||||
CombatEvent.dotTick(
|
CombatEvent.dotTick(
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
skillName: dot.skillId,
|
skillName: dotSkillName,
|
||||||
damage: damage,
|
damage: damage,
|
||||||
targetName: monsterStats.name,
|
targetName: monsterStats.name,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -459,11 +459,22 @@ class GameDataL10n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 각 단어의 첫 글자를 대문자로 (Title Case)
|
/// 각 단어의 첫 글자를 대문자로 (Title Case)
|
||||||
|
/// 하이픈으로 연결된 단어도 처리 (예: "off-by-one" → "Off-by-One")
|
||||||
static String _toTitleCase(String s) {
|
static String _toTitleCase(String s) {
|
||||||
return s
|
return s
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.map((word) {
|
.map((word) {
|
||||||
if (word.isEmpty) return 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);
|
return word[0].toUpperCase() + word.substring(1);
|
||||||
})
|
})
|
||||||
.join(' ');
|
.join(' ');
|
||||||
|
|||||||
@@ -174,6 +174,10 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
/// 전투 이벤트를 메시지와 타입으로 변환
|
/// 전투 이벤트를 메시지와 타입으로 변환
|
||||||
(String, CombatLogType) _formatCombatEvent(CombatEvent event) {
|
(String, CombatLogType) _formatCombatEvent(CombatEvent event) {
|
||||||
final target = event.targetName ?? '';
|
final target = event.targetName ?? '';
|
||||||
|
// 스킬/포션 이름 번역 (전역 로케일 사용)
|
||||||
|
final skillName = event.skillName != null
|
||||||
|
? game_l10n.translateSpell(event.skillName!)
|
||||||
|
: '';
|
||||||
return switch (event.type) {
|
return switch (event.type) {
|
||||||
CombatEventType.playerAttack =>
|
CombatEventType.playerAttack =>
|
||||||
event.isCritical
|
event.isCritical
|
||||||
@@ -208,41 +212,34 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
CombatEventType.playerSkill =>
|
CombatEventType.playerSkill =>
|
||||||
event.isCritical
|
event.isCritical
|
||||||
? (
|
? (
|
||||||
game_l10n.combatSkillCritical(
|
game_l10n.combatSkillCritical(skillName, event.damage),
|
||||||
event.skillName ?? '',
|
|
||||||
event.damage,
|
|
||||||
),
|
|
||||||
CombatLogType.critical,
|
CombatLogType.critical,
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
game_l10n.combatSkillDamage(event.skillName ?? '', event.damage),
|
game_l10n.combatSkillDamage(skillName, event.damage),
|
||||||
CombatLogType.spell,
|
CombatLogType.spell,
|
||||||
),
|
),
|
||||||
CombatEventType.playerHeal => (
|
CombatEventType.playerHeal => (
|
||||||
game_l10n.combatSkillHeal(
|
game_l10n.combatSkillHeal(
|
||||||
event.skillName ?? game_l10n.uiHeal,
|
skillName.isNotEmpty ? skillName : game_l10n.uiHeal,
|
||||||
event.healAmount,
|
event.healAmount,
|
||||||
),
|
),
|
||||||
CombatLogType.heal,
|
CombatLogType.heal,
|
||||||
),
|
),
|
||||||
CombatEventType.playerBuff => (
|
CombatEventType.playerBuff => (
|
||||||
game_l10n.combatBuffActivated(event.skillName ?? ''),
|
game_l10n.combatBuffActivated(skillName),
|
||||||
CombatLogType.buff,
|
CombatLogType.buff,
|
||||||
),
|
),
|
||||||
CombatEventType.dotTick => (
|
CombatEventType.dotTick => (
|
||||||
game_l10n.combatDotTick(event.skillName ?? '', event.damage),
|
game_l10n.combatDotTick(skillName, event.damage),
|
||||||
CombatLogType.dotTick,
|
CombatLogType.dotTick,
|
||||||
),
|
),
|
||||||
CombatEventType.playerPotion => (
|
CombatEventType.playerPotion => (
|
||||||
game_l10n.combatPotionUsed(
|
game_l10n.combatPotionUsed(skillName, event.healAmount, target),
|
||||||
event.skillName ?? '',
|
|
||||||
event.healAmount,
|
|
||||||
target,
|
|
||||||
),
|
|
||||||
CombatLogType.potion,
|
CombatLogType.potion,
|
||||||
),
|
),
|
||||||
CombatEventType.potionDrop => (
|
CombatEventType.potionDrop => (
|
||||||
game_l10n.combatPotionDrop(event.skillName ?? ''),
|
game_l10n.combatPotionDrop(skillName),
|
||||||
CombatLogType.potionDrop,
|
CombatLogType.potionDrop,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class _BuffRow extends StatelessWidget {
|
|||||||
if (effect.atkModifier != 0) {
|
if (effect.atkModifier != 0) {
|
||||||
modifiers.add(
|
modifiers.add(
|
||||||
_ModifierChip(
|
_ModifierChip(
|
||||||
label: 'ATK',
|
label: l10n.statAtk,
|
||||||
value: effect.atkModifier,
|
value: effect.atkModifier,
|
||||||
isPositive: effect.atkModifier > 0,
|
isPositive: effect.atkModifier > 0,
|
||||||
),
|
),
|
||||||
@@ -137,7 +137,7 @@ class _BuffRow extends StatelessWidget {
|
|||||||
if (effect.defModifier != 0) {
|
if (effect.defModifier != 0) {
|
||||||
modifiers.add(
|
modifiers.add(
|
||||||
_ModifierChip(
|
_ModifierChip(
|
||||||
label: 'DEF',
|
label: l10n.statDef,
|
||||||
value: effect.defModifier,
|
value: effect.defModifier,
|
||||||
isPositive: effect.defModifier > 0,
|
isPositive: effect.defModifier > 0,
|
||||||
),
|
),
|
||||||
@@ -147,7 +147,7 @@ class _BuffRow extends StatelessWidget {
|
|||||||
if (effect.criRateModifier != 0) {
|
if (effect.criRateModifier != 0) {
|
||||||
modifiers.add(
|
modifiers.add(
|
||||||
_ModifierChip(
|
_ModifierChip(
|
||||||
label: 'CRI',
|
label: l10n.statCri,
|
||||||
value: effect.criRateModifier,
|
value: effect.criRateModifier,
|
||||||
isPositive: effect.criRateModifier > 0,
|
isPositive: effect.criRateModifier > 0,
|
||||||
),
|
),
|
||||||
@@ -157,7 +157,7 @@ class _BuffRow extends StatelessWidget {
|
|||||||
if (effect.evasionModifier != 0) {
|
if (effect.evasionModifier != 0) {
|
||||||
modifiers.add(
|
modifiers.add(
|
||||||
_ModifierChip(
|
_ModifierChip(
|
||||||
label: 'EVA',
|
label: l10n.statEva,
|
||||||
value: effect.evasionModifier,
|
value: effect.evasionModifier,
|
||||||
isPositive: effect.evasionModifier > 0,
|
isPositive: effect.evasionModifier > 0,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||||
import 'package:askiineverdie/src/core/engine/item_service.dart';
|
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_item.dart';
|
||||||
import 'package:askiineverdie/src/core/model/equipment_slot.dart';
|
import 'package:askiineverdie/src/core/model/equipment_slot.dart';
|
||||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||||
@@ -80,6 +81,12 @@ class _EquipmentSlotTile extends StatelessWidget {
|
|||||||
|
|
||||||
final score = ItemService.calculateEquipmentScore(item);
|
final score = ItemService.calculateEquipmentScore(item);
|
||||||
final rarityColor = _getRarityColor(item.rarity);
|
final rarityColor = _getRarityColor(item.rarity);
|
||||||
|
// 슬롯 인덱스로 아이템 이름 번역 (0: weapon, 1: shield, 2+: armor)
|
||||||
|
final translatedName = GameDataL10n.translateEquipString(
|
||||||
|
context,
|
||||||
|
item.name,
|
||||||
|
item.slot.index,
|
||||||
|
);
|
||||||
|
|
||||||
return ExpansionTile(
|
return ExpansionTile(
|
||||||
initiallyExpanded: initiallyExpanded,
|
initiallyExpanded: initiallyExpanded,
|
||||||
@@ -92,7 +99,7 @@ class _EquipmentSlotTile extends StatelessWidget {
|
|||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
item.name,
|
translatedName,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: rarityColor,
|
color: rarityColor,
|
||||||
@@ -426,7 +433,7 @@ class _ItemMetaRow extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final rarityName = item.rarity.name.toUpperCase();
|
final rarityName = _getTranslatedRarity(item.rarity);
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -479,3 +486,14 @@ String _getSlotName(EquipmentSlot slot) {
|
|||||||
EquipmentSlot.sollerets => l10n.slotSollerets,
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -110,10 +110,10 @@ class _PotionRow extends StatelessWidget {
|
|||||||
_PotionIcon(type: potion.type, tier: potion.tier),
|
_PotionIcon(type: potion.type, tier: potion.tier),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
|
|
||||||
// 물약 이름
|
// 물약 이름 (번역 적용)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
potion.name,
|
l10n.translateSpell(potion.name),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: color,
|
color: color,
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
if (name.isEmpty) {
|
if (name.isEmpty) {
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(const SnackBar(content: Text('이름을 입력해주세요.')));
|
).showSnackBar(SnackBar(content: Text(game_l10n.uiEnterName)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +359,7 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_rollHistory.length} roll(s) in history',
|
game_l10n.uiRollHistory(_rollHistory.length),
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user