本地化就是根据不同地区语言显示不同的文字。
本文环境:vs2022 +.net 6.0
新的本地化方式
使用资源字典,然后动态引用资源字典,以下以UI界面汉化为例:
新建一个文件夹 Language
新建2个资源字典。
DefaultLanguage.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <sys:String x:Key="OK"> OK </sys:String> <sys:String x:Key="Cancel"> Cancel </sys:String> </ResourceDictionary>
zh-CN.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <sys:String x:Key="OK"> 确定 </sys:String> <sys:String x:Key="Cancel"> 取消 </sys:String> </ResourceDictionary>
App.xaml 引入路径
<Application x:Class="WpfApplication1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Language\DefaultLanguage.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>
App.cs代码:
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Windows; using System.Globalization; namespace WpfApplication1 { /// <summary> /// App.xaml 的交互逻辑 /// </summary> public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); LoadLanguage(); } private void LoadLanguage() { CultureInfo currentCultureInfo = CultureInfo.CurrentCulture; ResourceDictionary langRd = null; try { langRd = Application.LoadComponent(new Uri(string.Format("{0}{1}.xaml", "Language\\", currentCultureInfo.Name), UriKind.Relative)) as ResourceDictionary; } catch { } if (langRd != null) { if (this.Resources.MergedDictionaries.Count > 0) { this.Resources.MergedDictionaries.Clear(); } this.Resources.MergedDictionaries.Add(langRd); } } } }
页面使用:
<Button Content="{DynamicResource OK}"/> <Button Content="{DynamicResource Cancel}"/>
传统的本地化方式(包括wpf和winform)
为了构建一个轻量级的资源管理框架以满足简单的本地化(Localization)的需求,我试图直接对现有的Resource编程模型进行扩展。虽然最终没能满足我们的需求,但是这两天也算对.NET如何进行资源的存取进行了深入的学习,所以将我对此的认识通过博文的方式与诸位分享。在本篇文章中,我会通过自定义ResourceManager让资源的存储形式不仅仅局限于.ResX文件,你可以根据需要实现任意的存储方式,比如结构化的XML、数据库表,甚至是通过远程访问获取资源
一、从添加资源文件说起
说起资源,你首先想到的肯定是通过VS添加的扩展名为.resx的资源文件。在这个资源文件中,你不但可以添加单纯的文本资源条目,也可以添加图片、图标、文本文件以及其它类型文件。 不但如此,当你在.resx文件中定义任意类型资源条目的时候,默认定义的代码生成器会为你生成对应的托管代码,使你可以采用强类型编程的方式获取某个条目。
比如说,如果你在一个名称为Resources.resx的资源文件中定义了如上图所示的两个字符串资源条目,默认的代码生成器或为你生成如下的代码。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Demo.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } internal static string Greeting4Chris { get { return ResourceManager.GetString("Greeting4Chris", resourceCulture); } } internal static string Greeting4NewYear { get { return ResourceManager.GetString("Greeting4NewYear", resourceCulture); } } }
那么你就可以通过生成的这个Resources类(和资源文件同名)的对应的静态只读属性获取对应的值。
1: var greeting4Chris = Resources.Greeting4Chris; 2: var greeting4NewYear = Resources.Greeting4NewYear;
从通过代码生成器生成出来的Resources代码,我们可以看出Greeting4Chris和Greeting4NewYear这两个属性的实现是直接通过一个类型为ResourceManager对象的GetString方法获取的。那么ResourceManager在背后是通过怎样的机制进行资源文件的读取的呢?
二、ResourceManager、ResourceSet、ResourceReader与ResourceWriter
ResourceManager应该是.NET资源编程模型的核心,也可以说是整个资源编程模型的外观类(Facade Class),它提供资源条目提取的API。ResourceManager定义在System.Resources命名空间下,我们不防先来看看ResourceManager的定义。
1: public class ResourceManager 2: { 3: public ResourceManager(Type resourceSource); 4: public ResourceManager(string baseName, Assembly assembly); 5: public ResourceManager(string baseName, Assembly assembly, Type usingResourceSet); 6: 7: public virtual object GetObject(string name); 8: public virtual object GetObject(string name, CultureInfo culture); 9: public virtual string GetString(string name); 10: public virtual string GetString(string name, CultureInfo culture); 11: 12: public virtual ResourceSet GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents); 13: protected virtual ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents); 14: //Others... 15: }
虽然我们将相应的条目定义在.resx资源文件中(该文件实际上就是一个XML),但是该文件在编译的时候会变成.resources文件(二进制文件)被内嵌到程序集中,所以ResourceManager操作的实际上是内嵌在某个程序集中的.resources文件,这也是为什么在构造函数中需要指定Assembly的原因。构造函数的另一个参数BaseName表示不包括扩展名和Culture Code的.resources文件名,比如说资源文件名为Foo.en-US.resoures对应的BaseName就是Foo。
对于字符串类型的资源条目,通过GetString方法获取,其他类型的文件则通过GetObject获取。而ResourceManager的核心实际上是一个叫做GetResourceSet的方法,方法将所有的资源条目读取出来保存到一个类型为ResourceSet的对象中(该方法最终会调用受保护的方法InternalGetResourceSet)。而ResourceSet在整个资源体系中是一个重要的对象,它充当ResourceManager和物理存储的中介,下面是ResourceSet的定义。
1: public class ResourceSet : IDisposable, IEnumerable 2: { 3: public ResourceSet(Stream stream); 4: public ResourceSet(IResourceReader reader); 5: public ResourceSet(string fileName); 6: 7: public virtual Type GetDefaultReader(); 8: public virtual Type GetDefaultWriter(); 9: 10: public virtual object GetObject(string name); 11: public virtual object GetObject(string name, bool ignoreCase); 12: public virtual string GetString(string name); 13: public virtual string GetString(string name, bool ignoreCase); 14: 15: IEnumerator IEnumerable.GetEnumerator(); 16: public virtual IDictionaryEnumerator GetEnumerator(); 17: public void Dispose(); 18: //Others... 19: }
以持久化文件方式存储的资源最终需要加载到ResourceSet对象中,肯定需要IO操作,所以ResourceSet构造函数中参数分别是Stream、文件名和一个IResourceReader的对象。GetObject和GetString方法,不用多说你也知道是用于某个命名资源条目。由于资源条目实际上就是简单Key-Value对,所以ResourceSet仅仅需要为ResourceManager提供针对每个资源条目的迭代功能,所以ResourceSet的核心应该是返回类型为IDictionaryEnumerator虚方法GetEmunerator方法。
而ResourceSet得两个GetDefaultReader和GetDefaultWriter方法则涉及到另外两个重要的对象ResourceReader和ResourceWriter,故名思义它们分别负责资源的读取和写入。在System.Resources命名空间下,它们各自具有相应的接口:IResourceReader和IResourceWriter,定义如下:
1: public interface IResourceReader : IEnumerable, IDisposable 2: { 3: void Close(); 4: IDictionaryEnumerator GetEnumerator(); 5: } 1: public interface IResourceWriter : IDisposable 2: { 3: void AddResource(string name, object value); 4: void AddResource(string name, string value); 5: void AddResource(string name, byte[] value); 6: void Close(); 7: void Generate(); 8: }
到这里我们介绍了资源体系下四个重要的对象ResourceManager、ResourceSet、ResourceReader与ResourceWriter,至于他们是如何相互协作以实现对资源的读取和写入的,相信下面会给你答案。
三、自定义BinaryResourceManager管理单独二机制资源文件
我们说过上述的ResourceManager仅仅提供对内嵌于某个程序集中的.resources文件的操作,如果我们直接将资源定义在一个独立的.resources文件、.resx文件甚至是自定义结构的XML文件呢?在这种情况下,我们可通过自定义ResourceManager的方式来解决这个问题。为此我定义了如下一个抽象类FileResourceManager作为基于文件的ResourceManager的基类。
1: public abstract class FileResourceManager: ResourceManager 2: { 3: private string baseName; 4: public string Directory { get; private set; } 5: public string Extension { get; private set; } 6: 7: public override string BaseName 8: { 9: get{ return baseName;} 10: } 11: 12: public FileResourceManager(string directory, string baseName, string extension) 13: { 14: this.Directory = directory; 15: this.baseName = baseName; 16: this.Extension = extension; 17: } 18: 19: protected override string GetResourceFileName(CultureInfo culture) 20: { 21: string fileName = string.Format("{0}.{1}.{2}", this.baseName, culture, this.Extension.TrimStart('.')); 22: string path = Path.Combine(this.Directory, fileName); 23: if (File.Exists(path)) 24: { 25: return path; 26: } 27: return Path.Combine(this.Directory, string.Format("{0}.{1}", baseName, this.Extension.TrimStart('.'))); 28: } 29: }
属性Directory、BaseName和Extension分别表示资源文件所在的目录、不包括Culture Code和扩展名的文件名以及扩展名。FileResourceManager集成自ResourceManager类,并重写了GetResourceFileName方法用于获取基于某种Culture的资源文件路径。
现在我们定义如下一个BinaryResourceManager用于操作单独存在的.resources文件。我自需要重写InternalGetResourceSet,返回的是基于.resources文件名创建的ResourceSet对象。
1: public class BinaryResourceManager : FileResourceManager 2: { 3: public BinaryResourceManager(string directory, string baseName) 4: : base(directory, baseName, ".resources") 5: {} 6: 7: protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents) 8: { 9: return new ResourceSet(this.GetResourceFileName(culture)); 10: } 11: }
现在我们来看看如何使用我们创建的BinaryResourceManager。由于它直接操作ResourceSet来维护资源条目列表,当我们通过指定资源文件名创建ResourceSet的时候,系统会创建一个类型为System.Resources.ResourceReader的对象来读取二进制的.resources文件并将内容写入ResourceSet对象。而.resources文件具有默认的ResourceWrtier,即System.Resources.ResourceWriter。
为了让我们的Demo能够适用于后续的自定义ResourceManager,我写了一些辅助方法,首先是预先创建资源文件的方法PrepareFiles方法。通过传入的BaseName和扩展名,我会创建三个资源文件:<BaseName>.<Extension>、<BaseName>.en-US.<Extension>和<BaseName>.zh-CN.<Extension>,第一个代码语言文化中性,后者则基于美国英语和简体中文。
1: static void PrepareFiles(string baseName, string extension) 2: { 3: var fileNames = new string[]{ 4: baseName + "." + extension, 5: baseName + ".en-US." + extension, 6: baseName + ".zh-CN." + extension }; 7: 8: Array.ForEach(fileNames, fileName =>{ 9: if (!File.Exists(fileName)) File.Create(fileName).Dispose();}); 10: }
然后是用于资源写入操作的AddResource方法,该方法两个参数createWriter和culture表示创建IResourceWriter的委托和对应的语言文化。
1: static void AddResource(Func<IResourceWriter> createWriter, CultureInfo culture) 2: { 3: using (IResourceWriter resourceWriter = createWriter()) 4: { 5: if (culture.Name.StartsWith("en")) 6: { 7: resourceWriter.AddResource("Greeting4Chris", "Merry Christmas!"); 8: resourceWriter.AddResource("Greeting4NewYear", "Happy Chinese New Year!"); 9: } 10: if (culture.Name.StartsWith("zh")) 11: { 12: resourceWriter.AddResource("Greeting4Chris", "圣诞快乐!"); 13: resourceWriter.AddResource("Greeting4NewYear", "新年快乐!"); 14: } 15: resourceWriter.Generate(); 16: } 17: }
最后是用于资源读取和输出的DisplayResource方法,该方法通过指定的ResourceManager读取当前需要文化资源并输出。而我指定了三种不同的语言文化环境:en-US、zh-CN和ja-JP。
1: static void DisplayResource(ResourceManager resourceManager) 2: { 3: Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); 4: Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName); 5: Console.WriteLine("\t"+resourceManager.GetString("Greeting4Chris")); 6: Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n"); 7: 8: Thread.CurrentThread.CurrentUICulture = new CultureInfo("zh-CN"); 9: Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName); 10: Console.WriteLine("\t" + resourceManager.GetString("Greeting4Chris")); 11: Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n"); 12: 13: Thread.CurrentThread.CurrentUICulture = new CultureInfo("ja-JP"); 14: Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName); 15: Console.WriteLine("\t" + resourceManager.GetString("Greeting4Chris")); 16: Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n"); 17: }
最后我们的程序是这样的:最后我们的程序是这样的:
1: PrepareFiles("GreetingMessages", "resources"); 2: 3: AddResource(() => new ResourceWriter("GreetingMessages.resources"), new CultureInfo("en-US")); 4: AddResource(() => new ResourceWriter("GreetingMessages.en-US.resources"), new CultureInfo("en-US")); 5: AddResource(() => new ResourceWriter("GreetingMessages.zh-CN.resources"), new CultureInfo("zh-CN")); 6: 7: DisplayResource(new BinaryResourceManager("", "GreetingMessages")); 最终的输出为: 1: English (United States) 2: Merry Christmas! 3: Happy Chinese New Year! 4: 5: Chinese (Simplified, PRC) 6: 圣诞快乐! 7: 新年快乐! 8: 9: Japanese (Japan) 10: Merry Christmas! 11: Happy Chinese New Year!
标签:string,ResourceManager,本地化,System,应用程序,WPF,public,Resources,ResourceSet From: https://www.cnblogs.com/cdaniu/p/16778107.html