// Assets/Scripts/Localization/LocalizationManager.cs // 本地化管理器(运行时 JSON 文件驱动)。 // // 数据格式(放在 Resources/Localization/{Language}/{TableName}.json): // { // "entries": [ // { "key": "ui_start", "value": "开始游戏" }, // { "key": "ui_settings", "value": "设置" } // ] // } // // 推荐用法(通过 ServiceLocator 获取 ILocalizationService 实例): // ServiceLocator.GetOrDefault()?.Get("ui_start") // ServiceLocator.GetOrDefault()?.SetLanguage(Language.English) // // 便捷静态方法(内部仍走 ServiceLocator,推荐在热路径之外使用): // LocalizationManager.Get("ui_start") // LocalizationManager.Get("dlg_hero", "Dialogue") using System; using System.Collections.Generic; using UnityEngine; using BaseGames.Core; using BaseGames.Core.Save; namespace BaseGames.Localization { /// /// 本地化管理器(MonoBehaviour,挂在 Persistent 场景)。 /// 实现 ILocalizationService + ISaveable,通过 ServiceLocator 注册。 /// 语言偏好持久化到 SaveData.Settings.Language,不使用 PlayerPrefs。 /// public class LocalizationManager : MonoBehaviour, ILocalizationService, ISaveable { // 默认语言:回退链:当前语言 → 英语 → 直接返回 key private Language _currentLanguage = Language.ChineseSimplified; private readonly Language _fallbackLanguage = Language.English; // 双层缓存:languageKey("ChineseSimplified/UI") → (key → value) private readonly Dictionary> _cache = new(); // ILocalizationService 实例事件 private event Action _onLanguageChanged; event Action ILocalizationService.OnLanguageChanged { add => _onLanguageChanged += value; remove => _onLanguageChanged -= value; } // ── 生命周期 ────────────────────────────────────────────────────────── private void Awake() { if (ServiceLocator.GetOrDefault() != null) { Destroy(gameObject); return; } ServiceLocator.Register(this); } private void OnEnable() { ServiceLocator.GetOrDefault()?.Register(this); } private void OnDisable() { ServiceLocator.GetOrDefault()?.Unregister(this); } private void OnDestroy() { ServiceLocator.Unregister(this); } // ── ILocalizationService ────────────────────────────────────────────── public Language CurrentLanguage => _currentLanguage; /// 切换游戏语言并通知所有订阅者刷新文本。 public void SetLanguage(Language language) { if (_currentLanguage == language) return; _currentLanguage = language; _onLanguageChanged?.Invoke(language); } /// /// 获取本地化字符串(显式接口实现)。 /// 查找顺序:当前语言 → 回退语言(English)→ 直接返回 key。 /// string ILocalizationService.Get(string key, string table) { if (string.IsNullOrEmpty(key)) return string.Empty; // 1. 尝试当前语言 if (TryGetFromTable(_currentLanguage, table, key, out string text)) return text; // 2. 回退到 English if (_currentLanguage != _fallbackLanguage && TryGetFromTable(_fallbackLanguage, table, key, out text)) return text; // 3. 最终回退:原始 key return key; } // ── ISaveable ───────────────────────────────────────────────────────── public void OnSave(SaveData data) { if (data?.Settings == null) return; data.Settings.Language = _currentLanguage.ToString(); } public void OnLoad(SaveData data) { if (data?.Settings == null || string.IsNullOrEmpty(data.Settings.Language)) return; if (Enum.TryParse(data.Settings.Language, out var lang)) SetLanguage(lang); } // ── 静态便捷方法 ───────────────────────────────────────────────────────── /// /// 静态快捷获取本地化字符串。委托给 ILocalizationService 实例;服务未注册时直接返回 key。 /// public static string Get(string key, string table = "UI") => ServiceLocator.GetOrDefault()?.Get(key, table) ?? key; // ── 内部 ───────────────────────────────────────────────────────────── private bool TryGetFromTable(Language language, string table, string key, out string value) { var cacheKey = $"{language}/{table}"; if (!_cache.TryGetValue(cacheKey, out var dict)) { dict = LoadTable(language, table); _cache[cacheKey] = dict; // 即使加载失败也存入空字典,避免每帧重试 } if (dict != null && dict.TryGetValue(key, out value)) return true; value = null; return false; } /// /// 从 Resources/Localization/{language}/{table}.json 加载字符串表。 /// 返回 null 表示文件不存在。 /// private static Dictionary LoadTable(Language language, string table) { string path = $"Localization/{language}/{table}"; var asset = Resources.Load(path); if (asset == null) return null; var parsed = JsonUtility.FromJson(asset.text); if (parsed?.entries == null) return null; var dict = new Dictionary(parsed.entries.Count, StringComparer.Ordinal); foreach (var entry in parsed.entries) if (!string.IsNullOrEmpty(entry.key)) dict[entry.key] = entry.value ?? string.Empty; return dict; } // ── 序列化辅助类型 ──────────────────────────────────────────────────── [Serializable] private class StringTableJson { public List entries; } [Serializable] private class StringEntry { public string key; public string value; } } }