feat(i18n): 다국어 번역 확장
- game_data_l10n 개선 - 일본어, 한국어 번역 추가
This commit is contained in:
@@ -510,6 +510,17 @@ const Map<String, String> 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': '神モードアクティベーター',
|
||||
};
|
||||
|
||||
/// 鎧名日本語翻訳
|
||||
|
||||
@@ -510,6 +510,17 @@ const Map<String, String> 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': '신 모드 활성기',
|
||||
};
|
||||
|
||||
/// 갑옷 이름 한국어 번역
|
||||
|
||||
@@ -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<String, String> baseMap;
|
||||
if (isKo) {
|
||||
if (slotIndex == 0) {
|
||||
baseMap = weaponTranslationsKo;
|
||||
} else if (slotIndex == 1) {
|
||||
baseMap = shieldTranslationsKo;
|
||||
baseMap = allShieldTranslationsKo;
|
||||
} else {
|
||||
baseMap = armorTranslationsKo;
|
||||
baseMap = allArmorTranslationsKo;
|
||||
}
|
||||
} else {
|
||||
if (slotIndex == 0) {
|
||||
baseMap = weaponTranslationsJa;
|
||||
} else if (slotIndex == 1) {
|
||||
baseMap = allShieldTranslationsJa;
|
||||
} else {
|
||||
baseMap = allArmorTranslationsJa;
|
||||
}
|
||||
}
|
||||
|
||||
String baseTranslated = remaining;
|
||||
@@ -326,6 +347,7 @@ class GameDataL10n {
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList();
|
||||
final translatedMods = modWords.map((mod) {
|
||||
if (isKo) {
|
||||
if (isWeapon) {
|
||||
return offenseAttribTranslationsKo[mod] ??
|
||||
offenseBadTranslationsKo[mod] ??
|
||||
@@ -335,6 +357,17 @@ class GameDataL10n {
|
||||
defenseBadTranslationsKo[mod] ??
|
||||
mod;
|
||||
}
|
||||
} else {
|
||||
if (isWeapon) {
|
||||
return offenseAttribTranslationsJa[mod] ??
|
||||
offenseBadTranslationsJa[mod] ??
|
||||
mod;
|
||||
} else {
|
||||
return defenseAttribTranslationsJa[mod] ??
|
||||
defenseBadTranslationsJa[mod] ??
|
||||
mod;
|
||||
}
|
||||
}
|
||||
}).toList();
|
||||
|
||||
// 4. 조합
|
||||
@@ -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];
|
||||
|
||||
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 등) - 잡템 번역 시도
|
||||
if (isKo) {
|
||||
return boringItemTranslationsKo[itemString] ??
|
||||
dropItemTranslationsKo[itemString.toLowerCase()] ??
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user