From 41dd7593798155e554a914699bacb97d17a92d6d Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 5 Apr 2026 19:54:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(godot):=20=E6=B7=BB=E5=8A=A0Godot=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=8C=96=E8=AE=BE=E7=BD=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增LocalizationMap类实现用户语言到Godot locale和框架语言码的映射 - 创建GodotLocalizationSettings类同步应用本地化设置到Godot引擎和GFramework框架 - 添加测试项目配置文件GFramework.Game.Tests.csproj - 实现本地化设置的单元测试验证语言同步功能 --- .../GFramework.Game.Tests.csproj | 1 + .../Setting/GodotLocalizationSettingsTests.cs | 71 +++++++++++++++ .../Setting/Data/LocalizationMap.cs | 44 ++++++++- .../Setting/GodotLocalizationSettings.cs | 90 ++++++++++++++++--- 4 files changed, 191 insertions(+), 15 deletions(-) create mode 100644 GFramework.Game.Tests/Setting/GodotLocalizationSettingsTests.cs diff --git a/GFramework.Game.Tests/GFramework.Game.Tests.csproj b/GFramework.Game.Tests/GFramework.Game.Tests.csproj index 6ad45dbe..9b6d0bdf 100644 --- a/GFramework.Game.Tests/GFramework.Game.Tests.csproj +++ b/GFramework.Game.Tests/GFramework.Game.Tests.csproj @@ -19,6 +19,7 @@ + diff --git a/GFramework.Game.Tests/Setting/GodotLocalizationSettingsTests.cs b/GFramework.Game.Tests/Setting/GodotLocalizationSettingsTests.cs new file mode 100644 index 00000000..ee13a7c1 --- /dev/null +++ b/GFramework.Game.Tests/Setting/GodotLocalizationSettingsTests.cs @@ -0,0 +1,71 @@ +using GFramework.Godot.Setting; +using GFramework.Godot.Setting.Data; + +namespace GFramework.Game.Tests.Setting; + +/// +/// 覆盖 Godot 本地化设置应用器的语言同步行为,防止持久化语言仅影响 Godot 而未同步框架管理器。 +/// +[TestFixture] +public sealed class GodotLocalizationSettingsTests +{ + [Test] + public async Task Apply_ShouldSyncEnglishToGodotLocaleAndFrameworkLanguage() + { + var manager = new Mock(MockBehavior.Strict); + manager.Setup(it => it.SetLanguage("eng")); + string? appliedLocale = null; + + var applicator = CreateApplicator("English", manager.Object, locale => appliedLocale = locale); + + await applicator.Apply(); + + Assert.That(appliedLocale, Is.EqualTo("en")); + manager.Verify(it => it.SetLanguage("eng"), Times.Once); + } + + [Test] + public async Task Apply_ShouldSyncChineseToGodotLocaleAndFrameworkLanguage() + { + var manager = new Mock(MockBehavior.Strict); + manager.Setup(it => it.SetLanguage("zhs")); + string? appliedLocale = null; + + var applicator = CreateApplicator("简体中文", manager.Object, locale => appliedLocale = locale); + + await applicator.Apply(); + + Assert.That(appliedLocale, Is.EqualTo("zh_CN")); + manager.Verify(it => it.SetLanguage("zhs"), Times.Once); + } + + [Test] + public async Task Apply_ShouldFallbackUnknownLanguageToEnglish() + { + var manager = new Mock(MockBehavior.Strict); + manager.Setup(it => it.SetLanguage("eng")); + string? appliedLocale = null; + + var applicator = CreateApplicator("Esperanto", manager.Object, locale => appliedLocale = locale); + + await applicator.Apply(); + + Assert.That(appliedLocale, Is.EqualTo("en")); + manager.Verify(it => it.SetLanguage("eng"), Times.Once); + } + + private static GodotLocalizationSettings CreateApplicator( + string language, + ILocalizationManager manager, + Action applyGodotLocale) + { + var settingsModel = new Mock(MockBehavior.Strict); + settingsModel.Setup(it => it.GetData()).Returns(new LocalizationSettings + { + Language = language + }); + + return new GodotLocalizationSettings(settingsModel.Object, new LocalizationMap(), () => manager, + applyGodotLocale); + } +} \ No newline at end of file diff --git a/GFramework.Godot/Setting/Data/LocalizationMap.cs b/GFramework.Godot/Setting/Data/LocalizationMap.cs index a5cface8..1864f4ae 100644 --- a/GFramework.Godot/Setting/Data/LocalizationMap.cs +++ b/GFramework.Godot/Setting/Data/LocalizationMap.cs @@ -18,12 +18,54 @@ namespace GFramework.Godot.Setting.Data; /// public class LocalizationMap { + private const string DefaultFrameworkLanguage = "eng"; + private const string DefaultGodotLocale = "en"; + /// - /// 用户语言 -> Godot locale 映射表 + /// 用户语言 -> Godot locale 映射表。 /// public Dictionary LanguageMap { get; set; } = new() { { "简体中文", "zh_CN" }, { "English", "en" } }; + + /// + /// 用户语言 -> GFramework 本地化语言码映射表。 + /// + public Dictionary FrameworkLanguageMap { get; set; } = new() + { + { "简体中文", "zhs" }, + { "English", "eng" } + }; + + /// + /// 解析用户保存的语言值对应的 Godot locale。 + /// + /// 设置系统中保存的语言值。 + /// 对应的 Godot locale;未知值时回退为英文。 + public string ResolveGodotLocale(string? storedLanguage) + { + if (string.IsNullOrWhiteSpace(storedLanguage)) + { + return DefaultGodotLocale; + } + + return LanguageMap.GetValueOrDefault(storedLanguage, DefaultGodotLocale); + } + + /// + /// 解析用户保存的语言值对应的框架语言码。 + /// + /// 设置系统中保存的语言值。 + /// 对应的框架语言码;未知值时回退为英文。 + public string ResolveFrameworkLanguage(string? storedLanguage) + { + if (string.IsNullOrWhiteSpace(storedLanguage)) + { + return DefaultFrameworkLanguage; + } + + return FrameworkLanguageMap.GetValueOrDefault(storedLanguage, DefaultFrameworkLanguage); + } } \ No newline at end of file diff --git a/GFramework.Godot/Setting/GodotLocalizationSettings.cs b/GFramework.Godot/Setting/GodotLocalizationSettings.cs index abbc9bfd..a4e31b7f 100644 --- a/GFramework.Godot/Setting/GodotLocalizationSettings.cs +++ b/GFramework.Godot/Setting/GodotLocalizationSettings.cs @@ -11,32 +11,82 @@ // See the License for the specific language governing permissions and // limitations under the License. +using GFramework.Core.Abstractions.Localization; using GFramework.Game.Abstractions.Setting; using GFramework.Game.Abstractions.Setting.Data; using GFramework.Godot.Setting.Data; -using Godot; namespace GFramework.Godot.Setting; /// -/// Godot本地化设置类,负责应用本地化配置到Godot引擎 +/// Godot 本地化设置类,负责将持久化语言配置同时应用到 Godot 引擎与 GFramework 本地化管理器。 /// -/// 设置模型 -/// 本地化映射表 -public class GodotLocalizationSettings(ISettingsModel model, LocalizationMap localizationMap) - : IResetApplyAbleSettings +public class GodotLocalizationSettings : IResetApplyAbleSettings { + private readonly Action _applyGodotLocale; + private readonly Func _localizationManagerResolver; + private readonly LocalizationMap _localizationMap; + private readonly ISettingsModel _model; + /// - /// 应用本地化设置到Godot引擎 + /// 初始化 Godot 本地化设置应用器,并默认从当前架构上下文解析框架本地化管理器。 + /// + /// 设置模型。 + /// 本地化映射表。 + public GodotLocalizationSettings(ISettingsModel model, LocalizationMap localizationMap) + : this(model, localizationMap, TryResolveLocalizationManager, TranslationServer.SetLocale) + { + } + + /// + /// 初始化 Godot 本地化设置应用器。 + /// + /// 设置模型。 + /// 本地化映射表。 + /// 框架本地化管理器解析器。 + public GodotLocalizationSettings( + ISettingsModel model, + LocalizationMap localizationMap, + Func localizationManagerResolver) + : this(model, localizationMap, localizationManagerResolver, TranslationServer.SetLocale) + { + } + + /// + /// 初始化 Godot 本地化设置应用器,并显式指定 Godot locale 应用动作。 + /// 该重载主要用于测试或自定义引擎桥接。 + /// + /// 设置模型。 + /// 本地化映射表。 + /// 框架本地化管理器解析器。 + /// Godot locale 应用动作。 + public GodotLocalizationSettings( + ISettingsModel model, + LocalizationMap localizationMap, + Func localizationManagerResolver, + Action applyGodotLocale) + { + _model = model ?? throw new ArgumentNullException(nameof(model)); + _localizationMap = localizationMap ?? throw new ArgumentNullException(nameof(localizationMap)); + _localizationManagerResolver = + localizationManagerResolver ?? throw new ArgumentNullException(nameof(localizationManagerResolver)); + _applyGodotLocale = applyGodotLocale ?? throw new ArgumentNullException(nameof(applyGodotLocale)); + } + + /// + /// 应用本地化设置到 Godot 引擎与 GFramework 本地化管理器。 /// /// 完成的任务 public Task Apply() { - var settings = model.GetData(); - // 尝试从映射表获取 Godot locale - var locale = localizationMap.LanguageMap.GetValueOrDefault(settings.Language, "en"); - // 默认值 - TranslationServer.SetLocale(locale); + var settings = _model.GetData(); + var locale = _localizationMap.ResolveGodotLocale(settings.Language); + var frameworkLanguage = _localizationMap.ResolveFrameworkLanguage(settings.Language); + + _applyGodotLocale(locale); + + // 设置系统持久化的是用户可见语言值;这里需要同步框架语言码,避免 Godot 与框架状态分裂。 + _localizationManagerResolver()?.SetLanguage(frameworkLanguage); return Task.CompletedTask; } @@ -45,18 +95,30 @@ public class GodotLocalizationSettings(ISettingsModel model, LocalizationMap loc /// public void Reset() { - model.GetData().Reset(); + _model.GetData().Reset(); } /// /// 获取本地化设置的数据对象。 /// 该属性提供对本地化设置数据的只读访问。 /// - public ISettingsData Data { get; } = model.GetData(); + public ISettingsData Data => _model.GetData(); /// /// 获取本地化设置数据的类型。 /// 该属性返回本地化设置数据的具体类型信息。 /// public Type DataType { get; } = typeof(LocalizationSettings); + + private static ILocalizationManager? TryResolveLocalizationManager() + { + try + { + return GameContext.GetFirstArchitectureContext().GetSystem(); + } + catch (InvalidOperationException) + { + return null; + } + } } \ No newline at end of file