随着咱们的应用程序愈来愈受欢迎,咱们的下一步将要开发多语言功能。方便愈来愈多的国家使用咱们中国的应用程序,
基于 WPF 本地化,咱们不少时候使用的是系统资源文件,但是动态切换本地化,就比较麻烦了。
有没有一种方法既能够适用系统的资源文件,又能方便快捷的切换本地化呢?git
如今咱们将要实现的是基于 DotNetCore 3.0
以上版本 and WPF
桌面应用程序模块化的多语言功能。
动态切换多语言思路:
github
CurrentCulture
多语言来使用改变的语言文件里的key。Binding
拼接Path 在输出。咱们先来看实现结果
c#
来看一下xaml展现
app
经过ComboBox选择来切换语言
ide
建立一个WPF App(.NET Core)应用程序
模块化
建立完成后,咱们须要引入业务A模块及业务B模块和业务帮助模块
PS:根据本身的业务须要来完成项目的搭建。本教程彻底适配多语言功能。工具
在各个模块里添加Strings
文件夹用来包含 各个国家和地区的语言文件。
post
多语言能够参考:https://github.com/UnRunDeaD/WPF---Localization/blob/master/ComboListLanguages.txtui
资源文件能够放在任意模块内,好比业务模块A ,主程序,底层业务,控件工具集等this
Strings文件夹能够任意命名
SR资源文件能够任意命名
封装到底层供各个模块调用
public class TranslationSource : INotifyPropertyChanged { public static TranslationSource Instance { get; } = new TranslationSource(); private readonly Dictionary<string, ResourceManager> resourceManagerDictionary = new Dictionary<string, ResourceManager>(); public string this[string key] { get { Tuple<string, string> tuple = SplitName(key); string translation = null; if (resourceManagerDictionary.ContainsKey(tuple.Item1)) translation = resourceManagerDictionary[tuple.Item1].GetString(tuple.Item2, currentCulture); return translation ?? key; } } private CultureInfo currentCulture = CultureInfo.InstalledUICulture; public CultureInfo CurrentCulture { get { return currentCulture; } set { if (currentCulture != value) { currentCulture = value; // string.Empty/null indicates that all properties have changed PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty)); } } } // WPF bindings register PropertyChanged event if the object supports it and update themselves when it is raised public event PropertyChangedEventHandler PropertyChanged; public void AddResourceManager(ResourceManager resourceManager) { if (!resourceManagerDictionary.ContainsKey(resourceManager.BaseName)) { resourceManagerDictionary.Add(resourceManager.BaseName, resourceManager); } } public static Tuple<string, string> SplitName(string local) { int idx = local.ToString().LastIndexOf("."); var tuple = new Tuple<string, string>(local.Substring(0, idx), local.Substring(idx + 1)); return tuple; } } public class Translation : DependencyObject { public static readonly DependencyProperty ResourceManagerProperty = DependencyProperty.RegisterAttached("ResourceManager", typeof(ResourceManager), typeof(Translation)); public static ResourceManager GetResourceManager(DependencyObject dependencyObject) { return (ResourceManager)dependencyObject.GetValue(ResourceManagerProperty); } public static void SetResourceManager(DependencyObject dependencyObject, ResourceManager value) { dependencyObject.SetValue(ResourceManagerProperty, value); } } public class LocExtension : MarkupExtension { public string StringName { get; } public LocExtension(string stringName) { StringName = stringName; } private ResourceManager GetResourceManager(object control) { if (control is DependencyObject dependencyObject) { object localValue = dependencyObject.ReadLocalValue(Translation.ResourceManagerProperty); // does this control have a "Translation.ResourceManager" attached property with a set value? if (localValue != DependencyProperty.UnsetValue) { if (localValue is ResourceManager resourceManager) { TranslationSource.Instance.AddResourceManager(resourceManager); return resourceManager; } } } return null; } public override object ProvideValue(IServiceProvider serviceProvider) { // targetObject is the control that is using the LocExtension object targetObject = (serviceProvider as IProvideValueTarget)?.TargetObject; if (targetObject?.GetType().Name == "SharedDp") // is extension used in a control template? return targetObject; // required for template re-binding string baseName = GetResourceManager(targetObject)?.BaseName ?? string.Empty; if (string.IsNullOrEmpty(baseName)) { // rootObject is the root control of the visual tree (the top parent of targetObject) object rootObject = (serviceProvider as IRootObjectProvider)?.RootObject; baseName = GetResourceManager(rootObject)?.BaseName ?? string.Empty; } if (string.IsNullOrEmpty(baseName)) // template re-binding { if (targetObject is FrameworkElement frameworkElement) { baseName = GetResourceManager(frameworkElement.TemplatedParent)?.BaseName ?? string.Empty; } } Binding binding = new Binding { Mode = BindingMode.OneWay, Path = new PropertyPath($"[{baseName}.{StringName}]"), Source = TranslationSource.Instance, FallbackValue = StringName }; return binding.ProvideValue(serviceProvider); } }
//引用业务模块 xmlns:ext="clr-namespace:WpfUtil.Extension;assembly=WpfUtil" // 引用刚才你命名的文件夹名字 xmlns:resx="clr-namespace:ModuleA.Strings" // 每一个模块经过帮助类,将当前模块的资源类, // 加载到资源管理集合里面用于分配每一个键值 // 引用刚才你命名的资源文件名字 -> SR ext:Translation.ResourceManager="{x:Static resx:SR.ResourceManager}"
显示文字
//读取资源文件里的键值 <Label Content="{ext:Loc Test}" FontSize="21" />
根据业务的须要,咱们在界面上没法适用静态文字显示的,通常经过后台代码来完成,对于 code-behind 的变量使用,一样能够应用于资源字典。
好比在业余模块代码段里的模拟实现
// SR 是当前业务模块的资源文件类,管理当前模块的资源字符串。 // 根据不一样的 `CurrentCulture` 选择相对应的本地化 Message = string.Format(SR.ResourceManager.GetString("Message",Thread.CurrentThread.CurrentUICulture),System.DateTime.Now);
PS: 欢迎各位大佬慷慨指点,有不足之处,请指出!有疑问,请指出,喜欢它,请支持!
https://github.com/androllen/WpfNetCoreLocalization
https://github.com/Jinjinov/wpf-localization-multiple-resource-resx-one-language/blob/master/README.md
https://codinginfinity.me/post/2015-05-10/localization_of_a_wpf_app_the_simple_approach