zoukankan      html  css  js  c++  java
  • DotNetCore 3.0 助力 WPF本地化

    概览

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

    实现思路

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

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

    动态切换

    我们先来看实现结果

    • 第一行是我们的主程序的数据展示,用于业务中的本地化
    • 第二行是我们业务模块A的数据展示
    • 第三行是我们业务模块B的数据展示

    来看一下xaml展示

    通过ComboBox选择来切换语言

    搭建模拟业务项目

    创建一个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",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

  • 相关阅读:
    Grub 和 UEFI启动
    神舟战神插上耳机没有声音,重启又有声音..
    批处理 ------ @、ECHO OFF、ECHO ON 的使用
    linux command ------ find
    Adobe Premiere Pro CC ------ 快捷键
    分布式session一致性问题
    DNS域名解析
    CDN内容分发
    令牌桶限流算法和漏桶限流算法区别
    AOP与IOC区别
  • 原文地址:https://www.cnblogs.com/luquanmingren/p/11384104.html
Copyright © 2011-2022 走看看