using System; using System.Collections.Generic; using System.IO; using System.Text; #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; namespace Eden.AutoMorpher { public enum Language { Korean = 0, Japanese = 1, English = 2, } [Serializable] public class LocalizationEntry { public string key; public string value; } [Serializable] public class LocalizationFile { public LocalizationEntry[] entries; } #if UNITY_EDITOR [InitializeOnLoad] #endif public static class LanguageManager { private const string PREF_KEY_LANG = "EdenAutoMorpher_Language"; private const string PATH_KO = "Language/Language_ko"; private const string PATH_JA = "Language/Language_ja"; private const string PATH_EN = "Language/Language_en"; private static Language _currentLanguage = Language.Korean; private static Dictionary _koTable; private static Dictionary _jaTable; private static Dictionary _enTable; public static Language CurrentLanguage { get => _currentLanguage; set { if (_currentLanguage == value) return; _currentLanguage = value; #if UNITY_EDITOR EditorPrefs.SetInt(PREF_KEY_LANG, (int)_currentLanguage); #else PlayerPrefs.SetInt(PREF_KEY_LANG, (int)_currentLanguage); PlayerPrefs.Save(); #endif } } static LanguageManager() { // Editor 시작 시 언어 로드 #if UNITY_EDITOR int saved = EditorPrefs.GetInt(PREF_KEY_LANG, (int)Language.Korean); #else int saved = PlayerPrefs.GetInt(PREF_KEY_LANG, (int)Language.Korean); #endif _currentLanguage = (Language)Mathf.Clamp(saved, 0, 2); } /// /// 현재 언어에서 문자열 가져오기. /// key 없으면 fallback → 다른 언어 → 최종적으로 key 자체 반환. /// public static string Get(string key) { if (string.IsNullOrEmpty(key)) return string.Empty; // 현재 언어 테이블에서 먼저 검색 if (TryGetFromCurrentLanguage(key, out string value)) return value; return FormatKeyNotFound(key); } /// /// 언어별로 다른 키를 쓰고 싶을 때 (번호 기반 등)도 가능하도록 /// 번호를 바로 key 문자열로 변환해서 사용하는 버전. /// public static string GetById(int id) { // 예: "ID.1001" 이런 식으로 통일하면 관리가 편함 string key = $"ID.{id}"; return Get(key); } public static void SetLanguage(Language lang) { CurrentLanguage = lang; } // ================== 내부 구현 ================== private static bool TryGetFromCurrentLanguage(string key, out string value) { var table = GetTable(_currentLanguage); if (table != null && table.TryGetValue(key, out value)) return true; value = null; return false; } private static bool TryGetFromOtherLanguages(string key, out string value) { // 현재 언어 제외한 나머지 순회 foreach (Language lang in System.Enum.GetValues(typeof(Language))) { if (lang == _currentLanguage) continue; var table = GetTable(lang); if (table != null && table.TryGetValue(key, out value)) return true; } value = null; return false; } private static Dictionary GetTable(Language lang) { switch (lang) { case Language.Korean: if (_koTable == null) _koTable = LoadTable(PATH_KO); return _koTable; case Language.Japanese: if (_jaTable == null) _jaTable = LoadTable(PATH_JA); return _jaTable; case Language.English: if (_enTable == null) _enTable = LoadTable(PATH_EN); return _enTable; default: return null; } } // fileName: "lang_ko", "lang_ja", "lang_en" 처럼 사용 private static Dictionary LoadTable(string fileName) { var dict = new Dictionary(); #if UNITY_EDITOR // 스크립트 기준 Language 폴더 아래의 json 경로 string assetPath = GetLanguageJsonAssetPath(fileName); // Assets/... 형식 경로를 실제 디스크 경로로 변환 string fullPath = Path.GetFullPath(assetPath); if (!File.Exists(fullPath)) { Debug.LogWarning($"[AutoMorpherLanguage] Localization file not found: {fullPath}"); return dict; } string jsonText; try { // ✅ 강제로 UTF-8 로 읽기 (BOM 유무 상관 없음) jsonText = File.ReadAllText(fullPath, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } catch (System.Exception ex) { Debug.LogError($"[AutoMorpherLanguage] Failed to read localization file: {fullPath}\n{ex}"); return dict; } try { var file = JsonUtility.FromJson(jsonText); if (file?.entries != null) { foreach (var entry in file.entries) { if (string.IsNullOrEmpty(entry.key)) continue; dict[entry.key] = entry.value ?? string.Empty; } } } catch (System.Exception ex) { Debug.LogError($"[AutoMorpherLanguage] Failed to parse localization file: {fullPath}\n{ex}"); } #else // 런타임 빌드에서 쓸 계획이 없으면 비워둬도 됨 #endif return dict; } private static string FormatKeyNotFound(string key) { // 먼저 현재 언어의 "Error.KeyNotFound" 가져오기 if (TryGetFromCurrentLanguage("Error.KeyNotFound", out string template)) return template.Replace("{0}", key); // JSON 자체가 없어서 템플릿도 없는 경우 (최후 fallback) switch (CurrentLanguage) { case Language.Korean: return $"[KO] 해당 키를 찾을 수 없습니다: {key}"; case Language.Japanese: return $"[JP] キーが見つかりません: {key}"; default: return $"[EN] Could not find key: {key}"; } } public static string GetLanguageFolderPath() { string scriptDir = "Assets\\@Eden_Tools\\Eden_AutoMorpher\\LanguageManager\\Language"; #if UNITY_EDITOR // AutoMorpherLanguage 스크립트의 GUID 찾기 string[] guids = AssetDatabase.FindAssets("LanguageManager t:MonoScript"); if (guids == null || guids.Length == 0) { Debug.LogError("[LanguageManager] Could not find LanguageManager.cs script."); return "Assets"; // 최소 fallback } string scriptPath = AssetDatabase.GUIDToAssetPath(guids[0]); // 예: Assets/@Eden_Tools/.../AutoMorpherLanguage.cs scriptDir = Path.GetDirectoryName(scriptPath); // 그 폴더 // 폴더가 없으면 에디터에서 자동 생성 if (!Directory.Exists(scriptDir)) { Directory.CreateDirectory(scriptDir); AssetDatabase.Refresh(); } #endif // Unity Asset 경로 포맷으로 정리 scriptDir = scriptDir.Replace("\\", "/"); return scriptDir; } public static string GetLanguageJsonAssetPath(string fileNameWithoutExt) { string folder = GetLanguageFolderPath(); string path = Path.Combine(folder, fileNameWithoutExt + ".json"); return path.Replace("\\", "/"); } public static string GetFormat(string key, params object[] args) { string raw = Get(key); if (string.IsNullOrEmpty(raw)) return string.Empty; try { return string.Format(raw, args); } catch (Exception ex) { Debug.LogError($"[LanguageManager] Format failed for key: {key}\n{ex}"); return raw; // 최소한 원문이라도 보여주기 } } } }