0. Introduction
If you develop the need to go to the world, then certainly need to do different localization for each user, it is possible that your customers in Japan, need to use Japanese as the display text, it is possible that your customers in the United States, need to use English as the display text. If you're still writing dead error messages, or describing information, you won't be able to adapt to multiple languages.
The ABP framework itself provides a set of multi-lingual mechanisms to help us localize, and the basic idea is that the ABP itself maintains a set of key-value pairs. Only the text information that is presented to the customer is populated with a language Key, and when the user logs on to the system, it gets the current user's regional culture information for text rendering.
0.1 How to use
Let's start by looking at how to define a multilingual resource and use it. First, the ABP itself supports three types of localized resource sources, the first is the XML file, the second is the JSON file, the third is the embedded resource file, if none of the three are sufficient for your needs, you can implement the ILocalizationSource?
interface yourself to return the multilingual resource.
Tips:
The ABP Zero module provides the ability for database persistence to store multilingual resources.
0.1.1 defining the languages supported by the application
If you need to add support for your application in different languages, you will have to add a language to the pre-load method of any of your modules to configure it:
Configuration.Localization.Languages.Add(new LanguageInfo("en", "English", "famfamfam-flag-england", true));Configuration.Localization.Languages.Add(new LanguageInfo("tr", "Türk?e", "famfamfam-flag-tr"));
For example, the above code will allow our program to have multilingual processing capabilities for both English and Turkish.
Here is famfamfam-flag-england
famfamfam-flag-tr
a CSS type, which is the small flag icon encapsulated by the ABP for the front-end display.
0.1.2 Creating Multilingual resource files
With the language, ABP also needs you to provide standard multilingual resource files, here we take the XML resource file as an example, the file name is called abp-zh-hans.xml , the path is Abp\Localization\Sources\AbpXmlSource
.
<?xml version= "1.0" encoding= "Utf-8"? ><localizationdictionary culture= "Zh-hans" > <texts> <text Name= "SMTPHost" >SMTP host </text> <text name= "Smtpport" >smtp Port </text> <text name= "Username" > User name </text> <text name= "Password" > Password </text> <text name= "DomainName" > Domain </text> &L T;text name= "UseSSL" > Use ssl</text> <text name= "useDefaultCredentials" > Use default authentication </text> <text NA Me= "defaultfromsenderemailaddress" > Default sender email address </text> <text name= "Defaultfromsenderdisplayname" > Default sender name </text> <text name= "defaultlanguage" > Preset language </text> <text name= "Receivenotifications" > Receive notifications </text> <text name= "Currentuserdidnotlogintotheapplication" > The current user is not logged into the system! </text> <text name= "TimeZone" > Time zone </text> <text name= "allofthesepermissionsmustbegranted" > You do not have permission to do this, you need the following permissions: {0}</text> <text name= "AtleastoneofthesepermisSionsmustbegranted "> You do not have permission to do this, you need at least one of the following permissions: {0}</text> <text name=" MainMenu "> main Menu </text> </texts></localizationDictionary>
Inside each file, a node is <localizationDictionary culture="zh-Hans">
used to indicate which area the current file is for, while inside it is the form of a key-value pair, in which the <texts>
content of name is the key to the multilingual text item, and the real value inside the tag.
Open an XML resource file for a Russian-speaking country named Abp-ru.xml.
<?xml version= "1.0" encoding= "Utf-8"? ><localizationdictionary culture= "ru" > <texts> <text name= "SMTPHost" >SMTPсервер</text> <text name= "Smtpport" >SMTPпорт</text> <text name= "Username" &G t;имяпользователя</text> <text name= "Password" >Пароль</text> <text name= "DomainName" >домен </text> <text name= "UseSSL" >ИспользоватьSSL</text> <text name= "useDefaultCredentials" >испо льзоватьучетныеданныепоумолчанию</text> <text name= "Defaultfromsenderemailaddress" >электронныйадресо тправителяпоумолчанию</text> <text name= "Defaultfromsenderdisplayname" >Имяотправителяпоумолчанию< /text> <text name= "defaultlanguage" >Языкпоумолчанию</text> <text name= "Receivenotifications" > получатьуведомления</text> <text name= "Currentuserdidnotlogintotheapplication" >текущийпользовательне Вошёлвприложение!</text> </texts></localizationDictionary>
You can see that the Key value is the same, except that its <text>
internal values are different depending on the value of the region's country.
Next from the file name we can see the need to use the XML resource file for the file naming format will have some requirements, or the ABP comes with a resource file as an example, you can see that they are basically {SourceName}-{CultureInfo} . XML is composed of this.
0.1.3 registering a localized XML resource
So if we need to register the previous two XML resources into the ABP framework, we need to execute the registration at the preloaded module with the following code, and need to right-click the XML file and change its build operation to be an inline resource .
Configuration.Localization.Sources.Add( new DictionaryBasedLocalizationSource( // 本地化资源名称 AbpConsts.LocalizationSourceName, // 数据源提供者,这里使用的是 XML ,除了 XML 提供者,还有 JSON 等 new XmlEmbeddedFileLocalizationDictionaryProvider( typeof(AbpKernelModule).GetAssembly(), "Abp.Localization.Sources.AbpXmlSource" )));
0.1.4 Getting multilingual text
If you need to get the specific display text for the specified Key somewhere, just inject ILocalizationManager
it, and you GetString()
can get a specific value by its method. If you need to get localized resources where you cannot use dependency injection, you can use LocalizationHelper
static classes to manipulate them.
var @string = _localizationManager.GetString("Abp", "MainMenu");
It defaults to the Thread.CurrentThread.CurrentUICulture
current region information obtained from it to obtain the display value corresponding to a Key, and the current region information is provided by a series of Abp injections, which are set in the RequestCultureProviders
following order.
- Querystringrequestcultureprovider (ASP. NET Core is provided by default): The default provider initializes the value by using the
culture&ui-culture
provided regional culture information for QueryString, for example: culture=es-MX&ui-culture=es-MX
.
- Abpuserrequestcultureprovider (ABP provides): The provider reads the current user's
IAbpSession
information, and obtains the ISettingManager
user's configured "Abp.Localization.DefaultLanguageName"
attributes from it as the default regional culture information.
- Abplocalizationheaderrequestcultureprovider (ABP provided): used in each request header . The Aspnetcore.culture value serves as the current regional cultural information, for example
c=en|uic=en-US
.
- Cookierequestcultureprovider (ASP. NET Core provides): Use the Cookie in each request for the Key . Aspnetcore.culture value as the current regional cultural information.
- Abpdefaultrequestcultureprovider (ABP provided): If none of these providers have been assigned a value for the culture of the current region, the
ISettingMananger
default value is obtained from it Abp.Localization.DefaultLanguageName
.
- Acceptlanguageheaderrequestcultureprovider (ASP. NET Core is provided by default): The provider will eventually use the accept-language header passed by the user each time it is requested as the current regional culture information.
Tips:
The provider injected by the ABP here is sequential, injecting so many providers in order to finalize the current user's regional culture information in order to display the corresponding language text.
1. Start Process 1.1 Start flowchart
1.2 Code Flow
According to the use of the method, we can learn that to configure the multi-language ABP, you must wait until the IAbpStartupConfiguration
initialization is complete. That is AbpBootstrapper
, in the Initialize()
method:
public virtual void Initialize(){ // ... 其他代码 // 注入 IAbpStartupConfiguration 配置与本地化资源配置 IocManager.IocContainer.Install(new AbpCoreInstaller()); // ... 其他代码 // 初始化 AbpStartupConfiguration 类型 IocManager.Resolve<AbpStartupConfiguration>().Initialize(); // ... 其他代码}
The configuration class contains all the language and multilingual resource information that the user has configured, and after being successfully injected into the Ioc container, the ABP begins using the localization Explorer to initialize the multilingual data.
public override void PostInitialize(){ // 注册缺少的组件,防止遗漏注册组件 RegisterMissingComponents(); IocManager.Resolve<SettingDefinitionManager>().Initialize(); IocManager.Resolve<FeatureManager>().Initialize(); IocManager.Resolve<PermissionManager>().Initialize(); // 重点在这里,这个 PostInitialize 方法是存放在核心模块当中的,在这里调用了本地化资源管理器的初始化方法 IocManager.Resolve<LocalizationManager>().Initialize(); IocManager.Resolve<NotificationDefinitionManager>().Initialize(); IocManager.Resolve<NavigationManager>().Initialize(); if (Configuration.BackgroundJobs.IsJobExecutionEnabled) { var workerManager = IocManager.Resolve<IBackgroundWorkerManager>(); workerManager.Start(); workerManager.Add(IocManager.Resolve<IBackgroundJobManager>()); }}
Specific LocalizationManager
and internal implementations we'll tell you in detail in the next section of code analysis.
These actions are just some of the steps you need to take when injecting the ABP framework, and if you want to enable multiple languages, you need to change the state to True in the ASP. NET Core Program's Startup
class to Configure()
inject the UseAbpRequestLocalization
regional culture-aware middleware into the program.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory){ app.UseAbp(options => { options.UseAbpRequestLocalization = false; //disable automatic adding of request localization }); //...authentication middleware(s) app.UseAbpRequestLocalization(); //manually add request localization //...other middlewares app.UseMvc(routes => { //... });}
In fact, the above-mentioned UseAbpRequestLocalization()
Requestprovider have been injected into MVC in sequence.
2. Code Analysis
The ABP framework is stored in the Localization folder of the ABP library for both the type and method definitions associated with the localization process. The relationship is still relatively complex, so let's start with the core ABP library for multi-lingual processing.
2.1 Multi-language module configuration
All the information the ABP needs to use is the user's own boot module PreInitialize()
, which is ILocalizationConfiguration
configured by injection. In other words, in the ILocalizationConfiguration
internal, mainly contains the language, and the multilingual resource provider two key information.
public interface ILocalizationConfiguration{ // 当前应用程序可配置的语言列表 IList<LanguageInfo> Languages { get; } // 本地化资源列表 ILocalizationSourceList Sources { get; } // 是否启用多语言(本地化) 系统 bool IsEnabled { get; set; } // 以下四个布尔类型的参数主要用于确定当没有找到多语言文本时的处理逻辑,默认都为 True bool ReturnGivenTextIfNotFound { get; set; } bool WrapGivenTextIfNotFound { get; set; } bool HumanizeTextIfNotFound { get; set; } bool LogWarnMessageIfNotFound { get; set; }}
2.2 Language information
Which languages the current application can support depends on which languages are assigned to the multi-language Module configuration object when the user is preloaded. Through section 0.1.1 We see that users can add them directly to the Languages property by initializing a new LanguageInfo
object.
public class languageinfo{//<summary>/////culture code Name//////should be a valid area culture code name, more can be CultureInfo static class to get all the culture code. For example: "En-us" is applicable in North America and "TR-TR" applies to Turkey. </summary> public string Name {get; set;} <summary>///The language name that the language should display by default. For example: English should be displayed as "Chinese", "Zh-hans" should be displayed as "English"///</summary> public string DisplayName {get; set;} <summary>////The icon CSS class name for display, optional parameter////</summary> public string icon {get; set;} <summary>//is the default language///</summary> public bool IsDefault {get; set;} <summary>//Whether the language is disabled///</summary> public bool isdisabled {get; set;} <summary>///The presentation of languages is left-to-right or from right-to-left//</summary> public bool Isrighttoleft {get {try {return Cultureinfo.getcultureinfo (Name). TextInfo?. Isrighttoleft?? False } catch { return false; }}} public Languageinfo (string name, string displayName, string icon = NULL, bool IsDefault = FALSE, bool I Sdisabled = False) {name = name; DisplayName = DisplayName; icon = icon; IsDefault = IsDefault; isdisabled = isdisabled; }}
The definition of language is quite simple, the main parameter is the language of the regional culture code and the name of the display , the rest can be optional parameters.
Tips:
The regional culture code supported by the current system can be obtained by execution CultureInfo.GetCultures(CultureTypes.AllCultures);
.
2.3 Language Manager
The ABP also provides a manager for the language, which is called a ILanguageManager
simple, two-way definition.
public interface ILanguageManager{ // 获得当前语言 LanguageInfo CurrentLanguage { get; } // 获得所有语言 IReadOnlyList<LanguageInfo> GetLanguages();}
Implementation is not complex, its internal implementation is to take from a ILanguageProvider
certain language data.
private readonly ILanguageProvider _languageProvider;public IReadOnlyList<LanguageInfo> GetLanguages(){ return _languageProvider.GetLanguages();}// 获取当前语言,其实就是获取的 CultureInfo.CurrentUICulture.Name 的信息,然后去查询语言集合。private LanguageInfo GetCurrentLanguage(){ var languages = _languageProvider.GetLanguages(); // ... 省略了的代码 var currentCultureName = CultureInfo.CurrentUICulture.Name; var currentLanguage = languages.FirstOrDefault(l => l.Name == currentCultureName); if (currentLanguage != null) { return currentLanguage; } // ... 省略了的代码 return languages[0];}
The default implementation is to directly read the data in the Languages before passing through the Configuration.
There are two other implementations of the Abp.zero module, called ApplicationLanguageProvider
, that the provider is a ApplicationLanguage
list of these language data obtained from the database tables, and that the language information is also relevant to the tenant, and that the different tenants have the same language data that they can get.
public IReadOnlyList<LanguageInfo> GetLanguages(){ // 可以看到这里传入的当前登录用户的租户 Id,通过这个参数去查询的语言表数据 var languageInfos = AsyncHelper.RunSync(() => _applicationLanguageManager.GetLanguagesAsync(AbpSession.TenantId)) .OrderBy(l => l.DisplayName) .Select(l => l.ToLanguageInfo()) .ToList(); SetDefaultLanguage(languageInfos); return languageInfos;}
2.4 Localized Resources 2.4.1 Localized Resources list
A Sources property of a type is used internally in a multi-language module configuration, which ILocalizationSourceList
is actually IList<ILocalizationSource>
a concrete implementation of the inheritance, a ILocalizationSource
collection of types, but it extends a
The Extensions property is used to hold extended multilanguage data fields.
2.4.2 Localization Resources
Its interface is defined as ILocalizationSource
the ABP defaults to the implementation of four localized resources.
The first is an empty implementation, which can be skipped, the second is a localized resource read for the resource file, the third is a dictionary-based localized resource definition, and the last is a multilingual resource definition for the database version provided by the ABP Zero module.
First look at the definition of the interface:
public interface ilocalizationsource{//localized resource unique name string name {get;} Used to initialize localized resources, called void Initialize (ilocalizationconfiguration configuration, Iiocresolver iocresolver) when the ABP framework is initialized; Gets the multilingual text entry for the given keyword from the current localized resource, in the user's current language, string GetString (string name); Gets the multilingual text entry for the given keyword and regional culture from the current localized resource string GetString (string name, CultureInfo culture); function as above, except that there is no return NULL string Getstringornull (string name, bool Trydefaults = TRUE); function as above, except that there is no return NULL string Getstringornull (string name, CultureInfo culture, bool trydefaults = TRUE); Gets the collection of all multilingual text items for the current language ireadonlylist<localizedstring> getallstrings (bool includedefaults = TRUE); Gets the collection of all multilingual text items for a given region culture ireadonlylist<localizedstring> getallstrings (CultureInfo culture, bool Includedefaults = true);}
You can also see that we have a few sets of localized resources, they are identified by name, and if you need to get a set of localized resources in the Localization manager, you can navigate directly by name. Each set of localized resources, each with its own specific multilingual data, may come from a file or possibly from a database, depending on your specific implementation.
2.4.3 Dictionary-based localization resources
We started by using examples to DictionaryBasedLocalizationSource
build our localized resource objects. The object implements a ILocalizationSource
IDictionaryBasedLocalizationSource
localized resource dictionary provider that is internally defined with an interface.
When a method of localizing a resource is called Initialize()
, a specific localized resource dictionary provider is used to fetch the data, and the dictionary provider can be XmlFileLocalizationDictionaryProvider
, and JsonEmbeddedFileLocalizationDictionaryProvider
so on.
When initialized, these internal dictionary providers store their data in a dictionary in the form of a language/multi-lingual entry , which can be divided into XML, JSON, and so on ...
// 内部字典提供器public interface ILocalizationDictionaryProvider{ // 语言/多语言项字典 IDictionary<string, ILocalizationDictionary> Dictionaries { get; } // 本地化资源初始化时被调用 void Initialize(string sourceName);}
And here is ILocalizationDictionary
actually a key value pair, the key is associated with the multi-language item identification key, such as "Home", and Value is the specific display text information.
Instead of using a dictionary to localize a resource object to obtain data, it is in fact the dictionary provider inside it to fetch the data.
For example, there is a method for localizing a resource GetString()
, which has a dictionary provider Dictionaryprovider, and the steps I need to take to get a KEY to "Home" are as follows.
Public Ilocalizationdictionaryprovider Dictionaryprovider {get;} public string GetString (string name) {//Gets the current user zone culture, the display text labeled "Home" return GetString (name, Cultureinfo.currentuicul ture);} public string GetString (string name, CultureInfo culture) {//Gets the value var = getstringornull (name, culture); If the judgment value is null, whether to throw an exception according to the configured requirements (value = = null) {return returngivennameorthrowexception (name, culture); } return value; Gets the KEY associated text public string Getstringornull (string name, CultureInfo culture, bool Trydefaults = True) {var Culturenam E = culture. Name; var dictionaries = dictionaryprovider.dictionaries; In this case, starting from the initialization of the completed language dictionary, get the Specific multilingual dictionary ilocalizationdictionary originaldictionary; if (dictionaries. TryGetValue (culturename, out originaldictionary)) {//multilingual item Dictionary fetching specific multilingual text value var stroriginal = Originaldictio Nary. 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.getornu ll (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;}
2.3.4 Database-based localized resources
If you have an integrated Abp.zero module, you can enable the Zero multi-language mechanism by writing the following code in the pre-load method of the boot module.
Configuration.Modules.Zero().LanguageManagement.EnableDbLocalization();
Abp.zero for the original localization resources, the new localization resource class MultiTenantLocalizationSource
is called, the same language manager, is a multi-tenant implementation based on the localization of resources, the value of the internal dictionary is obtained from the database, its general logic and dictionary localization resources, Are internally maintained there is a dictionary provider.
The EnableDbLocalization()
default implementation is replaced directly by the method ILanguageProvider
, and is also added MultiTenantLocalizationSource
as a localized resource within the configured Sources source.
2.5 Localization Explorer
So much so, let's take a look at the core ILocalizationManager
interface, if we need to get the multilingual value for a key of a data source, it must be injected into this localization explorer to operate.
public interface ILocalizationManager{ // 根据名称获得本地化数据源 ILocalizationSource GetSource(string name); // 获取所有的本地化数据源 IReadOnlyList<ILocalizationSource> GetAllSources();}
Here the data source identifies the role of a namespace, such as I have a module in a key for "Home"
the multi-language items, in the B module also has a key for "Home"
the multi-language item, this time can be used to identify the data source to distinguish the two "Home"
.
The localized resource Manager initializes all injected localized resources by invoking them at initialization time Initialize()
, and finally puts them in a dictionary for later use.
private readonly IDictionary<string, ILocalizationSource> _sources;foreach (var source in _configuration.Sources){ // ... 其他代码 _sources[source.Name] = source; source.Initialize(_configuration, _iocResolver); // ... 其他代码}
3. Conclusion
Multi-language processing for ABP This article is not suitable as a primer, most of the knowledge needs to be read in conjunction with the ABP source code to deepen understanding, this article is only for the purpose of making a point, if any comments or suggestions are welcome in the comments.
4. Click here to jump to the total directory
[ABP Source code Analysis] 13, multi-lingual (localization) processing