- 桌面应用
- 前端
- UI组件
【免费下载链接】wpf
WPF is a .NET Core UI framework for building Windows desktop applications.
本篇技术指南以 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形式读取。
这种方式的缺陷在于:
- 延迟加载与间接性:
SR.XXX在运行时才从资源程序集中解析字符串,构建任务、命令系统每次需要字符串时都多一层资源查找,且字符串值(而非键名)散落在.resx中难以静态追踪。 - 本地化噪音:这些字符串中,一类(如任务名
"FileClassifier"、快捷键"Ctrl+C")不需要也不应该被翻译,却仍被纳入 xlf 翻译流程,产生大量"未翻译字符串"(untranslated strings)噪音——这正是本文档标题的由来。 - 不利于增量编译与裁剪:字符串驻留在资源文件里,编译器难以判断哪些是纯内部标识、哪些是面向用户的本地化文本。
迁移方案的核心思想是:将"非本地化"的字符串直接内联为 C# 的const string常量字段,与代码同编译、同裁剪;而真正面向用户的显示文本(如XXXKeyDisplayString后缀的资源)继续保留在Strings.resx/.xlf中走正常本地化流程。
二、PresentationBuildTasks:构建任务标识常量化
2.1 迁移清单(完整表格)
PresentationBuildTasks程序集(源码位于 src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks)共迁移了 8 个任务名常量,它们是 MSBuild 目标中引用的任务类名:
| ResourceID | Value(常量值) |
|---|---|
| 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 完整迁移清单
| ResourceID | Value(常量值) |
|---|---|
| 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 完整迁移清单
| ResourceID | Value(常量值) |
|---|---|
| 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或SR | HwndTarget_HardwareNotSupportDueToProtocolMismatch | 否 |
面向用户的显示文本(含DisplayString后缀) | Strings.resx+xlf/*.xlf | SR.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 对贡献者/本地化者的实践建议
- 新增编辑/应用命令快捷键时:将键位串定义为
private const string XXXKey = "...",并在命令注册处通过KeyGesture.CreateFromResourceStrings/AddGesturesFromResourceStrings消费;若该命令需要可读的显示名称,才在Strings.resx中添加XXXKeyDisplayString条目并同步各语言的 xlf 文件。 - 不要为纯内部标识添加 xlf 翻译单元:任务名(如
"ResourcesGenerator")、调试格式串(如"{0:0.##}%")属于"永远不需要翻译"的资源,应保持常量化,否则会产生"未翻译字符串"(untranslated strings)噪音并拖慢本地化流程。 - 查询某常量在哪被使用:可沿
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.
相关推荐
Valdi 国际化(Localization)实战指南:从 strings-en.json 到全平台本地化字符串
Valdi 国际化(Localization)实战指南:从 strings en.json 到全平台本地化字符串 Valdi 提供了一套与构建系统深度集成的本地
跨平台UI组件前端移动开发numpy.char 遗留定宽字符串操作模块完全指南:向量化字符串 API、比较语义与 chararray 弃用迁移
numpy.char 遗留定宽字符串操作模块完全指南:向量化字符串 API、比较语义与 chararray 弃用迁移 本文是 NumPy 官方参考文档 doc/
科学计算数据分析Kronos:用AI语言模型重新定义金融时序预测
Kronos:用AI语言模型重新定义金融时序预测 想象一下,当传统量化模型在2024年科技股财报季集体"失灵"时,一个基于Transformer架构的金融基础模
人工智能大模型基础模型预训练金融科技
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考