Files
zeling_v2/Assets/Scripts/Localization/LocalizationManager.cs
2026-05-13 09:19:54 +08:00

177 lines
7.3 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": "设置" }
// ]
// }
//
// 推荐用法(通过 ServiceLocator 获取 ILocalizationService 实例):
// ServiceLocator.GetOrDefault<ILocalizationService>()?.Get("ui_start")
// ServiceLocator.GetOrDefault<ILocalizationService>()?.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
{
/// <summary>
/// 本地化管理器MonoBehaviour挂在 Persistent 场景)。
/// 实现 ILocalizationService + ISaveable通过 ServiceLocator 注册。
/// 语言偏好持久化到 SaveData.Settings.Language不使用 PlayerPrefs。
/// </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();
// ILocalizationService 实例事件
private event Action<Language> _onLanguageChanged;
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);
}
// ── 静态便捷方法 ─────────────────────────────────────────────────────────
/// <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;
}
}
}