之前我在一篇blog中写过如何使用多语言工具包,见http://www.cnblogs.com/yanxiaodi/p/3800767.html

在WinEcos社区也发布过一篇详细的文章介绍多语言工具包的使用,但因社区改版那篇文章已经找不到了。

当时写的时候还没有出Win10的SDK,都是基于UAP框架写的。微软早已经发布了Win10的SDK,相应的项目结构也发生了变化,以前分为两个项目通过Share项目共享代码的方式被抛弃,改为合并为一个项目,真正实现了一套代码兼容PC和Mobile两个平台,我已经基于Win10 10586的SDK发布了Currency Exchanger的新版本,下载地址:https://www.microsoft.com/store/apps/9WZDNCRDQ91S

在开发Currency Exchanger的过程中,我又整理了一下支持多语言的问题,记录于此。

一、安装多语言工具包

使用VS2015开发UWP不能再使用老版3.0的多语言工具包,而应该使用新版的V4.0beta,这个还不是正式版,所以兼容性有问题,无法与V3.0共存,安装之后也无法再用VS2013或VS2015打开WP8.1之前的项目,所以安装之前请慎重!请慎重!请慎重!重要的事情说三遍。

我们在开发者后台的下载栏目可以找到多语言工具包的下载页面:https://dev.windows.com/zh-cn/develop/multilingual-app-toolkit

但是!截至到2015年12月31日,这个页面所下载的中文多语言工具包仍然是V3.0,而不是最新的V4.0beta,就算安装了,也无法在UWP项目中应用。

最新版的下载地址在此:

https://visualstudiogallery.msdn.microsoft.com/6dab9154-a7e1-46e4-bbfa-18b5e81df520

这也是我一直吐槽MSDN的原因之一,找个东西累死了,官方的东西都不好找。

还有一种方式,直接在VS2015的扩展里搜索Multilingual App Toolkit,主要要有空格,不然搜不到:

要安装V4.0beta这个。下面那个是旧版的,这两个无法共存。

二、启用多语言工具包

还是做个例子吧。新建一个MultilingualDemo项目,VS2015工具菜单-Multilingual App Toolkit -启用选定内容

会收到提示:1>  未启用项目"MultilingualDemo"-没有可本地化的资源被发现。

这是因为没有发现咳本地化的资源,双击Package.appxmanifest打开,设置一个默认语言,如果在设计的时候就想支持多语言,最好默认语言设置为英语,输入en-US:

然后在项目中添加一个Strings文件夹,再在其下添加一个en-US文件夹,这个文件夹名字要和默认语言代码保持一致,如果默认语言是zh-CN,那就建一个zh-CN的文件夹。

在这个目录下添加一个Resources.resw资源文件,在这里面编辑所需要的字符串:

添加几个资源,注意,如果是要显示在界面上的,可以根据控件的属性来设置,如TextBlock的文字是Text属性,那资源的名字就命名为HelloWorld.Text,Button的文字是Content属性,所以命名为ClickMe.Content,另外我还加了一个AppName,用于在代码中使用。

再次 VS2015工具菜单-Multilingual App Toolkit -启用选定内容

这次可以正常启用了:

1> 项目"MultilingualDemo"已启用。 该项目的来源,文化是 'en-US' [英语(美国)]。

三、在XAML界面上使用语言资源

在Page中放一个 TextBlock,一个Button,一个ComboBox,设置其x:Uid(资源标识符,注意不是x:Name)属性,这样控件就可以根据资源找到其对应的内容:

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"  DataContext="{StaticResource DesignVM}">
<TextBlock x:Name="pageTitle" Grid.Column="1" Margin="100" Text="{Binding Title}" />
<TextBlock x:Name="textBlock" x:Uid="HelloWorld" HorizontalAlignment="Left" Margin="100,156,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
<Button x:Name="button" x:Uid="ClickMe" HorizontalAlignment="Left" Margin="100,181,0,0" VerticalAlignment="Top"/>
<ComboBox x:Name="comboBox" x:Uid="ChangeLanguage" HorizontalAlignment="Left" Margin="100,249,0,0" VerticalAlignment="Top" Width="120"/> </Grid>

四、在代码中使用语言资源

添加一个

    public static class AppResources
{
private static ResourceLoader CurrentResourceLoader
{
get { return _loader ?? (_loader = ResourceLoader.GetForCurrentView("Resources")); }
} private static ResourceLoader _loader;
private static readonly Dictionary<string, string> ResourceCache = new Dictionary<string, string>(); public static string GetString(string key)
{
string s;
if (ResourceCache.TryGetValue(key, out s))
{
return s;
}
else
{
s = CurrentResourceLoader.GetString(key);
ResourceCache[key] = s;
return s;
}
} /// <summary>
/// AppName
/// </summary>
public static string AppName
{
get
{
return CurrentResourceLoader.GetString("AppName");
}
}
}

打开MainPage_Model.cs,取消OnBindedViewLoad方法的注释,在里面添加以下代码:

        /// <summary>
/// This will be invoked by view when the view fires Load event and this viewmodel instance is already in view's ViewModel property
/// </summary>
/// <param name="view">View that firing Load event</param>
/// <returns>Task awaiter</returns>
protected override Task OnBindedViewLoad(MVVMSidekick.Views.IView view)
{
this.Title = AppResources.AppName;
return base.OnBindedViewLoad(view);
}

现在运行看看:

现在默认语言是en-US。

五、翻译成本地化资源

如果没安装多语言工具包的话,可以在Strings目录下手动添加对应语言的文件夹和资源文件,不过有多语言工具包的话这个工作就变得很容易了,在项目上右键,添加翻译语言:

在这里可以选择要添加什么语言,选择简体中文:

这里建议只选择zh-Hans就可以了,不用选择zh-CN,因为语言与资源的匹配非常复杂,语言标记存在多种可能影响匹配优先级的可选组件,建议让系统来选择,MSDN文档是这么说的:(https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/mt607079.aspx

使用某个语言标记的可选组件的示例有:

  • 用于禁止脚本语言的脚本。例如,en-Latn-US 与 en-US 匹配。
  • 区域。例如,en-US 与 en 匹配。
  • 变体。例如,de-DE-1996 与 de-DE 匹配。
  • -x 和其他扩展名。例如,en-US-x-Pirate 与 en-US 匹配。

对于不采用 xx 或 xx-yy 形式的语言标记,也存在许多组件,且并非全部匹配。

  • zh-Hant 与 zh-Hans 不匹配。

Windows 以一个标准的易于理解的方式排定语言匹配的优先顺序。例如,按优先顺序,en-US 依次与 en-US、en、en-GB 等等匹配。

  • Windows 执行跨区域匹配。例如,en-US 与 en-US 匹配,然后依次与 en、en-* 匹配。
  • Windows 提供了一些额外数据,它们可用于区域内(如某种语言的主要区域)的相关性匹配。例如,fr-FR 比 fr-CA 更匹配 fr-BE。
  • 如果你使用 Windows API,则可以免费获取日后 Windows 在语言匹配方面的任何改进。

在与列表中的首个语言匹配之后才会与列表中第二个语言匹配,对于其他区域变体也是如此。例如,如果应用程序语言为 en-US,则会先于 fr-CA 资源选择用于 en-GB 的资源。仅当没有用于 en 形式的资源时才选择用于 fr-CA 的资源。

应用程序语言列表设置为用户的区域变体,尽管该变体不同于应用提供的区域变体。例如,如果用户使用 en-GB,但应用支持 en-US,则应用程序语言列表将包含 en-GB。这将确保日期、时间和数字的格式更接近用户的期望 (en-GB),但仍然使用应用支持的语言 (en-US) 加载 UI 资源(由于语言匹配)。

除非想把本地化做的非常完善,为中国用户和新加坡用户都提供不同的语言资源,否则只提供一种中文简体就够了。

选择后,Strings目录下会自动添加中文简体资源文件:

但是中文的资源里还是空的,我们需要翻译一下英文资源。右键单击MultilingualDemo.zh-Hans.xlf,选择打开方式:

现在可以输入翻译了,如果懒的话就点击菜单翻译按钮调用Bing的翻译接口自动翻译一下,保存。

重新编译生成一下项目,多语言工具包会根据默认资源去填充其他语言的资源文件:

现在可以看到中文的资源文件里也已经有了。翻译的时候要注意,字符串有几种状态,新、翻译、需要评审、最终等,可以根据这几种状态灵活切换显示哪些字符串来处理。

重新运行项目,如果我们的电脑系统默认是中文语言,那app应该已经变成中文界面了。如果用户电脑或手机默认语言是英语,则会调用en-US,如果是其他语言,则会调用默认语言en-US。

六、更改首选语言

App应具有可更改语言的设置。为了保存用户的首选语言,需要使用Windows.Storage.ApplicationData.Current.LocalSettings来保存用户的设置,关于如何使用这个来保存配置网上有很多介绍,这里就不详细介绍了。

在MainPage.xaml里头部添加以下命名空间:

xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" xmlns:Core="using:Microsoft.Xaml.Interactions.Core" 

把ComboBox代码改成这样:

        <ComboBox x:Name="comboBox" x:Uid="ChangeLanguage" HorizontalAlignment="Left" Margin="100,249,0,0" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding LanguageList}" SelectedIndex="{Binding LanguageCodeIndex, Mode=TwoWay}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="SelectionChanged">
<Core:InvokeCommandAction Command="{Binding CommandLanguageChanged}" CommandParameter="{Binding LanguageCodeIndex}"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</ComboBox>

这里我们主要使用了Windows.Globalization里面的类,Windows.Globalization 还具有作为帮助程序对象提供的 Language 对象。它帮助应用检查有关语言的详细信息,例如,语言的脚本、显示名称和本地名称。主要语言替代PrimaryLanguageOverride是一个简单的替代设置,它用于让用户独立选择语言的应用,或者有充分理由替代默认语言选择的应用。

相应的vm里添加以下代码:

public ObservableCollection<Language> LanguageList
{
get { return _LanguageListLocator(this).Value; }
set { _LanguageListLocator(this).SetValueAndTryNotify(value); }
}
#region Property ObservableCollection<Language> LanguageList Setup
protected Property<ObservableCollection<Language>> _LanguageList = new Property<ObservableCollection<Language>> { LocatorFunc = _LanguageListLocator };
static Func<BindableBase, ValueContainer<ObservableCollection<Language>>> _LanguageListLocator = RegisterContainerLocator<ObservableCollection<Language>>("LanguageList", model => model.Initialize("LanguageList", ref model._LanguageList, ref _LanguageListLocator, _LanguageListDefaultValueFactory));
static Func<ObservableCollection<Language>> _LanguageListDefaultValueFactory = () => { return new ObservableCollection<Language>(); };
#endregion /// <summary>
/// 语言设置
/// </summary>
public int LanguageCodeIndex
{
get { return _LanguageCodeIndexLocator(this).Value; }
set { _LanguageCodeIndexLocator(this).SetValueAndTryNotify(value); }
}
#region Property int LanguageCodeIndex Setup
protected Property<int> _LanguageCodeIndex = new Property<int> { LocatorFunc = _LanguageCodeIndexLocator };
static Func<BindableBase, ValueContainer<int>> _LanguageCodeIndexLocator = RegisterContainerLocator<int>("LanguageCodeIndex", model => model.Initialize("LanguageCodeIndex", ref model._LanguageCodeIndex, ref _LanguageCodeIndexLocator, _LanguageCodeIndexDefaultValueFactory));
static Func<int> _LanguageCodeIndexDefaultValueFactory = () => { return -; };
#endregion public CommandModel<ReactiveCommand, String> CommandLanguageChanged
{
get { return _CommandLanguageChangedLocator(this).Value; }
set { _CommandLanguageChangedLocator(this).SetValueAndTryNotify(value); }
}
#region Property CommandModel<ReactiveCommand, String> CommandLanguageChanged Setup protected Property<CommandModel<ReactiveCommand, String>> _CommandLanguageChanged = new Property<CommandModel<ReactiveCommand, String>> { LocatorFunc = _CommandLanguageChangedLocator };
static Func<BindableBase, ValueContainer<CommandModel<ReactiveCommand, String>>> _CommandLanguageChangedLocator = RegisterContainerLocator<CommandModel<ReactiveCommand, String>>("CommandLanguageChanged", model => model.Initialize("CommandLanguageChanged", ref model._CommandLanguageChanged, ref _CommandLanguageChangedLocator, _CommandLanguageChangedDefaultValueFactory));
static Func<BindableBase, CommandModel<ReactiveCommand, String>> _CommandLanguageChangedDefaultValueFactory =
model =>
{
var resource = "CommandLanguageChanged"; // Command resource
var commandId = "CommandLanguageChanged";
var vm = CastToCurrentType(model);
var cmd = new ReactiveCommand(canExecute: true) { ViewModel = model }; //New Command Core cmd.DoExecuteUIBusyTask(
vm,
async e =>
{
//Todo: Add LanguageChanged logic here, or
await MVVMSidekick.Utilities.TaskExHelper.Yield();
string oldLan = AppSettings.Instance.LanguageCode;
if (vm.LanguageCodeIndex >= )
{
var lan = vm.LanguageList[vm.LanguageCodeIndex];
AppSettings.Instance.LanguageCode = lan.LanguageTag;
ApplicationLanguages.PrimaryLanguageOverride = lan.LanguageTag;
}
})
.DoNotifyDefaultEventRouter(vm, commandId)
.Subscribe()
.DisposeWith(vm); var cmdmdl = cmd.CreateCommandModel(resource); cmdmdl.ListenToIsUIBusy(
model: vm,
canExecuteWhenBusy: false);
return cmdmdl;
}; #endregion

属性可以用propvm代码段,命令用propcmd代码段来快速生成。

在MainPage的vm的load事件中初始化语言列表:

if (!LanguageList.Any())
{
var lanList = ApplicationLanguages.ManifestLanguages;
foreach (var lan in lanList)
{
LanguageList.Add(new Language(lan));
}
}
if (!string.IsNullOrEmpty(AppSettings.Instance.LanguageCode))
{
Language userLan = LanguageList.FirstOrDefault(x => x.LanguageTag == AppSettings.Instance.LanguageCode);
LanguageCodeIndex = LanguageList.IndexOf(userLan);
}

如果用户更改语言后,在程序载入时应该按照用户选择的语言来调用,打开App.xaml.cs,在App构造 函数中添加以下代码:

public App()
{
//TODO 这里可以根据用户需要更改语言
if (!string.IsNullOrEmpty(AppSettings.Instance.LanguageCode))
{
ApplicationLanguages.PrimaryLanguageOverride = AppSettings.Instance.LanguageCode;
}
//ApplicationLanguages.PrimaryLanguageOverride = "cs";
//ResourceContext.GetForCurrentView().Reset();
this.InitializeComponent();
this.Suspending += OnSuspending;
}

现在一个具有基本多语言支持、可更改语言的app就完成了。用户选择不同的语言后,重新打开就会重新设置语言。

七、其他

这里是微软官方的一个例子,不过是win8平台的,可以参考:https://code.msdn.microsoft.com/windowsapps/Application-resources-and-cd0c6eaa/

还有一些特殊的情况需要考虑,如果非要做的那么完美的话:

  • 有可能有的语言字符数比较多,导致控件宽度不够,这就需要为每种语言设置控件的宽度,比如创建App_Name.Width的资源;
  • 有可能语言的排列方向不一致,比如阿拉伯语是右对齐,可以设置对齐属性;
  • 有可能图像也需要本地化,那就需要按照资源限定符的格式来定义图片路径:

    标准命名约定为foldername/qualifiername-value_qualifiername-value/filename.qualifiername-value_qualifiername-value.ext,当资源路径为Images/en-US/homeregion-USA/logo.scale-100_contrast-white.png时,应以Images/logo.png的方式来加载。

关于如何使用资源限定符,可参考:https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/hh965324.aspx

这个页面是一些本地化的最佳实践:https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/hh967762.aspx

CurrencyExchanger的本地化我也没有做的那么完善,基本就是只翻译了语言,没有考虑宽度啊排列方式这些,工作量太大了。

至于如何翻译这些文字,建议在app里留一个邮件,让志愿者来帮助翻译,可以将xlf文件上右键导出翻译,选择xlf格式或者csv格式,发送给翻译人员翻译,然后再导入进来即可。但这里又会遇到一个问题,导出的csv用excel打开的话,再另存,会丢失里面的双引号。

从xlf文件导出的csv格式是这样的:

除了表头,每个字段两边都有双引号。

这个文件用excel打开再另存后,再用文本编辑软件打开,会变成这样:

两边的引号没有了,导入的时候会提示错误,无法导入。

我想了个笨办法,在excel里编辑csv的时候,修改单元格属性,改为   !"@!" 然后再保存,这样在文本编辑软件里打开会发现一个双引号变成了两个,然后再使用批量替换功能,把"""替换为",同时别忘了把第一行表头的双引号去掉,才能正确导入。

其实如果直接把资源文件里的内容复制到excel里发送给翻译人员翻译,翻译好了再粘贴回来也行,但要是以后再增加修改默认语言的资源时,其他语言也得手动挨个改,不如用多语言工具包自动填充完整。各有利弊。

最后附上demo下载地址:

链接:http://pan.baidu.com/s/1i4jBn8L 密码:idpz

Win10 UWP 开发系列:使用多语言工具包让应用支持多语言的更多相关文章

  1. Win10 UWP开发系列:使用VS2015 Update2+ionic开发第一个Cordova App

    安装VS2015 Update2的过程是非常曲折的.还好经过不懈的努力,终于折腾成功了. 如果开发Cordova项目的话,推荐大家用一下ionic这个框架,效果还不错.对于Cordova.PhoneG ...

  2. Win10 UWP开发系列:实现Master/Detail布局

    在开发XX新闻的过程中,UI部分使用了Master/Detail(大纲/细节)布局样式.Win10系统中的邮件App就是这种样式,左侧一个列表,右侧是详情页面.关于这种 样式的说明可参看MSDN文档: ...

  3. Win10 UWP 开发系列:使用SQLite

    在App开发过程中,肯定需要有一些数据要存储在本地,简单的配置可以序列化后存成文件,比如LocalSettings的方式,或保存在独立存储中.但如果数据多的话,还是需要本地数据库的支持.在UWP开发中 ...

  4. Win10 UWP 开发系列:使用SplitView实现汉堡菜单及页面内导航

    在Win10之前,WP平台的App主要有枢轴和全景两种导航模式,我个人更喜欢Pivot即枢轴模式,可以左右切换,非常方便.全景视图因为对设计要求比较高,自己总是做不出好的效果.对于一般的新闻阅读类Ap ...

  5. Win10 UWP开发系列:解决Win10不同版本的Style差异导致的兼容性问题

    最近在开发一个项目时,遇到了一个奇怪的问题,项目依赖的最低版本是10586,目标版本是14393,开发完毕发布到商店后,很多用户报无法正常加载页面.经查,有问题的都是Win10 10586版本. 我上 ...

  6. Win10 UWP开发系列——开源控件库:UWPCommunityToolkit

    在开发应用的过程中,不可避免的会使用第三方类库.之前用过一个WinRTXamlToolkit.UWP,现在微软官方发布了一个新的开源控件库—— UWPCommunityToolkit 项目代码托管在G ...

  7. Win10 UWP 开发系列:支持异步的SQLite

    上篇文章已经实现了在UWP中使用SQLite作为本地存储,作为移动端的程序,及时响应用户的操作是提高用户体验的重要途径,因此UWP的很多api都是异步的.那么如何使SQLite支持异步呢? 参考SQL ...

  8. Win10 UWP开发系列:开发一个自定义控件——带数字徽章的AppBarButton

    最近有个项目有一个这样的需求,在文章浏览页底部有几个AppBarButton,其中有一个是评论按钮,需要在评论按钮上显示一个红色数字,类似微信的新消息提醒: 这种设计在iOS和Android平台都是很 ...

  9. Win 10 UWP开发系列:设置AppBarButton的图标

    在WP8以前,页面最下面的四个小圆按钮是不支持绑定的,WP8.1 RT之后,系统按钮升级成了AppBarButton,并且支持绑定了.在Win10 UWP开发中,按钮的样式发生了变化,外面的圆圈没有了 ...

随机推荐

  1. FMX保存JPG格式的Stream

    刚刚看以前的笔记,估计这个用的人很少 var surf:TBitmapSurface; astream:TmemoryStream; begin surf:=TbitmapSurface.Create ...

  2. python自动化测试(4)-使用第三方python库技术实现

    python自动化测试(4)-使用第三方python库技术实现 1   概述 关于测试的方法论,都是建立在之前的文章里面提到的观点: 功能测试不建议做自动化 接口测试性价比最高 接口测试可以做自动化 ...

  3. Azure PowerShell (6) 设置单个Virtual Machine Endpoint

    <Windows Azure Platform 系列文章目录> 请注意: - Azure不支持增加Endpoint Range - 最多可以增加Endpoint数量为150 http:// ...

  4. eclipse下打包实践

    前提: 配置好打包相关的插件,看打包的结果分别添加不同的plugin,装好m2eclipse. 以下步骤以war包的packing为例. 步骤: 如下图:右键,选择Run As 或者 Debug As ...

  5. 在JavaScript中,利用三元运算符生成当前日期yyyy-MM-dd

    <script type="text/javascript"> //得到当前时间yyyy-MM-dd var myDate = new Date(); var nowD ...

  6. ASP.NET Core 1.0 静态文件、路由、自定义中间件、身份验证简介

    概述 ASP.NET Core 1.0是ASP.NET的一个重要的重新设计. 例如,在ASP.NET Core中,使用Middleware编写请求管道. ASP.NET Core中间件对HttpCon ...

  7. IOS 消息机制(NSNotificationCenter)

    消息机制 NSNotificationCenter 一直都在频繁使用,但是却对其原理不是十分了解.今天就花些时间,把消息机制原理重头到尾好好过一遍. iOS 提供了一种 "同步的" ...

  8. AngularJS之代码风格36条建议【一】(九)

    前言 其实在新学一门知识时,我们应该注意下怎么书写代码更加规范,从开始就注意养成一个良好的习惯无论是对于bug的查找还是走人后别人熟悉代码都是非常好的,利人利己的事情何乐而不为呢,关于AngularJ ...

  9. HTML中哪些标签的值会被提交到服务器呢?

    <form> <input name="">标签 <select name="">标签 <textarea name= ...

  10. PHP关于web页面交互内容

    学php学了有一段时间了总结总结给大家分享一下 PHP中的引用 第一段程序: <?php $first_name="firstName"; $first=&$firs ...