在上篇文章中我们的重点是讲述怎样通过在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系列的主目录。