From d52dea56ea9fc83ff37704c02d145503aa48ac5f Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Wed, 14 Jan 2026 00:18:04 +0900 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20?= =?UTF-8?q?=EB=B2=88=EC=97=AD=20=ED=99=95=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - game_data_l10n 개선 - 일본어, 한국어 번역 추가 --- lib/data/game_translations_ja.dart | 11 +++ lib/data/game_translations_ko.dart | 11 +++ lib/src/core/l10n/game_data_l10n.dart | 133 +++++++++++++++++++------- 3 files changed, 118 insertions(+), 37 deletions(-) diff --git a/lib/data/game_translations_ja.dart b/lib/data/game_translations_ja.dart index e756772..54ecb83 100644 --- a/lib/data/game_translations_ja.dart +++ b/lib/data/game_translations_ja.dart @@ -510,6 +510,17 @@ const Map weaponTranslationsJa = { 'Dyson Sphere Core': 'ダイソン球コア', 'Black Hole Computer': 'ブラックホールコンピューター', 'Universe Simulator': '宇宙シミュレーター', + 'Dimensional Gateway': '次元の門', + 'Time Loop Device': 'タイムループデバイス', + 'Reality Compiler': 'リアリティコンパイラー', + 'Multiverse Bridge': 'マルチバースブリッジ', + 'Cosmic Debugger': 'コズミックデバッガー', + 'Entropy Reverser': 'エントロピーリバーサー', + 'Big Bang Trigger': 'ビッグバントリガー', + 'Heat Death Preventer': '熱的死防止装置', + 'Infinity Engine': '無限エンジン', + 'Omniscience Module': '全知モジュール', + 'God Mode Activator': '神モードアクティベーター', }; /// 鎧名日本語翻訳 diff --git a/lib/data/game_translations_ko.dart b/lib/data/game_translations_ko.dart index 388b82d..4cb25fe 100644 --- a/lib/data/game_translations_ko.dart +++ b/lib/data/game_translations_ko.dart @@ -510,6 +510,17 @@ const Map weaponTranslationsKo = { 'Dyson Sphere Core': '다이슨 구 코어', 'Black Hole Computer': '블랙홀 컴퓨터', 'Universe Simulator': '우주 시뮬레이터', + 'Dimensional Gateway': '차원의 관문', + 'Time Loop Device': '시간 루프 장치', + 'Reality Compiler': '현실 컴파일러', + 'Multiverse Bridge': '다중 우주 다리', + 'Cosmic Debugger': '우주 디버거', + 'Entropy Reverser': '엔트로피 역전기', + 'Big Bang Trigger': '빅뱅 트리거', + 'Heat Death Preventer': '열 죽음 방지기', + 'Infinity Engine': '무한 엔진', + 'Omniscience Module': '전지 모듈', + 'God Mode Activator': '신 모드 활성기', }; /// 갑옷 이름 한국어 번역 diff --git a/lib/src/core/l10n/game_data_l10n.dart b/lib/src/core/l10n/game_data_l10n.dart index 8dfdd94..b81ec55 100644 --- a/lib/src/core/l10n/game_data_l10n.dart +++ b/lib/src/core/l10n/game_data_l10n.dart @@ -1,4 +1,5 @@ import 'package:asciineverdie/data/game_text_l10n.dart' as l10n; +import 'package:asciineverdie/data/game_translations_ja.dart'; import 'package:asciineverdie/data/game_translations_ko.dart'; import 'package:asciineverdie/src/core/util/pq_logic.dart'; import 'package:flutter/widgets.dart'; @@ -18,6 +19,13 @@ class GameDataL10n { return locale.languageCode == 'ko'; } + /// 현재 로케일이 일본어인지 확인 (글로벌 로케일 사용) + static bool _isJapanese(BuildContext context) { + if (l10n.isJapaneseLocale) return true; + final locale = Localizations.localeOf(context); + return locale.languageCode == 'ja'; + } + /// 종족 이름 번역 static String getRaceName(BuildContext context, String englishName) { if (_isKorean(context)) { @@ -284,7 +292,9 @@ class GameDataL10n { String equipString, int slotIndex, ) { - if (!_isKorean(context) || equipString.isEmpty) return equipString; + final isKo = _isKorean(context); + final isJa = _isJapanese(context); + if ((!isKo && !isJa) || equipString.isEmpty) return equipString; // 1. +/- 값 추출 final plusMatch = RegExp(r'^([+-]?\d+)\s+').firstMatch(equipString); @@ -296,13 +306,24 @@ class GameDataL10n { } // 2. 기본 장비 이름 찾기 (가장 긴 매칭 우선) + // 통합 맵 사용 (추가 번역 포함) final Map baseMap; - if (slotIndex == 0) { - baseMap = weaponTranslationsKo; - } else if (slotIndex == 1) { - baseMap = shieldTranslationsKo; + if (isKo) { + if (slotIndex == 0) { + baseMap = weaponTranslationsKo; + } else if (slotIndex == 1) { + baseMap = allShieldTranslationsKo; + } else { + baseMap = allArmorTranslationsKo; + } } else { - baseMap = armorTranslationsKo; + if (slotIndex == 0) { + baseMap = weaponTranslationsJa; + } else if (slotIndex == 1) { + baseMap = allShieldTranslationsJa; + } else { + baseMap = allArmorTranslationsJa; + } } String baseTranslated = remaining; @@ -326,14 +347,26 @@ class GameDataL10n { .where((s) => s.isNotEmpty) .toList(); final translatedMods = modWords.map((mod) { - if (isWeapon) { - return offenseAttribTranslationsKo[mod] ?? - offenseBadTranslationsKo[mod] ?? - mod; + if (isKo) { + if (isWeapon) { + return offenseAttribTranslationsKo[mod] ?? + offenseBadTranslationsKo[mod] ?? + mod; + } else { + return defenseAttribTranslationsKo[mod] ?? + defenseBadTranslationsKo[mod] ?? + mod; + } } else { - return defenseAttribTranslationsKo[mod] ?? - defenseBadTranslationsKo[mod] ?? - mod; + if (isWeapon) { + return offenseAttribTranslationsJa[mod] ?? + offenseBadTranslationsJa[mod] ?? + mod; + } else { + return defenseAttribTranslationsJa[mod] ?? + defenseBadTranslationsJa[mod] ?? + mod; + } } }).toList(); @@ -353,15 +386,17 @@ class GameDataL10n { /// 예: "Golden Iterator of Compilation" → "컴파일의 황금 이터레이터" /// 예: "index out of bounds Array fragment" → "인덱스 초과의 배열 조각" static String translateItemString(BuildContext context, String itemString) { - if (!_isKorean(context) || itemString.isEmpty) return itemString; + final isKo = _isKorean(context); + final isJa = _isJapanese(context); + if ((!isKo && !isJa) || itemString.isEmpty) return itemString; // 1. specialItem 형식 체크: "Attrib Special of ItemOf" // itemOfs에 있는 값으로 끝나는지 확인 - final specialItemResult = _tryTranslateSpecialItem(itemString); + final specialItemResult = _tryTranslateSpecialItem(itemString, isKo); if (specialItemResult != null) return specialItemResult; // 2. 몬스터 드롭 형식 체크: "{monster_lowercase} {drop_ProperCase}" - final monsterDropResult = _tryTranslateMonsterDrop(itemString); + final monsterDropResult = _tryTranslateMonsterDrop(itemString, isKo); if (monsterDropResult != null) return monsterDropResult; // 3. interestingItem 형식: "Attrib Special" (2단어) @@ -370,21 +405,32 @@ class GameDataL10n { final attrib = words[0]; final special = words[1]; - final attribKo = itemAttribTranslationsKo[attrib] ?? attrib; - final specialKo = specialTranslationsKo[special] ?? special; - - return '$attribKo $specialKo'; + if (isKo) { + final attribKo = itemAttribTranslationsKo[attrib] ?? attrib; + final specialKo = specialTranslationsKo[special] ?? special; + return '$attribKo $specialKo'; + } else { + final attribJa = itemAttribTranslationsJa[attrib] ?? attrib; + final specialJa = specialTranslationsJa[special] ?? special; + return '$attribJa $specialJa'; + } } // 4. 단일 단어 (boringItem 등) - 잡템 번역 시도 - return boringItemTranslationsKo[itemString] ?? - dropItemTranslationsKo[itemString.toLowerCase()] ?? - itemString; + if (isKo) { + return boringItemTranslationsKo[itemString] ?? + allDropTranslationsKo[itemString.toLowerCase()] ?? + itemString; + } else { + return boringItemTranslationsJa[itemString] ?? + allDropTranslationsJa[itemString.toLowerCase()] ?? + itemString; + } } /// specialItem 형식 번역 시도 /// "Attrib Special of ItemOf" → "ItemOf의 Attrib Special" - static String? _tryTranslateSpecialItem(String itemString) { + static String? _tryTranslateSpecialItem(String itemString, bool isKo) { // "of" 뒤의 부분이 itemOfs에 있는지 확인 final ofMatch = RegExp(r'^(.+)\s+of\s+(.+)$').firstMatch(itemString); if (ofMatch == null) return null; @@ -393,7 +439,8 @@ class GameDataL10n { final afterOf = ofMatch.group(2)!; // afterOf가 itemOfs에 있어야 specialItem 형식 - if (!itemOfsTranslationsKo.containsKey(afterOf)) return null; + final itemOfsMap = isKo ? itemOfsTranslationsKo : itemOfsTranslationsJa; + if (!itemOfsMap.containsKey(afterOf)) return null; // beforeOf를 Attrib + Special로 분리 final words = beforeOf.split(' '); @@ -403,24 +450,32 @@ class GameDataL10n { final special = words.last; // Attrib와 Special이 유효한지 확인 - if (!itemAttribTranslationsKo.containsKey(attrib) && - !specialTranslationsKo.containsKey(special)) { + final attribMap = isKo ? itemAttribTranslationsKo : itemAttribTranslationsJa; + final specialMap = isKo ? specialTranslationsKo : specialTranslationsJa; + if (!attribMap.containsKey(attrib) && !specialMap.containsKey(special)) { return null; } - final attribKo = itemAttribTranslationsKo[attrib] ?? attrib; - final specialKo = specialTranslationsKo[special] ?? special; - final itemOfKo = itemOfsTranslationsKo[afterOf] ?? afterOf; + final attribT = attribMap[attrib] ?? attrib; + final specialT = specialMap[special] ?? special; + final itemOfT = itemOfsMap[afterOf] ?? afterOf; - return '$itemOfKo의 $attribKo $specialKo'; + if (isKo) { + return '$itemOfT의 $attribT $specialT'; + } else { + return '$itemOfTの$attribT $specialT'; + } } /// 몬스터 드롭 형식 번역 시도 /// "{monster_lowercase} {drop_ProperCase}" → "{몬스터}의 {드롭아이템}" - static String? _tryTranslateMonsterDrop(String itemString) { - // dropItemTranslationsKo에서 매칭되는 드롭 아이템 찾기 + static String? _tryTranslateMonsterDrop(String itemString, bool isKo) { + // 드롭 아이템 번역 맵 선택 (통합 맵 사용) + final dropMap = isKo ? allDropTranslationsKo : allDropTranslationsJa; + final monsterMap = isKo ? allMonsterTranslationsKo : allMonsterTranslationsJa; + // (대소문자 무시, 아이템 문자열 끝에서 매칭) - for (final entry in dropItemTranslationsKo.entries) { + for (final entry in dropMap.entries) { final dropItem = entry.key; final dropItemProperCase = _properCase(dropItem); @@ -443,10 +498,14 @@ class GameDataL10n { // 몬스터 이름 번역 (소문자를 원래 형태로 변환하여 찾기) final monsterNameKey = _toTitleCase(monsterPart); - final monsterKo = monsterTranslationsKo[monsterNameKey] ?? monsterPart; + final monsterT = monsterMap[monsterNameKey] ?? monsterPart; - final dropKo = entry.value; - return '$monsterKo의 $dropKo'; + final dropT = entry.value; + if (isKo) { + return '$monsterT의 $dropT'; + } else { + return '$monsterTの$dropT'; + } } } return null;