ABP中的本地化处理(下)
在上篇文章中我们的重点是讲述怎样通过在Domain层通过PreInitialize()配置ILocalizationConfiguration中的Sources(IList<ILocalizationSource>)集合,并且在调用L方法中配置LocalizationSourceName从而找到之前配置的ILocalizationSource对象,这一节主要来讲述ILocalizationSource中的GetString(string name)方法,通过这个方法来体会ABP中的设计思想。
还是和之前一样我们首先来看看ILocalizationSource这个接口的定义。
/// <summary>
/// A Localization Source is used to obtain localized strings.
/// </summary>
public interface ILocalizationSource
{
/// <summary>
/// Unique Name of the source.
/// </summary>
string Name { get; } /// <summary>
/// This method is called by ABP before first usage.
/// </summary>
void Initialize(ILocalizationConfiguration configuration, IIocResolver iocResolver); /// <summary>
/// Gets localized string for given name in current language.
/// Fallbacks to default language if not found in current culture.
/// </summary>
/// <param name="name">Key name</param>
/// <returns>Localized string</returns>
string GetString(string name); /// <summary>
/// Gets localized string for given name and specified culture.
/// Fallbacks to default language if not found in given culture.
/// </summary>
/// <param name="name">Key name</param>
/// <param name="culture">culture information</param>
/// <returns>Localized string</returns>
string GetString(string name, CultureInfo culture); /// <summary>
/// Gets localized string for given name in current language.
/// Returns null if not found.
/// </summary>
/// <param name="name">Key name</param>
/// <param name="tryDefaults">
/// True: Fallbacks to default language if not found in current culture.
/// </param>
/// <returns>Localized string</returns>
string GetStringOrNull(string name, bool tryDefaults = true); /// <summary>
/// Gets localized string for given name and specified culture.
/// Returns null if not found.
/// </summary>
/// <param name="name">Key name</param>
/// <param name="culture">culture information</param>
/// <param name="tryDefaults">
/// True: Fallbacks to default language if not found in current culture.
/// </param>
/// <returns>Localized string</returns>
string GetStringOrNull(string name, CultureInfo culture, bool tryDefaults = true); /// <summary>
/// Gets all strings in current language.
/// </summary>
/// <param name="includeDefaults">
/// True: Fallbacks to default language texts if not found in current culture.
/// </param>
IReadOnlyList<LocalizedString> GetAllStrings(bool includeDefaults = true); /// <summary>
/// Gets all strings in specified culture.
/// </summary>
/// <param name="culture">culture information</param>
/// <param name="includeDefaults">
/// True: Fallbacks to default language texts if not found in current culture.
/// </param>
IReadOnlyList<LocalizedString> GetAllStrings(CultureInfo culture, bool includeDefaults = true);
}
这个接口默认有四个实现:DictionaryBasedLocalizationSource、MultiTenantLocalizationSource、ResourceFileLocalizationSource、NullLocalizationSource这里我们重点来将我们项目中用到的DictionaryBasedLocalizationSource,这个我们也先来看看源码,热庵后再来做进一步的分析。
/// <summary>
/// This class is used to build a localization source
/// which works on memory based dictionaries to find strings.
/// </summary>
public class DictionaryBasedLocalizationSource : IDictionaryBasedLocalizationSource
{
/// <summary>
/// Unique Name of the source.
/// </summary>
public string Name { get; } public ILocalizationDictionaryProvider DictionaryProvider { get; } protected ILocalizationConfiguration LocalizationConfiguration { get; private set; } private ILogger _logger; /// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <param name="dictionaryProvider"></param>
public DictionaryBasedLocalizationSource(string name, ILocalizationDictionaryProvider dictionaryProvider)
{
Check.NotNullOrEmpty(name, nameof(name));
Check.NotNull(dictionaryProvider, nameof(dictionaryProvider)); Name = name;
DictionaryProvider = dictionaryProvider;
} /// <inheritdoc/>
public virtual void Initialize(ILocalizationConfiguration configuration, IIocResolver iocResolver)
{
LocalizationConfiguration = configuration; _logger = iocResolver.IsRegistered(typeof(ILoggerFactory))
? iocResolver.Resolve<ILoggerFactory>().Create(typeof(DictionaryBasedLocalizationSource))
: NullLogger.Instance; DictionaryProvider.Initialize(Name);
} /// <inheritdoc/>
public string GetString(string name)
{
return GetString(name, CultureInfo.CurrentUICulture);
} /// <inheritdoc/>
public string GetString(string name, CultureInfo culture)
{
var value = GetStringOrNull(name, culture); if (value == null)
{
return ReturnGivenNameOrThrowException(name, culture);
} return value;
} public string GetStringOrNull(string name, bool tryDefaults = true)
{
return GetStringOrNull(name, CultureInfo.CurrentUICulture, tryDefaults);
} public string GetStringOrNull(string name, CultureInfo culture, bool tryDefaults = true)
{
var cultureName = culture.Name;
var dictionaries = DictionaryProvider.Dictionaries; //Try to get from original dictionary (with country code)
ILocalizationDictionary originalDictionary;
if (dictionaries.TryGetValue(cultureName, out originalDictionary))
{
var strOriginal = originalDictionary.GetOrNull(name);
if (strOriginal != null)
{
return strOriginal.Value;
}
} if (!tryDefaults)
{
return null;
} //Try to get from same language dictionary (without country code)
if (cultureName.Contains("-")) //Example: "tr-TR" (length=5)
{
ILocalizationDictionary langDictionary;
if (dictionaries.TryGetValue(GetBaseCultureName(cultureName), out langDictionary))
{
var strLang = langDictionary.GetOrNull(name);
if (strLang != null)
{
return strLang.Value;
}
}
} //Try to get from default language
var defaultDictionary = DictionaryProvider.DefaultDictionary;
if (defaultDictionary == null)
{
return null;
} var strDefault = defaultDictionary.GetOrNull(name);
if (strDefault == null)
{
return null;
} return strDefault.Value;
} /// <inheritdoc/>
public IReadOnlyList<LocalizedString> GetAllStrings(bool includeDefaults = true)
{
return GetAllStrings(CultureInfo.CurrentUICulture, includeDefaults);
} /// <inheritdoc/>
public IReadOnlyList<LocalizedString> GetAllStrings(CultureInfo culture, bool includeDefaults = true)
{
//TODO: Can be optimized (example: if it's already default dictionary, skip overriding) var dictionaries = DictionaryProvider.Dictionaries; //Create a temp dictionary to build
var allStrings = new Dictionary<string, LocalizedString>(); if (includeDefaults)
{
//Fill all strings from default dictionary
var defaultDictionary = DictionaryProvider.DefaultDictionary;
if (defaultDictionary != null)
{
foreach (var defaultDictString in defaultDictionary.GetAllStrings())
{
allStrings[defaultDictString.Name] = defaultDictString;
}
} //Overwrite all strings from the language based on country culture
if (culture.Name.Contains("-"))
{
ILocalizationDictionary langDictionary;
if (dictionaries.TryGetValue(GetBaseCultureName(culture.Name), out langDictionary))
{
foreach (var langString in langDictionary.GetAllStrings())
{
allStrings[langString.Name] = langString;
}
}
}
} //Overwrite all strings from the original dictionary
ILocalizationDictionary originalDictionary;
if (dictionaries.TryGetValue(culture.Name, out originalDictionary))
{
foreach (var originalLangString in originalDictionary.GetAllStrings())
{
allStrings[originalLangString.Name] = originalLangString;
}
} return allStrings.Values.ToImmutableList();
} /// <summary>
/// Extends the source with given dictionary.
/// </summary>
/// <param name="dictionary">Dictionary to extend the source</param>
public virtual void Extend(ILocalizationDictionary dictionary)
{
DictionaryProvider.Extend(dictionary);
} protected virtual string ReturnGivenNameOrThrowException(string name, CultureInfo culture)
{
return LocalizationSourceHelper.ReturnGivenNameOrThrowException(
LocalizationConfiguration,
Name,
name,
culture,
_logger
);
} private static string GetBaseCultureName(string cultureName)
{
return cultureName.Contains("-")
? cultureName.Left(cultureName.IndexOf("-", StringComparison.Ordinal))
: cultureName;
}
}
在分析这个类之前,我们先来看看这个类的构造函数,分别传入了一个string类型的name和ILocalizationDictionaryProvider对象,这个name按照ABP的注释表示的是资源的名称,这个是不能够重名的,另外一个参数是ILocalizationDictionaryProvider一个对象,这个对象用于处理本地化文件是XML格式还是Json格式,每种格式都会实现这个接口,然后分别处理对应格式的数据,这个接口稍后会进行分析。
在这个类构造完成以后会调用其中的Initialize(ILocalizationConfiguration configuration, IIocResolver iocResolver)方法,用于向当前对象中传递本地化配置信息以及IIocResolver对象。并在这里面调用ILocalizationDictionaryProvider的Initialize方法。那么我们就来具体分析一下这个ILocalizationDictionaryProvider接口吧。
首先看接口定义
/// <summary>
/// Used to get localization dictionaries (<see cref="ILocalizationDictionary"/>)
/// for a <see cref="IDictionaryBasedLocalizationSource"/>.
/// </summary>
public interface ILocalizationDictionaryProvider
{
ILocalizationDictionary DefaultDictionary { get; } IDictionary<string, ILocalizationDictionary> Dictionaries { get; } void Initialize(string sourceName); void Extend(ILocalizationDictionary dictionary);
}
这个里面又定义了一个ILocalizationDictionary的接口,我们先来看看在我们的项目中添加的JsonEmbeddedFileLocalizationDictionaryProvider这个类里面的实现,这个类首先继承自一个LocalizationDictionaryProviderBase的抽象基类。
public abstract class LocalizationDictionaryProviderBase : ILocalizationDictionaryProvider
{
public string SourceName { get; private set; } public ILocalizationDictionary DefaultDictionary { get; protected set; } public IDictionary<string, ILocalizationDictionary> Dictionaries { get; private set; } protected LocalizationDictionaryProviderBase()
{
Dictionaries = new Dictionary<string, ILocalizationDictionary>();
} public virtual void Initialize(string sourceName)
{
SourceName = sourceName;
} public void Extend(ILocalizationDictionary dictionary)
{
//Add
ILocalizationDictionary existingDictionary;
if (!Dictionaries.TryGetValue(dictionary.CultureInfo.Name, out existingDictionary))
{
Dictionaries[dictionary.CultureInfo.Name] = dictionary;
return;
} //Override
var localizedStrings = dictionary.GetAllStrings();
foreach (var localizedString in localizedStrings)
{
existingDictionary[localizedString.Name] = localizedString.Value;
}
}
}
这个抽象基类里面主要定义一些常用的公共的属性以及一个公用的Extend方法。
/// <summary>
/// Provides localization dictionaries from JSON files embedded into an <see cref="Assembly"/>.
/// </summary>
public class JsonEmbeddedFileLocalizationDictionaryProvider : LocalizationDictionaryProviderBase
{
private readonly Assembly _assembly;
private readonly string _rootNamespace; /// <summary>
/// Creates a new <see cref="JsonEmbeddedFileLocalizationDictionaryProvider"/> object.
/// </summary>
/// <param name="assembly">Assembly that contains embedded json files</param>
/// <param name="rootNamespace">
/// <para>
/// Namespace of the embedded json dictionary files
/// </para>
/// <para>
/// Notice : Json folder name is different from Xml folder name.
/// </para>
/// <para>
/// You must name it like this : Json**** and Xml****; Do not name : ****Json and ****Xml
/// </para>
/// </param>
public JsonEmbeddedFileLocalizationDictionaryProvider(Assembly assembly, string rootNamespace)
{
_assembly = assembly;
_rootNamespace = rootNamespace;
} public override void Initialize(string sourceName)
{
var allCultureInfos = CultureInfo.GetCultures(CultureTypes.AllCultures);
var resourceNames = _assembly.GetManifestResourceNames().Where(resouceName =>
allCultureInfos.Any(culture => resouceName.EndsWith($"{sourceName}.json", true, null) ||
resouceName.EndsWith($"{sourceName}-{culture.Name}.json", true,
null))).ToList();
foreach (var resourceName in resourceNames)
{
if (resourceName.StartsWith(_rootNamespace))
{
using (var stream = _assembly.GetManifestResourceStream(resourceName))
{
var jsonString = Utf8Helper.ReadStringFromStream(stream); var dictionary = CreateJsonLocalizationDictionary(jsonString);
if (Dictionaries.ContainsKey(dictionary.CultureInfo.Name))
{
throw new AbpInitializationException(sourceName + " source contains more than one dictionary for the culture: " + dictionary.CultureInfo.Name);
} Dictionaries[dictionary.CultureInfo.Name] = dictionary; if (resourceName.EndsWith(sourceName + ".json"))
{
if (DefaultDictionary != null)
{
throw new AbpInitializationException("Only one default localization dictionary can be for source: " + sourceName);
} DefaultDictionary = dictionary;
}
}
}
}
} protected virtual JsonLocalizationDictionary CreateJsonLocalizationDictionary(string jsonString)
{
return JsonLocalizationDictionary.BuildFromJsonString(jsonString);
}
}
这个类的构造函数传入的是:Assembly assembly, string rootNamespace这个我们看命名就应该知道,第一个参数指的是包含嵌入式json file的程序集,第二个代表当前根命名空间。在这个类里面重点是Initialize方法,这个方法会循环遍历所有json格式的本地化的文件,然后读取每一个Json文件中定义的JsonString,然后通过JsonLocalizationDictionary类中的静态方法将JsonString反序列化为一个JsonLocalizationFile对象,我们也来看看这段代码的实现。
/// <summary>
/// This class is used to build a localization dictionary from json.
/// </summary>
/// <remarks>
/// Use static Build methods to create instance of this class.
/// </remarks>
public class JsonLocalizationDictionary : LocalizationDictionary
{
/// <summary>
/// Private constructor.
/// </summary>
/// <param name="cultureInfo">Culture of the dictionary</param>
private JsonLocalizationDictionary(CultureInfo cultureInfo)
: base(cultureInfo)
{
} /// <summary>
/// Builds an <see cref="JsonLocalizationDictionary" /> from given file.
/// </summary>
/// <param name="filePath">Path of the file</param>
public static JsonLocalizationDictionary BuildFromFile(string filePath)
{
try
{
return BuildFromJsonString(File.ReadAllText(filePath));
}
catch (Exception ex)
{
throw new AbpException("Invalid localization file format! " + filePath, ex);
}
} /// <summary>
/// Builds an <see cref="JsonLocalizationDictionary" /> from given json string.
/// </summary>
/// <param name="jsonString">Json string</param>
public static JsonLocalizationDictionary BuildFromJsonString(string jsonString)
{
JsonLocalizationFile jsonFile;
try
{
jsonFile = JsonConvert.DeserializeObject<JsonLocalizationFile>(
jsonString,
new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
catch (JsonException ex)
{
throw new AbpException("Can not parse json string. " + ex.Message);
} var cultureCode = jsonFile.Culture;
if (string.IsNullOrEmpty(cultureCode))
{
throw new AbpException("Culture is empty in language json file.");
} var dictionary = new JsonLocalizationDictionary(CultureInfo.GetCultureInfo(cultureCode));
var dublicateNames = new List<string>();
foreach (var item in jsonFile.Texts)
{
if (string.IsNullOrEmpty(item.Key))
{
throw new AbpException("The key is empty in given json string.");
} if (dictionary.Contains(item.Key))
{
dublicateNames.Add(item.Key);
} dictionary[item.Key] = item.Value.NormalizeLineEndings();
} if (dublicateNames.Count > 0)
{
throw new AbpException(
"A dictionary can not contain same key twice. There are some duplicated names: " +
dublicateNames.JoinAsString(", "));
} return dictionary;
}
}
当然这里面也会做一些类似于Key不能重复等一系列的操作,具体的实现我们来看代码。到这里为止我们已经明白了到底通过怎样的方式来获取到Json文件中的所有的信息并最终保存在JsonLocalizationDictionary,有了这个Dictionary我们就能够顺理成章的通过Key值来获取对应的value了。
最后,点击这里返回整个ABP系列的主目录。
ABP中的本地化处理(下)的更多相关文章
- ABP中的Filter(下)
接着上面的一个部分来叙述,这一篇我们来重点看ABP中的AbpUowActionFilter.AbpExceptionFilter.AbpResultFilter这三个部分也是按照之前的思路来一个个介绍 ...
- ABP中的本地化处理(上)
今天这篇文章主要来总结一下ABP中的多语言是怎么实现的,在后面我们将结合ABP中的源码和相关的实例来一步步进行说明,在介绍这个之前我们先来看看ABP的官方文档,通过这个文档我们就知道怎样在我们的系统中 ...
- ABP中的拦截器之ValidationInterceptor(下)
在上篇我分析了整个ABP中ValitationInterceptor的整个过程,就其中涉及到的Validator过程没有详细的论述,这篇文章就这个过程进行详细的论述,另外任何一个重要的特性如何应用是最 ...
- (新年快乐)ABP理论学习之本地化(2016第一篇)
返回总目录 本篇目录 应用语言 本地化资源 获取本地化文本 扩展本地化资源 最佳实践 应用语言 一个应用至少有一种UI语言,许多应用不止有一种语言.ABP为应用提供了一个灵活的本地化系统. 第一件事情 ...
- ABP架构学习系列二:ABP中配置的注册和初始化
一.手工搭建平台 1.创建项目 创建MVC5项目,手动引入Abp.Abp.Web.Abp.Web.Mvc.Abp.Web.Api 使用nuget添加Newtonsoft.Json.Castle.Cor ...
- ABP源码分析二:ABP中配置的注册和初始化
一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...
- ABP源码分析三十五:ABP中动态WebAPI原理解析
动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...
- ABP中使用Redis Cache(1)
本文将讲解如何在ABP中使用Redis Cache以及使用过程中遇到的各种问题.下面就直接讲解使用步骤,Redis环境的搭建请直接网上搜索. 使用步骤: 一.ABP环境搭建 到http://www.a ...
- ABP中动态WebAPI原理解析
ABP中动态WebAPI原理解析 动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类 ...
随机推荐
- 本地spark报:java.lang.UnsatisfiedLinkError: org.apache.hadoop.io.nativeio.NativeIO$Windows.createFileWithMode0(Ljava/lang/String;JJJI)Ljava/io/FileDescriptor;
我是在运行rdd.saveAsTextFile(fileName)的时候报的错,找了很多说法……最终是跑到hadoop/bin文件夹下删除了hadoop.dll后成功.之前某些说法甚至和这个解决方法自 ...
- 【随记】安装SQL Server 2008 R2 提示创建usersettings/microsoft.sqlserver.configuration.landingpage.properties.se
在安装SQL Server 2008 R2 提示创建usersettings/microsoft.sqlserver.configuration.landingpage.properties.se.. ...
- Spring Boot|监控-Actuator
Spring Boot 为我们提供了一个生产级特性-Actuator,包含很多实际有用的API,下面我们就一起来看看这些API. 一.Actuator 首先在程序中引入Actuator <!-- ...
- Perl深度优先迷宫算法
迷宫求解,可以用穷举法,将每个点的方向都穷举完:由于在求解过程中会遇到某一方向不可通过,此时就必须按原路返回. 想到用Perl数组来保存路径,记录每次所探索的方向,方便原路返回时得到上一步的方向,再退 ...
- Geometry and Appearances【转】
https://github.com/AnalyticalGraphicsInc/cesium/wiki/Geometry-and-Appearances Geometry and Appearanc ...
- TextBox光标定位到文本末尾
private void RichTextBox1_TextChanged(object sender, EventArgs e) { this.richTextBox1.Select(richTex ...
- 拒绝让Eclipse帮倒忙,解决其复制粘贴时把反斜杠变成双反斜杠的问题
比如,你粘贴到字符串的文本是“C:\Users\horn1\Desktop”,结果变成了“C:\\Users\\horn1\\Desktop\\”,这还好,不会带来麻烦. 但是,比如你输入的是正则表达 ...
- JS正则表达式使用
<script type="text/javascript"> function SubmitCk() { var reg = /^([a-zA-Z0-9]+[_|\_ ...
- Tosca 一不小心,我把那一排模块全关闭了,怎么打开
#写在前面, 之前用的时候,学了很多,基本都忘记了,现在再重新用,啥啥都不记得了,我还是应该事无巨细的全部记下来 红线这一排我关了好多,在哪儿打开 在这打开
- java使用json-lib库的json工具类.
import net.sf.ezmorph.object.DateMorpher;import net.sf.json.JSONArray;import net.sf.json.JSONObject; ...