DotNetCore 3.0 助力 WPF本地化

  • 2019 年 10 月 3 日
  • 筆記

概覽

隨着我們的應用程序越來越受歡迎,我們的下一步將要開發多語言功能。方便越來越多的國家使用我們中國的應用程序,
基於 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