上一篇博文地址:[Asp.net 5] Localization-简单易用的本地化-全球化信息
本文继续介绍asp.net 5多语言。今天重点讲的是多语言的resx文件。涉及到的工程有:Microsoft.Framework.Localization.Abstractions以及Microsoft.Framework.Localization。他们之间的类结构如下如所示:
- Abstractions包中,包含了定义在工程Microsoft.Framework.Localization.Abstractions的大部分类和接口
- Localization包中,包含了定义在工程Microsoft.Framework.Localization中的大部分类和接口
Microsoft.Framework.Localization.Abstractions
该工程定义了本地化(全球化)的基本接口,以及一些抽象类。
- LocalizedString 封装name、value等属性的结构体,用于表示本地化寻找的结果
- IStringLocalizer 本地化访问的基本接口,通过方法或者属性(通过扩展方法,使得方法和属性调用一套逻辑)获得LocalizedString结构体对象;本解决方案中,对资源文件就是该接口的 实例操作的
- IStringLocalizer<T> 本接口中未定义任何方法,是IStringLocalizer 的泛型版本
- StringLocalizer<TResourceSource>,该类是IStringLocalizer<T> 的实现类,内部分装IStringLocalizer 接口。使用了代理模式(前面我们介绍的配置文件、以及依赖注入都使用了该模式),所有方法都是内部IStringLocalizer对象的封装。该类的构造函数是IStringLocalizerFactory接口。
- IStringLocalizerFactory接口:根据不同的Type类型创建不同的IStringLocalizer对象
public struct LocalizedString { public LocalizedString([NotNull] string name, [NotNull] string value) : this(name, value, resourceNotFound: false) { } public LocalizedString([NotNull] string name, [NotNull] string value, bool resourceNotFound) { Name = name; Value = value; ResourceNotFound = resourceNotFound; } public static implicit operator string (LocalizedString localizedString) { return localizedString.Value; } public string Name { get; } public string Value { get; } public bool ResourceNotFound { get; } public override string ToString() => Value; }
public interface IStringLocalizer : IEnumerable<LocalizedString> { LocalizedString this[string name] { get; } LocalizedString this[string name, params object[] arguments] { get; } IStringLocalizer WithCulture(CultureInfo culture); }
public interface IStringLocalizer<T> : IStringLocalizer { } public class StringLocalizer<TResourceSource> : IStringLocalizer<TResourceSource> { private IStringLocalizer _localizer; public StringLocalizer([NotNull] IStringLocalizerFactory factory) { _localizer = factory.Create(typeof(TResourceSource)); } public virtual IStringLocalizer WithCulture(CultureInfo culture) => _localizer.WithCulture(culture); public virtual LocalizedString this[[NotNull] string name] => _localizer[name]; public virtual LocalizedString this[[NotNull] string name, params object[] arguments] => _localizer[name, arguments]; public virtual LocalizedString GetString([NotNull] string name) => _localizer.GetString(name); public virtual LocalizedString GetString([NotNull] string name, params object[] arguments) => _localizer.GetString(name, arguments); public IEnumerator<LocalizedString> GetEnumerator() => _localizer.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _localizer.GetEnumerator(); }
根据上面的介绍,我们可以推断该类库的使用方式如下:
public void Test() { IStringLocalizerFactory factory=new ResourceManagerStringLocalizerFactory(); IStringLocalizer localizer=new StringLocalizer<TType>(factory);
//IStringLocalizer<TType> localizer=new StringLocalizer<TType>(); //localizer=localizer.WithCulture(CultureInfo.CurrentUICulture); var localizedString=localizer.GetString(key); }
[StringLocalizer<TResource>的作用就是隐藏具体IStringLocalizer,使我们不用关心是哪个IStringLocalizer,这样做对于直接书写代码可能看不出直接意义,但是如果使用依赖注入,则可以明显的看出好处,我们可以为IStringLocalizer<TType>直接注入StringLocalizer<TType>类]
[如果对于依赖注入了解不多,可以看下篇博客:DependencyInjection项目代码分析-目录,略长]
Microsoft.Framework.Localization
- ResourceManagerStringLocalizerFactory:创建ResourceManagerStringLocalizer类资源
- ResourceManagerStringLocalizer:resx多语言文件的访问器(实际该代码并未另起灶炉,而是使用了ResourceManager类)
- ResourceManagerWithCultureStringLocalizer:ResourceManagerStringLocalizer的子类,提供多语言访问接口。该类实际上是在父类中WithCulture中创建,而该方法内实际也是一个类似于代理模式的设计
- LocalizationServiceCollectionExtensions类:该类将ResourceManagerStringLocalizerFactory注入到IStringLocalizerFactory接口,StringLocalizer<T> 注入到IStringLocalizer<T> 接口。
public class ResourceManagerStringLocalizerFactory : IStringLocalizerFactory { private readonly IApplicationEnvironment _applicationEnvironment; public ResourceManagerStringLocalizerFactory([NotNull] IApplicationEnvironment applicationEnvironment) { _applicationEnvironment = applicationEnvironment; } public IStringLocalizer Create([NotNull] Type resourceSource) { var typeInfo = resourceSource.GetTypeInfo(); var assembly = new AssemblyWrapper(typeInfo.Assembly); var baseName = typeInfo.FullName; return new ResourceManagerStringLocalizer(new ResourceManager(resourceSource), assembly, baseName); } public IStringLocalizer Create([NotNull] string baseName, [NotNull] string location) { var assembly = Assembly.Load(new AssemblyName(location ?? _applicationEnvironment.ApplicationName)); return new ResourceManagerStringLocalizer( new ResourceManager(baseName, assembly), new AssemblyWrapper(assembly), baseName); } }
public class ResourceManagerStringLocalizer : IStringLocalizer { private static readonly ConcurrentDictionary<string, IList<string>> _resourceNamesCache = new ConcurrentDictionary<string, IList<string>>(); private readonly ConcurrentDictionary<string, object> _missingManifestCache = new ConcurrentDictionary<string, object>(); private readonly ResourceManager _resourceManager; private readonly AssemblyWrapper _resourceAssemblyWrapper; private readonly string _resourceBaseName; public ResourceManagerStringLocalizer( [NotNull] ResourceManager resourceManager, [NotNull] Assembly resourceAssembly, [NotNull] string baseName) : this(resourceManager, new AssemblyWrapper(resourceAssembly), baseName) { } public ResourceManagerStringLocalizer( [NotNull] ResourceManager resourceManager, [NotNull] AssemblyWrapper resourceAssemblyWrapper, [NotNull] string baseName) { _resourceAssemblyWrapper = resourceAssemblyWrapper; _resourceManager = resourceManager; _resourceBaseName = baseName; } public virtual LocalizedString this[[NotNull] string name] { get { var value = GetStringSafely(name, null); return new LocalizedString(name, value ?? name, resourceNotFound: value == null); } } public virtual LocalizedString this[[NotNull] string name, params object[] arguments] { get { var format = GetStringSafely(name, null); var value = string.Format(format ?? name, arguments); return new LocalizedString(name, value, resourceNotFound: format == null); } } public IStringLocalizer WithCulture(CultureInfo culture) { return culture == null ? new ResourceManagerStringLocalizer(_resourceManager, _resourceAssemblyWrapper, _resourceBaseName) : new ResourceManagerWithCultureStringLocalizer(_resourceManager, _resourceAssemblyWrapper, _resourceBaseName, culture); } protected string GetStringSafely([NotNull] string name, CultureInfo culture) { var cacheKey = $"name={name}&culture={(culture ?? CultureInfo.CurrentUICulture).Name}"; if (_missingManifestCache.ContainsKey(cacheKey)) { return null; } try { return culture == null ? _resourceManager.GetString(name) : _resourceManager.GetString(name, culture); } catch (MissingManifestResourceException) { _missingManifestCache.TryAdd(cacheKey, null); return null; } } public virtual IEnumerator<LocalizedString> GetEnumerator() => GetEnumerator(CultureInfo.CurrentUICulture); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); protected IEnumerator<LocalizedString> GetEnumerator([NotNull] CultureInfo culture) { var resourceNames = GetResourceNamesFromCultureHierarchy(culture); foreach (var name in resourceNames) { var value = GetStringSafely(name, culture); yield return new LocalizedString(name, value ?? name, resourceNotFound: value == null); } } internal static void ClearResourceNamesCache() => _resourceNamesCache.Clear(); private IEnumerable<string> GetResourceNamesFromCultureHierarchy(CultureInfo startingCulture) { var currentCulture = startingCulture; var resourceNames = new HashSet<string>(); while (true) { try { var cultureResourceNames = GetResourceNamesForCulture(currentCulture); foreach (var resourceName in cultureResourceNames) { resourceNames.Add(resourceName); } } catch (MissingManifestResourceException) { } if (currentCulture == currentCulture.Parent) { // currentCulture begat currentCulture, probably time to leave break; } currentCulture = currentCulture.Parent; } return resourceNames; } private IList<string> GetResourceNamesForCulture(CultureInfo culture) { var resourceStreamName = _resourceBaseName; if (!string.IsNullOrEmpty(culture.Name)) { resourceStreamName += "." + culture.Name; } resourceStreamName += ".resources"; var cacheKey = $"assembly={_resourceAssemblyWrapper.FullName};resourceStreamName={resourceStreamName}"; var cultureResourceNames = _resourceNamesCache.GetOrAdd(cacheKey, key => { var names = new List<string>(); using (var cultureResourceStream = _resourceAssemblyWrapper.GetManifestResourceStream(key)) using (var resources = new ResourceReader(cultureResourceStream)) { foreach (DictionaryEntry entry in resources) { var resourceName = (string)entry.Key; names.Add(resourceName); } } return names; }); return cultureResourceNames; } }
public class ResourceManagerWithCultureStringLocalizer : ResourceManagerStringLocalizer { private readonly CultureInfo _culture; public ResourceManagerWithCultureStringLocalizer( [NotNull] ResourceManager resourceManager, [NotNull] Assembly assembly, [NotNull] string baseName, [NotNull] CultureInfo culture) : base(resourceManager, assembly, baseName) { _culture = culture; } public ResourceManagerWithCultureStringLocalizer( [NotNull] ResourceManager resourceManager, [NotNull] AssemblyWrapper assemblyWrapper, [NotNull] string baseName, [NotNull] CultureInfo culture) : base(resourceManager, assemblyWrapper, baseName) { _culture = culture; } public override LocalizedString this[[NotNull] string name] { get { var value = GetStringSafely(name, _culture); return new LocalizedString(name, value ?? name); } } public override LocalizedString this[[NotNull] string name, params object[] arguments] { get { var format = GetStringSafely(name, _culture); var value = string.Format(_culture, format ?? name, arguments); return new LocalizedString(name, value ?? name, resourceNotFound: format == null); } } public override IEnumerator<LocalizedString> GetEnumerator() => GetEnumerator(_culture); }
public static class LocalizationServiceCollectionExtensions { public static IServiceCollection AddLocalization([NotNull] this IServiceCollection services) { services.TryAdd(new ServiceDescriptor( typeof(IStringLocalizerFactory), typeof(ResourceManagerStringLocalizerFactory), ServiceLifetime.Singleton)); services.TryAdd(new ServiceDescriptor( typeof(IStringLocalizer<>), typeof(StringLocalizer<>), ServiceLifetime.Transient)); return services; } }
由于依赖注入的关系所以我们可以将代码简单的修改成如下:
//var services = new ServiceCollection(); //services.AddLocalization(); 系统初始化时执行过该部分代码 public string Test(string key) { var localizer=Provider.GetService<IStringLocalizer<RescoureType>>(); //localizer=localizer.WithCulture(CultureInfo.CurrentUICulture); var localizedString=localizer.GetString(key);
if(localizedString.ResourceNotFound){
reuturn null;
}
return localizedString.Value; }