
PowerToys 本地化开发指南从 LocProject.json、lcl 文件到 rc 转换与卫星程序集打包【免费下载链接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys本篇基于 PowerToys 本地化开发文档 展开系统讲解 PowerToys 的 CDPX 流水线本地化机制、LocProject.json配置、C/C#/UWP 三类项目的本地化启用步骤、lcl文件格式与安全防护机制以及如何把本地化资源C 的 rc 字符串表、UWP 的resources.pri、C# 的卫星程序集正确接入 MSI 安装包。读完本文你将能够在 PowerToys 中为新模块启用本地化、为字符串建立可翻译的资源文件并理解本地化产物从流水线到安装包的完整流转链路。一、本地化体系总览PowerToys 的本地化围绕一个核心原则代码中不允许硬编码 UI 显示字符串所有可展示文本必须来自资源文件。按项目类型分为三条资源链路项目类型资源格式本地化产物打包方式C#.NETResources.resx各语言卫星程序集langId\ProjName.resources.dll卫星 dll 需额外加入 MSICResources.resx转换源→ 生成的.rc/.h字符串表编译进 dll/exe 本身无需额外文件UWPResources.resw编译进resources.pri无需额外文件从仓库结构可以印证这套体系C# 模块的英文资源位于模块根目录例如 ActionRunner 的 Resources.resx 与 KeyboardManagerEditor 的 Resources.resxUWP 风格模块则使用Strings\en-us\Resources.resw例如 ShortcutGuide.Ui 的 Resources.resw 与 cmdpal UI 的 Resources.resw。二、流水线上的本地化CDPX2.1 build-localization 步骤与 LocProject.json本地化步骤在 CDPX 流水线中先于解决方案构建执行它运行build-localization脚本调用Localization.XLoc包为所有启用了本地化的项目生成 resx 文件。Localization.XLoc在仓库根目录运行扫描每一个LocProject.json文件。每个本地化项目的项目根目录下都有这样一份LocProject.json它描述英文 resx 源文件位置本地化的语言集合生成后的本地化 resx 文件复制到的输出路径以及其他参数例如语言 ID 是以目录名还是文件名形式体现在输出路径中。一个典型的LocProject.json项目位于src\path英文资源在resources\Resources.resx{ Projects: [ { LanguageSet: Azure_Languages, LocItems: [ { SourceFile: src\\path\\resources\\Resources.resx, CopyOption: LangIDOnName, OutputPath: src\\path\\resources } ] } ] }字段说明字段含义LanguageSet语言集合名称Azure_Languages表示使用 Azure 语言集含 27 种语言SourceFile英文 resx 源文件相对仓库根目录的路径CopyOption语言 ID 放置方式LangIDOnName拼进文件名或LangIDOnFolder作为目录OutputPath生成的各语言 resx 复制到的目录当 CDPX 流水线运行且英文 resx 发生变更时本地化团队会收到通知。对每个启用本地化的项目会在LocProject.json同目录生成一个loc文件夹例如 Microsoft.Launcher 模块下的loc目录其中按语言建立子目录子目录下再按LocProject.json中OutputPath对应的嵌套路径组织每个目录中有一个lcl文件。lcl文件包含英文资源及其对应语言的译文详见 第四节。resx 文件生成后会在Build PowerToys步骤中被用于构建各模块的本地化版本。2.2 restore-localization 与网络隔离本地化脚本依赖特定的 NuGet 包因此build-localization之前必须先运行restore-localization脚本安装所需包。该脚本必须放在流水线的restore阶段执行因为 CDPX 流水线在build阶段处于网络隔离状态无法在线还原包还原时使用流水线中配置的 Toolset 包源完成。2.3 IsPipeline 变量与 MSI 的联动C# 项目的本地化资源 dll 只在流水线构建时才被加入 MSI。判断方式是检查IsPipeline变量是否已定义——该变量在流水线构建安装器之前被设置。之所以需要这个开关是因为本地化 resx 文件只存在于流水线上本地开发者机器上没有这些文件若安装器工程无条件引用它们本地构建安装器项目会直接失败。2.4 当前仓库中的本地化流水线入口当前仓库快照中本地化流水线的入口是 loc.yml。该流水线采用定时触发cron: 0 3 * * 2-6太平洋工作时间每周一至五结束后于 03:00 UTC 运行且always: false即仅在代码有变更时执行通过MicrosoftTDBuild.tdbuild-taskTouchdown Build任务把资源文件推送到本地化团队资源匹配路径为resourceFilePath: | src\**\Resources.resx src\**\Resource.resx src\**\Resources.reswpseudoSetting: Included表示包含伪本地化pseudo输出便于在未获得真实译文前验证多语言布局输出目录LocOutput会被打包为LocOutput.tar.gz发布为流水线工件方便排查本地化输出问题。从这份配置可以确认本地化系统的输入即仓库中所有模块的英文Resources.resx/Resource.resx/Resources.resw与第二、三节的资源约定完全一致。三、为新项目启用本地化第一步对所有类型相同在项目根目录创建LocProject.json格式见 2.1 节。把本地化文件加入 MSI 的步骤见第六节。3.1 C 项目resx 到 rc 的转换链C 项目原生不支持resx而是使用.rcresource.h。由于 CDPX 流水线不支持直接本地化rc文件其替代方案是直接从二进制翻译资源难以维护PowerToys 采用了一条自定义转换链以 resx 为本地化源再用脚本把各语言 resx 转换为带字符串表的 rc 文件与 resource.h。第一步把已有字符串表转成 resx。如果项目已有.rc文件把字符串表拷贝到一个单独的 txt 文件然后运行 convert-stringtable-to-resx.ps1 脚本。该脚本对输入格式要求较严格每行必须是IDS_ResName LResourceValue形式IDS_ResName与L...之间可以有任意多个空格。脚本将其转换为resgen工具可识别的格式后再转成 resx。转换过程中资源名从全大写改为标题式Title Case并去掉IDS_前缀。转义字符可能需要手工处理例如.rc中双引号写作转 resx 前需替换为单个。第二步拆分 base 文件并挂接构建事件。resx 生成后把现有 rc 和 h 文件重命名为ProjName.base.rc与resource.base.h在 rc 文件中删除需要本地化的字符串表在 h 文件中删除所有对应本地化资源的#define。然后在 C 工程的 vcxproj 中添加如下构建事件Target NameGenerateResourceFiles BeforeTargetsPrepareForBuild Exec LogStandardErrorAsErrorfalse Commandpowershell -NonInteractive -executionpolicy Unrestricted -NoProfile $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h ProjName.base.rc ProjName.rc / /Target第三步理解 convert-resx-to-rc.ps1 的生成逻辑。convert-resx-to-rc.ps1 接收 5 个必填参数resx 所在目录、base 头文件名、目标头文件名、base rc 文件名、目标 rc 文件名和 1 个可选参数资源起始 ID默认101见脚本 第 18-26 行。它的处理流程递归遍历目录中所有.resx文件从文件名或父目录名解析语言代码脚本 第 95-118 行对zh-CN这类语言地区形式会回退匹配纯语言zh用resgen把 resx 转成 rc 所需的字符串表格式资源名恢复为IDS_前缀 全大写还原为原始命名字符串中的一律转义为以避免构建错误资源#define声明从 101 起依次编号且只根据其中一种语言生成一次避免重复编号每种语言的字符串表按如下格式追加到 rc 文件#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US STRINGTABLE BEGIN strings END #endif关键限制语言代码表是硬编码的。由于没有 API 可以从流水线给出的 langId 反查AFX_TARG_*、LANG_*、SUBLANG_*值脚本在 第 48-76 行 维护了一个语言哈希表覆盖 en、zh-Hans/zh-CN、cs、hu、pl、ro、sk、bg、ru、ca、de、es、fr、it、nl、nb-NO、pt-BR、eu-ES、tr、he、ar、ja、ko、sv、pt-PT、zh-Hant/zh-TW 等语言。若未来本地化团队新增语言必须同步更新这个哈希表否则脚本会输出Unknown language警告并跳过该语言。要确定某个语言的代码可以在 Resource View 中右键字符串表选Insert Copy并选择对应语言工具会自动生成所需代码供参考。生成物写入Generated Files目录该目录被.gitignore忽略且文件头部带有auto-generated警告注释。因此这两个生成文件内部的#include需要多加一层..\使用resource.h的代码要写成#include Generated Files\resource.hbase 文件加入 vcxproj 时应改为不参与构建的None项避免与生成物冲突None IncludeResources.resx /多工程共享 rc/resource.h 的情况有些 rc/resource.h 被多个项目共用例如 Keyboard Manager。此时把构建事件提升到目录级Directory.Build.targets保证任何项目开始构建前 rc 文件已生成。仓库中现成的例子是 keyboardmanager 的 Directory.Build.targetsTarget NameGenerateResourceFiles BeforeTargetsPrepareForBuild Exec Commandpowershell -NonInteractive -executionpolicy Unrestricted $(RepoRoot)tools\build\convert-resx-to-rc.ps1 ..\dll resource.base.h resource.h KeyboardManager.base.rc KeyboardManager.rc / /Target消费字符串C 侧统一使用GET_RESOURCE_STRING(resource_id)宏读取字符串表该宏定义在 src/common/utils/resources.h 第 210-211 行#define GET_RESOURCE_STRING(resource_id) get_resource_string(resource_id, reinterpret_castHINSTANCE(__ImageBase), L#resource_id) #define GET_RESOURCE_STRING_FALLBACK(resource_id, fallback) get_resource_string(resource_id, reinterpret_castHINSTANCE(__ImageBase), fallback)GET_RESOURCE_STRING_FALLBACK在资源缺失时提供回退字符串是本地化资源尚未就绪时的稳健选择。3.2 C# 项目直接纳入 resxC# 项目原生支持resx唯一要做的是把生成的各语言 resx 纳入构建.NET Core 项目自动包含csproj无需改动其他项目在 csproj 中加一行EmbeddedResource IncludeProperties\Resources.*.resx /两个已知注意事项带本地化资源构建时可能出现警告Referenced assembly mscorlib.dll targets a different processor这是 Visual Studio 的已知 bug可忽略XAML 资源迁移到 resx若项目原来用 XAMLSystem.String资源最简迁移路径是把资源改成分隔的纯文本手工全局替换或脚本再用resgen转为 resx。例如把system:String x:Keywox_plugin_calculator_plugin_nameCalculator/system:String system:String x:Keywox_plugin_calculator_plugin_descriptionAllows to do mathematical calculations.(Try 5*3-2 in Wox)/system:String system:String x:Keywox_plugin_calculator_not_a_numberNot a number (NaN)/system:String改写为wox_plugin_calculator_plugin_nameCalculator wox_plugin_calculator_plugin_descriptionAllows to do mathematical calculations.(Try 5*3-2 in Wox) wox_plugin_calculator_not_a_numberNot a number (NaN)然后在Developer Command Prompt for VS中运行resgen转成 resx。resx 加入工程并配置资源生成器后代码中对字符串的引用要改为Properties.Resources.resName替换掉原来的自定义 API。3.3 UWP 项目resw 通配包含UWP 项目期望resw文件格式与 resx 几乎相同但文件组织形式不同必须位于fullLangId\Resources.resw路径下。因此要把 csproj 中单语言的包含PRIResource IncludeStrings\en-us\Resources.resw /替换为通配形式以纳入流水线生成的全部语言目录PRIResource IncludeStrings\*\Resources.resw /当前仓库中 cmdpal UI、PowerDisplay、RegistryPreview 等模块均采用这种Strings\en-us\Resources.resw布局。四、lcl 文件格式与防失效机制lcl文件包含英文 resx 中的全部资源若某条资源已有译文则一并附上。一条资源的 lcl 条目形如Item ItemId;EditKeyboard_WindowName ItemType0;.resx PsrId211 Leaftrue Str CatText Val![CDATA[Remap keys]]/Val Tgt CatText StatLoc OrigNew Val![CDATA[Remapper des touches]]/Val /Tgt /Str Disp IconStr / /Item结构要点ValStr直属是英文原文Tgt元素是译文容器StatLoc表示已翻译OrigNew表示新字符串。lcl 文件的初始提交中只有英文没有Tgt元素条目结构对应 KeyboardManagerEditor 的 Resources.resx 中EditKeyboard_WindowName这样的资源键。防失效fail-safe机制CDPX 本地化系统对 lcl 文件做一致性检查——若Val![CDATA[*]]/Val中的英文字符串与英文Resources.resx中的值不一致则该条译文不会被复制到本地化 resx 中。这样设计的目的是当英文资源被修改后过期的旧译文不会被加载程序会回退使用英文原文等待本地化团队更新译文。这决定了上游团队的实践约束修改英文 resx 字符串时旧译文会自动失效回退为英文不会出现旧译文配新语境的错乱。五、LEGO 本地化 PR 的常见合并问题LEGO PR本地化团队提交的批量翻译 PR一次只更新部分字符串多个 PR 可能同时修改同一批文件从而产生合并冲突。大多数冲突会在 GitHub 上明确显示但偶尔会出现坏合并文件表面合并成功实际格式已损坏例如单个资源出现两个Tgt元素。排查与修复手段按第四节的 lcl 条目格式校正损坏文件确保每条资源至多一个Tgt每个 LEGO PR 都应跑一遍 build farm若本地化步骤报错检查对应项目的 resx/lcl 文件是否存在残留冲突标记或重复元素。六、为新项目启用本地化 MSI6.1 C 与 UWP无需额外操作C 项目的所有资源编译进 dll/exe 本身UWP 项目的资源进入resources.pri未本地化的项目同样有该文件因此这两类项目不产生需要额外加入 MSI 的本地化文件。验证 UWP 资源是否成功写入resources.pri的方法打开Developer Command Prompt for VS进入 pri 文件所在目录运行makepri.exe dump /if .\resources.pri检查生成的resources.pri.xml其末尾包含各语言的资源候选项例如NamedResource nameGeneralSettings_RunningAsAdminText urims-resource://f4f787a5-f0ae-47a9-be89-5408b1dd2b47/Resources/GeneralSettings_RunningAsAdminText Candidate qualifiersLanguage-FR typeString ValueRunning as administrator/Value /Candidate Candidate qualifiersLanguage-EN-US isDefaulttrue typeString ValueRunning as administrator/Value /Candidate /NamedResource6.2 C#卫星程序集加入 MSIC# 项目构建时会为每种语言生成卫星程序集项目ProjName会产出langId\ProjName.resources.dlllangId格式与 lcl 文件一致。这些卫星 dll 必须加入 MSI但只能来自流水线构建的解决方案——本地机器上没有本地化 resx无条件引用会导致本地安装器构建失败。做法是在 installer 目录下的 Product.wxs 中把项目目录名加入受IsPipeline检查控制的本地化资源列表并按以下模式为项目创建资源组件Component IdProjName_$(var.IdSafeLanguage)_Component DirectoryResource$(var.IdSafeLanguage)ProjNameInstallFolder File IdProjName_$(var.IdSafeLanguage)_File Source$(var.BinX64Dir)modules\ProjName\$(var.Language)\ProjName.resources.dll / /Component两个配套要点签名确保新增 dll 被流水线签名。当前所有*.resources.dll形式的程序集都在流水线签名清单中时机卫星 dll 的 MSI 组件应在本地化团队完成 lcl 文件初始提交之后再加——否则流水线上不存在任何 resx 可用来生成 dll流水线会失败。七、字符串使用规范Working With Strings要支持本地化代码中不得出现硬编码的 UI 显示字符串必须通过资源文件取字符串。7.1 C用StringTable资源存储字符串用resource.h存储与字符串绑定的 ID配合 Visual Studio 资源编辑器维护resource.hXXX 必须唯一通常取最后一个字符串 ID 1#define IDS_MODULE_DISPLAYNAME XXX资源定义脚本validmodulename.rcSTRINGTABLE BEGIN IDS_MODULE_DISPLAYNAME LModule Name END代码中消费#include common.h std::wstring s GET_RESOURCE_STRING(IDS_MODULE_DISPLAYNAME);7.2 C#用 XML 资源文件.resx存储 UI 字符串用ResourceManager消费data nameValidUIDisplayString xml:spacepreserve valueDescription to be displayed on UI./value commentThis text is displayed when XYZ button clicked./comment /data手工消费System.Resources.ResourceManager manager new System.Resources.ResourceManager(baseName, assembly); string validUIDisplayString manager.GetString(ValidUIDisplayString, resourceCulture);若资源文件由 Visual Studio 生成直接使用自动生成的Resources.Designer.cs封装的Resources类即可string validUIDisplayString Resources.ValidUIDisplayString;八、小结PowerToys 的本地化体系可以归纳为一条主链英文 resx/resw 是唯一的本地化源头→ CDPX 流水线通过LocProject.json与Localization.XLoc生成loc目录下的 lcl 文件 → 本地化团队在 lcl 中补充译文 → 流水线生成各语言 resx/resw → C# 产出卫星 dll经IsPipeline检查加入 MSI、C 经convert-resx-to-rc.ps1生成本地化 rc/resource.h 编译进二进制、UWP 汇入resources.pri。开发者的日常职责落在两端一端是按第三、七节的规范建立可翻译资源新模块配LocProject.json、C 项目挂接GenerateResourceFiles构建事件、UWP 用通配PRIResource另一端是理解 lcl 防失效机制与 LEGO PR 冲突处理保证英文字符串变更与译文更新之间的安全衔接。需要扩展支持语言时记得同步维护 convert-resx-to-rc.ps1 中硬编码的语言代码表。【免费下载链接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考