斗爷

导航

ABP框架系列之三十一:(Localization-本地化)

Introduction

Any application has at least one language for user interface. Many applications have more than one. ASP.NET Boilerplate provides a flexible localization system for an application.

任何应用程序至少有一种用于用户界面的语言。许多应用程序有多个。ASP.NET样板提供了一种柔性定位系统中的应用。

Application Languages(应用语言

The first thing is to declare which languages are supported. This is done in PreInitialize method of your module as shown below:

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"));

In server side, you can inject and use ILocalizationManager. In client side, you can use abp.localization javascript API to get list of all available languages and the current language. famfamfam-flag-england (and tr) is just a CSS class, you can change it upon your needs. Then you can use it on UI to show related flag.

ASP.NET Boilerplate templates uses this system to show a language-switch combobox to the user. Try to create a template and see source codes for more.

在服务器端,你可以注入和使用ilocalizationmanager。在客户端,您可以使用abp.localization JavaScript API获取所有可用的语言和当前的语言列表。famfamfam旗英格兰(TR)只是一个CSS类,你可以改变它对你的需要。然后您可以在UI上使用它来显示相关标志。

ASP.NET的模板模板使用本系统显示语言切换组合框的用户。尝试创建一个模板并查看更多的源代码。

Localization Sources

Localization texts can be stored in different sources. Even, you can use more than one source in same application (If you have more than one module, each module can define a seperated localization source, or one module can define multiple source). ILocalizationSource interface should be implemented by a localization source. Then it is registered to ASP.NET Boilerplate's localization configuration.

本地化文本可以存储在不同的源中。甚至,您也可以在同一个应用程序中使用多个源(如果有多个模块,每个模块可以定义一个分离的本地化源,或者一个模块可以定义多个源)。ilocalizationsource接口应该被定位源实现。然后是注册ASP.NET样板的定位结构。

Each localization source must have a unique source name. There are pre-defined localization source types as defined below.

XML Files

Localization texts can be stored in XML files. Content of an XML file is something like that:

<?xml version="1.0" encoding="utf-8" ?>
<localizationDictionary culture="en">
  <texts>
    <text name="TaskSystem" value="Task System" />
    <text name="TaskList" value="Task List" />
    <text name="NewTask" value="New Task" />
    <text name="Xtasks" value="{0} tasks" />
    <text name="CompletedTasks" value="Completed tasks" />
    <text name="EmailWelcomeMessage">Hi,
Welcome to Simple Task System! This is a sample
email content.</text>
  </texts>
</localizationDictionary>

XML file must be unicode (utf-8). culture="en" declares that this XML file contains English texts. For text nodes; name attribute is used to identify a text. You can use value attribute or inner text (like the last one) to set value of the localization text. We create a seperated XML file for each language as shown below:

XML文件必须是Unicode(UTF-8)。“=”声明这个XML文件包含英文文本。对于文本节点,name属性用于标识文本。可以使用值属性或内部文本(比如最后一个文本)来设置本地化文本的值。我们为每个语言创建一个分离的XML文件,如下所示:

Localization files

SimpleTaskSystem is the source name here and SimpleTaskSystem.xml defines the default language. When a text is requested, ASP.NET Boilerplate gets the text from current language's XML file (finds current language using Thread.CurrentThread.CurrentUICulture). If it does not exists in the current language, it gets the text from default language's XML file.

Registering XML Localization Sources

XML files can be stored in file system or can be embedded into an assembly.

For file system stored XMLs, we can register an XML localization source as shown below:

Configuration.Localization.Sources.Add(
    new DictionaryBasedLocalizationSource(
        "SimpleTaskSystem",
        new XmlFileLocalizationDictionaryProvider(
            HttpContext.Current.Server.MapPath("~/Localization/SimpleTaskSystem")
            )
        )
    );

This is done in PreInitialize event of a module (See module system for more info). ASP.NET Boilerplate finds all XML files in given directory and registers the localization source.

For embedded XML files, we should mark all localization XML files as embedded resource (Select XML files, open properties window (F4) and change Build Action as Embedded Resource). Then we can register the localization source as shown below:

对于嵌入的XML文件,我们应该将所有本地化XML文件标记为嵌入式资源(选择XML文件、打开属性窗口(F4)并将生成操作转换为嵌入资源)。然后我们可以注册本地化源如下所示:

Configuration.Localization.Sources.Add(
    new DictionaryBasedLocalizationSource(
        "SimpleTaskSystem",
        new XmlEmbeddedFileLocalizationDictionaryProvider(
            Assembly.GetExecutingAssembly(),
            "MyCompany.MyProject.Localization.Sources"
            )
        )
    );

XmlEmbeddedFileLocalizationDictionaryProvider gets an assembly containing XML files (GetExecutingAssembly simply refers to current assembly) and a namespace of XML files (namespace is calculated assembly name + folder hierarchy of XML files).

Note: When adding language postfix to embedded XML files, do not use dot notation like 'MySource.tr.xml', instead use dash like 'MySource-tr.xml' since dot notation causes namespacing problems when finding resources.

XmlEmbeddedFileLocalizationDictionaryProvider得到了包含XML文件汇编(getexecutingassembly仅仅是指当前装配)和命名空间的XML文件(命名空间计算组件名称+文件夹层次结构的XML文件)。

注:添加语言后缀嵌入式XML文件时,不要使用点符号如“mysource。tr.xml ',而用破折号像mysource-tr.xml自从点符号的原因时,发现资源命名空间问题。

JSON Files

JSON files can be used to store texts for a localization source. A sample JSON localization file is shown below:

JSON文件可用于存储本地化源的文本。下面显示了一个JSON本地化文件示例:

{
  "culture": "en",
  "texts": {
    "TaskSystem": "Task system",
    "Xtasks": "{0} tasks"
  }
}

JSON files should be unicode (utf-8). culture: "en" declares that this JSON file contains English texts. We create a seperated JSON file for each language as shown below:

JSON文件应该是Unicode(UTF-8)。文化:“EN”声明这个JSON文件包含英文文本。我们为每个语言创建一个分离的JSON文件,如下所示:

JSON localization files

MySourceName is the source name here, and MySourceName.json defines the default language. It's similar to XML files.

Registering JSON Localization Sources

JSON files can be stored in file system or can be embedded into an assembly.

File file system stored JSONs, we can register a JSON localization source as shown below:

Configuration.Localization.Sources.Add(
    new DictionaryBasedLocalizationSource(
        "MySourceName",
        new JsonFileLocalizationDictionaryProvider(
            HttpContext.Current.Server.MapPath("~/Localization/MySourceName")
            )
        )
    );

This is done in PreInitialize event of a module (See module system for more info). ASP.NET Boilerplate finds all JSON files in given directory and registers the localization source.

For embedded JSON files, we should mark all localization JSON files as embedded resource (Select JSON files, open properties window (F4) and change Build Action as Embedded Resource). Then we can register the localization source as shown below:

 Configuration.Localization.Sources.Add(
    new DictionaryBasedLocalizationSource(
        "MySourceName",
        new JsonEmbeddedFileLocalizationDictionaryProvider(
            Assembly.GetExecutingAssembly(),
            "MyCompany.MyProject.Localization.Sources"
            )
        )
    );

JsonEmbeddedFileLocalizationDictionaryProvider gets an assembly containing JSON files (GetExecutingAssembly simply refers to current assembly) and a namespace of JSON files (namespace is calculated assembly name + folder hierarchy of JSON files).

Note: When adding language postfix to embedded JSON files, do not use dot notation like 'MySource.tr.json', instead use dash like 'MySource-tr.json' since dot notation causes namespace problems when finding resources. 

Resource Files

Localization text can also be stored in .NET's resource files. We can create a resource file for each language as shown below (Right click to the project, choose add new item then find resources file).

Localization resource files

MyTexts.resx contains the default language texts and MyTexts.tr.resx contains texts for Turkish language. When we open MyTexts.resx, we can see all texts:

Content of a resource file

In this case, ASP.NET Boilerplate uses .NET's built-in resource manager for localization. You should configure a localization source for the resource:

Configuration.Localization.Sources.Add(
    new ResourceFileLocalizationSource(
        "MySource",
        MyTexts.ResourceManager
        ));

Uniqe name of the source is MySource here. And MyTexts.ResourceManager is a reference to the resource manager to be used to get localized texts. This is done in PreInitialize event of the module (See module systemfor more info).

Custom Source(自定义源

A custom localization source can be implemented to store texts in different sources such as in a database. You can directly implement the ILocalizationSource interface or you can use DictionaryBasedLocalizationSourceclass to make implementation easier (json and xml localization sources also use it). Module zero implements source in the database for example.

可以实现自定义本地化源,以在不同来源(如数据库中)存储文本。你可以直接实现ilocalizationsource界面也可以使用dictionarybasedlocalizationsourceclass实施容易(JSON和XML本地化源也使用它)。Module zero实现数据库中的源代码,例如。

How Current Language Is Determined(当前语言是如何确定的

ASP.NET Core

ASP.NET Core has it's own mechanism to determine the current language. Abp.AspNetCore package automatically adds ASP.NET Core's UseRequestLocalization middleware to request pipeline. It also adds some special providers. Here, default ordered list of all provides, which determines the current language for an HTTP request:

ASP.NET的核心都有它自己的机制来确定当前的语言。abp.aspnetcore包自动添加ASP.NET核心的userequestlocalization中间件请求管道。它还添加了一些特殊提供者。这里,所有命令的默认有序列表,它决定HTTP请求的当前语言:

  • QueryStringRequestCultureProvider (ASP.NET Core's default provider): Use culture & ui-culture URL query string values if present. Example value: "culture=es-MX&ui-culture=es-MX".
  • QueryStringRequestCultureProvider(ASP.NET核心的默认提供程序):用文化和UI URL查询字符串的值如果存在文化。示例值:“culture=es-MX&ui-culture=es-MX”。
  • AbpUserRequestCultureProvider (ABP's provider): If user is known via IAbpSession and explicitly selected a language before (and saved to ISettingManager) then use user's preferred language. If user is known but not selected any language and .AspNetCore.Culture cookie or header has a value, set user's language setting with that information and use this value as the current language. If user is unknown, this provides does nothing.
  • AbpUserRequestCultureProvider(ABP的提供者):如果用户是通过iabpsession和显式选择语言之前(并保存到isettingmanager)然后使用用户的首选语言。如果用户是已知的但没有选择任何语言。aspnetcore.culture cookie或头有一个值,设置用户的语言与信息的设置和使用该值作为当前语言。如果用户是未知的,则提供不做任何操作。
  • AbpLocalizationHeaderRequestCultureProvider (ABP's provider): Use .AspNetCore.Culture header value if present. Example value: "c=en|uic=en-US".
  • CookieRequestCultureProvider (ASP.NET Core's default provider): Use .AspNetCore.Culture cookie value if present. Example value: "c=en|uic=en-US".
  • AbpDefaultRequestCultureProvider (ABP's provider): If there is an default/application/tenant setting value for the language (named "Abp.Localization.DefaultLanguageName"), then use the setting's value.
  • AcceptLanguageHeaderRequestCultureProvider (ASP.NET Core's default provider): Use Accept-Language header value if present (automatically sent by browsers). Example value: "tr-TR,tr;q=0.8,en-US;q=0.6,en;q=0.4".

The UseRequestLocalization middleware is automatically added when you call app.UseAbp() method. But it's suggested that manually add it (in Configure method of Startup class) after authentication middleware if your application uses authentication. Otherwise, localization middleware can now know current user to determine the best language. Example usage:

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 =>
    {
        //...
    });
}

Most of time, you don't need to care about if you are using ABP's localization system properly. See ASP.NET Core localization document to understand it better.

ASP.NET MVC 5.x

ABP automatically determines current language in every web request and sets current thread's culture (and UI culture). This is how ABP determines it by default:

ABP自动确定每个Web请求中的当前语言,并设置当前线程的文化(和UI文化)。这就是ABP如何默认的:

  • Try to get from a special query string value, named "Abp.Localization.CultureName" by default.
  • If user is known via IAbpSession and explicitly selected a language before (and saved to ISettingManager) then use user's preferred language. If user is known but not selected any language and cookie/header (see below) has a value, set user's setting with that information.
  • Try to get from a special header value, named "Abp.Localization.CultureName" by default.
  • Try to get from a special header value, named "Abp.Localization.CultureName" by default.
  • Try to get from a special cookie value, named "Abp.Localization.CultureName" by default.
  • Try to get from default culture setting (setting name is "Abp.Localization.DefaultLanguageName", which is a constant defined in Abp.Localization.LocalizationSettingNames.DefaultLanguage and can be changed usingsetting management).
  • Try to get from browser's default language (HttpContext.Request.UserLanguages).

If you need, you can change the special cookie/header/querystring name in your module's PreInitialize method. Example:

Configuration.Modules.AbpWeb().Localization.CookieName = "YourCustomName";

ABP overrides Application_PostAuthenticateRequest (in global.asax) to implement that logic. You can override SetCurrentCulture in global.asax or replace ICurrentCultureSetter in order to override the logic described above.

Getting A Localized Text(获取本地化文本

After creating a source and register it to the ASP.NET Boilerplate's localization system, texts can be localized easily. 

In Server Side

In server side, we can inject ILocalizationManager and use it's GetString method.

var s1 = _localizationManager.GetString("SimpleTaskSystem", "NewTask");

GetString method gets the string from the localization source based on current thread's UI culture. If not found, it fallbacks to default language.

If given string is not defined anywhere then it returns the given string by humanizing and wrapping with [ and ] by default (instead of throwing Exception). Example: If given text is "ThisIsMyText", then result will be "[This is my text]". This behavior is configurable (you can use Configuration.Localization in PreInitialize of your module to change it).

To do not repeat source name always, you can first get the source, then get a string from the source:

getString方法获取字符串从基于当前线程UI的文化定位源。如果没有找到,它回退到默认语言。

如果没有定义任何给定的字符串并返回给定的人性化和默认的[和]包装字符串(而不是抛出异常)。例如:如果给定的文本是“thisismytext”,那么结果将是“[这是我的文字]”。这种行为是可配置的(你可以使用配置。定位在分发你的模块来改变它)。

为了不重复源代码名,您首先可以获取源代码,然后从源获取一个字符串:

var source = _localizationManager.GetSource("SimpleTaskSystem");
var s1 = source.GetString("NewTask");

This returns text in the current language. There are also overrides of GetString to get text in different languages and formatted by arguments.

If we can not inject ILocalizationManager (maybe in a static context that can not reach to the dependency injection), we can simply use LocalizationHelper static class. But prefer injecting and using ILocalizationManager where it's possible since LocalizationHelper is static and statics are not well testable (for who writes unit tests).

If you need to localization in an application service , in an MVC Controller, in a Razor View or in another class derived from AbpServiceBase, there are shortcut L methods.

这将返回当前语言中的文本。也有重写getString获得不同语言的文本和格式的参数。

如果我们不把ilocalizationmanager(也许在一个静态的背景下,找不到依赖注入),我们可以简单地使用localizationhelper静态类。但更喜欢注射和使用ilocalizationmanager有可能因为LocalizationHelper是静态的,静态不可测试的(谁写单元测试)。

如果你需要在一个应用程序的服务定位,在MVC控制器,在一个Razor视图或在另一个类的派生类abpservicebase,有快捷方法。

In MVC Controllers

Localization texts are generally needed in MVC Controller and Views. There is a shortcut for that. See the sample controller below:

public class HomeController : SimpleTaskSystemControllerBase
{
    public ActionResult Index()
    {
        var helloWorldText = L("HelloWorld");
        return View();
    }
}

L method is used to localize a string. Surely, you must supply a source name. It's done in SimpleTaskSystemControllerBase as shown below:

public abstract class SimpleTaskSystemControllerBase : AbpController
{
    protected SimpleTaskSystemControllerBase()
    {
        LocalizationSourceName = "SimpleTaskSystem";
    }
}

Notice that it is derived from AbpController. Thus, you can easily localize texts with L method.

In MVC Views

Same L method also exists in views:

<div>
    <form id="NewTaskForm" role="form">
        <div class="form-group">
            <label for="TaskDescription">@L("TaskDescription")</label>
            <textarea id="TaskDescription" data-bind="value: task.description" class="form-control" rows="3" placeholder="@L("EnterDescriptionHere")" required></textarea>
        </div>
        <div class="form-group">
            <label for="TaskAssignedPerson">@L("AssignTo")</label>
            <select id="TaskAssignedPerson" data-bind="options: people, optionsText: 'name', optionsValue: 'id', value: task.assignedPersonId, optionsCaption: '@L("SelectPerson")'" class="form-control"></select>
        </div>
        <button data-bind="click: saveTask" type="submit" class="btn btn-primary">@L("CreateTheTask")</button>
    </form>
</div>

To make this work, you should derive your views from a base class that sets the source name:

public abstract class SimpleTaskSystemWebViewPageBase : SimpleTaskSystemWebViewPageBase<dynamic>
{

}

public abstract class SimpleTaskSystemWebViewPageBase<TModel> : AbpWebViewPage<TModel>
{
    protected SimpleTaskSystemWebViewPageBase()
    {
        LocalizationSourceName = "SimpleTaskSystem";
    }
}

And set this view base class in web.config:

<pages pageBaseType="SimpleTaskSystem.Web.Views.SimpleTaskSystemWebViewPageBase">

All these for controllers and views are ready when you create your solution from one of the ASP.NET Boilerplate templates.

In Javascript

ASP.NET Boilerplate makes possible to use same localization texts in also javascript code. First, you should be added dynamic ABP scripts to the page:

<script src="/AbpScripts/GetScripts" type="text/javascript"></script>

ASP.NET Boilerplate automatically generates needed javascript code to get localized texts in the client side. Then you can easily get a localized text in javascript as shown below:

var s1 = abp.localization.localize('NewTask', 'SimpleTaskSystem');

NewTask is the text name and SimpleTaskSystem is the source name here. To do not repeat source name, you can first get the source then get the text:

var source = abp.localization.getSource('SimpleTaskSystem');
var s1 = source('NewTask');
Format Arguments(格式参数

Localization method can also get additional format arguments. Example:

本地化方法也可以获得额外的格式参数。例子:

abp.localization.localize('RoleDeleteWarningMessage', 'MySource', 'Admin');

//shortcut if source is got using getSource as shown above
source('RoleDeleteWarningMessage', 'Admin');

if RoleDeleteWarningMessage = 'Role {0} will be deleted', then localized text will be 'Role Admin will be deleted'.

Default Localization Source

You can set a default localization source and use abp.localization.localize method without source name.

abp.localization.defaultSourceName = 'SimpleTaskSystem';
var s1 = abp.localization.localize('NewTask');

defaultSourceName is global and works for only one source at a time.

Extending Localization Sources

Assume that we use a module which defines it's own localization source. We may need to change it's localized texts, add new texts or translate to other languages. ASP.NET Boilerplate allows extending a localization source. It currently works for XML and JSON files (Actually any localization source implements IDictionaryBasedLocalizationSource interface).

ASP.NET Boilerplate also defines some localization sources. For instance, Abp.Web nuget package defines a localization source named "AbpWeb" as embedded XML files:

假设我们使用了一个模块来定义它自己的本地化源。我们可能需要改变本地化的文本,添加新的文本或翻译成其他语言。ASP.NET样板可以延长定位源。目前为XML和JSON文件(实际上任何本地化源实现idictionarybasedlocalizationsource接口)。

ASP.NET样板也定义了一些本地化源。例如,ABP。Web NuGet包定义了一个名为“abpweb定位源”作为嵌入的XML文件:

AbpWeb localization source files

 Default (English) XML file is like below (only first two texts are shown):

<?xml version="1.0" encoding="utf-8" ?>
<localizationDictionary culture="en">
  <texts>
    <text name="InternalServerError" value="An internal error occurred during your request!" />
    <text name="ValidationError" value="Your request is not valid!" />
    ...
  </texts>
</localizationDictionary>

To extend AbpWeb source, we can define XML files. Assume that we only want to change InternalServerError text. We can define an XML file as shown below:

<?xml version="1.0" encoding="utf-8" ?>
<localizationDictionary culture="en">
  <texts>
    <text name="InternalServerError" value="Sorry :( It seems there is a problem. Let us to solve it and please try again later." />
  </texts>
</localizationDictionary>

Then we can register it on PreInitialize method of our module:

Configuration.Localization.Sources.Extensions.Add(
    new LocalizationSourceExtensionInfo("AbpWeb",
        new XmlFileLocalizationDictionaryProvider(
            HttpContext.Current.Server.MapPath("~/Localization/AbpWebExtensions")
            )
        )
    );

We could use XmlEmbeddedFileLocalizationDictionaryProvider if want to create embedded resource XML files (see Localization sources section). ASP.NET Boilerplate overrides (merges) base localization source with our XML files. We can also add new language files.

Note: We can use JSON files to extend XML files, or vice verse.

我们可以用xmlembeddedfilelocalizationdictionaryprovider如果想创建嵌入的资源的XML文件(见定位源部分)。ASP.NET样板重写(合并)与我们的XML文件的源基地定位。我们还可以添加新的语言文件。

注意:我们可以使用JSON文件来扩展XML文件

Getting Languages

ILanguageManager can be used to get a list of all available languages and the current language.

Best Practices(最佳实践

XML files, JSON files and Resource files have own strengths and weaknesses. We suggest to use XML or JSON files instead of Resource files, because;

  • XML/JSON files are easy to edit, extend or port.
  • XML/JSON files requires string keys while getting localized texts, instead of compile time properties like Resource files. This can be considered as a weekness. But, it's easier to change source later. Even we can move localization to a database without changing or code which use localization (Module-zero implements it to create a database based and per-tenant localization source. See documentation.)

If you use XML or JSON, it's suggested to do not sort texts by name. Sort them by creation date. Thus, when someone translates it to another language, he/she can easily see which texts are added newly.

XML文件、JSON文件和资源文件都有自己的长处和短处。我们建议使用XML或JSON文件而不是资源文件,因为;

XML / JSON文件易于编辑、扩展或端口。
XML / JSON文件在获取本地化文本时需要字符串键,而不是像资源文件那样编译时属性。这可以被看作是一个弱点。但是,稍后更改源代码更容易。甚至我们可以将本地化移动到数据库,而不用更改或使用本地化的代码(Module-zero实现它来创建基于数据库的和每个租户本地化源)。参阅文档)。
如果使用XML或JSON,建议不要按名称对文本进行排序。按创建日期排序。因此,当某人把它翻译成另一种语言时,他/她可以很容易地看到哪些文本是新添加的。

posted on 2018-01-16 13:53  斗哥哥  阅读(5769)  评论(0编辑  收藏  举报