UI系统组件
This commit is contained in:
@@ -2,7 +2,8 @@ namespace BaseGames.Localization
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏支持的语言列表。
|
||||
/// 添加新语言时同步在 Resources/Localization/ 下创建对应子目录和 JSON 表文件。
|
||||
/// 添加新语言时同步在 Assets/_Game/Data/Localization/ 下创建对应子目录和 JSON 表文件
|
||||
/// (推荐用「BaseGames / Localization / 表格编辑器」新建并自动注册 Addressables)。
|
||||
/// </summary>
|
||||
public enum Language
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Assets/Scripts/Localization/LocalizationManager.cs
|
||||
// 本地化管理器(运行时 JSON 文件驱动)。
|
||||
//
|
||||
// 数据格式(放在 Resources/Localization/{Language}/{TableName}.json):
|
||||
// 数据格式(放在 Assets/_Game/Data/Localization/{Language}/{TableName}.json):
|
||||
// {
|
||||
// "entries": [
|
||||
// { "key": "ui_start", "value": "开始游戏" },
|
||||
@@ -317,7 +317,7 @@ namespace BaseGames.Localization
|
||||
return cached;
|
||||
|
||||
// 编辑器按资产路径读取(与运行时 Addressable 地址对应的物理位置)
|
||||
string path = $"Assets/_Game/Data/Localization/{language}/{table}.json";
|
||||
string path = LocalizationPaths.AssetPath(language, table);
|
||||
var asset = UnityEditor.AssetDatabase.LoadAssetAtPath<TextAsset>(path);
|
||||
var dict = asset == null ? null : ParseTableText(asset.text);
|
||||
s_editorPreviewCache[cacheKey] = dict;
|
||||
@@ -349,7 +349,7 @@ namespace BaseGames.Localization
|
||||
/// </summary>
|
||||
private static Dictionary<string, string> LoadTable(Language language, string table)
|
||||
{
|
||||
string address = $"Localization/{language}/{table}";
|
||||
string address = LocalizationPaths.Address(language, table);
|
||||
// 统一经 AssetLoader 门面:缺键安全检查 + 同步加载 + 释放
|
||||
if (!AssetLoader.Exists(address, typeof(TextAsset))) return null;
|
||||
|
||||
@@ -360,21 +360,12 @@ namespace BaseGames.Localization
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 JSON 文本解析为 key→value 字典(内部共享解析逻辑)。
|
||||
/// 将 JSON 文本解析为 key→value 字典。
|
||||
/// 委托给 <see cref="LocalizationSerializer.Parse"/>,与编辑器写盘共用同一格式逻辑。
|
||||
/// 返回 null 表示格式无效。
|
||||
/// </summary>
|
||||
private static Dictionary<string, string> ParseTableText(string jsonText)
|
||||
{
|
||||
var parsed = JsonUtility.FromJson<StringTableJson>(jsonText);
|
||||
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;
|
||||
}
|
||||
=> LocalizationSerializer.Parse(jsonText);
|
||||
|
||||
// ── 缓存键(值类型,消除字符串插值 GC)──────────────────────────────
|
||||
private readonly struct CacheKey : IEquatable<CacheKey>
|
||||
@@ -398,20 +389,5 @@ namespace BaseGames.Localization
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine((int)_language, _table);
|
||||
}
|
||||
|
||||
// ── 序列化辅助类型 ────────────────────────────────────────────────────
|
||||
|
||||
[Serializable]
|
||||
private class StringTableJson
|
||||
{
|
||||
public List<StringEntry> entries;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class StringEntry
|
||||
{
|
||||
public string key;
|
||||
public string value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
Assets/_Game/Scripts/Localization/LocalizationPaths.cs
Normal file
40
Assets/_Game/Scripts/Localization/LocalizationPaths.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace BaseGames.Localization
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地化资源路径 / Addressable 地址的唯一真相源。
|
||||
///
|
||||
/// 运行时与编辑器工具的所有路径、地址都必须经此类构造,禁止再硬编码
|
||||
/// "Assets/_Game/Data/Localization" 或 "Localization/{lang}/{table}" 字符串。
|
||||
///
|
||||
/// 设计要点:
|
||||
/// - 纯静态、无 UnityEditor 依赖,放运行时 asmdef(<c>BaseGames.Localization</c>),
|
||||
/// 使运行时加载(<see cref="LocalizationManager"/>)与编辑器工具共用同一套路径逻辑。
|
||||
/// - 物理 JSON 路径(<see cref="AssetPath"/>)供编辑器直读 / 写盘;
|
||||
/// Addressable 地址(<see cref="Address"/>)供运行时 <c>AssetLoader</c> 加载。
|
||||
/// </summary>
|
||||
public static class LocalizationPaths
|
||||
{
|
||||
/// <summary>本地化 JSON 数据根目录(项目资产相对路径)。</summary>
|
||||
public const string DataRoot = "Assets/_Game/Data/Localization";
|
||||
|
||||
/// <summary>CSV 导入导出目录(供 Excel 往返)。</summary>
|
||||
public const string ExportRoot = "Assets/_Game/Localization/Export";
|
||||
|
||||
/// <summary>指定语言的子目录,如 <c>Assets/_Game/Data/Localization/English</c>。</summary>
|
||||
public static string LanguageFolder(Language language) => $"{DataRoot}/{language}";
|
||||
|
||||
/// <summary>指定语言 + 表的 JSON 资产路径。</summary>
|
||||
public static string AssetPath(Language language, string table)
|
||||
=> $"{DataRoot}/{language}/{table}.json";
|
||||
|
||||
/// <summary>
|
||||
/// 指定语言 + 表的 Addressable 地址(运行时加载用)。
|
||||
/// 必须与 <see cref="AssetPath"/> 指向的文件注册的地址一致。
|
||||
/// </summary>
|
||||
public static string Address(Language language, string table)
|
||||
=> $"Localization/{language}/{table}";
|
||||
|
||||
/// <summary>指定表的 CSV 导出路径。</summary>
|
||||
public static string CsvPath(string table) => $"{ExportRoot}/{table}.csv";
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Localization/LocalizationPaths.cs.meta
Normal file
11
Assets/_Game/Scripts/Localization/LocalizationPaths.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b557437701451774a81e3932e3486ff8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
79
Assets/_Game/Scripts/Localization/LocalizationSerializer.cs
Normal file
79
Assets/_Game/Scripts/Localization/LocalizationSerializer.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Localization
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地化表 JSON 的对称解析 / 序列化(唯一格式真相源)。
|
||||
///
|
||||
/// 表格式:<c>{ "entries": [ { "key": "...", "value": "..." } ] }</c>
|
||||
///
|
||||
/// 运行时加载(<see cref="LocalizationManager"/>)与编辑器写盘(<c>LocalizationFileIO</c>)
|
||||
/// 都经此类,确保读写格式永远一致,杜绝"格式漂移"。
|
||||
/// </summary>
|
||||
public static class LocalizationSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// 将表 JSON 文本解析为 key→value 字典。
|
||||
/// 返回 null 表示格式无效(entries 缺失)。空 key 条目被跳过。
|
||||
/// </summary>
|
||||
public static Dictionary<string, string> Parse(string jsonText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonText)) return null;
|
||||
|
||||
StringTableJson parsed;
|
||||
try { parsed = JsonUtility.FromJson<StringTableJson>(jsonText); }
|
||||
catch { return null; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 key→value 字典序列化为表 JSON 文本(<c>{entries:[…]}</c> 格式,pretty print)。
|
||||
/// 该输出可被 <see cref="Parse"/> 与运行时加载器无损还原。
|
||||
/// </summary>
|
||||
/// <param name="dict">要序列化的字典。</param>
|
||||
/// <param name="sortKeys">是否按 key 的序数顺序排序(默认 true,减少版本控制 diff 噪声)。</param>
|
||||
public static string Serialize(IReadOnlyDictionary<string, string> dict, bool sortKeys = true)
|
||||
{
|
||||
var table = new StringTableJson { entries = new List<StringEntry>(dict?.Count ?? 0) };
|
||||
if (dict != null)
|
||||
{
|
||||
IEnumerable<string> keys = dict.Keys;
|
||||
if (sortKeys)
|
||||
{
|
||||
var sorted = new List<string>(dict.Keys);
|
||||
sorted.Sort(StringComparer.Ordinal);
|
||||
keys = sorted;
|
||||
}
|
||||
foreach (var key in keys)
|
||||
table.entries.Add(new StringEntry { key = key, value = dict[key] ?? string.Empty });
|
||||
}
|
||||
|
||||
// JsonUtility 正确转义引号/反斜杠/换行,且保留中日韩字符原文(不转 \uXXXX)。
|
||||
return JsonUtility.ToJson(table, prettyPrint: true);
|
||||
}
|
||||
|
||||
// ── 序列化辅助类型(运行时与编辑器共用)────────────────────────────────
|
||||
[Serializable]
|
||||
internal class StringTableJson
|
||||
{
|
||||
public List<StringEntry> entries;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class StringEntry
|
||||
{
|
||||
public string key;
|
||||
public string value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 501e8b363022d9e4d8a69b399e6cdeb3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -5,7 +5,8 @@ namespace BaseGames.Localization
|
||||
/// 所有调用 <see cref="ILocalizationService.Get"/> 或 <see cref="LocalizationManager.Get"/> 时
|
||||
/// 必须引用此类的常量,禁止直接硬编码表名字符串。
|
||||
///
|
||||
/// 新增表时:在此追加常量,并在 Resources/Localization/{Language}/ 下创建同名 JSON 文件。
|
||||
/// 新增表时:在此追加常量,并用「BaseGames / Localization / 表格编辑器」新建同名表
|
||||
/// (自动在 Assets/_Game/Data/Localization/{Language}/ 下创建 JSON 并注册 Addressables)。
|
||||
/// </summary>
|
||||
public static class LocalizationTable
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user