跳转到主内容

SeatFlow 基于 .NET .resx 的国际化系统架构、资源文件结构、XAML/C# 使用规范和 i18n.py 脚本工具

国际化系统

架构概述

SeatFlow 使用标准 .NET .resx 资源文件实现国际化。资源文件位于 SeatFlow.Presentation.Avalonia/Lang/ 目录,由一个中性语言文件、一个英文卫星文件和手动维护的强类型访问器类组成。

SeatFlow.Presentation.Avalonia/Lang/
├── Resources.resx          # 中性语言(zh-CN),约 700 个键
├── Resources.en-US.resx    # 英文卫星程序集
├── Resources.Designer.cs   # 强类型访问器(手动维护)
└── .backup/                # 自动备份目录(已 gitignore)

设计决策

  • .resx 格式而非 .json.po,因为 .NET 原生支持且与 {x:Static} 在 Avalonia XAML 中完美集成
  • 不使用 Visual Studio 的 PublicResXFileCodeGenerator(与 dotnet build 不兼容),改用 Designer.cs 手动维护
  • 使用 python3 scripts/i18n.py 脚本统一管理三文件的增删改查和同步

资源键命名规范

格式:{Category}_{MeaningfulName}(PascalCase,下划线分隔)

已知分类前缀

前缀 用途
About_ 关于对话框
App_ 应用级消息
Common_ 共享 UI 标签(如 Common_OKCommon_Cancel
ConfigBlock_ 配置块 UI
Data_ 数据加载/保存
Freeform_ 自由布局管理
Gender_ 性别标签
Guide_ 引导系统
Home_ 首页
Lang_ 语言名称
Member_ 成员管理
Nav_ 导航栏
Plugin_ 插件管理
Seating_ 座位安排
Settings_ 设置页
Snapshot_ 快照历史
Startup_ 启动守卫
Strategy_ 策略配置
Theme_ 主题名称
Venue_ 会场配置
Watchdog_ 看门狗服务
Zoom_ 缩放级别

格式字符串

资源值中使用 {0}{1} 等占位符,键名建议以 Fmt 结尾标识。

<data name="Snapshot_VenuesLoadedFmt" xml:space="preserve">
  <value>已加载 {0} 个会场</value>
</data>

当前支持语言

语言 文件 状态
中文(简体) Resources.resx(中性语言) 完整,~700 键
英语(美国) Resources.en-US.resx 持续维护

添加新语言

  1. Lang/ 目录下创建 Resources.xx-XX.resx 文件(例如 Resources.ja-JP.resx
  2. 将所有键翻译为目标语言
  3. 运行 python3 scripts/i18n.py check 验证一致性
  4. 无需修改 C# 代码——.NET 运行时自动按 CultureInfo.CurrentUICulture 加载对应卫星程序集
  5. AppSettings.Language 中添加对应语言代码

XAML 中使用

属性语法(推荐,编译绑定支持)

<TextBlock Text="{x:Static lang:Resources.Settings_Title}" />
<Button Content="{x:Static lang:Resources.Common_OK}" />

命名空间声明

xmlns:lang="using:SeatFlow.Presentation.Avalonia.Lang"

重要限制

  • 仅支持属性语法<TextBlock Text="{x:Static ...}" />
  • 不支持元素内容语法:以下写法不会正确解析
<!-- 错误:元素内容语法不适用于 {x:Static} -->
<Button>
  <x:Static xmlns:lang="..." Member="lang:Resources.Common_OK" />
</Button>

C# 中使用

标准用法

using SeatFlow.Presentation.Avalonia.Lang;

// 直接引用
StatusMessage = Resources.Settings_Saved;

// 带格式参数
StatusMessage = string.Format(Resources.Snapshot_VenuesLoadedFmt, count);

// 条件判断
if (Resources.Culture.TwoLetterISOLanguageName == "zh")
{
    // 中文逻辑
}

Window 子类中的特殊处理

在继承自 Window 的类(DialogWindowInputWindow)中,Resources 解析为 Window.ResourcesIResourceDictionary)。必须使用完全限定名:

// 正确:在 Window 子类中使用完全限定名
string title = Lang.Resources.Settings_Title;

// 错误:会产生编译错误
string title = Resources.Settings_Title; // 解析为 Window.Resources

ViewModel 中的使用

ViewModel 继承自 ViewModelBase(继承 ObservableObject),不存在 Window.Resources 冲突,直接使用 Resources.xxx 即可。

语言切换流程

语言切换在应用启动时通过 App.ApplyLanguageFromSettings() 完成:

App.Initialize()
  ├── ApplyLanguageFromSettings()     ← 必须在 XAML 加载前调用
  │    ├── 从 AppSettings 读取 Language 配置
  │    ├── 设置 CultureInfo.CurrentUICulture
  │    └── 设置 Resources.Culture
  └── AvaloniaXamlLoader.Load(this)   ← XAML 加载时 {x:Static} 已正确解析

语言设置在 AppSettings.Language 中持久化,用户通过设置页面修改,重启后生效。

策略/插件内部 i18n

策略 manifest 中的用户可见文字使用内联 i18n 词典格式,而非 .resx 键:

{
  "label": { "zh-CN": "历史窗口大小", "en-US": "History Window Size" },
  "messages": {
    "DeskMate_Split": {
      "zh-CN": "同桌组({0})中的 {1} 已被前排策略分配",
      "en-US": "Desk-mate group ({0}) member(s) {1} already assigned"
    }
  }
}

LocalizeHelper.Resolve

Presentation 层通过 LocalizeHelper.Resolve(dict) 方法解析:

public static string Resolve(Dictionary<string, string> localizedDict)
{
    if (localizedDict.TryGetValue(CultureInfo.CurrentUICulture.Name, out var value))
        return value;
    if (localizedDict.TryGetValue("zh-CN", out var fallback))
        return fallback;
    return localizedDict.Values.FirstOrDefault() ?? "";
}

解析顺序:

  1. 精确匹配 CultureInfo.CurrentUICulture.Name(如 en-US
  2. 回退到 zh-CN
  3. 回退到第一个可用值

内置策略和插件策略共用同一机制——消息模板在 Manifests/{Id}.json 中声明,插件策略的在各插件包策略子目录下的 manifest.json 中声明。

i18n.py 脚本工具

scripts/i18n.py 统一管理三文件(zh-CN .resx、en-US .resx、Designer.cs)的增删改查和同步。所有命令从 scripts/ 目录执行。

常用命令

cd scripts

# 列出所有键
python3 i18n.py list

# 查找未翻译的键(zh-CN == en-US)
python3 i18n.py list --missing-en

# 搜索匹配特定模式的键
python3 i18n.py list --pattern "Export"

# 列出含格式占位符的键
python3 i18n.py list --format-strings

# 按分类过滤
python3 i18n.py list --category Settings

# 导出为 JSON 或 CSV
python3 i18n.py list --output json
python3 i18n.py list --output csv > translations.csv

校验

# 一致性校验
python3 i18n.py check

# 自动修复排序问题
python3 i18n.py check --fix

校验项包括:

  • 三文件 key 集合一致性
  • key 命名规范(Category_MeaningfulName PascalCase)
  • zh-CN 和 en-US 格式字符串参数数量匹配
  • 空值检测
  • XML 重复 key 检测

增删改查

# 添加新键
python3 i18n.py add Settings_NewOption --zh "新选项" --en "New Option"
python3 i18n.py add Common_NewAction --zh "执行操作" --en "Execute" --comment "工具栏按钮"

# 修改已有键
python3 i18n.py modify Settings_Title --zh "系统设置"
python3 i18n.py modify Settings_Title --en "System Settings"

# 重命名键(三文件同步)
python3 i18n.py rename Seating_Title Seating_WindowTitle

# 删除键
python3 i18n.py delete Obsolete_Key
python3 i18n.py delete Temp_DebugKey --force

Designer.cs 同步

# 预览变更
python3 i18n.py sync --dry-run

# 执行同步
python3 i18n.py sync

Resources.resx 的 key 列表重新生成 Resources.Designer.cs 中的所有属性,保留原有注释分隔符和代码结构。

批量翻译工作流

# 1. 导出为 CSV
python3 i18n.py export -o translations.csv

# 2. 在 Excel 中编辑 translations.csv

# 3. 预览导入
python3 i18n.py import translations.csv --dry-run

# 4. 执行导入
python3 i18n.py import translations.csv --force

安全机制

机制 说明
自动备份 所有写入操作自动备份三文件到 Lang/.backup/
Dry-run --dry-run 展示变更摘要但不实际写入
确认提示 默认要求输入 y 确认;--force 跳过
原子性 先完成全部校验,再一次性写入所有文件
XML 合法性 写入后立即重新解析验证 XML 格式

常见工作流

添加新的 UI 文案:

python3 i18n.py add Settings_AutoSave --zh "自动保存" --en "Auto Save"
# 在 C# 中使用:StatusMessage = Resources.Settings_AutoSave;
# 在 AXAML 中使用:<TextBlock Text="{x:Static lang:Resources.Settings_AutoSave}" />
python3 i18n.py check
dotnet build

批量修改翻译:

python3 i18n.py export -o translations.csv
# 在 Excel 中编辑 CSV
python3 i18n.py import translations.csv --dry-run
python3 i18n.py import translations.csv
python3 i18n.py check
dotnet build

用户自定义语言配置

语言通过 AppSettings.Language 存储,值为区域代码(如 zh-CNen-US)。用户在设置页面选择语言后:

  1. AppSettings.Language 持久化到 JSON
  2. 重启后 ApplyLanguageFromSettings() 读取并应用
  3. 所有 {x:Static} 绑定和 Resources.xxx 调用自动切换到目标语言

资源文件结构参考

完整脚本参考文档见 scripts/ToolsCollection.md。脚本单元测试在 scripts/tests/test_i18n.py(45 个测试用例)。