[ABP Source code Analysis] 13, multi-lingual (localization) processing

Source: Internet
Author: User
Tags static class
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 theILocalizationSource 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 isfamfamfam-flag-englandfamfamfam-flag-tra 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 isAbp\Localization\Sources\AbpXmlSource.

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 enables our program to have multi-language processing capabilities for English and Turkish.

Here famfamfam-flag-england and famfamfam-flag-tr are a CSS type, which is a small flag icon encapsulated by Abp for front-end display.

0.1.2 Create multilingual resource files
After having the language, Abp also needs you to provide a standard multi-language resource file. Here we take the built-in XML resource file as an example. The file name is Abp-zh-Hans.xml and 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 name</text>
    <text name="UseSSL">Use SSL</text>
    <text name="UseDefaultCredentials">Use default authentication</text>
    <text name="DefaultFromSenderEmailAddress">Default sender email address</text>
    <text name="DefaultFromSenderDisplayName">Default sender name</text>
    <text name="DefaultLanguage">Default language</text>
    <text name="ReceiveNotifications">Receive notifications</text>
    <text name="CurrentUserDidNotLoginToTheApplication">The current user is not logged in to the system! </text>
    <text name="TimeZone">Time zone</text>
    <text name="AllOfThesePermissionsMustBeGranted">You do not have permission to perform this operation, you need the following permissions: {0}</text>
    <text name="AtLeastOneOfThesePermissionsMustBeGranted">You do not have permission to perform this operation, you need at least one of the following permissions: {0}</text>
    <text name="MainMenu">Main Menu</text>
  </texts>
</localizationDictionary>
Inside each file, there will be a <localizationDictionary culture="zh-Hans"> node to indicate which region the current file is applicable to, and inside the <texts> is the form of combining key-value pairs, in the name The content of is the key of the multilingual text item, and the inside of the label is its real value.

Open an XML resource file for Russian countries, the file name is called 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">Имя пользователя</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 values are all the same, but the value inside the <text> is different depending on the regional country.

Secondly, from the file name, we can see that the need to use XML resource files will have certain requirements for the naming format of the files. Or take the resource files that come with Abp as an example. You can see that they are basically composed of {SourceName}-{CultureInfo }.xml is constructed like this.

0.1.3 Registering localized XML resources
Then if we need to register the previous two XML resources into the Abp framework, we need to perform the registration through the following code at the pre-loading module, and we need to right-click the XML file to change its construction operation to embedded resources.

Configuration.Localization.Sources.Add(
    new DictionaryBasedLocalizationSource(
        // Localized resource name
        AbpConsts.LocalizationSourceName,
        // Data source provider, XML is used here, in addition to XML provider, there are JSON etc.
        new XmlEmbeddedFileLocalizationDictionaryProvider(
            typeof(AbpKernelModule).GetAssembly(), "Abp.Localization.Sources.AbpXmlSource"
        )));
0.1.4 Get multilingual text
If you need to get the specific display text corresponding to the specified Key somewhere, you only need to inject ILocalizationManager, and you can get the specific value through its GetString() method. If you need to obtain localized resources where dependency injection cannot be used, you can use the LocalizationHelper static class to operate.

var @string = _localizationManager.GetString("Abp", "MainMenu");
It defaults to the current region information obtained from Thread.CurrentThread.CurrentUICulture to obtain the display value corresponding to a key, and the current region information is provided by a series of RequestCultureProviders injected by Abp, which is performed in the following order Set up.

QueryStringRequestCultureProvider (provided by ASP.NET Core by default): This default provider uses the regional cultural information provided by the culture&ui-culture of QueryString to initialize this value, for example: culture=es-MX&ui-culture=es-MX.
AbpUserRequestCultureProvider (provided by Abp): This provider will read the IAbpSession information of the current user, and obtain the "Abp.Localization.DefaultLanguageName" attribute configured by the user from ISettingManager, and use it as the default regional cultural information.
AbpLocalizationHeaderRequestCultureProvider (provided by Abp): Use the .AspNetCore.Culture value in each request header as the current regional culture information, for example, c=en|uic=en-US.
CookieRequestCultureProvider (provided by ASP .NET Core): Use the value of .AspNetCore.Culture in the cookie of each request as the current regional culture information.
AbpDefaultRequestCultureProvider (provided by Abp): If none of these providers have assigned a value for the current regional culture, the default value of Abp.Localization.DefaultLanguageName will be obtained from ISettingMananger.
AcceptLanguageHeaderRequestCultureProvider (provided by ASP.NET Core by default): This provider will eventually use the Accept-Language header passed by the user every time a request is made as the current regional culture information.
hint:

Here, the providers injected by Abp are ordered. The purpose of injecting so many providers is to finally determine the current user’s regional cultural information in order to display the corresponding language text.

1. Startup process 1.1 Startup flow chart
1.2 Code flow
According to the usage method, we can know that to configure Abp's multi-language, you must wait for the initialization of IAbpStartupConfiguration to be completed. That is, in the Initialize() method of AbpBootstrapper:

public virtual void Initialize()
{
    // ... other codes
    // Inject IAbpStartupConfiguration configuration and localized resource configuration
    IocManager.IocContainer.Install(new AbpCoreInstaller());

    // ... other codes
    // Initialize AbpStartupConfiguration type
    IocManager.Resolve<AbpStartupConfiguration>().Initialize();

    // ... other codes
}
The configuration class contains all the language and multilingual resource information configured by the user. After being successfully injected into the Ioc container, Abp starts to use the localized resource manager to initialize these multilingual data.

public override void PostInitialize()
{
    // Register missing components to prevent missing registered components
    RegisterMissingComponents();

    IocManager.Resolve<SettingDefinitionManager>().Initialize();
    IocManager.Resolve<FeatureManager>().Initialize();
    IocManager.Resolve<PermissionManager>().Initialize();
    
    // The point is here, the PostInitialize method is stored in the core module, where the initialization method of the localized resource manager is called
    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>());
    }
}
The specific LocalizationManager and its internal implementation will be described in detail in the next section of code analysis.

These actions are only some of the steps that need to be performed when injecting the Abp framework. If you want to enable multiple languages, you need to change the UseAbpRequestLocalization status to True at the Configure() in the Startup class of the ASP.NET Core program to change The regional culture recognition middleware is injected 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 UseAbpRequestLocalization() here has already injected the RequestProvider mentioned above into the MVC in order.

2. Code analysis
The type and method definitions related to localization processing of the Abp framework are stored in the Localization folder of the Abp library. The relationship is still relatively complicated. Here we start with its core Abp library for multi-language processing.

2.1 Multi-language module configuration
All the information that Abp needs to use is injected and configured by the user in the PreInitialize() of the self-starting module through ILocalizationConfiguration. That is to say, within ILocalizationConfiguration, it mainly contains two key information: language and multilingual resource providers.

public interface ILocalizationConfiguration
{
    // List of languages that can be configured in the current application
    IList<LanguageInfo> Languages {get;}

    // List of localized resources
    ILocalizationSourceList Sources {get;}

    // Whether to enable multilingual (localization) system
    bool IsEnabled {get; set;}

    // The following four Boolean parameters are mainly used to determine the processing logic when no multilingual text is found, and the default is 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 the user allocates to the multi-language module configuration object when preloading. Through Section 0.1.1 we see that users can directly initialize a new LanguageInfo object and add it to the Languages property.

public class LanguageInfo
{
    /// <summary>
    /// Regional culture code name
    /// It should be a valid regional culture code name, and more can get all culture codes through the CultureInfo static class.
    /// For example: "en-US" is for North America, "tr-TR" is for 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 "English", "zh-Hans" should be displayed as "Simplified Chinese"
    /// </summary>
    public string DisplayName {get; set;}

    /// <summary>
    /// CSS class name of the icon used for display, optional parameters
    /// </summary>
    public string Icon {get; set;}

    /// <summary>
    /// Is it the default language
    /// </summary>
    public bool IsDefault {get; set;}

    /// <summary>
    /// Whether the language is disabled
    /// </summary>
    public bool IsDisabled {get; set;}

    /// <summary>
    /// Is the language displayed from left to right or 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 isDisabled = false)
    {
        Name = name;
        DisplayName = displayName;
        Icon = icon;
        IsDefault = isDefault;
        IsDisabled = isDisabled;
    }
}
The definition of language is quite simple. The main parameters are the language’s regional culture code and the name of the display, and the rest can be optional parameters.

hint:

Regarding the regional culture codes supported by the current system, you can get it by executing CultureInfo.GetCultures(CultureTypes.AllCultures);.

2.3 Language Manager
Abp also provides a manager for the language. The interface is called ILanguageManager, which is simple to define and has two methods.

public interface ILanguageManager
{
    // Get the current language
    LanguageInfo CurrentLanguage {get;}

    // get all languages
    IReadOnlyList<LanguageInfo> GetLanguages();
}
The implementation is not complicated, its internal implementation is to get some language data from an ILanguageProvider.

private readonly ILanguageProvider _languageProvider;

public IReadOnlyList<LanguageInfo> GetLanguages()
{
    return _languageProvider.GetLanguages();
}

// Obtaining the current language is actually the information of the obtained CultureInfo.CurrentUICulture.Name, and then to query the language collection.
private LanguageInfo GetCurrentLanguage()
{
    var languages = _languageProvider.GetLanguages();
    
    // ... omitted code
    var currentCultureName = CultureInfo.CurrentUICulture.Name;

    var currentLanguage = languages.FirstOrDefault(l => l.Name == currentCultureName);
    if (currentLanguage != null)
    {
        return currentLanguage;
    }
    
    // ... omitted code
    
    return languages[0];
}
The default implementation is to directly read the data in Languages previously passed through Configuration.

There are two other implementations in the Abp.Zero module, called ApplicationLanguageProvider. This provider is the language list data obtained from the database table ApplicationLanguage, and the language information is also related to the tenant, and the language data that different tenants can obtain It's not the same.

public IReadOnlyList<LanguageInfo> GetLanguages()
{
    // You can see the tenant Id of the currently logged-in user passed in here, and use this parameter to query the language table data
    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 List of localized resources
What is used inside the multi-language module configuration is a Sources property of the ILocalizationSourceList type. This type is actually a specific implementation inherited from IList<ILocalizationSource>, a collection of type ILocalizationSource, but it extends a

The Extensions attribute is used to store extended multilingual data fields.

2.4.2 Localized resources
The interface is defined as ILocalizationSource, and Abp implements four localization resources for us by default.

The first is an empty implementation and can be skipped, the second is a localized resource for reading resource files, the third is a dictionary-based localized resource definition, and the last one is provided by the Abp Zero module Multilingual resource definition of the database version.

First look at the definition of the interface:

public interface ILocalizationSource
{
    // The unique name of the localized resource
    string Name {get;}

    // Used to initialize localized resources, called when the Abp framework is initialized
    void Initialize(ILocalizationConfiguration configuration, IIocResolver iocResolver);

    // Get the multilingual text item of the given keyword from the current localized resources, which is the current language of the user
    string GetString(string name);

    // Get multilingual text items of given keywords and regional culture from the current localized resources
    string GetString(string name, CultureInfo culture);

    // Same as above, except it returns NULL if it doesn't exist
    string GetStringOrNull(string name, bool tryDefaults = true);

    // Same as above, except it returns NULL if it doesn't exist
    string GetStringOrNull(string name, CultureInfo culture, bool tryDefaults = true);

    // Get all the multilingual text items collection of the current language
    IReadOnlyList<LocalizedString> GetAllStrings(bool includeDefaults = true);

    // Get a collection of all multilingual text items of a given regional culture
    IReadOnlyList<LocalizedString> GetAllStrings(CultureInfo culture, bool includeDefaults = true);
}
In other words, we have several sets of localized resources. They are identified by Name. If you need to obtain a set of localized resources in the localization manager, you can directly locate them by Name. Each set of localized resources has specific multi-language data. These multi-language data may come from files or databases, depending on your specific implementation.

2.4.3 Dictionary-based localized resources
At the beginning, we used the example to create our localization resource object through DictionaryBasedLocalizationSource. This object implements the ILocalizationSource and IDictionaryBasedLocalizationSource interfaces, and defines a localized resource dictionary provider.

When calling the Initialize() method of a localized resource, a specific localized resource dictionary provider will be used to,and this dictionary provider can be XmlFileLocalizationDictionaryProvider, JsonEmbeddedFileLocalizationDictionaryProvider, etc.

When these internal dictionary providers are initialized, they store their own data in the form of language/multilingual items in a dictionary, and this dictionary can be divided into XML, JSON, etc... .

// Internal dictionary provider
public interface ILocalizationDictionaryProvider
{
    // Language/Multilingual Item Dictionary
    IDictionary<string, ILocalizationDictionary> Dictionaries {get;}

    // Called when the localized resource is initialized
    void Initialize(string sourceName);
}
The ILocalizationDictionary here is actually a key-value pair. The key is associated with the multilingual item identification KEY, such as "Home", and Value is the specific display text information.

Instead, when using the dictionary localization resource object to get data, it actually gets the data from its internal dictionary provider.

For example, a localized resource has a GetString() method, which has a dictionary provider, DictionaryProvider. The steps I need to go through to get a KEY as "Home" are as follows.

public ILocalizationDictionaryProvider DictionaryProvider {get;}

public string GetString(string name)
{
    // Get the current user's regional culture, the display text marked as "Home"
    return GetString(name, CultureInfo.CurrentUICulture);
}

public string GetString(string name, CultureInfo culture)
{
    // Get the value
    var value = GetStringOrNull(name, culture);

    // If the judgment value is empty, whether an exception is thrown according to the configuration requirements
    if (value == null)
    {
        return ReturnGivenNameOrThrowException(name, culture);
    }

    return value;
}

// Get the text associated with KEY
public string GetStringOrNull(string name, CultureInfo culture, bool tryDefaults = true)
{
    var cultureName = culture.Name;
    var dictionaries = DictionaryProvider.Dictionaries;

    // Start here to get the specific multi-language item dictionary from the language dictionary loaded by the initialization
    ILocalizationDictionary originalDictionary;
    if (dictionaries.TryGetValue(cultureName, out originalDictionary))
    {
        // Multilingual item dictionary to get specific multilingual text value
        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;
}
2.3.4 Localized resources based on database
If you have integrated Abp.Zero module, you can enable Zero's multi-language mechanism by writing the following code in the preload method of the startup module.

Configuration.Modules.Zero().LanguageManagement.EnableDbLocalization();
Abp.Zero expands the original localization resources. The new localization resource class is called MultiTenantLocalizationSource. This class is the same as the language manager. It is a multi-tenant-based localization resource. The value of the internal dictionary is from the database. The general logic of what is obtained is the same as dictionary localization resources, and there is a dictionary provider internally maintained.

When passing the EnableDbLocalization() method, the default implementation of ILanguageProvider is directly replaced, and MultiTenantLocalizationSource is added as a localization resource in the configured Sources.

2.5 Localization Resource Manager
Having said so much, let's take a look at the core ILocalizationManager interface. If we need to obtain the multilingual value corresponding to a certain Key of a certain data source, we must inject this localization resource manager to operate.

public interface ILocalizationManager
{
    // Obtain localized data source based on name
    ILocalizationSource GetSource(string name);

    // Get all localized data sources
    IReadOnlyList<ILocalizationSource> GetAllSources();
}
The data source here identifies the role of a namespace. For example, I have a multi-language item with Key "Home" in module A, and a multi-language item with Key "Home" in module B. This is fine. Use the data source identifier to distinguish the two "Home".

The localized resource manager calls its In at initialization itialize() to initialize all injected localized resources, and finally put them in a dictionary for subsequent use.

private readonly IDictionary<string, ILocalizationSource> _sources;

foreach (var source in _configuration.Sources)
{
     // ... other codes
     _sources[source.Name] = source;
     source.Initialize(_configuration, _iocResolver);
    
     // ... other codes
}
3. Conclusion
For Abp multilingual processing, this article is not suitable as an introductory understanding. Most of the knowledge needs to be read in conjunction with Abp source code to deepen the understanding. This article is only for the purpose of introductory comments. If you have any comments or suggestions, please feel free to comment. Pointed out.

4. Click here to jump to the general catalog

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.