mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 19:03:29 +08:00
- 添加了配置项暂不支持的说明信息 - 扩展了语言变化监听的使用方式,增加命名方法订阅示例 - 完善了覆盖机制的文档说明 - 优化了异常处理逻辑,精确捕获本地化相关异常 - 实现了正则表达式的预编译以提升性能 - 添加了必要的命名空间引用
12 KiB
12 KiB
Localization 本地化系统
概述
Localization 包提供了完整的多语言本地化支持,实现了游戏文本的国际化管理。通过本地化系统,可以轻松实现多语言切换、动态变量替换、回退机制等功能。
本地化系统是 GFramework 架构中的 System 层组件,与其他系统无缝集成,支持类型安全的 API 和流畅的使用体验。
核心接口
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); // 取消订阅
ILocalizationTable
本地化表接口,表示单个语言的本地化数据表。
核心属性:
string Name { get; } // 表名
string Language { get; } // 语言代码
ILocalizationTable? Fallback { get; } // 回退表
核心方法:
string GetRawText(string key); // 获取原始文本
bool ContainsKey(string key); // 检查键是否存在
IEnumerable<string> GetKeys(); // 获取所有键
void Merge(IReadOnlyDictionary<string, string> overrides); // 合并覆盖数据
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(); // 检查键是否存在
ILocalizationFormatter
格式化器接口,用于自定义变量格式化逻辑。
核心属性:
string Name { get; } // 格式化器名称
核心方法:
bool TryFormat(string format, object value, IFormatProvider? provider, out string result);
配置类
LocalizationConfig
本地化配置类,用于配置本地化系统的行为。
配置属性:
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 文件格式
{
"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}) - 键名使用小写字母和点号
基本使用
初始化本地化管理器
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 中注册
public class GameArchitecture : Architecture<GameArchitecture>
{
protected override void OnInit()
{
// 注册本地化管理器
this.RegisterSystem<ILocalizationManager>(new LocalizationManager());
}
}
获取本地化文本
// 获取管理器
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);
}
使用变量
// 单个变量
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"
切换语言
// 切换到简体中文
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", ...
}
监听语言变化
// 方式 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);
高级功能
回退机制
当目标语言缺少某个键时,系统会自动回退到默认语言:
// 假设 zhs/common.json 中缺少 "new.feature" 键
locManager.SetLanguage("zhs");
var text = locManager.GetText("common", "new.feature");
// 自动从 eng/common.json 获取
// 回退顺序:
// 1. 当前语言的覆盖数据
// 2. 当前语言的原始数据
// 3. 回退语言的数据
覆盖机制
注意: 覆盖机制功能已规划但当前版本暂不支持,将在后续版本中实现。
未来版本中,用户可以在 user://localization_override/ 目录下放置覆盖文件:
// user://localization_override/eng/common.json
{
"game.title": "My Custom Game Title"
}
覆盖文件会自动合并到主本地化表中,优先级最高。
自定义格式化器
// 实现自定义格式化器
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
条件格式化器,根据布尔值选择不同文本。
// 格式: {condition:if:trueText|falseText}
// JSON: "status": "{upgraded:if:Upgraded|Normal}"
var text = locManager.GetString("common", "status")
.WithVariable("upgraded", true)
.Format();
// 结果: "Upgraded"
PluralFormatter
复数格式化器,根据数量选择单复数形式。
// 格式: {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
当请求的键不存在时抛出。
try
{
var text = locManager.GetText("common", "nonexistent.key");
}
catch (LocalizationKeyNotFoundException ex)
{
Debug.LogError($"Key not found: {ex.TableName}.{ex.Key}");
}
LocalizationTableNotFoundException
当请求的表不存在时抛出。
try
{
var table = locManager.GetTable("nonexistent_table");
}
catch (LocalizationTableNotFoundException ex)
{
Debug.LogError($"Table not found: {ex.TableName}");
}
最佳实践
1. 键名组织
// 推荐:使用层级结构
"ui.button.start"
"ui.button.quit"
"combat.damage.physical"
"combat.damage.magical"
// 不推荐:扁平结构
"start_button"
"quit_button"
2. 变量命名
// 推荐:使用驼峰命名
"{playerName}"
"{maxHealth}"
// 不推荐:使用下划线或大写
"{player_name}"
"{MAX_HEALTH}"
3. 表的划分
// 按功能模块划分表
common.json // 通用文本
ui.json // UI 文本
combat.json // 战斗文本
items.json // 物品文本
4. 安全获取
// 推荐:使用 TryGetText 避免异常
if (locManager.TryGetText("common", "key", out var text))
{
// 使用 text
}
// 或者提供默认值
var text = locManager.TryGetText("common", "key", out var result)
? result
: "Default Text";
5. 语言变化处理
// 在组件初始化时订阅
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();
}
性能考虑
缓存策略
- 本地化表在加载后会缓存在内存中
- 语言切换时只加载新语言的表
- 建议在游戏启动时预加载常用语言
内存优化
// 只加载当前语言,不预加载所有语言
var config = new LocalizationConfig
{
DefaultLanguage = "eng"
};
// 按需切换语言
locManager.SetLanguage(userSelectedLanguage);