feat(godot): 添加Godot本地化设置功能

- 新增LocalizationMap类实现用户语言到Godot locale和框架语言码的映射
- 创建GodotLocalizationSettings类同步应用本地化设置到Godot引擎和GFramework框架
- 添加测试项目配置文件GFramework.Game.Tests.csproj
- 实现本地化设置的单元测试验证语言同步功能
This commit is contained in:
GeWuYou 2026-04-05 19:54:02 +08:00
parent a22e522cf9
commit 41dd759379
4 changed files with 191 additions and 15 deletions

View File

@ -19,6 +19,7 @@
<ItemGroup>
<ProjectReference Include="..\GFramework.Game\GFramework.Game.csproj"/>
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
<ProjectReference Include="..\GFramework.Godot\GFramework.Godot.csproj"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>

View File

@ -0,0 +1,71 @@
using GFramework.Godot.Setting;
using GFramework.Godot.Setting.Data;
namespace GFramework.Game.Tests.Setting;
/// <summary>
/// 覆盖 Godot 本地化设置应用器的语言同步行为,防止持久化语言仅影响 Godot 而未同步框架管理器。
/// </summary>
[TestFixture]
public sealed class GodotLocalizationSettingsTests
{
[Test]
public async Task Apply_ShouldSyncEnglishToGodotLocaleAndFrameworkLanguage()
{
var manager = new Mock<ILocalizationManager>(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<ILocalizationManager>(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<ILocalizationManager>(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<string> applyGodotLocale)
{
var settingsModel = new Mock<ISettingsModel>(MockBehavior.Strict);
settingsModel.Setup(it => it.GetData<LocalizationSettings>()).Returns(new LocalizationSettings
{
Language = language
});
return new GodotLocalizationSettings(settingsModel.Object, new LocalizationMap(), () => manager,
applyGodotLocale);
}
}

View File

@ -18,12 +18,54 @@ namespace GFramework.Godot.Setting.Data;
/// </summary>
public class LocalizationMap
{
private const string DefaultFrameworkLanguage = "eng";
private const string DefaultGodotLocale = "en";
/// <summary>
/// 用户语言 -> Godot locale 映射表
/// 用户语言 -> Godot locale 映射表
/// </summary>
public Dictionary<string, string> LanguageMap { get; set; } = new()
{
{ "简体中文", "zh_CN" },
{ "English", "en" }
};
/// <summary>
/// 用户语言 -> GFramework 本地化语言码映射表。
/// </summary>
public Dictionary<string, string> FrameworkLanguageMap { get; set; } = new()
{
{ "简体中文", "zhs" },
{ "English", "eng" }
};
/// <summary>
/// 解析用户保存的语言值对应的 Godot locale。
/// </summary>
/// <param name="storedLanguage">设置系统中保存的语言值。</param>
/// <returns>对应的 Godot locale未知值时回退为英文。</returns>
public string ResolveGodotLocale(string? storedLanguage)
{
if (string.IsNullOrWhiteSpace(storedLanguage))
{
return DefaultGodotLocale;
}
return LanguageMap.GetValueOrDefault(storedLanguage, DefaultGodotLocale);
}
/// <summary>
/// 解析用户保存的语言值对应的框架语言码。
/// </summary>
/// <param name="storedLanguage">设置系统中保存的语言值。</param>
/// <returns>对应的框架语言码;未知值时回退为英文。</returns>
public string ResolveFrameworkLanguage(string? storedLanguage)
{
if (string.IsNullOrWhiteSpace(storedLanguage))
{
return DefaultFrameworkLanguage;
}
return FrameworkLanguageMap.GetValueOrDefault(storedLanguage, DefaultFrameworkLanguage);
}
}

View File

@ -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;
/// <summary>
/// Godot本地化设置类负责应用本地化配置到Godot引擎
/// Godot 本地化设置类,负责将持久化语言配置同时应用到 Godot 引擎与 GFramework 本地化管理器。
/// </summary>
/// <param name="model">设置模型</param>
/// <param name="localizationMap">本地化映射表</param>
public class GodotLocalizationSettings(ISettingsModel model, LocalizationMap localizationMap)
: IResetApplyAbleSettings
public class GodotLocalizationSettings : IResetApplyAbleSettings
{
private readonly Action<string> _applyGodotLocale;
private readonly Func<ILocalizationManager?> _localizationManagerResolver;
private readonly LocalizationMap _localizationMap;
private readonly ISettingsModel _model;
/// <summary>
/// 应用本地化设置到Godot引擎
/// 初始化 Godot 本地化设置应用器,并默认从当前架构上下文解析框架本地化管理器。
/// </summary>
/// <param name="model">设置模型。</param>
/// <param name="localizationMap">本地化映射表。</param>
public GodotLocalizationSettings(ISettingsModel model, LocalizationMap localizationMap)
: this(model, localizationMap, TryResolveLocalizationManager, TranslationServer.SetLocale)
{
}
/// <summary>
/// 初始化 Godot 本地化设置应用器。
/// </summary>
/// <param name="model">设置模型。</param>
/// <param name="localizationMap">本地化映射表。</param>
/// <param name="localizationManagerResolver">框架本地化管理器解析器。</param>
public GodotLocalizationSettings(
ISettingsModel model,
LocalizationMap localizationMap,
Func<ILocalizationManager?> localizationManagerResolver)
: this(model, localizationMap, localizationManagerResolver, TranslationServer.SetLocale)
{
}
/// <summary>
/// 初始化 Godot 本地化设置应用器,并显式指定 Godot locale 应用动作。
/// 该重载主要用于测试或自定义引擎桥接。
/// </summary>
/// <param name="model">设置模型。</param>
/// <param name="localizationMap">本地化映射表。</param>
/// <param name="localizationManagerResolver">框架本地化管理器解析器。</param>
/// <param name="applyGodotLocale">Godot locale 应用动作。</param>
public GodotLocalizationSettings(
ISettingsModel model,
LocalizationMap localizationMap,
Func<ILocalizationManager?> localizationManagerResolver,
Action<string> 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));
}
/// <summary>
/// 应用本地化设置到 Godot 引擎与 GFramework 本地化管理器。
/// </summary>
/// <returns>完成的任务</returns>
public Task Apply()
{
var settings = model.GetData<LocalizationSettings>();
// 尝试从映射表获取 Godot locale
var locale = localizationMap.LanguageMap.GetValueOrDefault(settings.Language, "en");
// 默认值
TranslationServer.SetLocale(locale);
var settings = _model.GetData<LocalizationSettings>();
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
/// </summary>
public void Reset()
{
model.GetData<LocalizationSettings>().Reset();
_model.GetData<LocalizationSettings>().Reset();
}
/// <summary>
/// 获取本地化设置的数据对象。
/// 该属性提供对本地化设置数据的只读访问。
/// </summary>
public ISettingsData Data { get; } = model.GetData<LocalizationSettings>();
public ISettingsData Data => _model.GetData<LocalizationSettings>();
/// <summary>
/// 获取本地化设置数据的类型。
/// 该属性返回本地化设置数据的具体类型信息。
/// </summary>
public Type DataType { get; } = typeof(LocalizationSettings);
private static ILocalizationManager? TryResolveLocalizationManager()
{
try
{
return GameContext.GetFirstArchitectureContext().GetSystem<ILocalizationManager>();
}
catch (InvalidOperationException)
{
return null;
}
}
}