DotNetCore 3.0 助力 WPF本地化

Posted luquanmingren

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DotNetCore 3.0 助力 WPF本地化相关的知识,希望对你有一定的参考价值。

概览

随着我们的应用程序越来越受欢迎,我们的下一步将要开发多语言功能。方便越来越多的国家使用我们中国的应用程序,
基于 WPF 本地化,我们很多时候使用的是系统资源文件,可是动态切换本地化,就比较麻烦了。
有没有一种方法既可以适用系统的资源文件,又能方便快捷的切换本地化呢?

实现思路

现在我们将要实现的是基于 DotNetCore 3.0 以上版本 and WPF 桌面应用程序模块化的多语言功能。
动态切换多语言思路:

  • 把所有模块的资源文件添加到字典集合。
  • 将资源文件里的key,绑定到前台。
  • 通过通知更改 CurrentCulture 多语言来使用改变的语言文件里的key。
  • 通过绑定 Binding 拼接Path 在输出。

搭建模拟业务项目

创建一个WPF App(.NET Core)应用程序
技术图片

创建完成后,我们需要引入业务A模块及业务B模块和业务帮助模块
技术图片
PS:根据自己的业务需要来完成项目的搭建。本教程完全适配多语言功能。

使用.resx资源文件

在各个模块里添加Strings 文件夹用来包含 各个国家和地区的语言文件。
技术图片

多语言可以参考:https://github.com/UnRunDeaD/WPF---Localization/blob/master/ComboListLanguages.txt

技术图片
资源文件可以放在任意模块内,比如业务模块A ,主程序,底层业务,控件工具集等

创建各个业务模块资源文件

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",TranslationSource.Instance.CurrentCulture),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

以上是关于DotNetCore 3.0 助力 WPF本地化的主要内容,如果未能解决你的问题,请参考以下文章

DotNetCore3.0 WPF - 错误 - 项目文件不完整。缺少预期的进口

链接3.0:Ardor 3.0架构助力Web 3.0生态发展

Centos-610-Jenkins-CI系列开篇

分布式事务| 使用 dotnetcore/CAP 的本地消息表模式

分布式事务| 使用 dotnetcore/CAP 的本地消息表模式

腾讯云助力ACChain,共享区块链3.0时代