Files
zeling_v2/Assets/Scripts/Localization/LocalizationManager.cs
2026-05-12 21:50:49 +08:00

180 lines
7.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Assets/Scripts/Localization/LocalizationManager.cs
// 本地化管理器(运行时 JSON 文件驱动)。
//
// 数据格式(放在 Resources/Localization/{Language}/{TableName}.json
// {
// "entries": [
// { "key": "ui_start", "value": "开始游戏" },
// { "key": "ui_settings", "value": "设置" }
// ]
// }
//
// 用法(静态 Facade保持调用兼容
// LocalizationManager.Get("ui_start") → "开始游戏"
// LocalizationManager.Get("dlg_hero", "Dialogue") → Dialogue 表中的对应文本
//
// 服务调用(通过 ServiceLocator
// ServiceLocator.GetOrDefault<ILocalizationService>()?.SetLanguage(Language.English)
using System;
using System.Collections.Generic;
using UnityEngine;
using BaseGames.Core;
using BaseGames.Core.Save;
namespace BaseGames.Localization
{
/// <summary>
/// 本地化管理器MonoBehaviour挂在 Persistent 场景)。
/// 实现 ILocalizationService + ISaveable通过 ServiceLocator 注册。
/// 语言偏好持久化到 SaveData.Settings.Language不使用 PlayerPrefs。
/// 保留静态 Get() Facade现有调用方无需修改。
/// </summary>
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<string, Dictionary<string, string>> _cache = new();
// ── 静态事件代理(向后兼容静态订阅方式)─────────────────────────────────
/// <summary>语言切换时触发(静态代理,可通过 LocalizationManager.OnLanguageChanged += 订阅)。</summary>
public static event Action<Language> OnLanguageChanged;
// ILocalizationService 显式实现:委托给静态事件
event Action<Language> ILocalizationService.OnLanguageChanged
{
add { OnLanguageChanged += value; }
remove { OnLanguageChanged -= value; }
}
// ── 生命周期 ──────────────────────────────────────────────────────────
private void Awake()
{
if (ServiceLocator.GetOrDefault<ILocalizationService>() != null) { Destroy(gameObject); return; }
ServiceLocator.Register<ILocalizationService>(this);
}
private void OnEnable()
{
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Register(this);
}
private void OnDisable()
{
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Unregister(this);
}
private void OnDestroy()
{
ServiceLocator.Unregister<ILocalizationService>(this);
}
// ── ILocalizationService ──────────────────────────────────────────────
public Language CurrentLanguage => _currentLanguage;
/// <summary>切换游戏语言并通知所有订阅者刷新文本。</summary>
public void SetLanguage(Language language)
{
if (_currentLanguage == language) return;
_currentLanguage = language;
OnLanguageChanged?.Invoke(language);
}
/// <summary>
/// 获取本地化字符串(显式接口实现)。
/// 查找顺序:当前语言 → 回退语言English→ 直接返回 key。
/// </summary>
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<Language>(data.Settings.Language, out var lang))
SetLanguage(lang);
}
// ── 静态 Facade保持现有调用方不变────────────────────────────────────
/// <summary>
/// 静态快捷获取本地化字符串。委托给 ILocalizationService 实例;服务未注册时直接返回 key。
/// </summary>
public static string Get(string key, string table = "UI")
=> ServiceLocator.GetOrDefault<ILocalizationService>()?.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;
}
/// <summary>
/// 从 Resources/Localization/{language}/{table}.json 加载字符串表。
/// 返回 null 表示文件不存在。
/// </summary>
private static Dictionary<string, string> LoadTable(Language language, string table)
{
string path = $"Localization/{language}/{table}";
var asset = Resources.Load<TextAsset>(path);
if (asset == null) return null;
var parsed = JsonUtility.FromJson<StringTableJson>(asset.text);
if (parsed?.entries == null) return null;
var dict = new Dictionary<string, string>(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<StringEntry> entries;
}
[Serializable]
private class StringEntry
{
public string key;
public string value;
}
}
}