1. 前言

上一篇文章介绍了各种WPF本地化的入门知识,这篇文章介绍UWP本地化的入门知识。

2. 使用resw资源文件实现本地化

在以前的XAML平台,resx资源文件是一种很方便的本地化方案,但在UWP中微软又再次推荐x:Uid方案,默认的资源文件也变成resw资源文件。虽然后缀名只差了一个字母,但使用方式完全不同。最主要的区别是resw资源文件不会创建对应的Designer.cs类,这就导致本地化的实现方案完全不同。

2.1 在XAML中实现本地化

在XAML中实现本地化的过程很简单。首先在项目中新建"strings"文件夹,在"strings"文夹下创建"en-US"和"zh-CN"文件夹,并在两个文件夹中分别添加"Resources.resw"资源文件。最终目录结构如下:

在zh-CN\Resources.resw和en-US\Resources.resw添加两个新资源,分别是UsernameTextBox.Width和UsernameTextBox.Header:

在XAML中添加一个TextBox,设置x:Uid为UsernameTextBox,x:Uid将XAML元素和资源文件中的资源进行关联:

<TextBox x:Uid="UsernameTextBox"/>

运行后即可看到UsernameTextBox的Header设置为"用户名",Width为100。

在“设置\区域和语言”中将"English"设置为默认语言,再次运行应用可看到运行在英语环境下的效果。

这样基本的本地化功能就实现了。这种本地化方式有如下优点:

  • 简单快速,容易上手
  • 语法简单,不需要Binding等知识
  • 可以指定任意属性进行本地化
  • 支持CLR属性

除此之外,上一篇文章提到的ResXManager也支持Resw资源文件,还可以使用多语言应用工具包对资源文件进行管理,博客园的这篇文章页对这个工具进行了详细介绍:

Win10 UWP 开发系列:使用多语言工具包让应用支持多语言

或者参考这个视频:

Windows 10 Apps Designing for Global Customers

2.2 关联到其它资源文件

UI元素默认与Resources.resw进行关联,如果需要和其它资源文件关联,可以加上资源文件的路径。如需要与/OtherResources.resw中的资源关联,x:Uid的语法如下:

x:Uid="/OtherResources/AddressTextBox"

2.3 附加属性的本地化

对系统提供的附加属性,资源的名称语法如下:

UsernameTextBox.Grid.Row

对自定义附加属性,语法稍微复杂一些:

ShowMessageButton.[using:LocalizationDemoUwp]ButtonEx.Content

奇怪的是,就这样直接运行应用会报错。只有应用这个资源的UI元素已经有这个附加属性的值才能正常运行,简单来说就是需要随便为这个附加属性设置一个值:

<Button Margin="5" x:Uid="ShowMessageButton"  local:ButtonEx.Content="ssssss"/>

2.4 其它资源的本地化

除了字符串资源,其它资源的本地化方式不需要设置x:Uid,只需要建立对应语言的目录结构及命名就可以在XAML中直接引用。如项目中有如下两张图片:

在XAML中可以直接通过Images/Flag.png引用。路径中的"zh-CN"、"en-US"称为资源限定符,用于支持多种显示比例、UI 语言、高对比度设置等,具体可参考Load images and assets tailored for scale, theme, high contrast, and others

2.5 在代码里访问资源

在代码中访问资源的代码如下:

var resourceLoader = ResourceLoader.GetForCurrentView();
var currentLanguage = resourceLoader.GetString("CurrentLanguage");
resourceLoader = ResourceLoader.GetForCurrentView("OtherResources");
var message = resourceLoader.GetString("Message");

上面的代码中,currentLanguage从默认的资源文件Resources.resw中获取,resourceLoader 无需指定资源文件的名称;而message 则从OtherResources.resw获取,resourceLoader 需要指定资源文件的名称。

如需要使用其它类库中的资源,代码如下:

resourceLoader = ResourceLoader.GetForCurrentView("LocalizationDemoUwp.ResourceLibrary/Resources");
currentLanguage = resourceLoader.GetString("CurrentLanguage");

虽然语法简单,但可以看到最大的问题是资源的名称没有智能感知和错误提示,这样使用资源很容易出错。

如上图所示,对错误的资源名称,ReSharper会有错误提示,不过这种构造ResourceLoader的方式已经被标记为Deprecated并提示使用GetForCurrentView获取ResourceLoader,而使用GetForCurrentView的情况下ReSharper又没有错误提示。不知道ReSharper什么时候才能支持在GetForCurrentView的方式下显示错误提示(我安装的ReSharper已是最新的2017.2)。

2.6 存在的问题

这个本地化方案虽然简单,但我觉得很难使用,因为这个方案存在很多问题。

首先是设计时支持,对本地化来说,设计时支持主要包含3部分:

  • 在编写XAML时可以得到资源的智能感知
  • 有完整的设计视图
  • 在不同语言之间切换

第一点,没有,而且写错属性名称还不会在编译时报错,而是用最惨烈的方式呈现:运行时崩溃。

第二点,在Fall Creators Update (16299)以前,没有,设计视图一片空白。也可以随便写一些内容(如TextBox x:Uid="UsernameTextBox" Header="(here is header)")以辅助设计。但在XAML中写的任何内容都可能被资源文件覆盖,无论是文本还是大小、对齐方式或其它所有属性对XAML的编写者来说都是不可控的,不到实际运行时根本不清楚UI的最终效果,这就很考验本地化人员和测试人员。在Fall Creators Update以后终于可以在设计视图看到本地化的效果,这不得不说是巨大的进步。

第三点,目前来看做不到。

另外,资源管理也是个很麻烦的问题。同一个字符串,如果要对应TextBlock.Text、ContentControl.Content、TextBox.Header,这样就需要三个资源,造成了冗余,而大量的冗余最终会导致错误。

总的来说,这个本地化方案有很多问题,虽然这个方案是微软推荐的。既然是微软推荐的,应该是支持最好的,也许是我的用法不对?

接下来在这个方案的基础上做些改动,希望可以让本地化更好用。

3. 动态切换语言

不是我太执着动态切换语言,是测试员真的喜欢这个功能,因为不用重启应用就可以测试到所有语言的UI。

UWP提供了ApplicationLanguages.PrimaryLanguageOverride属性用于更改语言首选项,即可以改变应用的语言,用法如下:

Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = "zh-CN";

这个变更是永久的,但不会对当前UI及一部分系统组件生效,只会影响之后创建的UI元素。更改ApplicationLanguages.PrimaryLanguageOverride,会异步地触发ResourceContext.QualifierValues的MapChanged事件,可以监听这个事件并更新UI。这样就可以实现简单的动态切换语言功能。

DynamicResources.cs

public class DynamicResources : INotifyPropertyChanged
{
public DynamicResources()
{
_defaultContextForCurrentView = ResourceContext.GetForCurrentView(); _defaultContextForCurrentView.QualifierValues.MapChanged += async (s, m) =>
{
await MainPage.Current.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
OnPropertyChanged("");
});
};
} private ResourceContext _defaultContextForCurrentView; public string Main
{
get { return ResourceManager.Current.MainResourceMap.GetValue("DynamicResources/Main", _defaultContextForCurrentView).ValueAsString; }
} public string Settings
{ get { return ResourceManager.Current.MainResourceMap.GetValue("DynamicResources/Settings", _defaultContextForCurrentView).ValueAsString; }
} public string RestartNote
{
get { return ResourceManager.Current.MainResourceMap.GetValue("DynamicResources/RestartNote", _defaultContextForCurrentView).ValueAsString; }
} public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

SettingView.xaml

<Page.Resources>
<local:DynamicResources x:Key="DynamicResources"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<ListView x:Name="LanguageListView" Margin="10">
<ListViewItem Tag="zh-Hans-CN" Content="中文"/>
<ListViewItem Tag="en-US" Content="English"/>
</ListView>
<TextBlock x:Name="NoteElement" Foreground="#FFF99F00" Margin="20,10" Visibility="Collapsed"
Text="{Binding RestartNote,Source={StaticResource DynamicResources}}"
/>
</StackPanel>
</Grid>

SettingView.xaml.cs

private async void OnLanguageListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var item = LanguageListView.SelectedItem as ListViewItem;
if (item == null)
return; ApplicationLanguages.PrimaryLanguageOverride = item.Tag as string;
_hasChangedLanguage = true;
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, ShowNoteElement);
} private void ShowNoteElement()
{
NoteElement.Visibility = Visibility.Visible;
var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
appView.Title = (LanguageListView.SelectedItem as ListViewItem)?.Content as string;
}

只在设置页面及菜单这些在切换语言时不会重新加载的UI上使用Binding,其它地方不变,这样简单的动态切换语言就实现了。运行结果如上,可以看到TextBox右键菜单仍未切换语言,需要重新启动。

UWP默认只安装电脑对应的语言,这样可以节省安装空间,但影响到动态切换语言的功能,要解决这个问题可以参考以下内容(我没有验证过):localization - How to always install all localized resources in Windows Store UWP app - Stack Overflow

4. 获得完整的设计视图

在Fall Creators Update以前为了获得设计时视图可以使用索引器。很少有机会在C#中用到索引器,XAML中也很少用到Binding到字符串索引的语法,就是这两个功能在本地化中帮了大忙。

public class ResourcesStrings
{
public string this[string key]
{
get
{
return ResourceLoader.GetForViewIndependentUse().GetString(key);
}
}
}
<Page.Resources>
<local:ResourcesStrings x:Key="S"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="{Binding Source={StaticResource S},Path=[MainTitle]}" />
</Grid>

只需要这样写就可以获得完整的设计时试图,可是还是没有解决智能感知和错误提示这两个问题。

在这个方案上也可简单地实现动态切换语言。

public class ApplicationResources : INotifyPropertyChanged
{
public ApplicationResources()
{
DynamicResources = new DynamicResourcesStrings();
Resources = new ResourcesStrings();
Current = this;
} public static ApplicationResources Current { get; private set; } public event PropertyChangedEventHandler PropertyChanged; public DynamicResourcesStrings DynamicResources { get; } public ResourcesStrings Resources { get; } public string Language
{
get
{
return ApplicationLanguages.PrimaryLanguageOverride;
}
set
{ if (ApplicationLanguages.PrimaryLanguageOverride == value)
return; ApplicationLanguages.PrimaryLanguageOverride = value;
if (MainPage.Current != null )
MainPage.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { OnPropertyChanged(""); });
}
} [NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
<ListViewItem Content="{Binding Source={StaticResource R},Path=DynamicResources[Main]}"/>

不知道为什么,在VisualStudio上有时没办法获得设计时视图,所有文字都显示为"Item"。

5. 使用resx资源文件

既然UWP是XAML大家族的一份子,那么应该也可以使用resx资源文件实现本地化,毕竟生成resx对应代码的是PublicResXFileCodeGenerator,而不是UWP本身。

  1. 打开“添加新项”对话框,选中“资源文件(.resw)”,在“名称”文本框中将文件名称改为“Labels.resx”,点击“添加”。
  2. 在“解决方案资源管理器”选中“Labels.resx”,邮件打开“属性”视图,“生成操作”选择“嵌入的资源”。
  3. 将“Labels.resx”复制为“Labels.zh-CN.resx”,打开“Labels.zh-CN.resx”,“访问修饰符”改为“无代码生成”。
  4. 在“AssemblyInfo.cs”添加如下代码:
[assembly: NeutralResourcesLanguage("en-US")]

这样就可以在UWP中使用resx资源文件了。实现本地化的代码和上一篇文章中介绍的WPF本地化方案差不多。

public class ApplicationResources : INotifyPropertyChanged
{
public static ApplicationResources Current { get; private set; } public ApplicationResources()
{
Labels = new Labels();
if (string.IsNullOrWhiteSpace(ApplicationLanguages.PrimaryLanguageOverride) == false)
Language = ApplicationLanguages.PrimaryLanguageOverride;
else
Language = Windows.System.UserProfile.GlobalizationPreferences.Languages.FirstOrDefault(); Current = this;
} public Labels Labels { get; set; } public event PropertyChangedEventHandler PropertyChanged; public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private string _language; /// <summary>
/// 获取或设置 Language 的值
/// </summary>
public string Language
{
get { return _language; }
set
{
if (_language == value)
return; _language = value;
Labels.Culture = new System.Globalization.CultureInfo(_language);
ApplicationLanguages.PrimaryLanguageOverride = _language;
OnPropertyChanged("");
}
}
}

使用体验和WPF中的resx本地化方案差不多,设计时支持几乎完美,包括智能感知和错误提示,不过还是没办法解决系统组件中的本地化问题(如TextBox右键菜单)。另外,编译时会报错:带有输出类型“appcontainerexe”的项目不支持生成操作“EmbeddedResource”。解决方案是不在UWP应用项目中添加resx资源文件,而在类库中添加resx资源文件,这样连错误都不报了。

不知道Xamarin.Forms是不是也可以这样实现,毕竟它也是XAML大家族的一员。

6. 结语

研究了这么多resw资源文件的方案,结果还是resx资源文件用得最顺手,毕竟这个方案我已经用了很多年(在silverlight中只能用这个方案)。具体使用哪个方案见仁见智。

需要强调的是resx并不能完全替代resw方案,很多时候需要混合使用,例如应用的Display Name可以使用resw轻松实现本地化:

本地化的主题仍有很多内容,这篇文章只打算介绍入门知识,更深入的知识可以参考下面给出的链接。

7. 参考

Guidelines for globalization - UWP app developer Microsoft Docs

Localize strings in your UI and app package manifest - UWP app developer Microsoft Docs

Load images and assets tailored for scale, theme, high contrast, and others - UWP app developer Microsoft Docs

快速入门:翻译 UI 资源 (XAML)

c# - UWP Resource file for languages is not deployed correctly - Stack Overflow

localization - How to always install all localized resources in Windows Store UWP app - Stack Overflow

Win10 UWP 开发系列:使用多语言工具包让应用支持多语言 - yan_xiaodi - 博客园

Windows 10 Apps Designing for Global Customers

8. 源码

GitHub - LocalizationDemo

[UWP]本地化入门的更多相关文章

  1. UWP开发入门(十六)——常见的内存泄漏的原因

    本篇借鉴了同事翔哥的劳动成果,在巨人的肩膀上把稿子又念了一遍. 内存泄漏的概念我这里就不说了,之前<UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏>中提到过,即使有 ...

  2. UWP开发入门系列笔记之(一):UWP初览

    标签: 随着微软Build2015带来的好消息,Win10正式版发布的日子已经离我们越来越近了,我们也终于欣喜地看到:一个统一的Windows平台对于开发人员来说充满了吸引力,这局棋下的好大的说--于 ...

  3. UWP开发入门(十)——通过继承来扩展ListView

    本篇之所以起这样一个名字,是因为重点并非如何自定义控件,不涉及创建CustomControl和UserControl使用的Template和XAML概念.而是通过继承的方法来扩展一个现有的类,在继承的 ...

  4. UWP开发入门(四)——自定义CommandBar

    各位好,再次回到UWP开发入门系列,刚回归可能有些不适应,所以今天我们讲个简单的,自定义CommandBar,说通俗点就是自定义类似AppBarButton的东西,然后扔到CommandBar中使用. ...

  5. UWP开发入门(25)——通过Radio控制Bluetooth, WiFi

    回顾写了许久的UWP开发入门,竟然没有讲过通过Windows.Devices.Radios.Radio来控制Bluetooth和WiFi等功能的开关.也许是因为相关的API设计的简单好用,以至于被我给 ...

  6. UWP开发入门(十一)——Attached Property的简单应用

    UWP中的Attached Property即附加属性,在实际开发中是很常见的,比如Grid.Row: <Grid Background="{ThemeResource Applica ...

  7. win10 uwp MVVM入门

    MVVM 是一个强大的架构,基本从 WPF 开始,wr(我说的就是微软)就提倡使用 MVVM.它可以将界面和后台分离,让开发人员可以不关心界面是怎样,全心投入到后台代码编写中. 然后在编写完后台代码后 ...

  8. [WPF]本地化入门

    1. 前言 WPF的本地化是个很常见的功能,我做过的WPF程序大部分都实现了本地化(不管最终有没有用到).通常本地化有以下几点需求: 在程序启动时根据CultureInfo.CurrentUICult ...

  9. UWP开发入门(七)——下拉刷新

    本篇意在给这几天Win10 Mobile负面新闻不断的某软洗地,想要证明实现一个简单的下拉刷新并不困难.UWP开发更大的困难在于懒惰,缺乏学习的意愿.而不是“某软连下拉刷新控件都没有”这样的想法. 之 ...

随机推荐

  1. RE:考勤系统的复盘

     一大早看了 <美团旅行前端技术体系的思考与实践> 这篇文,恰巧又在昨天完成了一个项目.确实让我忍不住码篇总结,为自己做一个复盘. 历时两个月,考勤系统 这个项目总算能够称得上完成了.项目 ...

  2. 修改 Sublime 按快捷键 ctrl+s 自动格式化(reindent lines)的问题

    Sublime 工具自带代码格式化的功能,但在某些场景下格式化代码后并不是我们想要的代码格式,且是点击保存ctrl+s才触发的格式代码事件,so,为关闭点击ctrl+s格式代码,我们需要改命令 sav ...

  3. 接受第三方app分享的数据

    前段时间公司项目需要一个需求: 把第三方的app分享的数据接受到自己的apk中, 涉及到的第三方app是: Youtube/Amazon/NetFlix, 这些app通过分享功能把当前的信息分享出去. ...

  4. cookie 操作(转载)

    /** * Create a cookie with the given name and value and other optional parameters. * * @example $.co ...

  5. .net 系列:事件和委托

    在.net 的世界里,离不开委托和事件,其实理解透了后很简单,总结了一下分为5步:   1)定义委托   public delegate void RevicedEventHandler(object ...

  6. Kafka设计解析(八)- Exactly Once语义与事务机制原理

    原创文章,首发自作者个人博客,转载请务必将下面这段话置于文章开头处. 本文转发自技术世界,原文链接 http://www.jasongj.com/kafka/transaction/ 写在前面的话 本 ...

  7. 记一次改造react脚手架的过程

    公司突然组织需要重新搭建一个基于node的论坛系统,前端采用react,上网找了一些脚手架,或多或少不能满足自己的需求,最终在基于YeoMan的react脚手架generator-react-webp ...

  8. Gulp livereload

    平时使用yeoman作为前端部署工具,感觉到yeoman构建工具虽然方便,但是速度和大小总是不尽人意. 最近看到了gulp http://gulpjs.com/ 比较感兴趣随动手一试 gulp的安装以 ...

  9. springboot-helloworld

    1使用idea创建springboot项目如下图所示 并选择web模块 2,登录springboot官网 http://projects.spring.io/spring-boot/ 引入相关依赖包如 ...

  10. Azure IoT Edge on Windows 10 IoT Core

    在今年的Build大会上,微软推出了Azure IoT Edge的第一个版本(https://github.com/Azure/iot-edge ).该版本的主要特点就是将计算能力由Azure端推送至 ...