.NET 和 Win32 应用和游戏通常本地化为不同的语言,以扩展其总可寻址市场。 有关本地化应用的价值主张的详细信息,请参阅 全球化和本地化。 通过将 .NET 或 Win32 应用或游戏打包为 .msix 或 .appx 包,可以利用资源管理系统加载针对运行时上下文定制的应用资源。 此深入主题介绍了这些技术。
有许多方法可以本地化传统的 Win32 应用程序,但 Windows 8 引入了一个 新的资源管理系统,该系统可跨编程语言、应用程序类型使用,并提供超越简单本地化的其他功能。 本主题中将此系统称为“MRT”。 历史上,它代表“现代资源技术”,但术语“现代”已经被弃用。 资源管理器也称为 MRM(新式资源管理器)或 PRI(包资源索引)。
MRT 可与基于 MSIX 或基于.appx的部署(例如,从 Microsoft 应用商店)结合使用,可以为给定的用户/设备自动提供最适用的资源,从而最大程度地减少应用程序的下载和安装大小。 对于具有大量本地化内容的应用程序来说,尺寸的减少可能非常显著,比如 AAA 游戏的本地化内容可能达到数个 千兆字节。 MRT 的其他优势包括 Windows Shell 和 Microsoft 应用商店中的本地化列表,当用户的首选语言与可用资源不匹配时,自动回退逻辑。
本文档介绍 MRT 的高级体系结构,并提供一个移植指南,以帮助将旧版 Win32 应用程序移动到 MRT,但代码更改最少。 迁移到 MRT 后,开发人员可以使用其他优势(例如按比例系数或系统主题细分资源)。 请注意,基于 MRT 的本地化适用于桌面桥处理的 UWP 应用程序和 Win32 应用程序(即“Centennial”)。
在许多情况下,你可以继续使用现有的本地化格式和源代码,同时与 MRT 集成,以在运行时解析资源,并尽量减少下载大小 - 这不是一种全无方法。 下表汇总了每个阶段的工作和估计成本/效益。 此表不包括非本地化任务,例如提供高分辨率或高对比度应用程序图标。 有关为磁贴、图标等提供多个资产的详细信息,请参阅 为语言、缩放、高对比度和其他限定符定制资源。
工作 | 益处 | 估计成本 |
---|---|---|
将包清单本地化 | 将本地化内容显示在 Windows Shell 和 Microsoft 应用商店中所需的最低工作量 | 小型 |
使用 MRT 识别和查找资源 | 最小化下载和安装大小的先决条件;自动语言回退 | 中等 |
生成资源包 | 最小化下载和安装大小的最后一步 | 小型 |
迁移到 MRT 资源格式和 API 接口 | 显著较小的文件大小(具体取决于现有资源技术) | 大 |
介绍
大多数复杂的应用程序包含一些称为资源 的用户界面元素,这些元素与应用程序代码分离(与直接在源代码中编写的硬编码值 形成对比)。 例如,出于多种原因,首选资源而不是硬编码值(例如,非开发人员易于编辑)但关键原因之一是使应用程序能够在运行时选取相同逻辑资源的不同表示形式。 例如,在按钮上显示的文本(或要显示在图标中的图像)可能有所不同,具体取决于用户理解的语言、显示设备的特征,或者用户是否启用了任何辅助技术。
因此,任何资源管理技术的主要用途是翻译, 在运行时,对逻辑或符号
示例:
下面是一个简单的应用程序示例,该应用程序在两个按钮(openButton
和saveButton
)和一个用于徽标的 PNG 文件上具有文本标签。logoImage
文本标签本地化为英语和德语,徽标针对普通桌面显示器(100% 比例因子)和高分辨率手机(300% 比例因子)进行了优化。 请注意,此图示提供了模型的高级概念视图;它不完全对应实现。
在图形中,应用程序代码引用三个逻辑资源名称。 在运行时,GetResource
伪函数使用 MRT 在资源表(称为 PRI 文件)中查找这些资源名称,并根据环境条件(用户的语言和显示的缩放因子)找到最合适的候选项。 对于标签,直接使用字符串。 对于徽标图像,字符串将解释为文件名,文件将读出磁盘。
如果用户使用英语或德语以外的语言,或者具有除 100% 或 300%以外的显示比例系数,则 MRT 会根据一组回退规则(请参阅 资源管理系统 了解更多背景)选取“最接近”的匹配候选项。
请注意,MRT 支持针对多个限定符定制的资源-例如,如果徽标图像包含还需要本地化的嵌入文本,则徽标将具有四个候选项:EN/Scale-100、DE/Scale-100、EN/Scale-300 和 DE/Scale-300。
本文档中的各节
以下部分概述了将 MRT 与应用程序集成所需的高级任务。
阶段 0:生成应用程序包
本部分概述了如何将现有的桌面软件打包为应用程序包。 此阶段未使用 MRT 功能。
阶段 1:本地化应用程序清单
本部分概述了如何本地化应用程序的清单(以便它在 Windows Shell 中正确显示),同时仍使用旧版资源格式和 API 打包和查找资源。
阶段 2:使用 MRT 识别和定位资源
本部分概述了如何修改应用程序代码(以及可能的资源布局)以使用 MRT 查找资源,同时仍使用现有资源格式和 API 来加载和使用资源。
阶段 3:生成资源包
本部分概述了将资源分成单独的 资源包所需的最终更改,从而最大程度地减少应用的下载(并安装)大小。
本文档没有涉及
完成上述第 0-3 阶段后,你将获得一个可以提交到 Microsoft 应用商店的应用程序“捆绑包”,并通过省略用户不需要的资源(例如,他们不会说的语言)来最大程度地减少下载和安装的大小。 可以通过采取最后一个步骤进一步改进应用程序大小和功能。
阶段 4:迁移到 MRT 资源格式和 API
此阶段超出了本文档的范围;它需要将资源(尤其是字符串)从旧格式(如 MUI DLL 或 .NET 资源程序集)移动到 PRI 文件中。 这可以带来下载和安装所需空间的进一步节省。 它还允许使用其他 MRT 功能,例如,根据缩放系数、辅助功能设置等,最大程度地减少映像文件的下载和安装。
阶段 0:生成应用程序包
在对应用程序资源进行任何更改之前,必须先将当前的打包和安装技术替换为标准的 UWP 打包和部署技术。 有三种方法可以做到这一点:
- 如果你有具有复杂安装程序的大型桌面应用程序,或者利用大量 OS 扩展点,则可以使用桌面应用转换器工具从现有应用安装程序(例如 MSI)生成 UWP 文件布局和清单信息。
- 如果你有一个较小的桌面应用程序,其中包含相对较少的文件或简单的安装程序,并且没有扩展性挂钩,则可以手动创建文件布局和清单信息。
- 如果要从源重新生成并想要将应用更新为纯 UWP 应用程序,则可以在 Visual Studio 中创建新项目,并依赖 IDE 为你完成大部分工作。
如果要使用 桌面应用转换器,请参阅 使用桌面应用转换器打包桌面应用程序 ,了解有关转换过程的详细信息。 可以在 Desktop Bridge 到 UWP 示例的 GitHub 存储库
注释
桌面应用转换器已弃用。 请使用新的改进 的 MSIX 打包工具 打包桌面应用。
如果要手动创建包,则需要创建一个目录结构,其中包含应用程序的所有文件(可执行文件和内容,而不是源代码)和包清单文件(.appxmanifest)。 可以在 Hello, World GitHub 示例中找到一个示例,但运行名为 ContosoDemo.exe
的桌面可执行文件的基本包清单文件如下所示,其中 高亮显示的文本 将被你自己的值替换。
<?xml version="1.0" encoding="utf-8" ?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
<Identity Name="Contoso.Demo"
Publisher="CN=Contoso.Demo"
Version="1.0.0.0" />
<Properties>
<DisplayName>Contoso App</DisplayName>
<PublisherDisplayName>Contoso, Inc</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0"
MaxVersionTested="10.0.14393.0" />
</Dependencies>
<Resources>
<Resource Language="en-US" />
</Resources>
<Applications>
<Application Id="ContosoDemo" Executable="ContosoDemo.exe"
EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="Contoso Demo" BackgroundColor="#777777"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="Contoso Demo">
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
有关包清单文件和包布局的详细信息,请参阅 应用包清单。
最后,如果使用 Visual Studio 创建新项目并迁移现有代码,请参阅 “创建”Hello, world“应用。 可以将现有代码包含在新项目中,但可能需要进行重大代码更改(尤其是在用户界面中),才能作为纯 UWP 应用运行。 这些更改超出了本文档的范围。
阶段 1:本地化清单文件
步骤 1.1:更新清单中的字符串和资产
在阶段 0 中,你为应用程序创建了一个基本包清单 (.appxmanifest) 文件(基于提供给转换器的值、从 MSI 中提取或手动输入清单),但它不包含本地化信息,也不会支持高分辨率的“开始”磁贴资产等附加功能。
若要确保应用程序的名称和说明正确本地化,必须在一组资源文件中定义一些资源,并更新包清单以引用它们。
创建默认资源文件
第一步是使用默认语言(例如美国英语)创建默认资源文件。 可以使用文本编辑器或通过 Visual Studio 中的资源设计器手动执行此作。
如果要手动创建资源:
- 创建一
resources.resw
个名为 XML 文件并将其放置在Strings\en-us
项目的子文件夹中。 如果默认语言不是美国英语,请使用适当的 BCP-47 代码。 - 在 XML 文件中,添加以下内容:将 突出显示的文本 替换为应用程序默认语言中的适当文本。
注释
这些字符串中的一些长度存在限制。 有关详细信息,请参阅 VisualElements。
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="ApplicationDescription">
<value>Contoso Demo app with localized resources (English)</value>
</data>
<data name="ApplicationDisplayName">
<value>Contoso Demo Sample (English)</value>
</data>
<data name="PackageDisplayName">
<value>Contoso Demo Package (English)</value>
</data>
<data name="PublisherDisplayName">
<value>Contoso Samples, USA</value>
</data>
<data name="TileShortName">
<value>Contoso (EN)</value>
</data>
</root>
如果要在 Visual Studio 中使用设计器:
- 在
Strings\en-us
项目中创建文件夹(或其他语言),并使用默认名称将resources.resw
添加到项目的根文件夹中。 请务必选择 资源文件(.resw) 而不是 资源字典 - 资源字典是 XAML 应用程序使用的文件。 - 使用设计器输入以下字符串(使用相同的
Names
,但将Values
替换为应用程序的相应文本):
注释
如果从 Visual Studio 设计器开始,您可以随时按下 F7
直接编辑 XML。 但是,如果从最小的 XML 文件开始, 设计器将无法识别该文件 ,因为它缺少大量额外的元数据;可以通过将样本 XSD 信息从设计器生成的文件复制到手动编辑的 XML 文件中来解决此问题。
更新清单以引用资源
在文件中定义 .resw
值后,下一步是更新清单以引用资源字符串。 同样,可以直接编辑 XML 文件,或依赖于 Visual Studio 清单设计器。
如果您直接编辑 XML,请打开 AppxManifest.xml
文件,对 标记出的值进行以下更改 - 使用此 确切 文本,而非特定于您应用程序的文本。 无需使用这些确切的资源名称(可以选择自己的名称),但你选择的任何名称必须完全匹配文件中的任何 .resw
名称。 这些名称应与在 Names
文件中创建的 .resw
匹配,其前缀为 ms-resource:
方案和 Resources/
命名空间。
注释
此代码片段中省略了清单的许多元素 - 请勿删除任何内容!
<?xml version="1.0" encoding="utf-8"?>
<Package>
<Properties>
<DisplayName>ms-resource:Resources/PackageDisplayName</DisplayName>
<PublisherDisplayName>ms-resource:Resources/PublisherDisplayName</PublisherDisplayName>
</Properties>
<Applications>
<Application>
<uap:VisualElements DisplayName="ms-resource:Resources/ApplicationDisplayName"
Description="ms-resource:Resources/ApplicationDescription">
<uap:DefaultTile ShortName="ms-resource:Resources/TileShortName">
<uap:ShowNameOnTiles>
<uap:ShowOn Tile="square150x150Logo" />
</uap:ShowNameOnTiles>
</uap:DefaultTile>
</uap:VisualElements>
</Application>
</Applications>
</Package>
如果使用 Visual Studio 清单设计器,请打开 .appxmanifest 文件,并在 *应用程序 选项卡中 和“打包”选项卡中更改突出显示的值 值:
步骤 1.2:生成 PRI 文件,生成 MSIX 包,并验证它是否正常工作
现在,应该能够生成 .pri
文件并部署应用程序,以验证“开始”菜单中是否显示正确的信息(采用默认语言)。
如果要在 Visual Studio 中生成,只需按 Ctrl+Shift+B
生成项目,然后右键单击项目并从 Deploy
上下文菜单中选择。
如果要手动生成,请按照以下步骤为工具创建配置文件 MakePRI
并生成 .pri
文件本身(可在 手动应用打包中找到更多信息):
从“开始”菜单中的 Visual Studio 2019 或 Visual Studio2022 文件夹中打开开发人员命令提示符。
切换到项目根目录(包含 .appxmanifest 文件和 Strings 文件夹)。
键入以下命令,将“contoso_demo.xml”替换为适合项目的名称,将“en-US”替换为应用的默认语言(或者将它保留为 en-US(如果适用)。 请注意,XML 文件是在父目录中(不在 项目目录中)创建的,因为它不是应用程序的一部分(可以选择所需的任何其他目录,但请务必在将来的命令中替换它)。
makepri createconfig /cf ..\contoso_demo.xml /dq en-US /pv 10.0 /o
可以键入
makepri createconfig /?
以查看每个参数的作用,但总结如下:/cf
设置配置文件名(此命令的输出)/dq
设置默认限定符,在本例中,语言en-US
/pv
设置平台版本,在本例中为 Windows 10/o
将其设置为覆盖输出文件(如果存在)
现在,你有一个配置文件,请再次运行
MakePRI
,以实际搜索磁盘中的资源并将其打包到 PRI 文件中。 将“contoso_demop.xml”替换为在上一步中使用的 XML 文件名,并确保为输入和输出指定父目录:makepri new /pr . /cf ..\contoso_demo.xml /of ..\resources.pri /mf AppX /o
可以键入
makepri new /?
以查看每个参数的作用,但简言之,/pr
设置项目根目录(在本例中为当前目录)/cf
设置在上一步中创建的配置文件名/of
设置输出文件/mf
创建映射文件(以便我们可以在后面的步骤中排除包中的文件)/o
将其设置为覆盖输出文件(如果存在)
现在,你有一个
.pri
具有默认语言资源的文件(例如,en-US)。 若要验证它是否正常工作,可以运行以下命令:makepri dump /if ..\resources.pri /of ..\resources /o
可以键入
makepri dump /?
以查看每个参数的作用,但简言之,/if
设置输入文件名/of
设置输出文件名(.xml
将自动追加)/o
将其设置为覆盖输出文件(如果存在)
最后,可以在文本编辑器中打开
..\resources.xml
,并验证它列出你的<NamedResource>
值(如ApplicationDescription
和PublisherDisplayName
),以及<Candidate>
所选默认语言的值(文件开头会有其他内容;暂时忽略该内容)。
可以打开映射文件 ..\resources.map.txt
来验证它是否包含项目所需的文件(包括不属于项目目录的 PRI 文件)。 重要的是,映射文件 不会 包含对 resources.resw
文件的引用,因为该文件的内容已嵌入 PRI 文件中。 但是,它将包含其他资源,例如图像的文件名。
构建和签署软件包
现在已生成 PRI 文件,您可以生成包并对其签署:
若要创建应用包,请运行以下命令,将其替换为
contoso_demo.appx
要创建的 .msix/.appx 文件的名称,并确保为文件选择其他目录(此示例使用父目录;它可以在任何地方,但 不应 是项目目录)。makeappx pack /m AppXManifest.xml /f ..\resources.map.txt /p ..\contoso_demo.appx /o
可以键入
makeappx pack /?
以查看每个参数的作用,但简言之,/m
设置要使用的清单文件/f
设置要使用的映射文件(在上一步中创建)/p
设置输出包名称/o
将其设置为覆盖输出文件(如果存在)
创建包后,必须对其进行签名。 获取签名证书的最简单方法是在 Visual Studio 中创建一个空的通用 Windows 项目并复制
.pfx
它创建的文件,但可以使用“MakeCert
”中所述手动Pvk2Pfx
创建一个签名证书和实用工具。重要
如果手动创建签名证书,请确保将文件放置在与源项目或包源不同的目录中,否则它可能会作为包的一部分包含,包括私钥!
若要对包进行签名,请使用以下命令。 请注意,
Publisher
Identity
元素中指定的AppxManifest.xml
必须与证书Subject
匹配(<PublisherDisplayName>
元素,这是要向用户显示的本地化显示名称)。 像往常一样,将contoso_demo...
文件名替换为适合项目的名称,并且(非常重要)确保.pfx
文件不在当前目录中(否则,它已被创建为包的一部分,包括私钥!):signtool sign /fd SHA256 /a /f ..\contoso_demo_key.pfx ..\contoso_demo.appx
可以键入
signtool sign /?
以查看每个参数的作用,但简言之,/fd
设置文件摘要算法(SHA256 是.appx的默认算法)/a
将自动选择最佳证书/f
指定包含签名证书的输入文件
最后,可以双击 .appx
文件进行安装,或者如果你更喜欢命令行,可以打开 PowerShell 提示符,更改为包含包的目录,然后键入以下内容(替换为 contoso_demo.appx
包名称):
add-appxpackage contoso_demo.appx
如果收到有关证书不受信任的错误,请确保将其添加到计算机存储(不 用户存储)。 若要将证书添加到计算机存储,可以使用命令行或 Windows 资源管理器。
若要使用命令行,
以管理员身份运行 Visual Studio 2019 或 Visual Studio 2022 命令提示符。
切换到包含
.cer
文件的目录(请记住确保该文件不在源目录或项目目录之外!)请键入以下命令,并将
contoso_demo.cer
替换为您的文件名:certutil -addstore TrustedPeople contoso_demo.cer
可以运行
certutil -addstore /?
以查看每个参数的作用,但简言之,-addstore
将证书添加到证书存储TrustedPeople
指示证书放置到的存储区
若要使用 Windows 资源管理器,请执行以下操作:
- 导航到包含
.pfx
文件的文件夹 - 双击
.pfx
该文件,此时会显示“证书导入向导” - 选择
Local Machine
并单击Next
- 接受用户帐户控制管理员提升提示(如果出现)并单击
Next
- 输入私钥的密码(如果有)并单击
Next
- 选择
Place all certificates in the following store
- 单击
Browse
,然后选择Trusted People
文件夹(不“受信任的发布者”) - 单击
Next
,然后单击Finish
将证书添加到 Trusted People
存储区后,请尝试再次安装包。
现在应该会看到应用程序出现在“开始”菜单的“所有应用”列表中,其中包含来自.resw
/ .pri
文件的正确信息。 如果看到空白字符串或字符串 ms-resource:...
,这表示有些地方出了问题。请仔细检查您的编辑并确保它们正确无误。 如果在“开始”菜单中右键单击您的应用程序,可以将其固定为磁贴,并确认在其中显示了正确的信息。
步骤 1.3:添加更多受支持的语言
对包清单进行更改并创建初始 resources.resw
文件后,添加其他语言很容易。
创建其他本地化资源
首先,创建其他本地化的资源值。
在该 Strings
文件夹中,使用相应的 BCP-47 代码(例如, Strings\de-DE
)为每个支持的语言创建其他文件夹。 在每个文件夹中,创建一个 resources.resw
文件(使用 XML 编辑器或 Visual Studio 设计器),其中包含翻译的资源值。 假设你已经在某个地方拥有本地化的字符串,只需将它们复制到 .resw
文件中。本文档不涵盖翻译步骤本身。
例如,Strings\de-DE\resources.resw
文件可能如下所示,其中 突出显示的文本 已从 en-US
更改:
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="ApplicationDescription">
<value>Contoso Demo app with localized resources (German)</value>
</data>
<data name="ApplicationDisplayName">
<value>Contoso Demo Sample (German)</value>
</data>
<data name="PackageDisplayName">
<value>Contoso Demo Package (German)</value>
</data>
<data name="PublisherDisplayName">
<value>Contoso Samples, DE</value>
</data>
<data name="TileShortName">
<value>Contoso (DE)</value>
</data>
</root>
以下步骤假定你为这两种de-DE
fr-FR
资源添加了资源,但对于任何语言,都可以遵循相同的模式。
更新包清单以列出支持的语言
必须更新包清单才能列出应用支持的语言。 桌面应用转换器添加默认语言,但必须显式添加其他语言。 如果直接编辑 AppxManifest.xml
文件,请按如下所示更新 Resources
节点,根据需要添加任意数量的元素,并替换 支持 的适当语言,并确保列表中的第一个条目是默认(回退)语言。
在此示例中,默认值为英语(美国),对德语(德国)和法语(法国)提供额外支持:
<Resources>
<Resource Language="EN-US" />
<Resource Language="DE-DE" />
<Resource Language="FR-FR" />
</Resources>
如果您正在使用 Visual Studio,通常无需执行任何操作,并且查看 Package.appxmanifest
时,应看到特定的 x-generate 值,这会让生成过程插入在您的项目中找到的语言(基于使用 BCP-47 代码命名的文件夹)。 请注意,这不是实际包清单的有效值;它仅适用于 Visual Studio 项目:
<Resources>
<Resource Language="x-generate" />
</Resources>
使用本地化值重新生成
现在,你可以再次生成和部署应用程序,如果你在 Windows 中更改语言首选项,则应在“开始”菜单中看到新本地化的值(有关如何更改语言的说明如下)。
对于 Visual Studio,可以使用 Ctrl+Shift+B
进行构建,然后右键单击项目执行 Deploy
。
如果要手动生成项目,请按照上述相同步骤作,但在创建配置文件时将其他语言(用下划线分隔)添加到默认限定符列表(/dq
)。 例如,若要支持上一步中添加的英语、德语和法语资源:
makepri createconfig /cf ..\contoso_demo.xml /dq en-US_de-DE_fr-FR /pv 10.0 /o
这将创建一个 PRI 文件,其中包含你可以轻松地用于测试的所有指定语言。 如果资源的总大小很小,或者你仅支持少量语言,则对于你的发货应用来说,这可能是可以接受的;仅当希望最大程度地减少资源安装/下载大小的好处时,才需要执行构建单独的语言包的其他工作。
使用本地化值进行测试
若要测试新的本地化更改,只需向 Windows 添加新的首选 UI 语言。 无需下载语言包、重新启动系统或让整个 Windows UI 以外语显示。
- 启动
Settings
应用(Windows + I
) - 转到
Time & language
- 转到
Region & language
- 单击
Add a language
- 键入(或选择)所需语言(例如
Deutsch
或German
)- 如果有子语言,请选择所需的语言(例如
Deutsch / Deutschland
)
- 如果有子语言,请选择所需的语言(例如
- 在语言列表中选择新语言
- 单击
Set as default
现在打开“开始”菜单并搜索应用程序,应会看到所选语言的本地化值(其他应用也可能显示为本地化)。 如果未立即看到本地化名称,请等待几分钟,直到刷新“开始”菜单的缓存。 若要返回到本机语言,只需将其设置为语言列表中的默认语言。
步骤 1.4:本地化包清单的更多部分(可选)
包清单的其他部分可以本地化。 例如,如果应用程序处理文件扩展名,则它应在清单中具有 windows.fileTypeAssociation
扩展名,使用 绿色突出显示的文本 完全如所示(因为它将引用资源),并将 黄色突出显示的文本 替换为特定于应用程序的信息:
<Extensions>
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="default">
<uap:DisplayName>ms-resource:Resources/FileTypeDisplayName</uap:DisplayName>
<uap:Logo>Assets\StoreLogo.png</uap:Logo>
<uap:InfoTip>ms-resource:Resources/FileTypeInfoTip</uap:InfoTip>
<uap:SupportedFileTypes>
<uap:FileType ContentType="application/x-contoso">.contoso</uap:FileType>
</uap:SupportedFileTypes>
</uap:FileTypeAssociation>
</uap:Extension>
</Extensions>
还可以通过 Visual Studio 的 Manifest 设计器,在 Declarations
选项卡中添加此信息,并记下 中突出显示的值。
现在,将相应的资源名称添加到每个 .resw
文件,将 突出显示的文本 替换为应用的适当文本(请记得为每个 受支持的语言执行此作!):
... existing content...
<data name="FileTypeDisplayName">
<value>Contoso Demo File</value>
</data>
<data name="FileTypeInfoTip">
<value>Files used by Contoso Demo App</value>
</data>
然后,这将显示在 Windows shell 的某些部分,例如文件资源管理器:
像以前一样生成和测试包,执行任何应该显示新 UI 字符串的新方案。
阶段 2:使用 MRT 识别和定位资源
上一部分介绍了如何使用 MRT 本地化应用的清单文件,以便 Windows Shell 能够正确显示应用的名称和其他元数据。 不需要对此进行任何代码更改;它只需要使用 .resw
文件和一些其他工具。 本部分将展示如何使用 MRT 定位您现有资源格式中的资源,以及在对现有资源处理代码进行最少更改的情况下使用它们。
有关现有文件布局和应用程序代码的假设
由于有许多方法可以本地化 Win32 桌面应用,因此本文将对现有应用程序结构做出一些简化的假设,这些假设需要映射到特定环境。 可能需要对现有代码库或资源布局进行一些更改,以符合 MRT 的要求,而这些更改在很大程度上不符合本文档的范围。
资源文件布局
本文假定本地化的资源具有相同的文件名(例如或 contoso_demo.exe.mui
contoso_strings.dll
或contoso.strings.xml
),但它们放置在具有 BCP-47 名称(en-US
de-DE
等)的不同文件夹中。 不管你有多少资源文件、其名称是什么、其文件格式/关联的 API 是什么等等。唯一重要的是每个 逻辑 资源具有相同的文件名(但放置在不同的 物理 目录中)。
作为反例,如果应用程序使用包含文件 Resources
和 english_strings.dll
的单个 french_strings.dll
目录的平面文件结构,则它不适合映射到 MRT。 更好的结构是一个Resources
目录,其中包含子目录en\strings.dll
和文件fr\strings.dll
。 也可以使用相同的基文件名并嵌入限定符(如 strings.lang-en.dll
和 strings.lang-fr.dll
),但使用带有语言代码的目录在概念上更加简单,因此我们将重点介绍这种方法。
注释
即使您不能遵循此文件命名约定,仍然可以使用 MRT 并享受打包的好处,只是需要更多的工作。
例如,应用程序可能有一组自定义 UI 命令(用于按钮标签等)在名为 ui.txt的简单文本文件中,在 UICommands 文件夹下布局:
+ ProjectRoot |--+ Strings | |--+ en-US | | \--- resources.resw | \--+ de-DE | \--- resources.resw |--+ UICommands | |--+ en-US | | \--- ui.txt | \--+ de-DE | \--- ui.txt |--- AppxManifest.xml |--- ...rest of project...
资源加载代码
本文假定在代码中的某个时刻,你希望找到包含本地化资源的文件,加载它,然后使用该文件。 用于加载资源的 API、用于提取资源的 API 等并不重要。 在伪代码中,基本有三个步骤:
set userLanguage = GetUsersPreferredLanguage() set resourceFile = FindResourceFileForLanguage(MY_RESOURCE_NAME, userLanguage) set resource = LoadResource(resourceFile) // now use 'resource' however you want
MRT 只需要更改此过程的前两个步骤 - 如何确定最佳候选资源以及如何找到它们。 它不需要更改你加载或使用资源的方式(尽管如果你想利用它们,它提供了工具来协助你执行这种更改)。
例如,应用程序可以使用 Win32 API GetUserPreferredUILanguages
、CRT 函数 sprintf
和 Win32 API CreateFile
替换上述三个伪代码函数,然后手动分析查找 name=value
对的文本文件。 (细节并不重要;这只是为了说明 MRT 对一旦找到资源时用于处理资源的技术没有影响)。
步骤 2.1:代码更改以使用 MRT 查找文件
切换代码以使用 MRT 查找资源并不困难。 它需要使用少量 WinRT 类型和几行代码。 你将使用的主要类型如下:
- ResourceContext,它封装当前活动的限定符值集(语言、刻度因子等)
- ResourceManager (WinRT 版本,而不是 .NET 版本),它允许从 PRI 文件访问所有资源
- ResourceMap,表示 PRI 文件中资源的特定子集(在此示例中,基于文件的资源与字符串资源)
- NamedResource,它表示逻辑资源及其所有可能的候选
- ResourceCandidate,表示一个具体的单一候选资源
在伪代码中,解析给定的资源文件名时(例如上述示例中的 UICommands\ui.txt
),方法如下:
// Get the ResourceContext that applies to this app set resourceContext = ResourceContext.GetForViewIndependentUse() // Get the current ResourceManager (there's one per app) set resourceManager = ResourceManager.Current // Get the "Files" ResourceMap from the ResourceManager set fileResources = resourceManager.MainResourceMap.GetSubtree("Files") // Find the NamedResource with the logical filename we're looking for, // by indexing into the ResourceMap set desiredResource = fileResources["UICommands\ui.txt"] // Get the ResourceCandidate that best matches our ResourceContext set bestCandidate = desiredResource.Resolve(resourceContext) // Get the string value (the filename) from the ResourceCandidate set absoluteFileName = bestCandidate.ValueAsString
请注意,代码 不 请求特定语言文件夹(例如 UICommands\en-US\ui.txt
),即使这就是文件在磁盘上存在的方式。 相反,它要求 逻辑 文件名 UICommands\ui.txt
,并依赖于 MRT 在某个语言目录中查找适当的磁盘文件。
从这里开始,示例应用可以继续使用 CreateFile
加载 absoluteFileName
,并且像以前一样解析 name=value
对;在应用中,这些逻辑都不需要更改。 如果您正在使用 C# 或 C++/CX 进行编写,实际代码并不会比这复杂得多(实际上,许多中间变量可以被省略),请参阅下文中的 “加载 .NET 资源” 部分。 C++/WRL 的应用程序将更为复杂,这是由于需要使用基于 COM 的低级 API 来激活和调用 WinRT API,但所需的基本步骤是相同的 - 请参阅下文的 加载 Win32 MUI 资源部分。
加载 .NET 资源
由于 .NET 具有用于查找和加载资源(称为“附属程序集”)的内置机制,因此与上述综合示例中需要替换显式代码的情况不同,在 .NET 中,只需将资源 DLL 放在适当的目录中,它们就会被自动找到。 当应用程序使用资源包打包为 MSIX 或 .appx 时,目录结构会有所不同——资源目录不是主应用程序目录的子目录,而是与主目录并列(如果用户的首选项中未列出该语言,则根本不存在)。
例如,假设有以下布局的 .NET 应用程序,其中文件夹下 MainApp
存在所有文件:
+ MainApp |--+ en-us | \--- MainApp.resources.dll |--+ de-de | \--- MainApp.resources.dll |--+ fr-fr | \--- MainApp.resources.dll \--- MainApp.exe
转换为.appx后,布局将如下所示,假设 en-US
是默认语言,并且用户的语言列表中同时列出了德语和法语:
+ WindowsAppsRoot |--+ MainApp_neutral | |--+ en-us | | \--- MainApp.resources.dll | \--- MainApp.exe |--+ MainApp_neutral_resources.language_de | \--+ de-de | \--- MainApp.resources.dll \--+ MainApp_neutral_resources.language_fr \--+ fr-fr \--- MainApp.resources.dll
由于本地化资源不再存在于主可执行文件安装位置下的子目录中,因此内置 .NET 资源解析失败。 幸运的是,.NET 有一个定义完善的机制来处理失败的程序集加载尝试 - 事件 AssemblyResolve
。 使用 MRT 的 .NET 应用必须注册此事件,并为 .NET 资源子系统提供缺少的程序集。
如何使用 WinRT API 查找 .NET 使用的卫星程序集的简明示例如下:所示代码经过有意压缩,用于展示最小的实现,虽然可以看到它紧密映射到上面的伪代码,传入的 ResolveEventArgs
提供了我们需要找到的程序集的名称。 可以在 GitHub 上的 PriResourceRsolver.cs
示例中的文件中找到此代码的可运行版本(带有详细注释和错误处理)。
static class PriResourceResolver
{
internal static Assembly ResolveResourceDll(object sender, ResolveEventArgs args)
{
var fullAssemblyName = new AssemblyName(args.Name);
var fileName = string.Format(@"{0}.dll", fullAssemblyName.Name);
var resourceContext = ResourceContext.GetForViewIndependentUse();
resourceContext.Languages = new[] { fullAssemblyName.CultureName };
var resource = ResourceManager.Current.MainResourceMap.GetSubtree("Files")[fileName];
// Note use of 'UnsafeLoadFrom' - this is required for apps installed with .appx, but
// in general is discouraged. The full sample provides a safer wrapper of this method
return Assembly.UnsafeLoadFrom(resource.Resolve(resourceContext).ValueAsString);
}
}
在上述类中,在应用程序的启动代码中提前添加以下代码(在需要加载任何本地化资源之前):
void EnableMrtResourceLookup()
{
AppDomain.CurrentDomain.AssemblyResolve += PriResourceResolver.ResolveResourceDll;
}
每当 .NET 运行时找不到资源 DLL 时,都会引发 AssemblyResolve
该事件,此时提供的事件处理程序将通过 MRT 找到所需的文件并返回程序集。
注释
如果应用已有 AssemblyResolve
用于其他目的的处理程序,则需要将资源解析代码与现有代码集成。
加载 Win32 MUI 资源
加载 Win32 MUI 资源本质上与加载 .NET 附属程序集相同,但改用 C++/CX 或 C++/WRL 代码。 使用 C++/CX 可以编写出比上述 C# 代码更简单的代码,这些代码与其非常接近,但由于使用了C++语言扩展、编译器开关和其他可能需要避免的运行时开销。 如果是这种情况,则使用 C++/WRL 可提供影响要小得多的解决方案,但代价是代码更详细。 尽管如此,如果你熟悉 ATL 编程(或 COM 编程),那么 WRL 编程应该也会显得熟悉。
以下示例函数演示如何使用 C++/WRL 加载特定的资源 DLL,并返回 HINSTANCE
可用于使用常规 Win32 资源 API 加载更多资源。 请注意,与显式使用 .NET 运行时请求的语言初始化 ResourceContext
的 C# 示例不同,此代码依赖于用户的当前语言。
#include <roapi.h>
#include <wrl\client.h>
#include <wrl\wrappers\corewrappers.h>
#include <Windows.ApplicationModel.resources.core.h>
#include <Windows.Foundation.h>
#define IF_FAIL_RETURN(hr) if (FAILED((hr))) return hr;
HRESULT GetMrtResourceHandle(LPCWSTR resourceFilePath, HINSTANCE* resourceHandle)
{
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::ApplicationModel::Resources::Core;
using namespace ABI::Windows::Foundation;
*resourceHandle = nullptr;
HRESULT hr{ S_OK };
RoInitializeWrapper roInit{ RO_INIT_SINGLETHREADED };
IF_FAIL_RETURN(roInit);
// Get Windows.ApplicationModel.Resources.Core.ResourceManager statics
ComPtr<IResourceManagerStatics> resourceManagerStatics;
IF_FAIL_RETURN(GetActivationFactory(
HStringReference(
RuntimeClass_Windows_ApplicationModel_Resources_Core_ResourceManager).Get(),
&resourceManagerStatics));
// Get .Current property
ComPtr<IResourceManager> resourceManager;
IF_FAIL_RETURN(resourceManagerStatics->get_Current(&resourceManager));
// get .MainResourceMap property
ComPtr<IResourceMap> resourceMap;
IF_FAIL_RETURN(resourceManager->get_MainResourceMap(&resourceMap));
// Call .GetValue with supplied filename
ComPtr<IResourceCandidate> resourceCandidate;
IF_FAIL_RETURN(resourceMap->GetValue(HStringReference(resourceFilePath).Get(),
&resourceCandidate));
// Get .ValueAsString property
HString resolvedResourceFilePath;
IF_FAIL_RETURN(resourceCandidate->get_ValueAsString(
resolvedResourceFilePath.GetAddressOf()));
// Finally, load the DLL and return the hInst.
*resourceHandle = LoadLibraryEx(resolvedResourceFilePath.GetRawBuffer(nullptr),
nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
return S_OK;
}
阶段 3:生成资源包
现在,你有一个包含所有资源的“fat pack”,因此有两个路径用于生成单独的主包和资源包,以便最大程度地减少下载和安装大小:
- 获取现有胖包并通过捆绑包生成器工具 运行它, 自动创建资源包。 如果你的构建系统已经生成了一个胖包,并且你希望对其进行后续处理以生成资源包,那么这就是首选方法。
- 直接制作单个资源包并将其打包为一个整体。 如果你对生成系统拥有更多控制权,并且可以直接生成包,则这是首选方法。
步骤 3.1:创建捆绑包
使用捆绑包生成器
若要使用捆绑生成器工具,需要手动更新为包创建的 PRI 配置文件才能删除该 <packaging>
节。
如果使用 Visual Studio,请参阅 确保在设备上安装资源,无论设备是否需要这些资源,了解如何通过创建文件 priconfig.packaging.xml
和 priconfig.default.xml
将所有语言生成到主包中。
如果要手动编辑文件,请执行以下步骤:
按照与之前相同的方式创建配置文件,替换正确的路径、文件名和语言:
makepri createconfig /cf ..\contoso_demo.xml /dq en-US_de-DE_es-MX /pv 10.0 /o
手动打开创建的
.xml
文件并删除整个<packaging&rt;
部分(但保留其他所有内容):<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <resources targetOsVersion="10.0.0" majorVersion="1"> <!-- Packaging section has been deleted... --> <index root="\" startIndexAt="\"> <default> ... ...
构建
.pri
文件和.appx
包,依照原先的方法使用更新的配置文件以及适当的目录和文件名(关于这些命令的更多信息,请参阅上文):makepri new /pr . /cf ..\contoso_demo.xml /of ..\resources.pri /mf AppX /o makeappx pack /m AppXManifest.xml /f ..\resources.map.txt /p ..\contoso_demo.appx /o
创建包后,使用以下命令创建捆绑包,并使用相应的目录和文件名:
BundleGenerator.exe -Package ..\contoso_demo.appx -Destination ..\bundle -BundleName contoso_demo
现在,可以转到最后一步进行签名(请参阅下文)。
手动创建资源包
手动创建资源包需要运行一组略有不同的命令来生成单独的 .pri
和 .appx
文件——这些命令与上面用于创建胖包的命令类似,因此仅提供简要的说明。 注意:所有命令都假定当前目录是包含 AppXManifest.xml
文件的目录,但所有文件都放置在父目录中(如有必要,可以使用其他目录,但不应使用其中任何文件污染项目目录)。 与往常一样,将“Contoso”文件名替换为自己的文件名。
使用以下命令创建一个配置文件,该文件名称 仅 默认语言作为默认限定符 -在本例中,
en-US
:makepri createconfig /cf ..\contoso_demo.xml /dq en-US /pv 10.0 /o
为主包创建默认
.pri
文件和.map.txt
文件,并为项目中找到的每种语言创建一组额外的文件,并运行以下命令:makepri new /pr . /cf ..\contoso_demo.xml /of ..\resources.pri /mf AppX /o
使用以下命令创建主包(其中包含可执行代码和默认语言资源)。 与往常一样,根据需要更改名称,尽管应将包放在单独的目录中,以便稍后创建捆绑包(此示例使用
..\bundle
目录):makeappx pack /m .\AppXManifest.xml /f ..\resources.map.txt /p ..\bundle\contoso_demo.main.appx /o
创建主包后,对每个其他语言使用以下命令一次(例如,对上一步中生成的每个语言映射文件重复此命令)。 同样,输出应位于单独的目录中(与主包相同的目录)。 请注意,语言 ,以及使用新的
/f
参数(指示需要资源包):makeappx pack /r /m .\AppXManifest.xml /f ..\resources.language-de.map.txt /p ..\bundle\contoso_demo.de.appx /o
将捆绑目录中的所有包合并到单个
.appxbundle
文件中。 新的/d
选项指定了一个目录,用于放置捆绑包中的所有文件。这就是文件.appx
在上一步中被放入单独目录的原因。makeappx bundle /d ..\bundle /p ..\contoso_demo.appxbundle /o
生成包的最后一步是签名。
步骤 3.2:对捆绑包进行签名
创建 .appxbundle
文件(通过捆绑生成器工具或手动创建)后,将有一个包含主包和所有资源包的文件。 最后一步是对文件进行签名,以便 Windows 安装该文件:
signtool sign /fd SHA256 /a /f ..\contoso_demo_key.pfx ..\contoso_demo.appxbundle
这将生成包含主包和所有特定于语言的资源包的签名 .appxbundle
文件。 可以像包文件一样双击它,根据用户的 Windows 语言首选项,安装应用程序及其支持的适当语言。