对 UI 和应用包清单中的字符串进行本地化
有关对应用进行本地化的价值主张的详细信息,请参阅全球化和本地化。
如果你希望应用支持其他显示语言,并且你的代码或 XAML 标记或应用包清单中有字符串文本,则将这些字符串移到资源文件 (.resw) 中。 然后,你可以针对应用支持的每种语言制作该资源文件的翻译副本。
硬编码的字符串文本可以出现在命令性代码或 XAML 标记中,例如,作为 TextBlock 的 Text 属性。 它们也会出现在应用包清单源文件(Package.appxmanifest
文件)中,例如,Visual Studio 清单设计器的“视觉对象资产”选项卡上的应用图标值。 将这些字符串移动到资源文件 (.resw),并将应用中和清单中的硬编码字符串文本替换为对资源标识符的引用。
与图像资源不同,一个图像资源文件中只包含一个图像资源,而一个字符串资源文件中包含多份多个字符串资源。 字符串资源文件是资源文件(.resw),此类资源文件通常在项目中的 \Strings 文件夹中创建。 有关如何使用限定符表示资源文件 (.resw) 的背景知识,请参阅为语言、缩放和其他限定符的调整资源。
将字符串存储在资源文件中
设置应用的默认语言。
- 在 Visual Studio 中打开解决方案后:打开
Package.appxmanifest
。 - 在应用程序选项卡上,确认已正确设置默认语言(例如“en”或“en-US”)。 其余步骤假定已将默认语言设置为“en-US”。
注意
至少需要为默认语言本地化提供字符串资源。 如果找不到用户首选语言或显示语言设置的更好匹配项,则会加载该资源。
- 在 Visual Studio 中打开解决方案后:打开
为默认语言创建资源文件(.resw)。
- 在项目节点下,新建文件夹,并将其命名为
Strings
。 - 在
Strings
下,创建新的子文件夹并将其命名为en-US
。 - 在
en-US
下,创建新的资源文件 (.resw)(在 添加项对话框中的 XAML 文件类型下)并确认命名为Resources.resw
。
注意
如果要移植 .NET 资源文件(.resx),请参阅 移植 XAML 和 UI。
- 在项目节点下,新建文件夹,并将其命名为
打开
Resources.resw
并添加这些字符串资源。Strings/en-US/Resources.resw
在此示例中,“Greeting”是可从标记中引用的字符串资源标识符,如下所示。 对于标识符“Greeting”,会为 Text 属性提供字符串,并为 Width 属性提供字符串。 “Greeting.Text”是属性标识符的示例,因为它对应于 UI 元素的属性。 例如,还可以在名称列中添加“Greeting.Foreground”,并将其值设置为“Red”。 “告别”标识符是一个简单的字符串资源标识符;它没有子属性,可以由命令性代码加载,请见下文。 批注列适合向翻译人员提供特殊说明。
在此示例中,由于名为“Farewell”的简单字符串资源标识符条目已存在,因此我们也不能为相同的标识符创建属性标识符。 因此,添加“Farewell.Text”会导致构建
Resources.resw
时出现重复条目错误。资源标识符不区分大小写,并且对于每个资源文件都必须是唯一的。 请务必使用有意义的资源标识符为翻译人员提供其他上下文。 在发送字符串资源以供翻译后,不要更改资源标识符。 本地化团队使用资源标识符跟踪资源中的添加、删除和更新。 资源标识符中的更改(也称为“资源标识符转换”)需要重新翻译字符串,因为它看起来就像删除了字符串并添加了其他字符串。
引用 XAML 中的字符串资源标识符
使用 x:Uid 指令 将标记中的控件或其他元素与字符串资源标识符相关联。
<TextBlock x:Uid="Greeting"/>
运行时将加载 \Strings\en-US\Resources.resw
(因为这是此时项目中唯一的资源文件)。 TextBlock 上的 x:Uid 指令会查找 Resources.resw
中包含字符串资源标识符“Greeting”的属性标识符。 找到“Greeting.Text”和“Greeting.Width”属性标识符后,将它们的值应用于 TextBlock,覆盖本地设置的标记值。 如果已添加,也会应用“Greeting.Foreground”值。 但是,只有属性标识符用于设置 XAML 标记元素上的属性,因此在该 Textbook 中将 x:Uid 设置为“Farewell”将无效。 Resources.resw
确实包含字符串资源标识符“Farewell”,但它不包含其属性标识符。
将字符串资源标识符分配给 XAML 元素时,请确保该标识符的所有属性标识符都适用于 XAML 元素。 例如,如果在 TextBlock 上设置 x:Uid="Greeting"
,则“Greeting.Text”将解析,因为 TextBlock 类型具有 Text 属性。 但是,如果在 按钮 上设置 x:Uid="Greeting"
,则“Greeting.Text”将导致运行时错误,因为 按钮 类型没有 Text 属性。 这种情况的另一种解决方案是创建名为“ButtonGreeting.Content”的属性标识符,并对按钮设置 x:Uid="ButtonGreeting"
。
你可能希望允许控件动态调整内容大小,而不是在资源文件中设置 Width。
注意 对于 附加属性,需要在 .resw 文件的 Name 列中使用特殊语法。 例如,若要为“Greeting”标识符的 AutomationProperties.Name 附加属性设置值,即你要在名称列中输入的值。
Greeting.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name
引用编码中的字符串资源标识符
可以根据简单的字符串资源标识符显式加载字符串资源。
注意
如果调用任何可能运行在后台/工作线程的 GetForCurrentView 方法,请使用 if (Windows.UI.Core.CoreWindow.GetForCurrentThread() != null)
测试进行保护。 从后台/工作线程调用 GetForCurrentView 会导致异常。“在没有 CoreWindow 的线程中创建<typename> 可能会失败。”
var resourceLoader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView();
this.myXAMLTextBlockElement.Text = resourceLoader.GetString("Farewell");
auto resourceLoader{ Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView() };
myXAMLTextBlockElement().Text(resourceLoader.GetString(L"Farewell"));
auto resourceLoader = Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView();
this->myXAMLTextBlockElement->Text = resourceLoader->GetString("Farewell");
可以在类库(通用 Windows)或 Windows 运行时库(通用 Windows)项目中使用相同的代码。 在运行时,将加载托管库的应用资源。 建议库从托管它的应用中加载资源,因为应用的本地化程度可能更高。 如果库确实需要提供资源,则应作为输入为托管应用提供替换资源选项。
如果资源名称已分段(包含“.”字符),则将资源名称中的点替换为正斜杠(“/”)字符。 例如,属性标识符包含点;因此需要执行替换后才能从代码中加载其中一个标识符。
this.myXAMLTextBlockElement.Text = resourceLoader.GetString("Fare/Well"); // <data name="Fare.Well" ...> ...
如有疑问,可以使用 MakePri.exe 转储应用的 PRI 文件。 每个资源的 uri
都显示在转储文件中。
<ResourceMapSubtree name="Fare"><NamedResource name="Well" uri="ms-resource://<GUID>/Resources/Fare/Well">...
引用应用包清单中的字符串资源标识符
打开应用包清单源文件(
Package.appxmanifest
文件),默认情况下,应用的Display name
表示为字符串文本。要制作此字符串的本地化版本,请打开
Resources.resw
并添加名为“AppDisplayName”和值“Adventure Works Cycles”的新字符串资源。将显示名称字符串文本替换为对刚刚创建的字符串资源标识符(“AppDisplayName”)的引用。 可以使用
ms-resource
URI(统一资源标识符)方案执行此操作。对清单中要本地化的每个字符串重复此过程。 例如,应用的短名称(可以配置为“开始”时在应用磁贴上显示)。 有关应用包清单中可本地化的所有项的列表,请参阅 可本地化的清单项。
本地化字符串资源
为其他语言创建资源文件(.resw)副本。
- 在“字符串”下,为 Deutsch(Deutschland)创建新的子文件夹并将其命名为“de-DE”。
注意 文件夹名称可以使用任何 BCP-47 语言标记。 有关语言限定符和公共语言标记列表的详细信息,请参阅定制语言、缩放和其他限定符资源。 - 在文件夹
Strings/de-DE
中创建Strings/en-US/Resources.resw
的副本。
- 在“字符串”下,为 Deutsch(Deutschland)创建新的子文件夹并将其命名为“de-DE”。
翻译字符串。
- 打开
Strings/de-DE/Resources.resw
并转换值列中的值。 无需翻译批注。
Strings/de-DE/Resources.resw
- 打开
如果需要,可以重复步骤 1 和步骤 2 以获取其他语言。
Strings/fr-FR/Resources.resw
测试应用
测试应用的默认显示语言。 之后,可以在设置>时间和语言>区域和语言>语言中更改显示语言并重新测试应用。 查看 UI 和 shell 中的字符串,例如,标题栏(即显示名称)以及磁贴上的短名称。
请注意,如果找到与显示语言设置匹配的文件夹名称,则加载该文件夹中的资源文件。 否则,将进行回退,使用应用默认语言的资源。
将字符串分解为多个资源文件
可以将所有字符串保留在单个资源文件(resw)中,也可以将它们分解为多个资源文件。 例如,你可能想要将错误消息保存在一个资源文件中、应用包清单字符串在另一个资源文件中, UI 字符串在第三个资源文件中。 这就是文件夹结构在这种情况下的情形。
若要限定对特定文件字符串资源标识符引用的范围,只需在标识符之前添加 /<resources-file-name>/
。 下面的标记示例假定 ErrorMessages.resw
包含一个资源,其名称为“PasswordTooWeak.Text”,其值描述错误。
<TextBlock x:Uid="/ErrorMessages/PasswordTooWeak"/>
只需在资源文件以外的Resources.resw
资源文件的字符串资源标识符之前添加/<resources-file-name>/
。 这是因为“Resources.resw”是默认文件名,因此,如果省略文件名(如本主题前面的示例中所示),则假定为这种情况。
下面的代码示例假定 ErrorMessages.resw
包含名为“MismatchedPasswords”且其值描述错误的资源。
注意
如果调用任何可能运行在后台/工作线程的 GetForCurrentView 方法,请使用 if (Windows.UI.Core.CoreWindow.GetForCurrentThread() != null)
测试进行保护。 从后台/工作线程调用 GetForCurrentView 会导致异常。“在没有 CoreWindow 的线程中创建<typename> 可能会失败。”
var resourceLoader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView("ErrorMessages");
this.myXAMLTextBlockElement.Text = resourceLoader.GetString("MismatchedPasswords");
auto resourceLoader{ Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView(L"ErrorMessages") };
myXAMLTextBlockElement().Text(resourceLoader.GetString(L"MismatchedPasswords"));
auto resourceLoader = Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView("ErrorMessages");
this->myXAMLTextBlockElement->Text = resourceLoader->GetString("MismatchedPasswords");
如果要将“AppDisplayName”资源移出 Resources.resw
并移入 ManifestResources.resw
中,则在应用包清单中,将 ms-resource:AppDisplayName
更改为 ms-resource:/ManifestResources/AppDisplayName
。
如果资源文件名已分段(包含“.”字符),请在引用时保留名称中的点。 请勿像引用资源名称时一样,将点替换为正斜杠(“/”)字符。
var resourceLoader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView("Err.Msgs");
如有疑问,可以使用 MakePri.exe 转储应用的 PRI 文件。 每个资源的 uri
都显示在转储文件中。
<ResourceMapSubtree name="Err.Msgs"><NamedResource name="MismatchedPasswords" uri="ms-resource://<GUID>/Err.Msgs/MismatchedPasswords">...
加载特定语言或其他上下文的字符串
默认 ResourceContext(从 ResourceContext.GetForCurrentView 获取)包含每个限定符名称的限定符值,表示默认运行时上下文(换句话说,当前用户和计算机的设置)。 根据该运行时上下文中的限定符值匹配资源文件 (.resw) - 基于其名称中的限定符。
但是,有时你可能希望你的应用替代系统设置,并在查找要加载的匹配资源文件时显式显示语言、缩放或其他限定符值。 例如,你可能希望用户能够为工具提示或错误消息选择替换语言。
为此,可以构造新的 ResourceContext(而不是使用默认资源), 重写其值,然后在字符串查找中使用该上下文对象。
var resourceContext = new Windows.ApplicationModel.Resources.Core.ResourceContext(); // not using ResourceContext.GetForCurrentView
resourceContext.QualifierValues["Language"] = "de-DE";
var resourceMap = Windows.ApplicationModel.Resources.Core.ResourceManager.Current.MainResourceMap.GetSubtree("Resources");
this.myXAMLTextBlockElement.Text = resourceMap.GetValue("Farewell", resourceContext).ValueAsString;
如上面的代码示例所示,使用 QualifierValues 适用于任何限定符。 对于语言的特殊情况,也可以改为执行此操作。
resourceContext.Languages = new string[] { "de-DE" };
对于全局级别的相同效果,可以在默认 ResourceContext 中重写限定符值。 但我们建议你调用 ResourceContext.SetGlobalQualifierValue。 每次调用 SetGlobalQualifierValue 时设置值,然后每次使用它进行查找时,这些值都会对默认 ResourceContext 生效。
Windows.ApplicationModel.Resources.Core.ResourceContext.SetGlobalQualifierValue("Language", "de-DE");
var resourceLoader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView();
this.myXAMLTextBlockElement.Text = resourceLoader.GetString("Farewell");
某些限定符具有系统数据提供程序。 因此,可以改为通过自己的 API 调整提供程序,而不是调用 SetGlobalQualifierValue 。 例如,此代码演示如何设置 PrimaryLanguageOverride。
Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = "de-DE";
响应限定符值更改事件更新图像
正在运行的应用可以响应影响默认 ResourceContext 中限定符值的系统设置中的更改。 这些系统设置中的任何一个都调用 ResourceContext.QualifierValues 上的 MapChanged 事件。
为了响应此事件,可以从默认 ResourceContext 重新加载字符串。
public MainPage()
{
this.InitializeComponent();
...
// Subscribe to the event that's raised when a qualifier value changes.
var qualifierValues = Windows.ApplicationModel.Resources.Core.ResourceContext.GetForCurrentView().QualifierValues;
qualifierValues.MapChanged += new Windows.Foundation.Collections.MapChangedEventHandler<string, string>(QualifierValues_MapChanged);
}
private async void QualifierValues_MapChanged(IObservableMap<string, string> sender, IMapChangedEventArgs<string> @event)
{
var dispatcher = this.myXAMLTextBlockElement.Dispatcher;
if (dispatcher.HasThreadAccess)
{
this.RefreshUIText();
}
else
{
await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => this.RefreshUIText());
}
}
private void RefreshUIText()
{
var resourceLoader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView();
this.myXAMLTextBlockElement.Text = resourceLoader.GetString("Farewell");
}
从类库或 Windows 运行时库加载字符串
引用的类库(通用 Windows)或Windows 运行时库(通用 Windows)的字符串资源通常添加到包的子文件夹中,这些子文件夹中包含在生成过程中。 此类字符串的资源标识符通常采用 LibraryName/ResourcesFileName/ResourceIdentifier 格式。
库可以为自己的资源获取 ResourceLoader。 例如,下面的代码说明了库或引用该库的应用如何为库的字符串资源获取 ResourceLoader。
var resourceLoader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView("ContosoControl/Resources");
this.myXAMLTextBlockElement.Text = resourceLoader.GetString("exampleResourceName");
对于 Windows 运行时库(通用 Windows),如果默认命名空间已分段(包含“.”字符),则在资源映射名称中使用点。
var resourceLoader = Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView("Contoso.Control/Resources");
对于类库(通用 Windows),无需执行此操作。 如有疑问,可以指定 MakePri.exe 命令行选项来转储组件或库的 PRI 文件。 每个资源的 uri
都显示在转储文件中。
<NamedResource name="exampleResourceName" uri="ms-resource://Contoso.Control/Contoso.Control/ReswFileName/exampleResourceName">...
从其他包加载字符串
应用包的资源通过包自身的顶级 ResourceMap(可从当前的 ResourceManager 访问)进行管理和访问。 在每个包中,各种组件可以有自己的 ResourceMap 子树,可以通过 ResourceMap.GetSubtree 进行访问。
框架包可以使用绝对资源标识符 URI 访问自己的资源。 另请参阅 URI 方案。
在未打包应用程序中加载字符串
从 Windows 版本 1903(2019 年 5 月更新)开始,未打包应用程序也可以利用资源管理系统。
只需创建 UWP 用户控件/库并将所有字符串存储在资源文件中。 然后,就可以引用 XAML 中的字符串资源标识符、引用代码中的字符串资源标识符,或从类库或 Windows 运行时库加载字符串。
若要在未打包应用程序中使用资源,应执行以下几项操作:
- 解析代码中的资源时使用 GetForViewIndependentUse 而不是 GetForCurrentView,因为非打包方案中没有当前视图。 如果在非打包方案中调用 GetForCurrentView,则会出现以下异常:可能无法在没有 CoreWindow 的线程上创建资源上下文。
- 使用 MakePri.exe 手动生成应用的 resources.pri 文件。
makepri new /pr <PROJECTROOT> /cf <PRICONFIG> /of resources.pri
运行- <PRICONFIG> 必须省略“<packaging>”节,以便将所有资源捆绑在一个 resources.pri 文件中。 如果使用 createconfig 创建的默认 MakePri.exe 配置文件,则需要在创建“<packaging>”节后手动删除该节。
- <PRICONFIG> 必须包含将项目中的所有资源合并到单个 resources.pri 文件所需的所有相关索引器。 createconfig 创建的默认 MakePri.exe 配置文件包含所有索引器。
- 如果不使用默认配置,请确保启用 PRI 索引器(查看默认配置,了解如何执行此操作),以合并从项目根目录下的 UWP 项目引用、NuGet 引用等引用中找到的 PRI。
注意
通过省略
/IndexName
,并且让项目没有应用清单,PRI 文件的 IndexName/根命名空间会自动设置为 Application,运行时可将其识别为未打包应用的 IndexName/根命名空间(这会删除以前对包 ID 的硬依赖)。 指定资源 URI 时,省略根命名空间的 ms-resource:/// 引用会将 Application 推断为未打包应用的根命名空间(或者可以像在 ms-resource://Application/ 中一样显式指定 Application)。
- 将 PRI 文件复制到 .exe 的生成输出目录
- 运行 .exe
注意
资源管理系统根据未打包应用中的语言解析资源时,使用系统显示语言而不是用户首选语言列表。 用户首选语言列表仅用于 UWP 应用。
重要
每次修改资源时,都必须手动重新生成 PRI 文件。 建议使用生成后脚本来处理 MakePri.exe 命令并将 resources.pri 输出复制到 .exe 目录。