Merge pull request #178 from GeWuYou/feat/godot-localization-settings

Feat/godot localization settings
This commit is contained in:
gewuyou 2026-04-05 20:21:10 +08:00 committed by GitHub
commit 658a36fdd0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 239 additions and 26 deletions

View File

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

View File

@ -0,0 +1,74 @@
using GFramework.Core.Abstractions.Localization;
using GFramework.Game.Abstractions.Setting;
using GFramework.Game.Abstractions.Setting.Data;
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

@ -15,4 +15,5 @@ global using System;
global using System.Collections.Generic; global using System.Collections.Generic;
global using System.Linq; global using System.Linq;
global using System.Threading; global using System.Threading;
global using System.Threading.Tasks; global using System.Threading.Tasks;
global using Godot;

View File

@ -18,12 +18,54 @@ namespace GFramework.Godot.Setting.Data;
/// </summary> /// </summary>
public class LocalizationMap public class LocalizationMap
{ {
private const string DefaultFrameworkLanguage = "eng";
private const string DefaultGodotLocale = "en";
/// <summary> /// <summary>
/// 用户语言 -> Godot locale 映射表 /// 用户语言 -> Godot locale 映射表
/// </summary> /// </summary>
public Dictionary<string, string> LanguageMap { get; set; } = new() public Dictionary<string, string> LanguageMap { get; set; } = new()
{ {
{ "简体中文", "zh_CN" }, { "简体中文", "zh_CN" },
{ "English", "en" } { "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,83 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
using GFramework.Core.Abstractions.Localization;
using GFramework.Core.Architectures;
using GFramework.Game.Abstractions.Setting; using GFramework.Game.Abstractions.Setting;
using GFramework.Game.Abstractions.Setting.Data; using GFramework.Game.Abstractions.Setting.Data;
using GFramework.Godot.Setting.Data; using GFramework.Godot.Setting.Data;
using Godot;
namespace GFramework.Godot.Setting; namespace GFramework.Godot.Setting;
/// <summary> /// <summary>
/// Godot本地化设置类负责应用本地化配置到Godot引擎 /// Godot 本地化设置类,负责将持久化语言配置同时应用到 Godot 引擎与 GFramework 本地化管理器。
/// </summary> /// </summary>
/// <param name="model">设置模型</param> public class GodotLocalizationSettings : IResetApplyAbleSettings
/// <param name="localizationMap">本地化映射表</param>
public class GodotLocalizationSettings(ISettingsModel model, LocalizationMap localizationMap)
: IResetApplyAbleSettings
{ {
private readonly Action<string> _applyGodotLocale;
private readonly Func<ILocalizationManager?> _localizationManagerResolver;
private readonly LocalizationMap _localizationMap;
private readonly ISettingsModel _model;
/// <summary> /// <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> /// </summary>
/// <returns>完成的任务</returns> /// <returns>完成的任务</returns>
public Task Apply() public Task Apply()
{ {
var settings = model.GetData<LocalizationSettings>(); var settings = _model.GetData<LocalizationSettings>();
// 尝试从映射表获取 Godot locale var locale = _localizationMap.ResolveGodotLocale(settings.Language);
var locale = localizationMap.LanguageMap.GetValueOrDefault(settings.Language, "en"); var frameworkLanguage = _localizationMap.ResolveFrameworkLanguage(settings.Language);
// 默认值
TranslationServer.SetLocale(locale); _applyGodotLocale(locale);
// 设置系统持久化的是用户可见语言值;这里需要同步框架语言码,避免 Godot 与框架状态分裂。
_localizationManagerResolver()?.SetLanguage(frameworkLanguage);
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -45,18 +96,30 @@ public class GodotLocalizationSettings(ISettingsModel model, LocalizationMap loc
/// </summary> /// </summary>
public void Reset() public void Reset()
{ {
model.GetData<LocalizationSettings>().Reset(); _model.GetData<LocalizationSettings>().Reset();
} }
/// <summary> /// <summary>
/// 获取本地化设置的数据对象。 /// 获取本地化设置的数据对象。
/// 该属性提供对本地化设置数据的只读访问。 /// 该属性提供对本地化设置数据的只读访问。
/// </summary> /// </summary>
public ISettingsData Data { get; } = model.GetData<LocalizationSettings>(); public ISettingsData Data => _model.GetData<LocalizationSettings>();
/// <summary> /// <summary>
/// 获取本地化设置数据的类型。 /// 获取本地化设置数据的类型。
/// 该属性返回本地化设置数据的具体类型信息。 /// 该属性返回本地化设置数据的具体类型信息。
/// </summary> /// </summary>
public Type DataType { get; } = typeof(LocalizationSettings); public Type DataType { get; } = typeof(LocalizationSettings);
private static ILocalizationManager? TryResolveLocalizationManager()
{
try
{
return GameContext.GetFirstArchitectureContext().GetSystem<ILocalizationManager>();
}
catch (InvalidOperationException)
{
return null;
}
}
} }

View File

@ -3,7 +3,7 @@
## 概述 ## 概述
Godot 设置模块是 GFramework.Godot 的核心组件之一,专门为 Godot 引擎提供游戏设置系统的实现。该模块将通用的设置框架与 Godot Godot 设置模块是 GFramework.Godot 的核心组件之一,专门为 Godot 引擎提供游戏设置系统的实现。该模块将通用的设置框架与 Godot
引擎的特定功能相结合,提供了音频设置和图形设置的完整解决方案。 引擎的特定功能相结合,提供了音频设置、图形设置和本地化设置的完整解决方案。
## 核心类 ## 核心类
@ -61,23 +61,55 @@ Godot 图形设置实现类,继承自 GraphicsSettings 并实现 IApplyAbleSet
- 窗口位置自动居中 - 窗口位置自动居中
- 多显示器支持 - 多显示器支持
### 本地化设置系统
#### LocalizationMap
本地化映射配置类,用于把设置系统中保存的用户可见语言值解析为:
- Godot `TranslationServer` 使用的 locale
- GFramework `ILocalizationManager` 使用的语言码
默认映射如下:
- `"简体中文"` -> Godot `zh_CN`,框架语言码 `zhs`
- `"English"` -> Godot `en`,框架语言码 `eng`
未知语言值会稳定回退到英文,避免重启后出现设置值与运行时语言状态不一致。
#### GodotLocalizationSettings
Godot 本地化设置实现类,负责把 `LocalizationSettings` 同时应用到 Godot 引擎与 GFramework 本地化管理器。
**功能:**
- 将语言设置应用到 `TranslationServer.SetLocale(...)`
- 同步 `ILocalizationManager.SetLanguage(...)`
- 通过统一映射避免 Godot locale 与框架语言码分裂
## 架构设计 ## 架构设计
```mermaid ```mermaid
graph TD graph TD
A[AudioSettings] --> B[GodotAudioSettings] A[AudioSettings] --> B[GodotAudioSettings]
C[GraphicsSettings] --> D[GodotGraphicsSettings] C[GraphicsSettings] --> D[GodotGraphicsSettings]
E[IApplyAbleSettings] --> B E[LocalizationSettings] --> F[GodotLocalizationSettings]
E --> D G[IApplyAbleSettings] --> B
G --> D
G --> F
G[AudioBusMap] --> B H[AudioBusMap] --> B
I[LocalizationMap] --> F
B --> I[AudioServer API] B --> J[AudioServer API]
D --> J[DisplayServer API] D --> K[DisplayServer API]
F --> L[TranslationServer API]
F --> M[ILocalizationManager]
K[SettingsSystem] --> L[Apply Method] N[SettingsSystem] --> O[Apply Method]
L --> B O --> B
L --> D O --> D
O --> F
``` ```
## 使用示例 ## 使用示例
@ -568,4 +600,4 @@ var windowMode = DisplayServer.WindowGetMode();
GD.Print($"Screen: {screenSize}"); GD.Print($"Screen: {screenSize}");
GD.Print($"Window: {windowSize} at {windowPos}"); GD.Print($"Window: {windowSize} at {windowPos}");
GD.Print($"Mode: {windowMode}"); GD.Print($"Mode: {windowMode}");
``` ```