欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

DotNetCore 3.0 助力 WPF本地化

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

概览

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

实现思路

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

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

动态切换

我们先来看实现结果
DotNetCore 3.0 助力 WPF本地化
第一行是我们的主程序的数据展示,用于业务中的本地化
第二行是我们业务模块a的数据展示
第三行是我们业务模块b的数据展示

来看一下xaml展示
DotNetCore 3.0 助力 WPF本地化

搭建模拟业务项目

创建一个wpf app(.net core)应用程序
DotNetCore 3.0 助力 WPF本地化

创建完成后,我们需要引入业务a模块及业务b模块和业务帮助模块
DotNetCore 3.0 助力 WPF本地化
ps:根据自己的业务需要来完成项目的搭建。本教程完全适配多语言功能。

使用.resx资源文件

在各个模块里添加strings 文件夹用来包含 各个国家和地区的语言文件。
DotNetCore 3.0 助力 WPF本地化

多语言可以参考:https://github.com/unrundead/wpf---localization/blob/master/combolistlanguages.txt

DotNetCore 3.0 助力 WPF本地化
资源文件可以放在任意模块内,比如业务模块a ,主程序,底层业务,控件工具集等

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

strings文件夹可以任意命名
sr资源文件可以任意命名
DotNetCore 3.0 助力 WPF本地化

帮助类

封装到底层供各个模块调用

    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);
        }
    }

前台绑定

DotNetCore 3.0 助力 WPF本地化

//引用业务模块
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