mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-26 05:48:46 +08:00
Compare commits
No commits in common. "ba8369c8b32bb6b11d2953ad4e64ba73fa7d6cee" and "aee13c3c1d588a36567568b4215931c6a404f680" have entirely different histories.
ba8369c8b3
...
aee13c3c1d
@ -1,22 +0,0 @@
|
|||||||
namespace GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化格式化器接口
|
|
||||||
/// </summary>
|
|
||||||
public interface ILocalizationFormatter
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 格式化器名称
|
|
||||||
/// </summary>
|
|
||||||
string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 尝试格式化值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="format">格式字符串</param>
|
|
||||||
/// <param name="value">要格式化的值</param>
|
|
||||||
/// <param name="provider">格式提供者</param>
|
|
||||||
/// <param name="result">格式化结果</param>
|
|
||||||
/// <returns>是否成功格式化</returns>
|
|
||||||
bool TryFormat(string format, object value, IFormatProvider? provider, out string result);
|
|
||||||
}
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using GFramework.Core.Abstractions.Systems;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化管理器接口
|
|
||||||
/// </summary>
|
|
||||||
public interface ILocalizationManager : ISystem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 当前语言代码
|
|
||||||
/// </summary>
|
|
||||||
string CurrentLanguage { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前文化信息
|
|
||||||
/// </summary>
|
|
||||||
CultureInfo CurrentCulture { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 可用语言列表
|
|
||||||
/// </summary>
|
|
||||||
IReadOnlyList<string> AvailableLanguages { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置当前语言
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="languageCode">语言代码</param>
|
|
||||||
void SetLanguage(string languageCode);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取本地化表
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tableName">表名</param>
|
|
||||||
/// <returns>本地化表</returns>
|
|
||||||
ILocalizationTable GetTable(string tableName);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取本地化文本
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="table">表名</param>
|
|
||||||
/// <param name="key">键名</param>
|
|
||||||
/// <returns>本地化文本</returns>
|
|
||||||
string GetText(string table, string key);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取本地化字符串(支持变量和格式化)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="table">表名</param>
|
|
||||||
/// <param name="key">键名</param>
|
|
||||||
/// <returns>本地化字符串</returns>
|
|
||||||
ILocalizationString GetString(string table, string key);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 尝试获取本地化文本
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="table">表名</param>
|
|
||||||
/// <param name="key">键名</param>
|
|
||||||
/// <param name="text">输出文本</param>
|
|
||||||
/// <returns>是否成功获取</returns>
|
|
||||||
bool TryGetText(string table, string key, out string text);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 注册格式化器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">格式化器名称</param>
|
|
||||||
/// <param name="formatter">格式化器实例</param>
|
|
||||||
void RegisterFormatter(string name, ILocalizationFormatter formatter);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取格式化器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">格式化器名称</param>
|
|
||||||
/// <returns>格式化器实例,如果不存在则返回 null</returns>
|
|
||||||
ILocalizationFormatter? GetFormatter(string name);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 订阅语言变化事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="callback">回调函数</param>
|
|
||||||
void SubscribeToLanguageChange(Action<string> callback);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 取消订阅语言变化事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="callback">回调函数</param>
|
|
||||||
void UnsubscribeFromLanguageChange(Action<string> callback);
|
|
||||||
}
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
namespace GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化字符串接口(支持变量和格式化)
|
|
||||||
/// </summary>
|
|
||||||
public interface ILocalizationString
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 表名
|
|
||||||
/// </summary>
|
|
||||||
string Table { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 键名
|
|
||||||
/// </summary>
|
|
||||||
string Key { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 添加变量
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">变量名</param>
|
|
||||||
/// <param name="value">变量值</param>
|
|
||||||
/// <returns>当前实例(支持链式调用)</returns>
|
|
||||||
ILocalizationString WithVariable(string name, object value);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 批量添加变量
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="variables">变量数组</param>
|
|
||||||
/// <returns>当前实例(支持链式调用)</returns>
|
|
||||||
ILocalizationString WithVariables(params (string name, object value)[] variables);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 格式化并返回最终文本
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>格式化后的文本</returns>
|
|
||||||
string Format();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取原始文本(不进行格式化)
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>原始文本</returns>
|
|
||||||
string GetRaw();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查键是否存在
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>是否存在</returns>
|
|
||||||
bool Exists();
|
|
||||||
}
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
namespace GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化表接口
|
|
||||||
/// </summary>
|
|
||||||
public interface ILocalizationTable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 表名
|
|
||||||
/// </summary>
|
|
||||||
string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 语言代码
|
|
||||||
/// </summary>
|
|
||||||
string Language { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回退表(当前表中找不到键时使用)
|
|
||||||
/// </summary>
|
|
||||||
ILocalizationTable? Fallback { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取原始文本(不进行格式化)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">键名</param>
|
|
||||||
/// <returns>原始文本</returns>
|
|
||||||
string GetRawText(string key);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查是否包含指定键
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">键名</param>
|
|
||||||
/// <returns>是否包含</returns>
|
|
||||||
bool ContainsKey(string key);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取所有键
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>键集合</returns>
|
|
||||||
IEnumerable<string> GetKeys();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 合并覆盖数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="overrides">覆盖数据</param>
|
|
||||||
void Merge(IReadOnlyDictionary<string, string> overrides);
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
namespace GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化配置
|
|
||||||
/// </summary>
|
|
||||||
public class LocalizationConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 默认语言代码
|
|
||||||
/// </summary>
|
|
||||||
public string DefaultLanguage { get; set; } = "eng";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 回退语言代码(当目标语言缺少键时使用)
|
|
||||||
/// </summary>
|
|
||||||
public string FallbackLanguage { get; set; } = "eng";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化文件路径(Godot 资源路径)
|
|
||||||
/// </summary>
|
|
||||||
public string LocalizationPath { get; set; } = "res://localization";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户覆盖文件路径(用于热更新和自定义翻译)
|
|
||||||
/// </summary>
|
|
||||||
public string OverridePath { get; set; } = "user://localization_override";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否启用热重载(监视覆盖文件变化)
|
|
||||||
/// </summary>
|
|
||||||
public bool EnableHotReload { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否在加载时验证本地化文件
|
|
||||||
/// </summary>
|
|
||||||
public bool ValidateOnLoad { get; set; } = true;
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
namespace GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化异常基类
|
|
||||||
/// </summary>
|
|
||||||
public class LocalizationException : Exception
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化本地化异常
|
|
||||||
/// </summary>
|
|
||||||
public LocalizationException()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化本地化异常
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">异常消息</param>
|
|
||||||
public LocalizationException(string message) : base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化本地化异常
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">异常消息</param>
|
|
||||||
/// <param name="innerException">内部异常</param>
|
|
||||||
public LocalizationException(string message, Exception innerException) : base(message, innerException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
namespace GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化键未找到异常
|
|
||||||
/// </summary>
|
|
||||||
public class LocalizationKeyNotFoundException : LocalizationException
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化键未找到异常
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tableName">表名</param>
|
|
||||||
/// <param name="key">键名</param>
|
|
||||||
public LocalizationKeyNotFoundException(string tableName, string key)
|
|
||||||
: base($"Localization key '{key}' not found in table '{tableName}'")
|
|
||||||
{
|
|
||||||
TableName = tableName;
|
|
||||||
Key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 表名
|
|
||||||
/// </summary>
|
|
||||||
public string TableName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 键名
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; }
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
namespace GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化表未找到异常
|
|
||||||
/// </summary>
|
|
||||||
public class LocalizationTableNotFoundException : LocalizationException
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化表未找到异常
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tableName">表名</param>
|
|
||||||
public LocalizationTableNotFoundException(string tableName)
|
|
||||||
: base($"Localization table '{tableName}' not found")
|
|
||||||
{
|
|
||||||
TableName = tableName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 表名
|
|
||||||
/// </summary>
|
|
||||||
public string TableName { get; }
|
|
||||||
}
|
|
||||||
@ -1,8 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TestTargetFrameworks Condition="'$(TestTargetFrameworks)' == ''">net10.0</TestTargetFrameworks>
|
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
|
||||||
<TargetFrameworks>$(TestTargetFrameworks)</TargetFrameworks>
|
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|||||||
@ -1,142 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using GFramework.Core.Abstractions.Localization;
|
|
||||||
using GFramework.Core.Localization;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Tests.Localization;
|
|
||||||
|
|
||||||
[TestFixture]
|
|
||||||
public class LocalizationIntegrationTests
|
|
||||||
{
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_testDataPath = Path.Combine(Path.GetTempPath(), $"gframework_localization_{Guid.NewGuid():N}");
|
|
||||||
CreateTestLocalizationFiles(_testDataPath);
|
|
||||||
|
|
||||||
var config = new LocalizationConfig
|
|
||||||
{
|
|
||||||
DefaultLanguage = "eng",
|
|
||||||
FallbackLanguage = "eng",
|
|
||||||
LocalizationPath = _testDataPath,
|
|
||||||
EnableHotReload = false,
|
|
||||||
ValidateOnLoad = false
|
|
||||||
};
|
|
||||||
|
|
||||||
_manager = new LocalizationManager(config);
|
|
||||||
_manager.Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
[TearDown]
|
|
||||||
public void TearDown()
|
|
||||||
{
|
|
||||||
if (Directory.Exists(_testDataPath))
|
|
||||||
{
|
|
||||||
Directory.Delete(_testDataPath, recursive: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocalizationManager? _manager;
|
|
||||||
private string _testDataPath = null!;
|
|
||||||
|
|
||||||
private static void CreateTestLocalizationFiles(string rootPath)
|
|
||||||
{
|
|
||||||
var engPath = Path.Combine(rootPath, "eng");
|
|
||||||
var zhsPath = Path.Combine(rootPath, "zhs");
|
|
||||||
Directory.CreateDirectory(engPath);
|
|
||||||
Directory.CreateDirectory(zhsPath);
|
|
||||||
|
|
||||||
File.WriteAllText(Path.Combine(engPath, "common.json"), """
|
|
||||||
{
|
|
||||||
"game.title": "My Game",
|
|
||||||
"ui.message.welcome": "Welcome, {playerName}!",
|
|
||||||
"status.health": "Health: {current}/{max}"
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
|
|
||||||
File.WriteAllText(Path.Combine(zhsPath, "common.json"), """
|
|
||||||
{
|
|
||||||
"game.title": "我的游戏",
|
|
||||||
"ui.message.welcome": "欢迎, {playerName}!",
|
|
||||||
"status.health": "生命值: {current}/{max}"
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void GetText_ShouldReturnEnglishText()
|
|
||||||
{
|
|
||||||
// Act
|
|
||||||
var title = _manager!.GetText("common", "game.title");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.That(title, Is.EqualTo("My Game"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void GetString_WithVariable_ShouldFormatCorrectly()
|
|
||||||
{
|
|
||||||
// Act
|
|
||||||
var message = _manager!.GetString("common", "ui.message.welcome")
|
|
||||||
.WithVariable("playerName", "Alice")
|
|
||||||
.Format();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.That(message, Is.EqualTo("Welcome, Alice!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void SetLanguage_ShouldSwitchToChineseText()
|
|
||||||
{
|
|
||||||
// Act
|
|
||||||
_manager!.SetLanguage("zhs");
|
|
||||||
var title = _manager.GetText("common", "game.title");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.That(title, Is.EqualTo("我的游戏"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void GetString_WithMultipleVariables_ShouldFormatCorrectly()
|
|
||||||
{
|
|
||||||
// Act
|
|
||||||
var health = _manager!.GetString("common", "status.health")
|
|
||||||
.WithVariable("current", 80)
|
|
||||||
.WithVariable("max", 100)
|
|
||||||
.Format();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.That(health, Is.EqualTo("Health: 80/100"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void LanguageChange_ShouldTriggerCallback()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var callbackTriggered = false;
|
|
||||||
var newLanguage = string.Empty;
|
|
||||||
|
|
||||||
_manager!.SubscribeToLanguageChange(lang =>
|
|
||||||
{
|
|
||||||
callbackTriggered = true;
|
|
||||||
newLanguage = lang;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
_manager.SetLanguage("zhs");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.That(callbackTriggered, Is.True);
|
|
||||||
Assert.That(newLanguage, Is.EqualTo("zhs"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void AvailableLanguages_ShouldContainBothLanguages()
|
|
||||||
{
|
|
||||||
// Act
|
|
||||||
var languages = _manager!.AvailableLanguages;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.That(languages, Contains.Item("eng"));
|
|
||||||
Assert.That(languages, Contains.Item("zhs"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
using GFramework.Core.Localization;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Tests.Localization;
|
|
||||||
|
|
||||||
[TestFixture]
|
|
||||||
public class LocalizationTableTests
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public void GetRawText_ShouldReturnCorrectText()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var data = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
["test.key"] = "Test Value"
|
|
||||||
};
|
|
||||||
var table = new LocalizationTable("test", "eng", data);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = table.GetRawText("test.key");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.That(result, Is.EqualTo("Test Value"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void GetRawText_WithFallback_ShouldReturnFallbackValue()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var fallbackData = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
["test.key"] = "Fallback Value"
|
|
||||||
};
|
|
||||||
var fallbackTable = new LocalizationTable("test", "eng", fallbackData);
|
|
||||||
|
|
||||||
var data = new Dictionary<string, string>();
|
|
||||||
var table = new LocalizationTable("test", "zhs", data, fallbackTable);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = table.GetRawText("test.key");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.That(result, Is.EqualTo("Fallback Value"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ContainsKey_ShouldReturnTrue_WhenKeyExists()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var data = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
["test.key"] = "Test Value"
|
|
||||||
};
|
|
||||||
var table = new LocalizationTable("test", "eng", data);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = table.ContainsKey("test.key");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.That(result, Is.True);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Merge_ShouldOverrideExistingValues()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var data = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
["test.key"] = "Original Value"
|
|
||||||
};
|
|
||||||
var table = new LocalizationTable("test", "eng", data);
|
|
||||||
|
|
||||||
var overrides = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
["test.key"] = "Override Value"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
table.Merge(overrides);
|
|
||||||
var result = table.GetRawText("test.key");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.That(result, Is.EqualTo("Override Value"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -18,11 +18,6 @@ public class PriorityEvent<T> : IEvent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<EventHandler> _handlers = new();
|
private readonly List<EventHandler> _handlers = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保护处理器集合的并发访问
|
|
||||||
/// </summary>
|
|
||||||
private readonly object _syncRoot = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 标记事件是否已被处理(用于 UntilHandled 传播模式)
|
/// 标记事件是否已被处理(用于 UntilHandled 传播模式)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -57,13 +52,10 @@ public class PriorityEvent<T> : IEvent
|
|||||||
public IUnRegister Register(Action<T> onEvent, int priority)
|
public IUnRegister Register(Action<T> onEvent, int priority)
|
||||||
{
|
{
|
||||||
var handler = new EventHandler(onEvent, priority);
|
var handler = new EventHandler(onEvent, priority);
|
||||||
lock (_syncRoot)
|
|
||||||
{
|
|
||||||
_handlers.Add(handler);
|
_handlers.Add(handler);
|
||||||
|
|
||||||
// 按优先级降序排序(高优先级在前)
|
// 按优先级降序排序(高优先级在前)
|
||||||
_handlers.Sort((a, b) => b.Priority.CompareTo(a.Priority));
|
_handlers.Sort((a, b) => b.Priority.CompareTo(a.Priority));
|
||||||
}
|
|
||||||
|
|
||||||
return new DefaultUnRegister(() => UnRegister(onEvent));
|
return new DefaultUnRegister(() => UnRegister(onEvent));
|
||||||
}
|
}
|
||||||
@ -73,12 +65,9 @@ public class PriorityEvent<T> : IEvent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="onEvent">需要被注销的事件处理方法</param>
|
/// <param name="onEvent">需要被注销的事件处理方法</param>
|
||||||
public void UnRegister(Action<T> onEvent)
|
public void UnRegister(Action<T> onEvent)
|
||||||
{
|
|
||||||
lock (_syncRoot)
|
|
||||||
{
|
{
|
||||||
_handlers.RemoveAll(h => h.Handler == onEvent);
|
_handlers.RemoveAll(h => h.Handler == onEvent);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册一个上下文事件监听器,并指定优先级
|
/// 注册一个上下文事件监听器,并指定优先级
|
||||||
@ -89,13 +78,10 @@ public class PriorityEvent<T> : IEvent
|
|||||||
public IUnRegister RegisterWithContext(Action<EventContext<T>> onEvent, int priority = 0)
|
public IUnRegister RegisterWithContext(Action<EventContext<T>> onEvent, int priority = 0)
|
||||||
{
|
{
|
||||||
var handler = new ContextEventHandler(onEvent, priority);
|
var handler = new ContextEventHandler(onEvent, priority);
|
||||||
lock (_syncRoot)
|
|
||||||
{
|
|
||||||
_contextHandlers.Add(handler);
|
_contextHandlers.Add(handler);
|
||||||
|
|
||||||
// 按优先级降序排序(高优先级在前)
|
// 按优先级降序排序(高优先级在前)
|
||||||
_contextHandlers.Sort((a, b) => b.Priority.CompareTo(a.Priority));
|
_contextHandlers.Sort((a, b) => b.Priority.CompareTo(a.Priority));
|
||||||
}
|
|
||||||
|
|
||||||
return new DefaultUnRegister(() => UnRegisterContext(onEvent));
|
return new DefaultUnRegister(() => UnRegisterContext(onEvent));
|
||||||
}
|
}
|
||||||
@ -105,12 +91,9 @@ public class PriorityEvent<T> : IEvent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="onEvent">需要被注销的事件处理方法</param>
|
/// <param name="onEvent">需要被注销的事件处理方法</param>
|
||||||
public void UnRegisterContext(Action<EventContext<T>> onEvent)
|
public void UnRegisterContext(Action<EventContext<T>> onEvent)
|
||||||
{
|
|
||||||
lock (_syncRoot)
|
|
||||||
{
|
{
|
||||||
_contextHandlers.RemoveAll(h => h.Handler == onEvent);
|
_contextHandlers.RemoveAll(h => h.Handler == onEvent);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 触发事件处理程序,并指定传播模式
|
/// 触发事件处理程序,并指定传播模式
|
||||||
@ -189,7 +172,8 @@ public class PriorityEvent<T> : IEvent
|
|||||||
/// <param name="t">事件参数</param>
|
/// <param name="t">事件参数</param>
|
||||||
private void TriggerHighest(T t)
|
private void TriggerHighest(T t)
|
||||||
{
|
{
|
||||||
var (normalSnapshot, contextSnapshot) = CreateSnapshots();
|
var normalSnapshot = _handlers.ToArray();
|
||||||
|
var contextSnapshot = _contextHandlers.ToArray();
|
||||||
var highestPriority = GetHighestPriority(normalSnapshot, contextSnapshot);
|
var highestPriority = GetHighestPriority(normalSnapshot, contextSnapshot);
|
||||||
|
|
||||||
if (highestPriority != int.MinValue)
|
if (highestPriority != int.MinValue)
|
||||||
@ -207,7 +191,8 @@ public class PriorityEvent<T> : IEvent
|
|||||||
private List<(int Priority, Action? Handler, Action<EventContext<T>>? ContextHandler, bool IsContext)>
|
private List<(int Priority, Action? Handler, Action<EventContext<T>>? ContextHandler, bool IsContext)>
|
||||||
MergeAndSortHandlers(T t)
|
MergeAndSortHandlers(T t)
|
||||||
{
|
{
|
||||||
var (normalSnapshot, contextSnapshot) = CreateSnapshots();
|
var normalSnapshot = _handlers.ToArray();
|
||||||
|
var contextSnapshot = _contextHandlers.ToArray();
|
||||||
// 使用快照避免迭代期间修改
|
// 使用快照避免迭代期间修改
|
||||||
return normalSnapshot
|
return normalSnapshot
|
||||||
.Select(h => (h.Priority, Handler: (Action?)(() => h.Handler.Invoke(t)),
|
.Select(h => (h.Priority, Handler: (Action?)(() => h.Handler.Invoke(t)),
|
||||||
@ -274,20 +259,9 @@ public class PriorityEvent<T> : IEvent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>监听器总数量</returns>
|
/// <returns>监听器总数量</returns>
|
||||||
public int GetListenerCount()
|
public int GetListenerCount()
|
||||||
{
|
|
||||||
lock (_syncRoot)
|
|
||||||
{
|
{
|
||||||
return _handlers.Count + _contextHandlers.Count;
|
return _handlers.Count + _contextHandlers.Count;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private (EventHandler[] NormalHandlers, ContextEventHandler[] ContextHandlers) CreateSnapshots()
|
|
||||||
{
|
|
||||||
lock (_syncRoot)
|
|
||||||
{
|
|
||||||
return (_handlers.ToArray(), _contextHandlers.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 事件处理器包装类,包含处理器和优先级
|
/// 事件处理器包装类,包含处理器和优先级
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
using GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Localization.Formatters;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 条件格式化器
|
|
||||||
/// 格式: {condition:if:trueText|falseText}
|
|
||||||
/// 示例: {upgraded:if:Upgraded|Normal}
|
|
||||||
/// </summary>
|
|
||||||
public class ConditionalFormatter : ILocalizationFormatter
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string Name => "if";
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool TryFormat(string format, object value, IFormatProvider? provider, out string result)
|
|
||||||
{
|
|
||||||
result = string.Empty;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var parts = format.Split('|');
|
|
||||||
|
|
||||||
if (parts.Length != 2)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var condition = value is bool b ? b : Convert.ToBoolean(value);
|
|
||||||
result = condition ? parts[0] : parts[1];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
using GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Localization.Formatters;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 复数格式化器
|
|
||||||
/// 格式: {count:plural:singular|plural}
|
|
||||||
/// 示例: {count:plural:item|items}
|
|
||||||
/// </summary>
|
|
||||||
public class PluralFormatter : ILocalizationFormatter
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string Name => "plural";
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool TryFormat(string format, object value, IFormatProvider? provider, out string result)
|
|
||||||
{
|
|
||||||
result = string.Empty;
|
|
||||||
|
|
||||||
if (value is not IConvertible convertible)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var number = convertible.ToDecimal(provider);
|
|
||||||
var parts = format.Split('|');
|
|
||||||
|
|
||||||
if (parts.Length != 2)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = Math.Abs(number) == 1 ? parts[0] : parts[1];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,308 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text.Json;
|
|
||||||
using GFramework.Core.Abstractions.Localization;
|
|
||||||
using GFramework.Core.Systems;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化管理器实现
|
|
||||||
/// </summary>
|
|
||||||
public class LocalizationManager : AbstractSystem, ILocalizationManager
|
|
||||||
{
|
|
||||||
private readonly LocalizationConfig _config;
|
|
||||||
private readonly Dictionary<string, ILocalizationFormatter> _formatters;
|
|
||||||
private readonly List<Action<string>> _languageChangeCallbacks;
|
|
||||||
private readonly Dictionary<string, Dictionary<string, ILocalizationTable>> _tables;
|
|
||||||
private List<string> _availableLanguages;
|
|
||||||
private CultureInfo _currentCulture;
|
|
||||||
private string _currentLanguage;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化本地化管理器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="config">配置</param>
|
|
||||||
public LocalizationManager(LocalizationConfig? config = null)
|
|
||||||
{
|
|
||||||
_config = config ?? new LocalizationConfig();
|
|
||||||
_tables = new Dictionary<string, Dictionary<string, ILocalizationTable>>();
|
|
||||||
_formatters = new Dictionary<string, ILocalizationFormatter>();
|
|
||||||
_languageChangeCallbacks = new List<Action<string>>();
|
|
||||||
_currentLanguage = _config.DefaultLanguage;
|
|
||||||
_currentCulture = GetCultureInfo(_currentLanguage);
|
|
||||||
_availableLanguages = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string CurrentLanguage => _currentLanguage;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public CultureInfo CurrentCulture => _currentCulture;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public IReadOnlyList<string> AvailableLanguages => _availableLanguages;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void SetLanguage(string languageCode)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(languageCode))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(languageCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_currentLanguage == languageCode)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadLanguage(languageCode);
|
|
||||||
_currentLanguage = languageCode;
|
|
||||||
_currentCulture = GetCultureInfo(languageCode);
|
|
||||||
|
|
||||||
// 触发语言变化回调
|
|
||||||
TriggerLanguageChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public ILocalizationTable GetTable(string tableName)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(tableName))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(tableName));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_tables.TryGetValue(_currentLanguage, out var languageTables))
|
|
||||||
{
|
|
||||||
throw new LocalizationTableNotFoundException(tableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!languageTables.TryGetValue(tableName, out var table))
|
|
||||||
{
|
|
||||||
throw new LocalizationTableNotFoundException(tableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string GetText(string table, string key)
|
|
||||||
{
|
|
||||||
return GetTable(table).GetRawText(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public ILocalizationString GetString(string table, string key)
|
|
||||||
{
|
|
||||||
return new LocalizationString(this, table, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool TryGetText(string table, string key, out string text)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
text = GetText(table, key);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (LocalizationException)
|
|
||||||
{
|
|
||||||
// 只捕获本地化相关的异常(键不存在、表不存在等)
|
|
||||||
text = string.Empty;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void RegisterFormatter(string name, ILocalizationFormatter formatter)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
_formatters[name] = formatter ?? throw new ArgumentNullException(nameof(formatter));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public ILocalizationFormatter? GetFormatter(string name)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _formatters.TryGetValue(name, out var formatter) ? formatter : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void SubscribeToLanguageChange(Action<string> callback)
|
|
||||||
{
|
|
||||||
if (callback == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_languageChangeCallbacks.Contains(callback))
|
|
||||||
{
|
|
||||||
_languageChangeCallbacks.Add(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void UnsubscribeFromLanguageChange(Action<string> callback)
|
|
||||||
{
|
|
||||||
if (callback == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
_languageChangeCallbacks.Remove(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void OnInit()
|
|
||||||
{
|
|
||||||
// 扫描可用语言
|
|
||||||
ScanAvailableLanguages();
|
|
||||||
|
|
||||||
// 加载默认语言
|
|
||||||
LoadLanguage(_config.DefaultLanguage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void OnDestroy()
|
|
||||||
{
|
|
||||||
_tables.Clear();
|
|
||||||
_formatters.Clear();
|
|
||||||
_languageChangeCallbacks.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 扫描可用语言
|
|
||||||
/// </summary>
|
|
||||||
private void ScanAvailableLanguages()
|
|
||||||
{
|
|
||||||
_availableLanguages.Clear();
|
|
||||||
|
|
||||||
var localizationPath = _config.LocalizationPath;
|
|
||||||
if (!Directory.Exists(localizationPath))
|
|
||||||
{
|
|
||||||
_availableLanguages.Add(_config.DefaultLanguage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var directories = Directory.GetDirectories(localizationPath);
|
|
||||||
foreach (var dir in directories)
|
|
||||||
{
|
|
||||||
var languageCode = Path.GetFileName(dir);
|
|
||||||
if (!string.IsNullOrEmpty(languageCode))
|
|
||||||
{
|
|
||||||
_availableLanguages.Add(languageCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_availableLanguages.Count == 0)
|
|
||||||
{
|
|
||||||
_availableLanguages.Add(_config.DefaultLanguage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载语言
|
|
||||||
/// </summary>
|
|
||||||
private void LoadLanguage(string languageCode)
|
|
||||||
{
|
|
||||||
if (_tables.ContainsKey(languageCode))
|
|
||||||
{
|
|
||||||
return; // 已加载
|
|
||||||
}
|
|
||||||
|
|
||||||
var languageTables = new Dictionary<string, ILocalizationTable>();
|
|
||||||
|
|
||||||
// 加载回退语言(如果不是默认语言)
|
|
||||||
Dictionary<string, ILocalizationTable>? fallbackTables = null;
|
|
||||||
if (languageCode != _config.FallbackLanguage)
|
|
||||||
{
|
|
||||||
LoadLanguage(_config.FallbackLanguage);
|
|
||||||
_tables.TryGetValue(_config.FallbackLanguage, out fallbackTables);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载目标语言
|
|
||||||
var languagePath = Path.Combine(_config.LocalizationPath, languageCode);
|
|
||||||
if (Directory.Exists(languagePath))
|
|
||||||
{
|
|
||||||
var jsonFiles = Directory.GetFiles(languagePath, "*.json");
|
|
||||||
foreach (var file in jsonFiles)
|
|
||||||
{
|
|
||||||
var tableName = Path.GetFileNameWithoutExtension(file);
|
|
||||||
var data = LoadJsonFile(file);
|
|
||||||
|
|
||||||
ILocalizationTable? fallback = null;
|
|
||||||
fallbackTables?.TryGetValue(tableName, out fallback);
|
|
||||||
|
|
||||||
languageTables[tableName] = new LocalizationTable(tableName, languageCode, data, fallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_tables[languageCode] = languageTables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载 JSON 文件
|
|
||||||
/// </summary>
|
|
||||||
private static Dictionary<string, string> LoadJsonFile(string filePath)
|
|
||||||
{
|
|
||||||
var json = File.ReadAllText(filePath);
|
|
||||||
var data = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
|
|
||||||
return data ?? new Dictionary<string, string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取文化信息
|
|
||||||
/// </summary>
|
|
||||||
private static CultureInfo GetCultureInfo(string languageCode)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 尝试映射常见的语言代码
|
|
||||||
var cultureCode = languageCode switch
|
|
||||||
{
|
|
||||||
"eng" => "en-US",
|
|
||||||
"zhs" => "zh-CN",
|
|
||||||
"zht" => "zh-TW",
|
|
||||||
"jpn" => "ja-JP",
|
|
||||||
"kor" => "ko-KR",
|
|
||||||
"fra" => "fr-FR",
|
|
||||||
"deu" => "de-DE",
|
|
||||||
"spa" => "es-ES",
|
|
||||||
"rus" => "ru-RU",
|
|
||||||
_ => languageCode
|
|
||||||
};
|
|
||||||
|
|
||||||
return new CultureInfo(cultureCode);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return CultureInfo.InvariantCulture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 触发语言变化事件
|
|
||||||
/// </summary>
|
|
||||||
private void TriggerLanguageChange()
|
|
||||||
{
|
|
||||||
foreach (var callback in _languageChangeCallbacks.ToList())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
callback(_currentLanguage);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// 忽略回调异常
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,237 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
using GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化字符串实现
|
|
||||||
/// </summary>
|
|
||||||
public class LocalizationString : ILocalizationString
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 匹配 {variableName} 或 {variableName:formatter:args} 的正则表达式模式
|
|
||||||
/// </summary>
|
|
||||||
private const string FormatVariablePattern =
|
|
||||||
@"\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([a-zA-Z_][a-zA-Z0-9_]*)(?::([^}]+))?)?\}";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 预编译的静态正则表达式,用于格式化字符串中的变量替换
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Regex FormatVariableRegex =
|
|
||||||
new(FormatVariablePattern, RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
|
||||||
|
|
||||||
private readonly ILocalizationManager _manager;
|
|
||||||
private readonly Dictionary<string, object> _variables;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化本地化字符串
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="manager">本地化管理器实例</param>
|
|
||||||
/// <param name="table">本地化表名,用于定位本地化资源表</param>
|
|
||||||
/// <param name="key">本地化键名,用于在表中定位具体的本地化文本</param>
|
|
||||||
/// <exception cref="ArgumentNullException">当 manager、table 或 key 为 null 时抛出</exception>
|
|
||||||
public LocalizationString(ILocalizationManager manager, string table, string key)
|
|
||||||
{
|
|
||||||
_manager = manager ?? throw new ArgumentNullException(nameof(manager));
|
|
||||||
Table = table ?? throw new ArgumentNullException(nameof(table));
|
|
||||||
Key = key ?? throw new ArgumentNullException(nameof(key));
|
|
||||||
_variables = new Dictionary<string, object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string Table { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string Key { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 添加单个变量到本地化字符串中
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">变量名称,用于在模板中匹配对应的占位符</param>
|
|
||||||
/// <param name="value">变量值,将被转换为字符串并替换到对应位置</param>
|
|
||||||
/// <returns>返回当前的 LocalizationString 实例,支持链式调用</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">当 name 为 null 时抛出</exception>
|
|
||||||
public ILocalizationString WithVariable(string name, object value)
|
|
||||||
{
|
|
||||||
if (name == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
_variables[name] = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 批量添加多个变量到本地化字符串中
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="variables">变量元组数组,每个元组包含变量名称和对应的值</param>
|
|
||||||
/// <returns>返回当前的 LocalizationString 实例,支持链式调用</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">当 variables 为 null 时抛出</exception>
|
|
||||||
public ILocalizationString WithVariables(params (string name, object value)[] variables)
|
|
||||||
{
|
|
||||||
if (variables == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(variables));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (name, value) in variables)
|
|
||||||
{
|
|
||||||
WithVariable(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 格式化本地化字符串,将模板中的变量占位符替换为实际值
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>格式化后的完整字符串。如果本地化文本不存在,则返回 "[Table.Key]" 格式的占位符</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// 支持两种格式:
|
|
||||||
/// 1. {variableName} - 简单变量替换
|
|
||||||
/// 2. {variableName:formatter:args} - 使用格式化器进行格式化
|
|
||||||
/// </remarks>
|
|
||||||
public string Format()
|
|
||||||
{
|
|
||||||
var rawText = GetRaw();
|
|
||||||
return FormatString(rawText, _variables, _manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取原始的本地化文本,不进行任何变量替换
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>本地化文本。如果在本地化管理器中未找到对应的文本,则返回 "[Table.Key]" 格式的占位符</returns>
|
|
||||||
public string GetRaw()
|
|
||||||
{
|
|
||||||
if (!_manager.TryGetText(Table, Key, out var text))
|
|
||||||
{
|
|
||||||
return $"[{Table}.{Key}]";
|
|
||||||
}
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查当前本地化键是否存在于本地化管理器中
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>如果存在返回 true;否则返回 false</returns>
|
|
||||||
public bool Exists()
|
|
||||||
{
|
|
||||||
return _manager.TryGetText(Table, Key, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 格式化字符串(支持变量替换和格式化器)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="template">包含占位符的模板字符串</param>
|
|
||||||
/// <param name="variables">包含变量名称和值的字典</param>
|
|
||||||
/// <param name="manager">本地化管理器实例,用于获取格式化器</param>
|
|
||||||
/// <returns>格式化后的字符串。如果模板为空或 null,则直接返回原模板</returns>
|
|
||||||
private static string FormatString(
|
|
||||||
string template,
|
|
||||||
Dictionary<string, object> variables,
|
|
||||||
ILocalizationManager manager)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(template))
|
|
||||||
{
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用预编译的静态正则表达式匹配 {variableName} 或 {variableName:formatter:args}
|
|
||||||
return FormatVariableRegex.Replace(template, match => FormatMatch(match, variables, manager));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 处理单个正则表达式匹配项,根据是否有格式化器决定如何处理变量值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="match">正则表达式匹配结果</param>
|
|
||||||
/// <param name="variables">变量字典</param>
|
|
||||||
/// <param name="manager">本地化管理器实例</param>
|
|
||||||
/// <returns>替换后的字符串。如果变量不存在则返回原始匹配值;如果有格式化器则尝试格式化,失败则使用默认格式化</returns>
|
|
||||||
private static string FormatMatch(
|
|
||||||
Match match,
|
|
||||||
Dictionary<string, object> variables,
|
|
||||||
ILocalizationManager manager)
|
|
||||||
{
|
|
||||||
var variableName = match.Groups[1].Value;
|
|
||||||
if (!variables.TryGetValue(variableName, out var value))
|
|
||||||
{
|
|
||||||
return match.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
var formatterName = GetOptionalGroupValue(match, 2);
|
|
||||||
if (string.IsNullOrEmpty(formatterName))
|
|
||||||
{
|
|
||||||
return FormatValue(value, manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TryFormatValue(match, value, formatterName, manager, out var result)
|
|
||||||
? result
|
|
||||||
: FormatValue(value, manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 尝试使用指定的格式化器格式化变量值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="match">正则表达式匹配结果,用于获取格式化参数</param>
|
|
||||||
/// <param name="value">要格式化的变量值</param>
|
|
||||||
/// <param name="formatterName">格式化器名称</param>
|
|
||||||
/// <param name="manager">本地化管理器实例</param>
|
|
||||||
/// <param name="result">格式化后的结果字符串</param>
|
|
||||||
/// <returns>如果格式化成功返回 true;否则返回 false,此时 result 为空字符串</returns>
|
|
||||||
private static bool TryFormatValue(
|
|
||||||
Match match,
|
|
||||||
object value,
|
|
||||||
string formatterName,
|
|
||||||
ILocalizationManager manager,
|
|
||||||
out string result)
|
|
||||||
{
|
|
||||||
var formatterArgs = GetOptionalGroupValue(match, 3) ?? string.Empty;
|
|
||||||
if (GetFormatter(manager, formatterName) is { } formatter &&
|
|
||||||
formatter.TryFormat(formatterArgs, value, manager.CurrentCulture, out result))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = string.Empty;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对变量值进行默认格式化,不使用自定义格式化器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">要格式化的值</param>
|
|
||||||
/// <param name="manager">本地化管理器实例,提供当前文化信息</param>
|
|
||||||
/// <returns>格式化后的字符串。如果值实现 IFormattable 接口则使用其 ToString 方法,否则调用默认的 ToString 方法</returns>
|
|
||||||
private static string FormatValue(object value, ILocalizationManager manager)
|
|
||||||
{
|
|
||||||
return value switch
|
|
||||||
{
|
|
||||||
IFormattable formattable => formattable.ToString(null, manager.CurrentCulture),
|
|
||||||
_ => value.ToString() ?? string.Empty
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取正则表达式匹配组中的可选值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="match">正则表达式匹配结果</param>
|
|
||||||
/// <param name="groupIndex">要获取的组索引</param>
|
|
||||||
/// <returns>如果该组匹配成功则返回其值;否则返回 null</returns>
|
|
||||||
private static string? GetOptionalGroupValue(Match match, int groupIndex)
|
|
||||||
{
|
|
||||||
return match.Groups[groupIndex].Success ? match.Groups[groupIndex].Value : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 从本地化管理器获取指定名称的格式化器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="manager">本地化管理器实例</param>
|
|
||||||
/// <param name="name">格式化器名称</param>
|
|
||||||
/// <returns>如果找到对应的格式化器则返回;否则返回 null</returns>
|
|
||||||
private static ILocalizationFormatter? GetFormatter(ILocalizationManager manager, string name)
|
|
||||||
{
|
|
||||||
return manager.GetFormatter(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
using GFramework.Core.Abstractions.Localization;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 本地化表实现
|
|
||||||
/// </summary>
|
|
||||||
public class LocalizationTable : ILocalizationTable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 存储原始本地化数据的字典
|
|
||||||
/// </summary>
|
|
||||||
private readonly Dictionary<string, string> _data;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 存储覆盖数据的字典,优先级高于原始数据
|
|
||||||
/// </summary>
|
|
||||||
private readonly Dictionary<string, string> _overrides;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化本地化表
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">表名</param>
|
|
||||||
/// <param name="language">语言代码</param>
|
|
||||||
/// <param name="data">数据字典</param>
|
|
||||||
/// <param name="fallback">回退表</param>
|
|
||||||
public LocalizationTable(
|
|
||||||
string name,
|
|
||||||
string language,
|
|
||||||
IReadOnlyDictionary<string, string> data,
|
|
||||||
ILocalizationTable? fallback = null)
|
|
||||||
{
|
|
||||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
|
||||||
Language = language ?? throw new ArgumentNullException(nameof(language));
|
|
||||||
_data = new Dictionary<string, string>(data);
|
|
||||||
_overrides = new Dictionary<string, string>();
|
|
||||||
Fallback = fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取本地化表的名称
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取语言代码
|
|
||||||
/// </summary>
|
|
||||||
public string Language { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取回退表,当当前表找不到键时用于查找
|
|
||||||
/// </summary>
|
|
||||||
public ILocalizationTable? Fallback { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取指定键的原始文本内容
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">要查找的本地化键</param>
|
|
||||||
/// <returns>找到的本地化文本值</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">当 key 为 null 时抛出</exception>
|
|
||||||
/// <exception cref="LocalizationKeyNotFoundException">当键在表中不存在且无回退表时抛出</exception>
|
|
||||||
public string GetRawText(string key)
|
|
||||||
{
|
|
||||||
if (key == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 优先使用覆盖数据
|
|
||||||
if (_overrides.TryGetValue(key, out var overrideValue))
|
|
||||||
{
|
|
||||||
return overrideValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 然后使用原始数据
|
|
||||||
if (_data.TryGetValue(key, out var value))
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最后尝试回退表
|
|
||||||
if (Fallback is { } fb && fb.ContainsKey(key))
|
|
||||||
{
|
|
||||||
return fb.GetRawText(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new LocalizationKeyNotFoundException(Name, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查是否包含指定的键
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">要检查的本地化键</param>
|
|
||||||
/// <returns>如果存在则返回 true,否则返回 false</returns>
|
|
||||||
public bool ContainsKey(string key)
|
|
||||||
{
|
|
||||||
return _overrides.ContainsKey(key)
|
|
||||||
|| _data.ContainsKey(key)
|
|
||||||
|| (Fallback is { } fb && fb.ContainsKey(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取所有可用的本地化键集合
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>包含所有键的可枚举集合</returns>
|
|
||||||
public IEnumerable<string> GetKeys()
|
|
||||||
{
|
|
||||||
var keys = new HashSet<string>(_data.Keys);
|
|
||||||
keys.UnionWith(_overrides.Keys);
|
|
||||||
|
|
||||||
if (Fallback != null)
|
|
||||||
{
|
|
||||||
keys.UnionWith(Fallback.GetKeys());
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 合并覆盖数据到当前表
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="overrides">要合并的覆盖数据字典</param>
|
|
||||||
/// <exception cref="ArgumentNullException">当 overrides 为 null 时抛出</exception>
|
|
||||||
public void Merge(IReadOnlyDictionary<string, string> overrides)
|
|
||||||
{
|
|
||||||
if (overrides == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(overrides));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (key, value) in overrides)
|
|
||||||
{
|
|
||||||
_overrides[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TestTargetFrameworks Condition="'$(TestTargetFrameworks)' == ''">net10.0</TestTargetFrameworks>
|
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
|
||||||
<TargetFrameworks>$(TestTargetFrameworks)</TargetFrameworks>
|
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TestTargetFrameworks Condition="'$(TestTargetFrameworks)' == ''">net10.0</TestTargetFrameworks>
|
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
|
||||||
<TargetFrameworks>$(TestTargetFrameworks)</TargetFrameworks>
|
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TestTargetFrameworks Condition="'$(TestTargetFrameworks)' == ''">net10.0</TestTargetFrameworks>
|
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
|
||||||
<TargetFrameworks>$(TestTargetFrameworks)</TargetFrameworks>
|
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|||||||
@ -66,23 +66,6 @@ IoC 容器命名空间。
|
|||||||
| `IObjectPool<T>` | 对象池接口 |
|
| `IObjectPool<T>` | 对象池接口 |
|
||||||
| `ObjectPool<T>` | 对象池实现 |
|
| `ObjectPool<T>` | 对象池实现 |
|
||||||
|
|
||||||
### GFramework.Core.Localization
|
|
||||||
|
|
||||||
本地化系统命名空间。
|
|
||||||
|
|
||||||
#### 主要类型
|
|
||||||
|
|
||||||
| 类型 | 说明 |
|
|
||||||
|--------------------------|----------|
|
|
||||||
| `ILocalizationManager` | 本地化管理器接口 |
|
|
||||||
| `ILocalizationTable` | 本地化表接口 |
|
|
||||||
| `ILocalizationString` | 本地化字符串接口 |
|
|
||||||
| `ILocalizationFormatter` | 格式化器接口 |
|
|
||||||
| `LocalizationConfig` | 本地化配置类 |
|
|
||||||
| `LocalizationManager` | 本地化管理器实现 |
|
|
||||||
| `LocalizationTable` | 本地化表实现 |
|
|
||||||
| `LocalizationString` | 本地化字符串实现 |
|
|
||||||
|
|
||||||
## 常用 API
|
## 常用 API
|
||||||
|
|
||||||
### Architecture
|
### Architecture
|
||||||
@ -264,99 +247,6 @@ public class BindableProperty<T>
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### ILocalizationManager
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public interface ILocalizationManager : ISystem
|
|
||||||
{
|
|
||||||
// 获取当前语言代码
|
|
||||||
string CurrentLanguage { get; }
|
|
||||||
|
|
||||||
// 获取当前文化信息
|
|
||||||
CultureInfo CurrentCulture { get; }
|
|
||||||
|
|
||||||
// 获取可用语言列表
|
|
||||||
IReadOnlyList<string> AvailableLanguages { get; }
|
|
||||||
|
|
||||||
// 设置当前语言
|
|
||||||
void SetLanguage(string languageCode);
|
|
||||||
|
|
||||||
// 获取本地化表
|
|
||||||
ILocalizationTable GetTable(string tableName);
|
|
||||||
|
|
||||||
// 获取本地化文本
|
|
||||||
string GetText(string table, string key);
|
|
||||||
|
|
||||||
// 获取本地化字符串(支持变量)
|
|
||||||
ILocalizationString GetString(string table, string key);
|
|
||||||
|
|
||||||
// 尝试获取本地化文本
|
|
||||||
bool TryGetText(string table, string key, out string text);
|
|
||||||
|
|
||||||
// 注册格式化器
|
|
||||||
void RegisterFormatter(string name, ILocalizationFormatter formatter);
|
|
||||||
|
|
||||||
// 订阅语言变化事件
|
|
||||||
void SubscribeToLanguageChange(Action<string> callback);
|
|
||||||
|
|
||||||
// 取消订阅语言变化事件
|
|
||||||
void UnsubscribeFromLanguageChange(Action<string> callback);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ILocalizationString
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public interface ILocalizationString
|
|
||||||
{
|
|
||||||
// 获取表名
|
|
||||||
string Table { get; }
|
|
||||||
|
|
||||||
// 获取键名
|
|
||||||
string Key { get; }
|
|
||||||
|
|
||||||
// 添加变量
|
|
||||||
ILocalizationString WithVariable(string name, object value);
|
|
||||||
|
|
||||||
// 批量添加变量
|
|
||||||
ILocalizationString WithVariables(params (string name, object value)[] variables);
|
|
||||||
|
|
||||||
// 格式化并返回文本
|
|
||||||
string Format();
|
|
||||||
|
|
||||||
// 获取原始文本
|
|
||||||
string GetRaw();
|
|
||||||
|
|
||||||
// 检查键是否存在
|
|
||||||
bool Exists();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### LocalizationConfig
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class LocalizationConfig
|
|
||||||
{
|
|
||||||
// 默认语言代码
|
|
||||||
public string DefaultLanguage { get; set; } = "eng";
|
|
||||||
|
|
||||||
// 回退语言代码
|
|
||||||
public string FallbackLanguage { get; set; } = "eng";
|
|
||||||
|
|
||||||
// 本地化文件路径
|
|
||||||
public string LocalizationPath { get; set; } = "res://localization";
|
|
||||||
|
|
||||||
// 用户覆盖路径
|
|
||||||
public string OverridePath { get; set; } = "user://localization_override";
|
|
||||||
|
|
||||||
// 是否启用热重载
|
|
||||||
public bool EnableHotReload { get; set; } = true;
|
|
||||||
|
|
||||||
// 是否在加载时验证
|
|
||||||
public bool ValidateOnLoad { get; set; } = true;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 扩展方法
|
## 扩展方法
|
||||||
|
|
||||||
### 架构扩展
|
### 架构扩展
|
||||||
@ -516,36 +406,6 @@ public class PlayerSystem : AbstractSystem
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 使用本地化
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 初始化本地化管理器
|
|
||||||
var config = new LocalizationConfig
|
|
||||||
{
|
|
||||||
DefaultLanguage = "eng",
|
|
||||||
LocalizationPath = "res://localization"
|
|
||||||
};
|
|
||||||
var locManager = new LocalizationManager(config);
|
|
||||||
locManager.Initialize();
|
|
||||||
|
|
||||||
// 获取简单文本
|
|
||||||
string title = locManager.GetText("common", "game.title");
|
|
||||||
|
|
||||||
// 使用变量
|
|
||||||
var message = locManager.GetString("common", "ui.message.welcome")
|
|
||||||
.WithVariable("playerName", "Alice")
|
|
||||||
.Format();
|
|
||||||
|
|
||||||
// 切换语言
|
|
||||||
locManager.SetLanguage("zhs");
|
|
||||||
|
|
||||||
// 监听语言变化
|
|
||||||
locManager.SubscribeToLanguageChange(language =>
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Language changed to: {language}");
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
更多详情请查看各模块的详细文档。
|
更多详情请查看各模块的详细文档。
|
||||||
|
|||||||
@ -421,7 +421,6 @@ public class PlayerController : IController
|
|||||||
| **extensions** | 扩展方法,简化 API 调用 | [查看](./extensions) |
|
| **extensions** | 扩展方法,简化 API 调用 | [查看](./extensions) |
|
||||||
| **logging** | 日志系统,记录运行日志 | [查看](./logging) |
|
| **logging** | 日志系统,记录运行日志 | [查看](./logging) |
|
||||||
| **environment** | 环境接口,提供运行环境信息 | [查看](./environment) |
|
| **environment** | 环境接口,提供运行环境信息 | [查看](./environment) |
|
||||||
| **localization** | 本地化系统,多语言支持 | [查看](./localization) |
|
|
||||||
|
|
||||||
## 组件联动
|
## 组件联动
|
||||||
|
|
||||||
|
|||||||
@ -1,501 +0,0 @@
|
|||||||
# Localization 本地化系统
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
Localization 包提供了完整的多语言本地化支持,实现了游戏文本的国际化管理。通过本地化系统,可以轻松实现多语言切换、动态变量替换、回退机制等功能。
|
|
||||||
|
|
||||||
本地化系统是 GFramework 架构中的 System 层组件,与其他系统无缝集成,支持类型安全的 API 和流畅的使用体验。
|
|
||||||
|
|
||||||
## 核心接口
|
|
||||||
|
|
||||||
### ILocalizationManager
|
|
||||||
|
|
||||||
本地化管理器接口,继承自 `ISystem`,提供本地化的核心功能。
|
|
||||||
|
|
||||||
**核心属性:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
string CurrentLanguage { get; } // 当前语言代码
|
|
||||||
CultureInfo CurrentCulture { get; } // 当前文化信息
|
|
||||||
IReadOnlyList<string> AvailableLanguages { get; } // 可用语言列表
|
|
||||||
```
|
|
||||||
|
|
||||||
**核心方法:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
void SetLanguage(string languageCode); // 设置当前语言
|
|
||||||
ILocalizationTable GetTable(string tableName); // 获取本地化表
|
|
||||||
string GetText(string table, string key); // 获取本地化文本
|
|
||||||
ILocalizationString GetString(string table, string key); // 获取本地化字符串(支持变量)
|
|
||||||
bool TryGetText(string table, string key, out string text); // 尝试获取文本
|
|
||||||
void RegisterFormatter(string name, ILocalizationFormatter formatter); // 注册格式化器
|
|
||||||
void SubscribeToLanguageChange(Action<string> callback); // 订阅语言变化
|
|
||||||
void UnsubscribeFromLanguageChange(Action<string> callback); // 取消订阅
|
|
||||||
```
|
|
||||||
|
|
||||||
### ILocalizationTable
|
|
||||||
|
|
||||||
本地化表接口,表示单个语言的本地化数据表。
|
|
||||||
|
|
||||||
**核心属性:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
string Name { get; } // 表名
|
|
||||||
string Language { get; } // 语言代码
|
|
||||||
ILocalizationTable? Fallback { get; } // 回退表
|
|
||||||
```
|
|
||||||
|
|
||||||
**核心方法:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
string GetRawText(string key); // 获取原始文本
|
|
||||||
bool ContainsKey(string key); // 检查键是否存在
|
|
||||||
IEnumerable<string> GetKeys(); // 获取所有键
|
|
||||||
void Merge(IReadOnlyDictionary<string, string> overrides); // 合并覆盖数据
|
|
||||||
```
|
|
||||||
|
|
||||||
### ILocalizationString
|
|
||||||
|
|
||||||
本地化字符串接口,支持变量替换和格式化。
|
|
||||||
|
|
||||||
**核心属性:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
string Table { get; } // 表名
|
|
||||||
string Key { get; } // 键名
|
|
||||||
```
|
|
||||||
|
|
||||||
**核心方法:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
ILocalizationString WithVariable(string name, object value); // 添加变量
|
|
||||||
ILocalizationString WithVariables(params (string name, object value)[] variables); // 批量添加变量
|
|
||||||
string Format(); // 格式化并返回文本
|
|
||||||
string GetRaw(); // 获取原始文本
|
|
||||||
bool Exists(); // 检查键是否存在
|
|
||||||
```
|
|
||||||
|
|
||||||
### ILocalizationFormatter
|
|
||||||
|
|
||||||
格式化器接口,用于自定义变量格式化逻辑。
|
|
||||||
|
|
||||||
**核心属性:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
string Name { get; } // 格式化器名称
|
|
||||||
```
|
|
||||||
|
|
||||||
**核心方法:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
bool TryFormat(string format, object value, IFormatProvider? provider, out string result);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 配置类
|
|
||||||
|
|
||||||
### LocalizationConfig
|
|
||||||
|
|
||||||
本地化配置类,用于配置本地化系统的行为。
|
|
||||||
|
|
||||||
**配置属性:**
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
string DefaultLanguage { get; set; } // 默认语言代码,默认 "eng"
|
|
||||||
string FallbackLanguage { get; set; } // 回退语言代码,默认 "eng"
|
|
||||||
string LocalizationPath { get; set; } // 本地化文件路径,默认 "res://localization"
|
|
||||||
string OverridePath { get; set; } // 用户覆盖路径,默认 "user://localization_override" (暂不支持)
|
|
||||||
bool EnableHotReload { get; set; } // 是否启用热重载,默认 true (暂不支持)
|
|
||||||
bool ValidateOnLoad { get; set; } // 是否在加载时验证,默认 true (暂不支持)
|
|
||||||
```
|
|
||||||
|
|
||||||
**注意:** `OverridePath`、`EnableHotReload` 和 `ValidateOnLoad` 配置项已定义但当前版本暂不支持,将在后续版本中实现。
|
|
||||||
|
|
||||||
## 文件组织
|
|
||||||
|
|
||||||
### 目录结构
|
|
||||||
|
|
||||||
```
|
|
||||||
res://localization/
|
|
||||||
├── eng/ # 英文
|
|
||||||
│ ├── common.json # 通用文本
|
|
||||||
│ ├── ui.json # UI 文本
|
|
||||||
│ ├── cards.json # 卡牌文本
|
|
||||||
│ └── ...
|
|
||||||
├── zhs/ # 简体中文
|
|
||||||
│ ├── common.json
|
|
||||||
│ ├── ui.json
|
|
||||||
│ └── ...
|
|
||||||
└── ...
|
|
||||||
|
|
||||||
user://localization_override/ # 用户覆盖(可选)
|
|
||||||
├── eng/
|
|
||||||
└── zhs/
|
|
||||||
```
|
|
||||||
|
|
||||||
### JSON 文件格式
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"game.title": "My Game",
|
|
||||||
"game.version": "Version {version}",
|
|
||||||
"ui.button.start": "Start Game",
|
|
||||||
"ui.message.welcome": "Welcome, {playerName}!",
|
|
||||||
"combat.damage": "Deal {damage} damage",
|
|
||||||
"status.health": "Health: {current}/{max}"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**命名约定:**
|
|
||||||
|
|
||||||
- 使用点号分隔的层级结构(如 `ui.button.start`)
|
|
||||||
- 变量使用花括号包裹(如 `{playerName}`)
|
|
||||||
- 键名使用小写字母和点号
|
|
||||||
|
|
||||||
## 基本使用
|
|
||||||
|
|
||||||
### 初始化本地化管理器
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using GFramework.Core.Abstractions.Localization;
|
|
||||||
using GFramework.Core.Localization;
|
|
||||||
|
|
||||||
// 创建配置
|
|
||||||
var config = new LocalizationConfig
|
|
||||||
{
|
|
||||||
DefaultLanguage = "eng",
|
|
||||||
FallbackLanguage = "eng",
|
|
||||||
LocalizationPath = "res://localization"
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建管理器
|
|
||||||
var locManager = new LocalizationManager(config);
|
|
||||||
locManager.Initialize();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 在 Architecture 中注册
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class GameArchitecture : Architecture<GameArchitecture>
|
|
||||||
{
|
|
||||||
protected override void OnInit()
|
|
||||||
{
|
|
||||||
// 注册本地化管理器
|
|
||||||
this.RegisterSystem<ILocalizationManager>(new LocalizationManager());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 获取本地化文本
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 获取管理器
|
|
||||||
var locManager = this.GetSystem<ILocalizationManager>();
|
|
||||||
|
|
||||||
// 简单文本
|
|
||||||
string title = locManager.GetText("common", "game.title");
|
|
||||||
// 结果: "My Game"
|
|
||||||
|
|
||||||
// 安全获取
|
|
||||||
if (locManager.TryGetText("common", "game.title", out var text))
|
|
||||||
{
|
|
||||||
Debug.Log(text);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用变量
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 单个变量
|
|
||||||
var message = locManager.GetString("common", "ui.message.welcome")
|
|
||||||
.WithVariable("playerName", "Alice")
|
|
||||||
.Format();
|
|
||||||
// 结果: "Welcome, Alice!"
|
|
||||||
|
|
||||||
// 多个变量
|
|
||||||
var health = locManager.GetString("common", "status.health")
|
|
||||||
.WithVariable("current", 80)
|
|
||||||
.WithVariable("max", 100)
|
|
||||||
.Format();
|
|
||||||
// 结果: "Health: 80/100"
|
|
||||||
|
|
||||||
// 链式调用
|
|
||||||
var text = locManager.GetString("common", "game.version")
|
|
||||||
.WithVariable("version", "1.0.0")
|
|
||||||
.Format();
|
|
||||||
// 结果: "Version 1.0.0"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 切换语言
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 切换到简体中文
|
|
||||||
locManager.SetLanguage("zhs");
|
|
||||||
|
|
||||||
// 获取文本(自动使用新语言)
|
|
||||||
string title = locManager.GetText("common", "game.title");
|
|
||||||
// 结果: "我的游戏"
|
|
||||||
|
|
||||||
// 获取当前语言
|
|
||||||
string currentLang = locManager.CurrentLanguage; // "zhs"
|
|
||||||
|
|
||||||
// 获取可用语言列表
|
|
||||||
var languages = locManager.AvailableLanguages;
|
|
||||||
foreach (var lang in languages)
|
|
||||||
{
|
|
||||||
Debug.Log(lang); // "eng", "zhs", ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 监听语言变化
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 方式 1: 使用 lambda 表达式(无法取消订阅)
|
|
||||||
locManager.SubscribeToLanguageChange(language =>
|
|
||||||
{
|
|
||||||
Debug.Log($"Language changed to: {language}");
|
|
||||||
// 更新 UI、重新加载资源等
|
|
||||||
});
|
|
||||||
|
|
||||||
// 方式 2: 使用命名方法(推荐,可以取消订阅)
|
|
||||||
void OnLanguageChanged(string language)
|
|
||||||
{
|
|
||||||
Debug.Log($"Language changed to: {language}");
|
|
||||||
// 更新 UI、重新加载资源等
|
|
||||||
}
|
|
||||||
|
|
||||||
// 订阅
|
|
||||||
locManager.SubscribeToLanguageChange(OnLanguageChanged);
|
|
||||||
|
|
||||||
// 取消订阅(使用相同的方法引用)
|
|
||||||
locManager.UnsubscribeFromLanguageChange(OnLanguageChanged);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 高级功能
|
|
||||||
|
|
||||||
### 回退机制
|
|
||||||
|
|
||||||
当目标语言缺少某个键时,系统会自动回退到默认语言:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 假设 zhs/common.json 中缺少 "new.feature" 键
|
|
||||||
locManager.SetLanguage("zhs");
|
|
||||||
var text = locManager.GetText("common", "new.feature");
|
|
||||||
// 自动从 eng/common.json 获取
|
|
||||||
|
|
||||||
// 回退顺序:
|
|
||||||
// 1. 当前语言的覆盖数据
|
|
||||||
// 2. 当前语言的原始数据
|
|
||||||
// 3. 回退语言的数据
|
|
||||||
```
|
|
||||||
|
|
||||||
### 覆盖机制
|
|
||||||
|
|
||||||
**注意:** 覆盖机制功能已规划但当前版本暂不支持,将在后续版本中实现。
|
|
||||||
|
|
||||||
未来版本中,用户可以在 `user://localization_override/` 目录下放置覆盖文件:
|
|
||||||
|
|
||||||
```json
|
|
||||||
// user://localization_override/eng/common.json
|
|
||||||
{
|
|
||||||
"game.title": "My Custom Game Title"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
覆盖文件会自动合并到主本地化表中,优先级最高。
|
|
||||||
|
|
||||||
### 自定义格式化器
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 实现自定义格式化器
|
|
||||||
public class UpperCaseFormatter : ILocalizationFormatter
|
|
||||||
{
|
|
||||||
public string Name => "upper";
|
|
||||||
|
|
||||||
public bool TryFormat(string format, object value, IFormatProvider? provider, out string result)
|
|
||||||
{
|
|
||||||
result = value?.ToString()?.ToUpper() ?? string.Empty;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册格式化器
|
|
||||||
locManager.RegisterFormatter("upper", new UpperCaseFormatter());
|
|
||||||
|
|
||||||
// 使用格式化器(需要在 LocalizationString 中实现格式化器支持)
|
|
||||||
// 格式: {variableName:formatterName:args}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 内置格式化器
|
|
||||||
|
|
||||||
#### ConditionalFormatter
|
|
||||||
|
|
||||||
条件格式化器,根据布尔值选择不同文本。
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 格式: {condition:if:trueText|falseText}
|
|
||||||
// JSON: "status": "{upgraded:if:Upgraded|Normal}"
|
|
||||||
|
|
||||||
var text = locManager.GetString("common", "status")
|
|
||||||
.WithVariable("upgraded", true)
|
|
||||||
.Format();
|
|
||||||
// 结果: "Upgraded"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### PluralFormatter
|
|
||||||
|
|
||||||
复数格式化器,根据数量选择单复数形式。
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 格式: {count:plural:singular|plural}
|
|
||||||
// JSON: "items": "{count:plural:item|items}"
|
|
||||||
|
|
||||||
var text = locManager.GetString("common", "items")
|
|
||||||
.WithVariable("count", 1)
|
|
||||||
.Format();
|
|
||||||
// 结果: "item"
|
|
||||||
|
|
||||||
var text2 = locManager.GetString("common", "items")
|
|
||||||
.WithVariable("count", 3)
|
|
||||||
.Format();
|
|
||||||
// 结果: "items"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 异常处理
|
|
||||||
|
|
||||||
### LocalizationException
|
|
||||||
|
|
||||||
本地化异常基类。
|
|
||||||
|
|
||||||
### LocalizationKeyNotFoundException
|
|
||||||
|
|
||||||
当请求的键不存在时抛出。
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var text = locManager.GetText("common", "nonexistent.key");
|
|
||||||
}
|
|
||||||
catch (LocalizationKeyNotFoundException ex)
|
|
||||||
{
|
|
||||||
Debug.LogError($"Key not found: {ex.TableName}.{ex.Key}");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### LocalizationTableNotFoundException
|
|
||||||
|
|
||||||
当请求的表不存在时抛出。
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var table = locManager.GetTable("nonexistent_table");
|
|
||||||
}
|
|
||||||
catch (LocalizationTableNotFoundException ex)
|
|
||||||
{
|
|
||||||
Debug.LogError($"Table not found: {ex.TableName}");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 键名组织
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 推荐:使用层级结构
|
|
||||||
"ui.button.start"
|
|
||||||
"ui.button.quit"
|
|
||||||
"combat.damage.physical"
|
|
||||||
"combat.damage.magical"
|
|
||||||
|
|
||||||
// 不推荐:扁平结构
|
|
||||||
"start_button"
|
|
||||||
"quit_button"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 变量命名
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 推荐:使用驼峰命名
|
|
||||||
"{playerName}"
|
|
||||||
"{maxHealth}"
|
|
||||||
|
|
||||||
// 不推荐:使用下划线或大写
|
|
||||||
"{player_name}"
|
|
||||||
"{MAX_HEALTH}"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 表的划分
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 按功能模块划分表
|
|
||||||
common.json // 通用文本
|
|
||||||
ui.json // UI 文本
|
|
||||||
combat.json // 战斗文本
|
|
||||||
items.json // 物品文本
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 安全获取
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 推荐:使用 TryGetText 避免异常
|
|
||||||
if (locManager.TryGetText("common", "key", out var text))
|
|
||||||
{
|
|
||||||
// 使用 text
|
|
||||||
}
|
|
||||||
|
|
||||||
// 或者提供默认值
|
|
||||||
var text = locManager.TryGetText("common", "key", out var result)
|
|
||||||
? result
|
|
||||||
: "Default Text";
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 语言变化处理
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 在组件初始化时订阅
|
|
||||||
public override void OnInit()
|
|
||||||
{
|
|
||||||
var locManager = this.GetSystem<ILocalizationManager>();
|
|
||||||
locManager.SubscribeToLanguageChange(OnLanguageChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在组件销毁时取消订阅
|
|
||||||
public override void OnDestroy()
|
|
||||||
{
|
|
||||||
var locManager = this.GetSystem<ILocalizationManager>();
|
|
||||||
locManager.UnsubscribeFromLanguageChange(OnLanguageChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLanguageChanged(string language)
|
|
||||||
{
|
|
||||||
// 更新 UI
|
|
||||||
UpdateUI();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能考虑
|
|
||||||
|
|
||||||
### 缓存策略
|
|
||||||
|
|
||||||
- 本地化表在加载后会缓存在内存中
|
|
||||||
- 语言切换时只加载新语言的表
|
|
||||||
- 建议在游戏启动时预加载常用语言
|
|
||||||
|
|
||||||
### 内存优化
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// 只加载当前语言,不预加载所有语言
|
|
||||||
var config = new LocalizationConfig
|
|
||||||
{
|
|
||||||
DefaultLanguage = "eng"
|
|
||||||
};
|
|
||||||
|
|
||||||
// 按需切换语言
|
|
||||||
locManager.SetLanguage(userSelectedLanguage);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 相关资源
|
|
||||||
|
|
||||||
- [Architecture 架构系统](./architecture.md)
|
|
||||||
- [System 系统层](./system.md)
|
|
||||||
- [Configuration 配置管理](./configuration.md)
|
|
||||||
Loading…
x
Reference in New Issue
Block a user