DotNetCore 3.0 helps WPF localization

Posted by alwaysinit on Wed, 21 Aug 2019 09:42:38 +0200

overview

As our applications become more and more popular, our next step is to develop multilingual functionality. It is convenient for more and more countries to use our Chinese applications.
Based on WPF localization, we often use system resource files, but dynamic switching localization is more troublesome.
Is there a way that can not only be applied to the resource files of the system, but also be convenient and fast to switch localization?

Ideas for Realization

Now we are going to implement a multilingual function based on the modularization of DotNetCore version 3.0 and WPF desktop applications.
Dynamic switching multilingual thinking:

  • Add resource files for all modules to the dictionary collection.
  • Bind the key in the resource file to the front desk.
  • Use the key in the changed language file by notifying the Current Culture multilingual change.
  • Path is output by binding Binding splicing.

Dynamic handover

Let's look at the implementation first.

The first line is the data presentation of our main program for localization in the business.
The second line is the data presentation of our business module A.
The third line is the data presentation of our business module B.

Take a look at the xaml presentation

Building Simulated Business Projects

Create a WPF App(.NET Core) application

After creation, we need to introduce business A module, business B module and business help module.

PS: According to their own business needs to complete the construction of the project. This tutorial is fully suited to multilingual functions.

Use. resx resource files

Add Strings folder to each module to contain language files from different countries and regions.

Multilingualism can be consulted: https://github.com/UnRunDeaD/WPF---Localization/blob/master/ComboListLanguages.txt


Resource files can be placed in any module, such as business module A, main program, underlying business, control toolset, etc.

Create resource files for each business module

Strings folder can be named arbitrarily
SR resource files can be named arbitrarily

Help class

Encapsulation to the bottom for each module to call

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

Front End Binding

//Reference business module
xmlns:ext="clr-namespace:WpfUtil.Extension;assembly=WpfUtil"
// Quote the name of the folder you just named
xmlns:resx="clr-namespace:ModuleA.Strings"
// Each module uses help classes to classify the resource classes of the current module.
// Loaded into the resource management collection to allocate each key value
// Refer to the resource file name you just named - > SR
ext:Translation.ResourceManager="{x:Static resx:SR.ResourceManager}"

display text

//Read the key values in the resource file
<Label Content="{ext:Loc Test}" FontSize="21" />

Background implementation

According to business needs, we can not apply static text display on the interface, usually through the background code to complete, for the use of code-behind variables, can also be applied to the resource dictionary.
For example, simulation implementation in amateur module code segment

// SR is the resource file class of the current business module, which manages the resource string of the current module.
// Select localization based on different `Current Culture'.
Message = string.Format(SR.ResourceManager.GetString("Message",TranslationSource.Instance.CurrentCulture),System.DateTime.Now);

PS: Welcome your generous advice, there are shortcomings, please point out! If you have any questions, please point out that you like it and support it.

Download address

https://github.com/androllen/WpfNetCoreLocalization

Related links

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

Topics: C# github