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

原文地址:https://www.cnblogs.com/luquanmingren/p/11384104.html

时间: 2024-10-12 16:16:41

DotNetCore 3.0 助力 WPF本地化的相关文章

DotNetCore 3.0 助力 WPF 开发

DotNetCore Is AnyWhere. 前言 Visual Studio 2019 已经正式发布了,DotNetCore 3.0 的正式版也指日可待.在之前的版本中,作为一名基于微软生态的传统 WPF 程序员看着隔壁同学在开发 DotNetCore 网站时用着各种特性好生羡慕,想着巨硬啥时候能让客户端开发者也能尝尝甜头. 那么,现在是时候可以尝试一下了. 需要说明的一点的是,DotNetCore 3.0 虽然跨平台,但是基于此的 WPF 却是针对 Windows 特定平台的实现,并不能跨

使用 MSIX 打包 DotNetCore 3.0 客户端程序

原文:使用 MSIX 打包 DotNetCore 3.0 客户端程序 如何你希望你的 WPF 程序能够以 Windows 的保护机制保护起来,不被轻易反编译的话,那么这篇文章应该能帮到你. 介绍 MSIX 是微软于去年的 Windows 开发者日峰会 上推出的全新应用打包解决方案.其目的是取代旧式的软件打包方式,可用于 Win32.WindowsForm . WPF 和 UWP 等应用程序,该打包方式将支持 Windows7 和 Windows8.x.并且让我们的程序不会轻易反编译. 本文,我们

[WPF]本地化入门

1. 前言 WPF的本地化是个很常见的功能,我做过的WPF程序大部分都实现了本地化(不管最终有没有用到).通常本地化有以下几点需求: 在程序启动时根据CultureInfo.CurrentUICulture或配置项显示对应语言的UI. 在程序运行时可以动态切换UI语言(无需重启程序). 制作对应不同语言的安装包. 通过下载语言包实现多种语言的本地化. 其中只有第一点是必要的. 第二点最好也可以实现,很多时候切换语言只为了看看某个专业术语在英语中的原文是什么,或者临时打印个英文报表,平时使用还是用

在 DotNetCore 3.0 程序中使用通用协议方式启动文件关联应用

原文:在 DotNetCore 3.0 程序中使用通用协议方式启动文件关联应用 问题描述 在传统的基于 .NET Framework 的 WPF 程序中,我们可以使用如下代码段启动相关的默认应用: # 启动默认文本编辑器打开 helloworld.txt Process.Start("helloworld.txt"); # 启动默认浏览器打开 https://hippiezhou.fun/ Process.Start("https://hippiezhou.fun/"

解决 DotNetCore.1.0.1-VS2015Tools.Preview2.0.3.exe 在VS2015 Update3 安装失败的问题

今天抽空升级VS2015 Update3. 在安装DotNetCore.1.0.1-VS2015Tools.Preview2.0.3.exe 时报错了,看了错误日志 显示: 看到我标红的两个地方,那么找了半天网上的答案都不对,可能跟我这个情况不一样吧.最后我就试了一下打开Cmd 然后输入tNetCore.1.0.1-VS2015Tools.Preview2.0.3.exe 的绝对路径,加上这两个参数之后成功安装.如下所示: 两个参数就是SKIP_VSU_CHECK=1 WixStdBALangu

DotNetCore.1.0.1-VS2015Tools.Preview2.0.3 相关问题及解决办法

本月16号,MS发布了 .NET Core 1.1.作为一个用贯MS产品的小盆友,我第一时间就把相关的安装包下载下来了,然后果断安装(入坑). 我猜你来看这篇博客可能遇到了和我一样的问题. 问题0:正确的安装顺需 正确的顺序在MS的dotnet Core官网上可以找到,请根据自己的VS版本对号入座. 如果你觉得这个太长或者存在疑问,简短的版本是: 1.VS2015 1. 检查VS2015 是否已经安装了Update3.3:打开VS2015,然后点击[帮助]-[关于Microsoft Visual

无法安装 DotNetCore.1.0.0-VS2015Tools.Preview2解决方法

安装 DotNetCore.1.0.0-VS2015Tools.Preview2,已经安装vs2015update3,还是提示检测到 Visual Studio 2015 Update 3没有完全安装,请修复Visual Studio 2015 Update 3 解决方法:控制台下输入命令 DotNetCore.1.0.0-VS2015Tools.Preview2.exe SKIP_VSU_CHECK=1

[转] 安装DotNetCore.1.0.1-VS2015Tools.Preview2.0.2出现0x80072f8a未指定的错误

原文地址:安装DotNetCore.1.0.1-VS2015Tools.Preview2.0.2出现0x80072f8a未指定的错误 最近DotNetCore更新到了1.0.1,Azure tools也更新到了2.9.5,尝试更新时发现,DotNetCore更新失败,提示:0x80072f8a未指定的错误,而Azure Tools中也包含了DotNetCore的更新,0x80072f8a问题,导致两个软件都不能成功地完成更新. 研究安装的错误日志后才发现,原来使因为证书过期导致的无法下载微软在线

从0 开始 WPF MVVM 企业级框架实现与说明 ---- 第七讲 WPF 系统UI结构说明与AvalonDock的使用

说到WPF UI, 现在网上到处都有现成的UI, 我觉得还是AvalonDock算是比较适合企业级系统点,一般向ModernUI之类的只能做做小的App还凑合这用. 这边我分享一个DLL, AvalonDock.dll 访问密码 2f90 , 你们可以去下载,后面我们的demo中就是用这样一种UI结构. 其实对于一个系统的设计,我们要考虑到整体的业务逻辑,数据结构,业务需求与拓展等各方面,我这主要还是分模块一步步慢慢介绍下去,没有具体的项目,我就分模块去慢慢介绍. 这里就说Avalondock的