本文介绍一种支持在设计时和运行时来动态更改程序运行语言支撑的实现方法。WPF多语言支持我们期望能够实现以下功能:
(1)能够与WPF的XAML直接集成;
(2)支持编码方式来访问资源;
(3)支持在运行时动态切换和动态编辑语言。
1 概述
以往多语言方式都是使用Resource文件来支持,这个文件最终会编译成资源dll文件。以这种方式实现多语言的优点是在开发的时候可以直接使用IDE,但是缺点就是动态性支撑较差,要实现动态语言切换比较麻烦,并且无法实现在运行时实现语言编辑。下图是语言切换的页面。
下图是切换成英语的显示效果。在这里,导航栏、系统菜单、状态栏和内容区域均实现语言动态切换。
以下是动态切换到中文的显示效果。
下图是运行时语言编辑器。在这里直接对资源进行翻译,翻译后立刻生效。
2 设计实现
本地化框架基于OSGi.NET框架设计,作为OSGi.NET的插件,其设计遵守以下原则:
(1)插件不依赖于具体的本地化支持方法。支持本地化的方法可以有Resource文件、自定义的XML文件等来存储本地化资源。不过,插件不能依赖于特定的实现。
(2)本地化的实现使用分级方式,第一级为应用环境无关,第二级为WPF相关。WPF相关的本地化支持,必须很好的考虑与WPF的XAML界面技术结合,尽可能提高在WPF中使用的易用性。
(3)支持本地化资源设计时、运行时动态编辑,特别是运行时编辑。支持在运行时变更语言环境。
本地化框架有本地化基础服务(UIShell.LocalizationService)、WPF本地化服务(UIShell.WpfLocalizationService)插件实现,其依赖关系如下所示。
如上图所示,第三方插件需要访问本地化资源时,只需要依赖于本地化基础服务,未来可以在不更改插件代码的情况下方便的扩展到其它本地化方法的实现。
本地化基础服务接口在UIShell.LocalizationService插件中定义,如下所示。它定义了资源获取接口、支持语言列表以及语言变更事件。
以下是本地化服务接口的描述。每一个插件均有自己的本地化存储空间,通过GetResource和GetString可以获取插件本身的本地化资源。
/// <summary> /// 本地化服务。 /// </summary> public interface ILocalizationService { /// <summary> /// 支持的语言列表。 /// </summary> Dictionary<string, CultureInfo> SupportCultureInfos { get; } /// <summary> /// 当前语言。 /// </summary> CultureInfo CurrentCultureInfo { get; set; } /// <summary> /// 获取资源。 /// </summary> /// <typeparam name="T">资源类型。</typeparam> /// <param name="bundle">所属插件。</param> /// <param name="resourceKey">资源Key。</param> /// <returns>返回资源对象。</returns> T GetResource<T>(IBundle bundle, string resourceKey, T defaultValue); /// <summary> /// 获取资源。 /// </summary> /// <typeparam name="T">资源类型。</typeparam> /// <param name="bundleSymbolicName">所属插件标识。</param> /// <param name="resourceKey">资源Key。</param> /// <returns>返回资源对象。</returns> T GetResource<T>(string bundleSymbolicName, string resourceKey, T defaultValue); /// <summary> /// 获取字符串资源。 /// </summary> /// <param name="bundle">插件。</param> /// <param name="resourceKey">资源Ke。</param> /// <returns>如果不存在返回string.Empty。</returns> string GetString(IBundle bundle, string resourceKey); /// <summary> /// 获取字符串资源。 /// </summary> /// <param name="bundleSymbolicName">插件名称。</param> /// <param name="resourceKey">资源Ke。</param> /// <returns>如果不存在返回string.Empty。</returns> string GetString(string bundleSymbolicName, string resourceKey); /// <summary> /// 语言变更事件。 /// </summary> event EventHandler<CultureInfoEventArgs> CultureInfoChanged; } public class CultureInfoEventArgs : EventArgs { public CultureInfo PreviousCultureInfo { get; set; } public CultureInfo CurrentCultureInfo { get; set; } }
WPF本地化服务的实现方法描述如下:
(1)语言文件使用XAML格式来存储,在插件根目录的Language目录下,语言文件文件名称为Resource.<语言>.xaml,自定义语言文件为Resource.xaml。简体中文的语言文件为Resource.zh-CN.xaml,繁体中文语言文件为Resource.zh-TW.xaml,英语为Resource.en-US.xaml。
(2)支持设置当前语言,一旦当前语言发生变更时,该服务将动态搜索当前语言的文件,并使用XamlReader类来读取资源,将资源文件序列化成ResourceDictionary类。
(3)编辑语言时,根据支持的语言动态生成一个DataGrid的列,每一个列就是对应语言的资源,语言的Key为所有语言的合并集合。
(4)语言编辑后,使用XamlWriter将ResourceDictionary序列化到指定语言文件。
WPF界面框架加载插件的用户控件后,会利用WPF本地化服务加载插件的资源,并将其ResourceDictionary与插件用户控件进行合并,因此,对于插件中直接在界面框架显示区域呈现的用户控件,可以直接使用以下方式来简便的访问服务。
{DynamicResource [ResourceKey]} ,可以在支持绑定的控件属性中直接设置。
此外,插件可以使用ILocalizationService.GetResource/GetString来获取当前语言的资源。
对于没有在主界面框架内容区域加载的控件,比如弹出一个窗口,为了使该窗口也支持通过DynamicResource来访问资源,必须通过IWpfLocalizationSerivce.GetResourceDitionary方法获取插件的资源集合,并与窗口资源进行合并。该服务实现如下所示。
WpfLocalizationServiceImpl具备动态性:
(1)它能够动态监听插件的状态变更,当插件启动时,加载插件语言资源;插件停止时,则删除语言资源。
(2)当前语言发生变更时,所有语言文件会被重新加载,并发出CultureInfoChanged事件,通知界面进行刷新。
3 XAML资源文件的处理
多语言的实现依赖于XAML文件的处理。以下代码是使用XamlReader将资源文件转换成ResourceDictionary。
以下代码则是使用XamlWriter将ResourceDictionary保存成XAML文件。
这种基于XAML的本地化实现方法能够很好的应用在WPF界面本地化、代码本地化资源访问,并且支持动态语言切换和运行时语言编辑。