Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions GFramework.Game.Tests/GFramework.Game.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>
Expand Down
74 changes: 74 additions & 0 deletions GFramework.Game.Tests/Setting/GodotLocalizationSettingsTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
3 changes: 2 additions & 1 deletion GFramework.Godot/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Threading.Tasks;
global using Godot;
44 changes: 43 additions & 1 deletion GFramework.Godot/Setting/Data/LocalizationMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
91 changes: 77 additions & 14 deletions GFramework.Godot/Setting/GodotLocalizationSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,83 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using GFramework.Core.Abstractions.Localization;
using GFramework.Core.Architectures;
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 本地化设置应用器,并默认从当前架构上下文解析框架本地化管理器。
/// </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 本地化设置应用器,并显式指定 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;
}

Expand All @@ -45,18 +96,30 @@ public Task Apply()
/// </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;
}
}
}
52 changes: 42 additions & 10 deletions docs/zh-CN/godot/setting.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## 概述

Godot 设置模块是 GFramework.Godot 的核心组件之一,专门为 Godot 引擎提供游戏设置系统的实现。该模块将通用的设置框架与 Godot
引擎的特定功能相结合,提供了音频设置和图形设置的完整解决方案
引擎的特定功能相结合,提供了音频设置、图形设置和本地化设置的完整解决方案

## 核心类

Expand Down Expand Up @@ -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
graph TD
A[AudioSettings] --> B[GodotAudioSettings]
C[GraphicsSettings] --> D[GodotGraphicsSettings]
E[IApplyAbleSettings] --> B
E --> D
E[LocalizationSettings] --> F[GodotLocalizationSettings]
G[IApplyAbleSettings] --> B
G --> D
G --> F

G[AudioBusMap] --> B
H[AudioBusMap] --> B
I[LocalizationMap] --> F

B --> I[AudioServer API]
D --> J[DisplayServer API]
B --> J[AudioServer API]
D --> K[DisplayServer API]
F --> L[TranslationServer API]
F --> M[ILocalizationManager]

K[SettingsSystem] --> L[Apply Method]
L --> B
L --> D
N[SettingsSystem] --> O[Apply Method]
O --> B
O --> D
O --> F
```

## 使用示例
Expand Down Expand Up @@ -568,4 +600,4 @@ var windowMode = DisplayServer.WindowGetMode();
GD.Print($"Screen: {screenSize}");
GD.Print($"Window: {windowSize} at {windowPos}");
GD.Print($"Mode: {windowMode}");
```
```
Loading