feat(i18n): 다국어 번역 확장
- game_data_l10n 개선 - 일본어, 한국어 번역 추가
This commit is contained in:
@@ -510,6 +510,17 @@ const Map<String, String> weaponTranslationsJa = {
|
|||||||
'Dyson Sphere Core': 'ダイソン球コア',
|
'Dyson Sphere Core': 'ダイソン球コア',
|
||||||
'Black Hole Computer': 'ブラックホールコンピューター',
|
'Black Hole Computer': 'ブラックホールコンピューター',
|
||||||
'Universe Simulator': '宇宙シミュレーター',
|
'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': '다이슨 구 코어',
|
'Dyson Sphere Core': '다이슨 구 코어',
|
||||||
'Black Hole Computer': '블랙홀 컴퓨터',
|
'Black Hole Computer': '블랙홀 컴퓨터',
|
||||||
'Universe Simulator': '우주 시뮬레이터',
|
'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_text_l10n.dart' as l10n;
|
||||||
|
import 'package:asciineverdie/data/game_translations_ja.dart';
|
||||||
import 'package:asciineverdie/data/game_translations_ko.dart';
|
import 'package:asciineverdie/data/game_translations_ko.dart';
|
||||||
import 'package:asciineverdie/src/core/util/pq_logic.dart';
|
import 'package:asciineverdie/src/core/util/pq_logic.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
@@ -18,6 +19,13 @@ class GameDataL10n {
|
|||||||
return locale.languageCode == 'ko';
|
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) {
|
static String getRaceName(BuildContext context, String englishName) {
|
||||||
if (_isKorean(context)) {
|
if (_isKorean(context)) {
|
||||||
@@ -284,7 +292,9 @@ class GameDataL10n {
|
|||||||
String equipString,
|
String equipString,
|
||||||
int slotIndex,
|
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. +/- 값 추출
|
// 1. +/- 값 추출
|
||||||
final plusMatch = RegExp(r'^([+-]?\d+)\s+').firstMatch(equipString);
|
final plusMatch = RegExp(r'^([+-]?\d+)\s+').firstMatch(equipString);
|
||||||
@@ -296,13 +306,24 @@ class GameDataL10n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 기본 장비 이름 찾기 (가장 긴 매칭 우선)
|
// 2. 기본 장비 이름 찾기 (가장 긴 매칭 우선)
|
||||||
|
// 통합 맵 사용 (추가 번역 포함)
|
||||||
final Map<String, String> baseMap;
|
final Map<String, String> baseMap;
|
||||||
if (slotIndex == 0) {
|
if (isKo) {
|
||||||
baseMap = weaponTranslationsKo;
|
if (slotIndex == 0) {
|
||||||
} else if (slotIndex == 1) {
|
baseMap = weaponTranslationsKo;
|
||||||
baseMap = shieldTranslationsKo;
|
} else if (slotIndex == 1) {
|
||||||
|
baseMap = allShieldTranslationsKo;
|
||||||
|
} else {
|
||||||
|
baseMap = allArmorTranslationsKo;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
baseMap = armorTranslationsKo;
|
if (slotIndex == 0) {
|
||||||
|
baseMap = weaponTranslationsJa;
|
||||||
|
} else if (slotIndex == 1) {
|
||||||
|
baseMap = allShieldTranslationsJa;
|
||||||
|
} else {
|
||||||
|
baseMap = allArmorTranslationsJa;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String baseTranslated = remaining;
|
String baseTranslated = remaining;
|
||||||
@@ -326,14 +347,26 @@ class GameDataL10n {
|
|||||||
.where((s) => s.isNotEmpty)
|
.where((s) => s.isNotEmpty)
|
||||||
.toList();
|
.toList();
|
||||||
final translatedMods = modWords.map((mod) {
|
final translatedMods = modWords.map((mod) {
|
||||||
if (isWeapon) {
|
if (isKo) {
|
||||||
return offenseAttribTranslationsKo[mod] ??
|
if (isWeapon) {
|
||||||
offenseBadTranslationsKo[mod] ??
|
return offenseAttribTranslationsKo[mod] ??
|
||||||
mod;
|
offenseBadTranslationsKo[mod] ??
|
||||||
|
mod;
|
||||||
|
} else {
|
||||||
|
return defenseAttribTranslationsKo[mod] ??
|
||||||
|
defenseBadTranslationsKo[mod] ??
|
||||||
|
mod;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return defenseAttribTranslationsKo[mod] ??
|
if (isWeapon) {
|
||||||
defenseBadTranslationsKo[mod] ??
|
return offenseAttribTranslationsJa[mod] ??
|
||||||
mod;
|
offenseBadTranslationsJa[mod] ??
|
||||||
|
mod;
|
||||||
|
} else {
|
||||||
|
return defenseAttribTranslationsJa[mod] ??
|
||||||
|
defenseBadTranslationsJa[mod] ??
|
||||||
|
mod;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
@@ -353,15 +386,17 @@ class GameDataL10n {
|
|||||||
/// 예: "Golden Iterator of Compilation" → "컴파일의 황금 이터레이터"
|
/// 예: "Golden Iterator of Compilation" → "컴파일의 황금 이터레이터"
|
||||||
/// 예: "index out of bounds Array fragment" → "인덱스 초과의 배열 조각"
|
/// 예: "index out of bounds Array fragment" → "인덱스 초과의 배열 조각"
|
||||||
static String translateItemString(BuildContext context, String itemString) {
|
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"
|
// 1. specialItem 형식 체크: "Attrib Special of ItemOf"
|
||||||
// itemOfs에 있는 값으로 끝나는지 확인
|
// itemOfs에 있는 값으로 끝나는지 확인
|
||||||
final specialItemResult = _tryTranslateSpecialItem(itemString);
|
final specialItemResult = _tryTranslateSpecialItem(itemString, isKo);
|
||||||
if (specialItemResult != null) return specialItemResult;
|
if (specialItemResult != null) return specialItemResult;
|
||||||
|
|
||||||
// 2. 몬스터 드롭 형식 체크: "{monster_lowercase} {drop_ProperCase}"
|
// 2. 몬스터 드롭 형식 체크: "{monster_lowercase} {drop_ProperCase}"
|
||||||
final monsterDropResult = _tryTranslateMonsterDrop(itemString);
|
final monsterDropResult = _tryTranslateMonsterDrop(itemString, isKo);
|
||||||
if (monsterDropResult != null) return monsterDropResult;
|
if (monsterDropResult != null) return monsterDropResult;
|
||||||
|
|
||||||
// 3. interestingItem 형식: "Attrib Special" (2단어)
|
// 3. interestingItem 형식: "Attrib Special" (2단어)
|
||||||
@@ -370,21 +405,32 @@ class GameDataL10n {
|
|||||||
final attrib = words[0];
|
final attrib = words[0];
|
||||||
final special = words[1];
|
final special = words[1];
|
||||||
|
|
||||||
final attribKo = itemAttribTranslationsKo[attrib] ?? attrib;
|
if (isKo) {
|
||||||
final specialKo = specialTranslationsKo[special] ?? special;
|
final attribKo = itemAttribTranslationsKo[attrib] ?? attrib;
|
||||||
|
final specialKo = specialTranslationsKo[special] ?? special;
|
||||||
return '$attribKo $specialKo';
|
return '$attribKo $specialKo';
|
||||||
|
} else {
|
||||||
|
final attribJa = itemAttribTranslationsJa[attrib] ?? attrib;
|
||||||
|
final specialJa = specialTranslationsJa[special] ?? special;
|
||||||
|
return '$attribJa $specialJa';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 단일 단어 (boringItem 등) - 잡템 번역 시도
|
// 4. 단일 단어 (boringItem 등) - 잡템 번역 시도
|
||||||
return boringItemTranslationsKo[itemString] ??
|
if (isKo) {
|
||||||
dropItemTranslationsKo[itemString.toLowerCase()] ??
|
return boringItemTranslationsKo[itemString] ??
|
||||||
itemString;
|
allDropTranslationsKo[itemString.toLowerCase()] ??
|
||||||
|
itemString;
|
||||||
|
} else {
|
||||||
|
return boringItemTranslationsJa[itemString] ??
|
||||||
|
allDropTranslationsJa[itemString.toLowerCase()] ??
|
||||||
|
itemString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// specialItem 형식 번역 시도
|
/// specialItem 형식 번역 시도
|
||||||
/// "Attrib Special of ItemOf" → "ItemOf의 Attrib Special"
|
/// "Attrib Special of ItemOf" → "ItemOf의 Attrib Special"
|
||||||
static String? _tryTranslateSpecialItem(String itemString) {
|
static String? _tryTranslateSpecialItem(String itemString, bool isKo) {
|
||||||
// "of" 뒤의 부분이 itemOfs에 있는지 확인
|
// "of" 뒤의 부분이 itemOfs에 있는지 확인
|
||||||
final ofMatch = RegExp(r'^(.+)\s+of\s+(.+)$').firstMatch(itemString);
|
final ofMatch = RegExp(r'^(.+)\s+of\s+(.+)$').firstMatch(itemString);
|
||||||
if (ofMatch == null) return null;
|
if (ofMatch == null) return null;
|
||||||
@@ -393,7 +439,8 @@ class GameDataL10n {
|
|||||||
final afterOf = ofMatch.group(2)!;
|
final afterOf = ofMatch.group(2)!;
|
||||||
|
|
||||||
// afterOf가 itemOfs에 있어야 specialItem 형식
|
// afterOf가 itemOfs에 있어야 specialItem 형식
|
||||||
if (!itemOfsTranslationsKo.containsKey(afterOf)) return null;
|
final itemOfsMap = isKo ? itemOfsTranslationsKo : itemOfsTranslationsJa;
|
||||||
|
if (!itemOfsMap.containsKey(afterOf)) return null;
|
||||||
|
|
||||||
// beforeOf를 Attrib + Special로 분리
|
// beforeOf를 Attrib + Special로 분리
|
||||||
final words = beforeOf.split(' ');
|
final words = beforeOf.split(' ');
|
||||||
@@ -403,24 +450,32 @@ class GameDataL10n {
|
|||||||
final special = words.last;
|
final special = words.last;
|
||||||
|
|
||||||
// Attrib와 Special이 유효한지 확인
|
// Attrib와 Special이 유효한지 확인
|
||||||
if (!itemAttribTranslationsKo.containsKey(attrib) &&
|
final attribMap = isKo ? itemAttribTranslationsKo : itemAttribTranslationsJa;
|
||||||
!specialTranslationsKo.containsKey(special)) {
|
final specialMap = isKo ? specialTranslationsKo : specialTranslationsJa;
|
||||||
|
if (!attribMap.containsKey(attrib) && !specialMap.containsKey(special)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final attribKo = itemAttribTranslationsKo[attrib] ?? attrib;
|
final attribT = attribMap[attrib] ?? attrib;
|
||||||
final specialKo = specialTranslationsKo[special] ?? special;
|
final specialT = specialMap[special] ?? special;
|
||||||
final itemOfKo = itemOfsTranslationsKo[afterOf] ?? afterOf;
|
final itemOfT = itemOfsMap[afterOf] ?? afterOf;
|
||||||
|
|
||||||
return '$itemOfKo의 $attribKo $specialKo';
|
if (isKo) {
|
||||||
|
return '$itemOfT의 $attribT $specialT';
|
||||||
|
} else {
|
||||||
|
return '$itemOfTの$attribT $specialT';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 몬스터 드롭 형식 번역 시도
|
/// 몬스터 드롭 형식 번역 시도
|
||||||
/// "{monster_lowercase} {drop_ProperCase}" → "{몬스터}의 {드롭아이템}"
|
/// "{monster_lowercase} {drop_ProperCase}" → "{몬스터}의 {드롭아이템}"
|
||||||
static String? _tryTranslateMonsterDrop(String itemString) {
|
static String? _tryTranslateMonsterDrop(String itemString, bool isKo) {
|
||||||
// dropItemTranslationsKo에서 매칭되는 드롭 아이템 찾기
|
// 드롭 아이템 번역 맵 선택 (통합 맵 사용)
|
||||||
|
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 dropItem = entry.key;
|
||||||
final dropItemProperCase = _properCase(dropItem);
|
final dropItemProperCase = _properCase(dropItem);
|
||||||
|
|
||||||
@@ -443,10 +498,14 @@ class GameDataL10n {
|
|||||||
|
|
||||||
// 몬스터 이름 번역 (소문자를 원래 형태로 변환하여 찾기)
|
// 몬스터 이름 번역 (소문자를 원래 형태로 변환하여 찾기)
|
||||||
final monsterNameKey = _toTitleCase(monsterPart);
|
final monsterNameKey = _toTitleCase(monsterPart);
|
||||||
final monsterKo = monsterTranslationsKo[monsterNameKey] ?? monsterPart;
|
final monsterT = monsterMap[monsterNameKey] ?? monsterPart;
|
||||||
|
|
||||||
final dropKo = entry.value;
|
final dropT = entry.value;
|
||||||
return '$monsterKo의 $dropKo';
|
if (isKo) {
|
||||||
|
return '$monsterT의 $dropT';
|
||||||
|
} else {
|
||||||
|
return '$monsterTの$dropT';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user