☰
WPF 本地化重构:从 strings.resx 到常量字符串的 ResourceID 迁移全指南
2026/9/27 9:20:05 网站建设 项目流程
  • 桌面应用
  • 前端
  • UI组件

【免费下载链接】wpf

WPF is a .NET Core UI framework for building Windows desktop applications.

项目地址:https://gitcode.com/gh_mirrors/wp/wpf
点击查看免费下载

本篇技术指南以 localization_untranslated_strings.md 为核心骨架,系统梳理 WPF 开源仓库(gh_mirrors/wp/wpf)中三类资源标识符(ResourceID)从strings.resx文件迁移为 C#const string常量字符串的完整方案。文中覆盖 PresentationBuildTasks、PresentationCore、PresentationFramework 三个程序集共 150+ 个资源项的迁移清单、各资源在源码中的实际消费位置,以及迁移后本地化工作流(xlf 翻译文件、SR 类型访问器)的变化,帮助开发者在贡献或维护 WPF 本地化资源时快速定位常量、理解键值语义并规避"字符串未翻译"类问题。

一、背景:为什么要把 ResourceID 从 resx 迁移到常量字符串

在 WPF 的早期实现中,大量与命令快捷键(KeyGesture)、构建任务名称、错误消息相关的字符串存放在程序集的Resources/Strings.resx资源文件中,通过生成的强类型SR(String Resources)访问器以SR.XXX形式读取。

这种方式的缺陷在于:

  1. 延迟加载与间接性:SR.XXX在运行时才从资源程序集中解析字符串,构建任务、命令系统每次需要字符串时都多一层资源查找,且字符串值(而非键名)散落在.resx中难以静态追踪。
  2. 本地化噪音:这些字符串中,一类(如任务名"FileClassifier"、快捷键"Ctrl+C")不需要也不应该被翻译,却仍被纳入 xlf 翻译流程,产生大量"未翻译字符串"(untranslated strings)噪音——这正是本文档标题的由来。
  3. 不利于增量编译与裁剪:字符串驻留在资源文件里,编译器难以判断哪些是纯内部标识、哪些是面向用户的本地化文本。

迁移方案的核心思想是:将"非本地化"的字符串直接内联为 C# 的const string常量字段,与代码同编译、同裁剪;而真正面向用户的显示文本(如XXXKeyDisplayString后缀的资源)继续保留在Strings.resx/.xlf中走正常本地化流程。

二、PresentationBuildTasks:构建任务标识常量化

2.1 迁移清单(完整表格)

PresentationBuildTasks程序集(源码位于 src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks)共迁移了 8 个任务名常量,它们是 MSBuild 目标中引用的任务类名:

ResourceIDValue(常量值)
FileClassifierTask"FileClassifier"
GetWinFXPathTask"GetWinFXPath"
MarkupCompilePass1Task"MarkupCompilePass1"
MarkupCompilePass2Task"MarkupCompilePass2"
MergeLocalizationDirectivesTask"MergeLocalizationDirectives"
ResourcesGeneratorTask"ResourcesGenerator"
UidManagerTask"UidManager"
UpdateManifestForBrowserApplicationTask"UpdateManifestForBrowserApplication"

2.2 这些常量的真实消费位置

  • MarkupCompilePass1/MarkupCompilePass2是 WPF XAML 编译管线的两个核心阶段。构建入口文件 Microsoft.WinFX.targets 中定义了名为MarkupCompilePass1的<Target>,并在 第 92 行 通过Targets="MarkupCompilePass1"建立依赖;MergeLocalizationDirectives目标定义于 第 909 行。
  • 源码中,任务类型被大量引用:如 CompilerWrapper.cs 的注释明确指出"该属性由 MarkupCompilePass1 与 Pass2 构建任务调用";TaskFileService.cs 通过_buildTask as MarkupCompilePass1进行类型判断;IncrementalCompileAnalyzer.cs 构造器接收MarkupCompilePass1实例用于增量编译分析。
  • ResourcesGenerator的静态方法GetResourceIdForResourceFile在 MarkupCompiler.cs 中被调用,用于生成 BAML 资源的规范化资源 ID(由源文件相对路径、TargetPath、当前目录等拼合而成),随后参与构造形如/{AssemblyName}/component/{resourceID}的组件 URI(见 MarkupCompiler.cs)。

迁移收益:这些任务名是 MSBuild 内部契约字符串,若进入 resx 翻译流程毫无意义。常量化后,任务名与任务类定义共存于同一编译单元,ResourceManager查找被彻底消除。

三、PresentationCore:命令快捷键与硬件错误消息常量化

PresentationCore(源码位于 src/Microsoft.DotNet.Wpf/src/PresentationCore)迁移了两大类内容:应用/导航命令的默认键位绑定(约 40 个)与1 条硬件错误消息。

3.1 完整迁移清单

ResourceIDValue(常量值)
BrowseBackKey"Alt+Left;Backspace"
BrowseForwardKey"Alt+Right;Shift+Backspace"
BrowseHomeKey"Alt+Home;BrowserHome"
BrowseStopKey"Alt+Esc;BrowserStop"
ContextMenuKey"Shift+F10;Apps"
CopyKey"Ctrl+C;Ctrl+Insert"
CutKey"Ctrl+X;Shift+Delete"
DeleteKey"Del"
ExtendSelectionDownKey"Shift+Down"
ExtendSelectionLeftKey"Shift+Left"
ExtendSelectionRightKey"Shift+Right"
ExtendSelectionUpKey"Shift+Up"
FindKey"Ctrl+F"
HelpKey"F1"
HwndTarget_HardwareNotSupportDueToProtocolMismatch"Due to protocol mismatch hardware support is not available."
MoveDownKey"Down"
MoveFocusBackKey"Ctrl+Left"
MoveFocusDownKey"Ctrl+Down"
MoveFocusForwardKey"Ctrl+Right"
MoveFocusPageDownKey"Ctrl+PageDown"
MoveFocusPageUpKey"Ctrl+PageUp"
MoveFocusUpKey"Ctrl+Up"
MoveLeftKey"Left"
MoveRightKey"Right"
MoveToEndKey"End"
MoveToHomeKey"Home"
MoveToPageDownKey"PageDown"
MoveToPageUpKey"PageUp"
MoveUpKey"Up"
NewKey"Ctrl+N"
OpenKey"Ctrl+O"
PasteKey"Ctrl+V;Shift+Insert"
PrintKey"Ctrl+P"
PrintPreviewKey"Ctrl+F2"
PropertiesKey"F4"
RedoKey"Ctrl+Y"
ReplaceKey"Ctrl+H"
SaveKey"Ctrl+S"
ScrollPageDownKey"PageDown"
ScrollPageUpKey"PageUp"
SelectAllKey"Ctrl+A"
SelectToEndKey"Shift+End"
SelectToHomeKey"Shift+Home"
SelectToPageDownKey"Shift+PageDown"
SelectToPageUpKey"Shift+PageUp"
StopKey"Esc"
UndoKey"Ctrl+Z"

3.2 快捷键常量的底层消费链路

  • ApplicationCommands:文件 ApplicationCommands.cs 定义了命令 ID 枚举CommandId(Cut/Copy/Paste/Undo/Redo/Delete/Find/Replace/Help/SelectAll/New/Open/Save/Print/PrintPreview/Properties/ContextMenu/Stop 等,见 第 453-481 行),并在QueryDefaultGestures中通过KeyGesture.AddGesturesFromResourceStrings(常量键值, SR.XXXKeyDisplayString, gestures)为每条命令注册默认键位(如 第 394-398 行 的 ContextMenu 分支)。常量字段本身定义于文件末尾的#region Private Fields(如 第 486 行 的private const string ContextMenuKey = "Shift+F10;Apps";)。
  • NavigationCommands:文件 NavigationCommands.cs 以相同模式消费BrowseBackKey等导航命令键位(第 224-225 行),常量定义于 第 376 行。
  • 值格式说明:"Alt+Left;Backspace"这类分号分隔的多手势字符串由KeyGesture.AddGesturesFromResourceStrings解析——分号将字符串拆分为多个独立的KeyGesture,即BrowseBack命令默认同时绑定Alt+Left与Backspace两个手势。

3.3 硬件错误消息的消费位置

HwndTarget_HardwareNotSupportDueToProtocolMismatch("Due to protocol mismatch hardware support is not available.")在 HwndTarget.cs 中以throw new InvalidOperationException(SR.HwndTarget_HardwareNotSupportDueToProtocolMismatch)的形式抛出——这是硬件加速渲染在通道协议不匹配时(例如远程桌面/兼容性场景)的运行时异常消息,本质是面向开发者的诊断文本,本地化优先级极低,因此一并常量化。

四、PresentationFramework:文本编辑命令键位与界面格式串常量化

PresentationFramework(源码位于 src/Microsoft.DotNet.Wpf/src/PresentationFramework)迁移内容最丰富:文本编辑(RichTextBox 等控件)的段落/表格/选择/移动等编辑命令键位、InkCanvas与ListBox的全局快捷键,以及少量格式化字符串。

4.1 完整迁移清单

ResourceIDValue(常量值)
InkCanvasDeselectKey"Esc"
KeyAlignCenter"Ctrl+E"
KeyAlignLeft"Ctrl+L"
KeyAlignRight"Ctrl+R"
KeyAltUndo"Alt+Backspace"
KeyApplyDoubleSpace"Ctrl+2"
KeyApplyOneAndAHalfSpace"Ctrl+5"
KeyApplySingleSpace"Ctrl+1"
KeyBackspace"Backspace"
KeyCopy"Ctrl+C"
KeyCopyFormat"Ctrl+Shift+C"
KeyCtrlInsert"Ctrl+Insert"
KeyCut"Ctrl+X"
KeyDecreaseFontSize"Ctrl+OemOpenBrackets"
KeyDecreaseIndentation"Ctrl+Shift+T"
KeyDelete"Delete"
KeyDeleteColumns"Alt+Ctrl+Shift+D"
KeyDeleteNextWord"Ctrl+Delete"
KeyDeletePreviousWord"Ctrl+Backspace"
KeyEnterLineBreak"Shift+Enter"
KeyEnterParagraphBreak"Enter"
KeyIncreaseFontSize"Ctrl+OemCloseBrackets"
KeyInsertColumns"Alt+Ctrl+Shift+C"
KeyInsertRows"Alt+Ctrl+Shift+R"
KeyInsertTable"Alt+Ctrl+Shift+T"
KeyMergeCells"Alt+Ctrl+Shift+M"
KeyMoveDownByLine"Down"
KeyMoveDownByPage"PageDown"
KeyMoveDownByParagraph"Ctrl+Down"
KeyMoveLeftByCharacter"Left"
KeyMoveLeftByWord"Ctrl+Left"
KeyMoveRightByCharacter"Right"
KeyMoveRightByWord"Ctrl+Right"
KeyMoveToColumnEnd"Alt+PageDown"
KeyMoveToColumnStart"Alt+PageUp"
KeyMoveToDocumentEnd"Ctrl+End"
KeyMoveToDocumentStart"Ctrl+Home"
KeyMoveToLineEnd"End"
KeyMoveToLineStart"Home"
KeyMoveToWindowBottom"Alt+Ctrl+PageDown"
KeyMoveToWindowTop"Alt+Ctrl+PageUp"
KeyMoveUpByLine"Up"
KeyMoveUpByPage"PageUp"
KeyMoveUpByParagraph"Ctrl+Up"
KeyPasteFormat"Ctrl+Shift+V"
KeyRedo"Ctrl+Y"
KeyRemoveListMarkers"Ctrl+Shift+R"
KeyResetFormat"Ctrl+Space"
KeySelectDownByLine"Shift+Down"
KeySelectDownByPage"Shift+PageDown"
KeySelectDownByParagraph"Ctrl+Shift+Down"
KeySelectLeftByCharacter"Shift+Left"
KeySelectLeftByWord"Ctrl+Shift+Left"
KeySelectRightByCharacter"Shift+Right"
KeySelectRightByWord"Ctrl+Shift+Right"
KeySelectToColumnEnd"Alt+Shift+PageDown"
KeySelectToColumnStart"Alt+Shift+PageUp"
KeySelectToDocumentEnd"Ctrl+Shift+End"
KeySelectToDocumentStart"Ctrl+Shift+Home"
KeySelectToLineEnd"Shift+End"
KeySelectToLineStart"Shift+Home"
KeySelectToWindowBottom"Alt+Ctrl+Shift+PageDown"
KeySelectToWindowTop"Alt+Ctrl+Shift+PageUp"
KeySelectUpByLine"Shift+Up"
KeySelectUpByPage"Shift+PageUp"
KeySelectUpByParagraph"Ctrl+Shift+Up"
KeyShiftBackspace"Shift+Backspace"
KeyShiftDelete"Shift+Delete"
KeyShiftInsert"Shift+Insert"
KeyShiftSpace"Shift+Space"
KeySpace"Space"
KeySplitCell"Alt+Ctrl+Shift+S"
KeySwitchViewingMode"Ctrl+M"
KeyTabBackward"Shift+Tab"
KeyTabForward"Tab"
KeyToggleBullets"Ctrl+Shift+L"
KeyToggleInsert"Insert"
KeyToggleNumbering"Ctrl+Shift+N"
KeyToggleSubscript"Ctrl+OemPlus"
KeyToggleSuperscript"Ctrl+Shift+OemPlus"
KeyToggleUnderline"Ctrl+U"
KeyUndo"Ctrl+Z"
ListBoxSelectAllKey"Ctrl+A"
SwitchViewingMode"_Switch ViewingMode"
ToStringFormatString_Control"{0}: {1}"
ToStringFormatString_GridView"{0} Columns.Count:{1}"
ZoomPercentageConverterStringFormat"{0:0.##}%"

4.2 文本编辑命令的注册链路(源码佐证)

文本编辑命令的键位常量集中在System.Windows.Documents命名空间下,以TextEditorParagraphs等内部静态类为载体:

  • TextEditorParagraphs.cs 在_RegisterClassHandlers中为 RichTextBox 等富文本控件注册编辑命令,例如EditingCommands.AlignLeft通过CommandHelpers.RegisterCommandHandler(..., KeyGesture.CreateFromResourceStrings(KeyAlignLeft, nameof(SR.KeyAlignLeftDisplayString)))绑定"Ctrl+L";KeyAlignCenter("Ctrl+E")、KeyApplySingleSpace("Ctrl+1")等同理(常量字段见 第 181-187 行)。
  • 文本编辑核心 TextEditor.cs 中同样定义了private const string KeyUndo = "Ctrl+Z"等常量,供撤销/重做等命令的默认手势使用。

注意区分两类键:表内Key*系列(如KeyMoveDownByLine = "Down")是编辑命令(EditingCommands)的默认键位;而 PresentationCore 中的MoveDownKey是应用级命令(ApplicationCommands/NavigationCommands)的键位。二者同名异义,迁移时不可混淆。

4.3 非快捷键资源说明

表中最后 4 项不属于键位:

  • SwitchViewingMode = "_Switch ViewingMode":带_下划线的UI 菜单文本(下划线表示助记键/快捷键前缀);
  • ToStringFormatString_Control = "{0}: {1}"、ToStringFormatString_GridView = "{0} Columns.Count:{1}":调试用 ToString 格式化串,不面向最终用户;
  • ZoomPercentageConverterStringFormat = "{0:0.##}%":缩放百分比转换格式串,用于文档查看器(如 FlowDocumentPageViewer)的缩放显示。

这些字符串格式化模板与快捷键一样属于"不需要翻译"的资源,因此同样被移出 resx 翻译流程。

五、迁移后的本地化工作流:常量字符串与 resx 的职责分工

迁移完成后,WPF 的资源系统形成清晰分工:

资源类别存放位置示例是否参与本地化(xlf)
内部标识/契约串(任务名、命令名)C#const string"MarkupCompilePass1"、"FileClassifier"否
命令默认键位(KeyGesture 串)C#const string"Ctrl+C;Ctrl+Insert"、"Alt+Left;Backspace"否
运行时异常/诊断消息C#const string或SRHwndTarget_HardwareNotSupportDueToProtocolMismatch否
面向用户的显示文本(含DisplayString后缀)Strings.resx+xlf/*.xlfSR.CopyKeyDisplayString、SR.KeyAlignLeftDisplayString是

5.1 键位常量与显示文本的解耦模式

从源码消费方式可以观察到一套双份资源模式:键位值本身作为const string内联(供KeyGesture解析),而面向屏幕阅读器/无障碍或用户界面的显示描述仍保留在Strings.resx中,例如:

  • Strings.resx 中的BrowseBackKeyDisplayString;
  • ExceptionStringTable.txt 中的BrowseBackKeyDisplayString=Alt+Left;Backspace;
  • xlf 翻译文件(如 Strings.cs.xlf)中的BrowseBackKeyDisplayString翻译单元。

调用侧代码KeyGesture.AddGesturesFromResourceStrings(常量键值, SR.XXXKeyDisplayString, gestures)的两个参数正是这一分工的体现:第一个参数是"机器可解析"的键位契约(常量),第二个参数是"人可读"的显示文本(本地化资源)。

5.2 对贡献者/本地化者的实践建议

  1. 新增编辑/应用命令快捷键时:将键位串定义为private const string XXXKey = "...",并在命令注册处通过KeyGesture.CreateFromResourceStrings/AddGesturesFromResourceStrings消费;若该命令需要可读的显示名称,才在Strings.resx中添加XXXKeyDisplayString条目并同步各语言的 xlf 文件。
  2. 不要为纯内部标识添加 xlf 翻译单元:任务名(如"ResourcesGenerator")、调试格式串(如"{0:0.##}%")属于"永远不需要翻译"的资源,应保持常量化,否则会产生"未翻译字符串"(untranslated strings)噪音并拖慢本地化流程。
  3. 查询某常量在哪被使用:可沿const string KeyUndo = "Ctrl+Z"这类定义反查引用点,或在TextEditor*.cs、ApplicationCommands.cs、NavigationCommands.cs、HwndTarget.cs中搜索对应资源名;跨语言本地化工作由 PresentationCore/Resources/xlf、PresentationFramework/Resources/xlf 等目录中的.xlf文件承接。

六、总结

本仓库的本地化资源重构将"非本地化"的 ResourceID 统一收敛为 C# 常量字符串,与代码一起编译、裁剪、维护:

  • PresentationBuildTasks(8 项):MSBuild 构建任务名,契约串,无需翻译;
  • PresentationCore(约 41 项):应用/导航命令默认键位 + 1 条硬件协议错误消息;
  • PresentationFramework(90 余项):富文本编辑命令键位、InkCanvas/ListBox 快捷键及调试格式串。

这一改动让strings.resx只保留真正需要翻译的用户可见文本,从源头消除了"未翻译字符串"噪音,同时让键位契约在源码中可静态检索、可被编译器优化——理解这套分工模式,是在 WPF 仓库中正确维护命令系统与本地化资源的前提。

  • 桌面应用
  • 前端
  • UI组件

【免费下载链接】wpf

WPF is a .NET Core UI framework for building Windows desktop applications.

项目地址:https://gitcode.com/gh_mirrors/wp/wpf
点击查看免费下载

相关推荐

上一篇:终极指南:DalmatinerDB写入优化策略与最佳实践
下一篇:一文读懂KnowledgeEditingPapers:LLM知识编辑的核心方法与前沿技术对比

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询