一个应用至少有一种UI语言,许多应用不止有一种语言。ABP为应用提供了一个灵活的本地化系统。
第一件事情就是声明支持哪些语言。这个是在模块的 PreInitialize 方法中完成的,如下所示:
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"));
在服务端,可以先注入 ILocalizationManager ,然后使用它。在客户端,可以使用 abp.localization Javascript API来获得所有可使用的语言和当前的语言。famfamfam-flag-england (和tr)只是一个Css类而已,你可以根据自己的需要改变它。然后在UI上使用它来展示相关的旗帜(比如各种国旗)。
ABP模板使用了本地化系统给用户呈现的是一个 切换语言 的下拉列表。你可以创建一个模板项目然后学习一下源代码。
本地化文本可以存储在不同的资源中。甚至你可以在相同的应用中使用不止一种语言(如果你有不止一个模块,每个模块可以定义一个单独的本地化资源)。应该为本地化资源实现 ILocalizationSource 接口,然后将它注册到ABP的本地化配置中。
每个本地化资源必须有一个 唯一的资源名 。 XML文件 和 资源 文件是预定义的本地化资源类型。
本地化文本可以存储在XML文件中,XML文件的内容就像下面展示的那样:
<?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文件必须是 utf-8 编码, culture="en" 声明该XML文件包含了英语文本。对于文本节点, name 特性用于标识一个文本。你可以使用 value 特性或者 inner text (如上面的最后一个)给本地化文本赋值。如下所示,我们为 每种语言 创建了一个单独的XML文件:
这里, SimpleTaskSystem 是 资源名称 ,SimpleTaskSystem定义了 默认的语言 。当请求一个文本时,ABP会从当前语言的XML文件中获取文本(使用Thread.CurrentThread. CurrentUICulture 找到当前的语言)。如果不存在当前语言的文本,就会从默认语言的XML文件中获得文本。
XML文件可以存储在文件系统中或者可以内嵌在一个程序集中。
对于 文件系统 存储的XML,我们可以注册一个XML本地化资源,如下所示:
Configuration.Localization.Sources.Add( new DictionaryBasedLocalizationSource( "SimpleTaskSystem", new XmlFileLocalizationDictionaryProvider( HttpContext.Current.Server.MapPath("~/Localization/SimpleTaskSystem") ) ) );
这个是在模块的 PreInitialize 事件中完成的(看 模块系统 获取更多信息)。ABP会找到所有给定目录的XML文件并注册这些本地化资源。
对于 内嵌的XML文件 ,我们应该将所有的本地化XML文件标记为内嵌的资源(选中xml文件,打开属性窗口,将生成操作的值改为‘内嵌的资源’)。然后像下面那样注册该本地化资源:
Configuration.Localization.Sources.Add( new DictionaryBasedLocalizationSource( "SimpleTaskSystem", new XmlEmbeddedFileLocalizationDictionaryProvider( Assembly.GetExecutingAssembly(), "MyCompany.MyProject.Localization.Sources" ) ) );
XmlEmbeddedFileLocalizationDictionaryProvider会获得包含XML文件的程序集(GetExecutingAssembly简单地指向当前的程序集)和XML文件的 命名空间 (程序集名称+xml文件的文件夹层次)。
注意:当给内嵌的XML文件起名字时,要加上语言后缀,但是不要使用“.”,比如“MySource.ch.xml”,而要使用短号“-”,比如“MySource-en.xml”。因为当寻找资源时,“.”会造成问题。
本地化文本也可以存储在.NET的资源文件中。我们可以为每种语言创建一个资源文件,如下所示:
MyTexts.resx包含了默认的语言文本, MyTexts.tr.resx包含了土耳其语言的文本。当我们打开MyTexts.resx时,我们可以看到所有文本:
在这种情况下,ABP使用了.NET中内置的本地化资源管理者。你应该为该资源配置一个本地化资源:
Configuration.Localization.Sources.Add( new ResourceFileLocalizationSource( "MySource", MyTexts.ResourceManager ));
这里, MySource 是资源的 唯一名字 ,而且 MyTexts.ResourceManager 是获取本地化文本的资源管理者的引用。这个是在模块的 Initialize 事件中完成的。
自定义本地化资源实现了在不同的资源中(比如数据库)存储文本。你可以直接实现 ILocalizationSource 接口或者从 DictionaryBasedLocalizationSource 类中派生可以使得实现更容易。
当创建了资源并把它注册到ABP的本地化系统之后,文本就能轻易地本地化了。
在服务端,我们可以注入 ILocalizationManager ,然后使用它的 GetString 方法。
var s1 = _localizationManager.GetString("SimpleTaskSystem", "NewTask");
GetString方法会基于 当前线程的UI文化(culture) 获取字符串。如果没有找到,就会返回 默认语言 对应的字符串。如果任何地方都没有定义该字符串,就会默认返回使用[and]包装的 给定字符串 (而不是抛出异常)。这种行为是可以配置的(你可以在模块的PreInitialize中使用Configuration.Loacalization.ReturnGivenTextIfNotFound属性进行配置)。
记得不要重复资源的名字(运行时会报错),你可以首先获得 该资源 ,然后从该资源中获得字符串:
var source = _localizationManager.GetSource("SimpleTaskSystem"); var s1 = source.GetString("NewTask");
这会返回当前语言的文本。GetString方法也有重载方法通过参数获取不同语言和格式的文本。
如果我们不能注入ILocalizationManager(也许在一个不能到达依赖注入系统的静态上下文中),那么可以简单地使用 LocalizationHelper 静态类。但是尽可能地注入并使用ILocalizationManager,因为LocalizationHelper是静态的而且静态不好测试(对于写单元测试的人来说)。
如果你需要在 应用服务 、MVC控制器、Razor视图或者其他派生自 AbpServiceBase 的类中本地化,那么可以使用快捷的 L 方法。
一般在MVC控制器和视图中需要本地化文本。这里有一个快捷方式,如下所示:
public class HomeController : SimpleTaskSystemControllerBase { public ActionResult Index() { var helloWorldText = L("HelloWorld"); return View(); } }
L方法用于本地化一个字符串。当然,你必须提供一个资源名,这里的HelloWorld就是从资源中找到的。它是在控制器基类SimpleTaskSystemControllerBase(以ControllerBase为后缀的控制器)中完成的,如下所示:
public abstract class SimpleTaskSystemControllerBase : AbpController { protected SimpleTaskSystemControllerBase() { LocalizationSourceName = "SimpleTaskSystem"; } }
注意L派生自 AbpController 。因此,你可以使用 L 方法轻松地本地化文本。
在视图中也可以使用相同的 L 方法:
<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>
为了能够这样使用,你应该让你的视图派生自设置了资源名的一个基类:
public abstract class SimpleTaskSystemWebViewPageBase : SimpleTaskSystemWebViewPageBase<dynamic> { } public abstract class SimpleTaskSystemWebViewPageBase<TModel> : AbpWebViewPage<TModel> { protected SimpleTaskSystemWebViewPageBase() { LocalizationSourceName = "SimpleTaskSystem"; } }
而且要在web.config中设置这个视图基类:
<pages pageBaseType="SimpleTaskSystem.Web.Views.SimpleTaskSystemWebViewPageBase">
当你从ABP模板创建解决方案时,对于视图和控制器的所有这些都已经准备好了。
ABP也使得在javascript代码中使用相同的本地化文本成为了可能。首先,你应该将动态的ABP脚本添加到页面中:
<script src="/AbpScripts/GetScripts" type="text/javascript"></script>
ABP在客户端会自动生成需要的javascript代码来获得本地化的文本。然后,你就可以轻松地使用javascript获得一个本地化的文本,如下所示:
var s1 = abp.localization.localize('NewTask', 'SimpleTaskSystem');
这里,NewTask是文本名,SimpleTaskSystem是资源名。记住不要重复资源名。你也可以先获得资源名,然后获得文本:
var source = abp.localization.getSource('SimpleTaskSystem'); var s1 = source('NewTask');
本地化方法也可以获得额外的格式参数。例子:
abp.localization.localize('RoleDeleteWarningMessage', 'MySource', 'Admin'); //如果资源找到了,就使用上面的getSource作为快捷方式 source('RoleDeleteWarningMessage', 'Admin');
如果RoleDeleteWarningMessage = 'Role {0} will be deleted', 那么本地化后的文本就是'Role Admin will be deleted'。
假设我们已经有一个定义了自己本地化资源的模块。我们可能需要更改它的本地化文本,或者添加一些新的文本或者翻译为其他的语言。如何解决这个呢?ABP允许扩展一个本地化资源,当前只对XML文件有效(实际上任何本地化资源都实现了IDictionaryBasedLocalizationSource接口)。
ABP也定义了一些本地化资源。比如, Abp.Web nuget包定义了一个叫做 AbpWeb 的本地化资源作为内嵌XML文件:
默认的(英语)XML文件像下面这个样子(这里只展示一部分文本):
<?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>
要扩展AbpWeb资源,我们可以定义XML文件。假设我们只想更改 InternalServerError 文本,我们就可以像下面那样定义一个XML文件:
<?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>
然后我们在模块的PreInitialize方法中注册它:
Configuration.Localization.Sources.Extensions.Add( new LocalizationSourceExtensionInfo("AbpWeb", new XmlFileLocalizationDictionaryProvider( HttpContext.Current.Server.MapPath("~/Localization/AbpWebExtensions") ) ) );
如果我们想创建内嵌的资源XML文件(上面有讲),那么可以使用XmlEmbeddedFileLocalizationDictionaryProvider。ABP使用XML文件合并了基本的本地化资源。我们也可以添加新的语言文件。
ABP提供了使用不同资源进行本地化的功能,也提供了在服务端和客户端代码中使用相同的本地化文本的基础设施。
XML文件和资源文件都有自己的长处和弱势。建议为可重复使用的模块使用XML文件,因为它很容易在代码之外为模块添加新的语言。此外,如果你使用XML,建议不要按照name进行排序文本,而要按照创建日期进行排序。这样,当别人要将文本翻译为其他语言时,ta就可以轻松地看到哪一个文本是新添加的。
通过实现 ILocalizationSource 接口,你也可以创建你自己的本地���资源并集成到ABP中。 Module-zero 实现了一个 基于数据库 和 各租户 的本地化资源。 点击查看文档