在 ASP.NET MVC 中使用 Griffin.MvcContrib 进行本地化
Griffin.MvcContrib 提供了本地化功能,无需任何代码更改。
引言
Griffin.MvcContrib 是我为 ASP.NET MVC3 贡献的项目,包含多项功能。本文将介绍框架中的本地化功能。
功能包括以下几项(我将逐一介绍)
- 验证本地化(无需属性即可本地化验证消息)
- 模型本地化(无需 Display Attribute)
- 视图本地化
背景
MVC3 的默认本地化方法是在属性中指定信息,这会产生类似的代码
public class UserViewModel { [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))] [DisplayName(ErrorMessageResourceName = "UserId", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))] [Description(ErrorMessageResourceName = "UserIdDescription", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))] public int Id { get; set; } [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))] [DisplayName(ErrorMessageResourceName = "UserFirstName", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))] [Description(ErrorMessageResourceName = "UserFirstNameDescription", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))] public string FirstName { get; set; } [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))] [DisplayName(ErrorMessageResourceName = "UserLastName", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))] [Description(ErrorMessageResourceName = "UserLastNameDescription", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))] public string LastName { get; set; } }
这使得代码难以阅读,并且您必须为每个想要本地化的类重复此操作。
稍微谷歌一下,您会发现另一种方法可以减少配置量,那就是继承默认属性并引入自己的属性。
public class UserViewModel { [LocalizedRequired] [LocalizedDisplayName(ErrorMessageResourceName = "UserId")] [LocalizedDescription(ErrorMessageResourceName = "UserIdDescription")] public int Id { get; set; } [LocalizedRequired] [LocalizedDisplayName(ErrorMessageResourceName = "UserFirstName")] [LocalizedDescription(ErrorMessageResourceName = "UserFirstNameDescription")] public string FirstName { get; set; } [LocalizedRequired] [LocalizedDisplayName(ErrorMessageResourceName = "UserLastName")] [LocalizedDescription(ErrorMessageResourceName = "UserLastNameDescription")] public string LastName { get; set; } }
该解决方案更简洁,代码重复也减少了。问题是自定义验证属性会导致客户端验证停止工作,因为 MVC 使用的适配器无法识别您的属性。可以通过创建内置适配器和自定义属性之间的映射来解决此问题。
使用 Griffin.MvcContrib 进行本地化
Griffin.MvcContrib 中的模型和验证本地化使您的模型更加简洁。
public class UserViewModel
{
[Required]
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
就是这样。框架会处理其余的事情。这就是框架的初始目标。本地化功能后来也扩展到了一个管理区域,您可以在其中管理翻译和视图字符串的翻译。
使用字符串表进行模型/验证本地化
假设我们创建了一个新的 ASP.NET MVC3 项目,并希望本地化模型及其验证消息。为此,我们将使用 NuGet 通过包管理器控制台(工具 -> 库包管理器 -> 包管理器控制台)安装 Griffin.MvcContrib 核心包。
这将安装该包。它还会安装 App_ReadMe 中的一个自述文件,其中包含更多说明。但我们先跳过它,继续。
安装包后,我们需要配置框架并添加一个用于翻译的字符串表。
创建字符串表
- 右键单击项目文件
- 选择“添加新项”
- 向下滚动列表并选择“资源文件”
- 将其命名为“LocalizedStrings”
- 单击“确定”
配置框架
我们需要用框架中的元数据提供程序替换内置的元数据提供程序。通常在 global.asax
中执行此操作。我们还将指定我们将使用一个字符串表及其名称。
var stringProvider = new ResourceStringProvider(Resources.LocalizedStrings.ResourceManager); ModelMetadataProviders.Current = new LocalizedModelMetadataProvider(stringProvider); ModelValidatorProviders.Providers.Clear(); ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider(stringProvider));
所用类的简要说明
-
ModelValidatorProviders
- ASP.NET MVC 中用于跟踪所有验证提供程序的类。默认提供程序称为DataAnnotationsModelValidatorProvider
。 -
ModelMetadataProviders
- ASP.NET MVC 类,用于向 HTML 助手和验证器提供程序提供元数据。 -
ResourceStringProvider
- 用于从资源文件/字符串表加载字符串翻译。 -
LocalizedXxxxxProvider
- Griffin.MvcContrib 提供的提供程序。
字符串翻译
每个要翻译的字符串都应遵循以下格式 ClassName_PropertyName
。假设我们的一个视图模型如下所示
public class UsersViewModel { public int Age { get; set; } [Required] public string FirstName { get; set; } [Required] [StringLength(50)] public string LastName { get; set; } }
这意味着我们应该在字符串表中输入以下条目
请注意,验证属性仅按其名称输入(不带 Attribute 后缀)。
然而,属性名称是常见的名称,您可能需要多次重复 FirstName 的翻译(每个模型一次)。有一个内置的解决方案。将类名替换为“CommonPrompts”
正如您可能注意到的,我们仍然有一个 UserViewModel
的翻译。框架将始终优先选择特定模型的翻译而不是通用翻译。
检测缺失的翻译
该框架使用一个名为 DefaultUICulture
的类来检测默认语言是否处于活动状态。默认语言永远不会显示缺失提示的标签(假定属性为默认语言)。只需将 DefaultUICulture
更改为其他内容即可启用检测。
langCode:[]
环绕所有缺失的翻译。
使用动态源
使用字符串表相当僵化。您无法跟踪更改,也无法让字符串自动插入到数据源中。您也看不到翻译在哪里被使用。一段时间后,您可能会有一个包含许多未使用的字符串的字符串表。
解决方案是切换到本地化存储库。该框架为视图定义了一个存储库,为类型定义了一个存储库。
该框架包含三种支持的源
- 平面文件,使用 JSON 存储翻译
- SqlServer(可以轻松适应其他数据库引擎)
- RavenDb(NoSQL 数据库)
使用这些源之一将在每次访问尚未翻译的视图(或请求模型字符串)时自动在数据源(数据库/平面文件等)中创建字符串。但由于翻译文本为空,缺失文本检测仍然有效。
使用 SqlServer
在本文中,我选择 SqlServer 作为数据源。GitHub 上的 Wiki 展示了如何使用其他源。
请注意,SQL 源需要与数据库建立连接,并且我们不能在应用程序生命周期内保持一个打开的连接。因此,SQL 存储库需要一个反转控制容器来管理存储库及其依赖项的生命周期。
以下示例使用 Autofac 作为容器。配置它超出了本文的范围(它在演示项目中完成)
安装 nuget 包。
首先要做的是安装 SqlServer 的 nuget 包。该包名为 griffin.mvccontrib.sqlserver
。
创建数据库表。
SQL 脚本可以在 这里 找到。从 Visual Studio 或 SQL Server Management Console 中运行它。
在 web.config 中配置连接字符串
典型的连接字符串
<add name="DemoDb" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|LocalizationDb.mdf;User Instance=true" providerName="System.Data.SqlClient" />
在 global.asax 中配置框架
以下代码在反向控制容器中注册框架类。它在 Application_Start()
中完成
// Register the framework providers ModelValidatorProviders.Providers.Clear(); ModelMetadataProviders.Current = new LocalizedModelMetadataProvider(); ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider()); // Loads strings from repositories. builder.RegisterType<RepositoryStringProvider>().AsImplementedInterfaces().InstancePerLifetimeScope(); builder.RegisterType<ViewLocalizer>().AsImplementedInterfaces().InstancePerLifetimeScope(); // Connection factory used by the SQL providers. builder.RegisterInstance(new AdoNetConnectionFactory("DemoDb")).AsSelf(); builder.RegisterType<LocalizationDbContext>().AsImplementedInterfaces().InstancePerLifetimeScope(); // and the repositories builder.RegisterType<SqlLocalizedTypesRepository>().AsImplementedInterfaces().InstancePerLifetimeScope(); builder.RegisterType<SqlLocalizedViewsRepository>().AsImplementedInterfaces().InstancePerLifetimeScope();
所用类的简要说明
-
builder
是用于构建 Autofac 容器的对象。 -
RepositoryStringProvider
是一个 Griffin.MvcContrib 类,它使用存储库查找所有翻译。 -
ViewLocalizer
使用ILocalizedStringProvider
(RepositoryStringProvider
实现)来查找视图翻译。 -
AdoNetConnectionFactory
使用 web.config 中的连接字符串来构建 ADO.NET 连接类。 -
LocalizationDbContext
在一个 HTTP 请求中保持相同的连接。 -
SqlLocalizedTypesRepository
&SqlLocalizedViewsRepository
是本地化存储库类的 SQL Server 实现。
任何缺失的字符串现在都应该被写入您的数据库(以便您可以翻译它们)。
视图本地化
您也可以让框架处理视图本地化。您需要做的唯一激活功能就是更改视图的基类。这在 Views\web.config
中完成。
<system.web.webPages.razor> <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <pages pageBaseType="Griffin.MvcContrib.GriffinWebViewPage">
要处理翻译,只需使用 @T("")
包装文本。这是一个示例视图
@{ ViewBag.Title = T("About Us"); } <h2>@ViewBag.Title</h2> <p> @T("Put content here.") </p> <p> @T("You can also use {0} formatting!", "string") </p> <p> @T("And format the {0}.", T("Formatters")) </p>
Administration
另一项繁琐的任务是处理翻译和翻译文本。有一个内置的管理区域(**仍有些基础/处于开发中**),您可以将其包含在您的项目中。
背景
管理部分是一个常规的 ASP.NET MVC Area。不同之处在于它位于一个类库中,因此您需要重新配置 ASP.NET MVC 以能够定位该 Area 的视图。
这是借助自定义 VirtualPathProvider
完成的。这种方法的缺点是只能存在一个 VirtualPathProvider
,这可能会有问题。幸运的是,Griffin.MvcContrib 中提供的提供程序是可扩展的,并允许我们使用多个源来定位文件。不仅仅是文件系统或嵌入式资源。 您可以在 这里 找到虚拟路径提供程序。
Authorization
管理区域使用基于角色的授权,默认角色名称为
-
Admin
- 访问该区域 -
Translator
- 可以翻译视图和类型 -
AccountAdmin
- 账户管理(账户管理尚未完成)
可以通过更改名为 GriffinAdminRoles.
的类的属性值来更改角色的名称。
配置
首先安装 nuget 包 griffin.mvccontrib.admin
。然后转到您的 global.asax
并按以下方式配置
// you can assign a custom WebViewPage or a custom layout in EmbeddedViewFixer.
var fixer = new EmbeddedViewFixer();
var provider = new EmbeddedViewFileProvider(fixer);
provider.Add(new NamespaceMapping(typeof(MvcContrib.Areas.Griffin.GriffinAreaRegistration).Assembly, "Griffin.MvcContrib"));
GriffinVirtualPathProvider.Current.Add(provider);
HostingEnvironment.RegisterVirtualPathProvider(GriffinVirtualPathProvider.Current);
所用类的简要说明
-
EmbeddedViewFixer
转换嵌入式视图,使其像常规视图一样工作。这意味着您无需对它们做任何特殊处理,仅仅因为它们是嵌入式的。 -
EmbeddedViewFileProvider
用于处理嵌入在程序集中的视图。 -
GriffinVirtualPathProvider
是实际的虚拟路径提供程序。 -
HostingEnvironment
是 ASP.NET 中用于配置环境的类;)
我们还需要告诉 autofac 它应该从 Griffin.MvcContrib.Admin DLL 提供控制器。这是这样实现的
builder.RegisterControllers(typeof (GriffinAdminRoles).Assembly);
这就是所有必需的,基本上是如何借助 Griffin.MvcContrib 创建一个插件系统。
类型管理
类型与视图翻译不同之处在于它们包含大量元数据。MVC3 允许您指定描述、水印、空显示文本等。这些元数据字符串默认在管理区域中隐藏,但可以通过切换复选框来显示。
翻译提示的屏幕截图:
视图翻译
视图翻译的工作方式与类型翻译类似,只是整个段落一次性被翻译,并且它们支持字符串格式化(如 string.Format()
)。
导出翻译
您可能有一个包含所有翻译并经过验证的测试/开发系统。那么您可能希望将这些翻译也迁移到生产系统。这可以通过导出/导入功能来实现。
您首先筛选出要导出的视图(或类型)。
然后按“预览”按钮查看您将获得哪些提示。
如果满意,请按“创建”,系统将提示您下载一个包含所有翻译的 JSON 文件。
导入翻译
很简单。只需上传 JSON 文件。所有现有提示将被替换,所有新提示将被插入。
一些技巧
以下部分包含一些可以帮助您进行本地化过程的技巧。
选择语言
该框架内置了一个动作过滤器,可以为您选择语言。设置保存在 cookie 中,因此只要用户允许 cookie,它就可以工作。您所要做的就是添加一个包含 ?lang=sv-se
作为查询字符串的链接。它会被动作过滤器捕获。
动作过滤器本身应该装饰您的基控制器。
[Localized]
public class BaseController : Controller
{
}
缓存
消息缓存未内置于框架中。但我建议为流量高的网站实现缓存。最简单的方法是继承 RepositoryStringProvider
和 ViewLocalizer
,如下所示。
// Caching view texts
public class CachedViewLocalizer : ViewLocalizer
{
MyCacheClass _cache;
public override string Translate(RouteData routeData, string text)
{
string prompt;
if (_cache.TryGetValue(routeData, text, out prompt)
return prompt;
prompt = base.Translate(routeData, text);
_cache.Insert(routeData, text, prompt);
return prompt;
}
}
// caching type translations
public class CachedTypeLocalizer : RepositoryStringProvider
{
MyCacheClass _cache;
public override string Translate(Type type, string name)
{
var promptName = type.FullName + "." + text;
string prompt;
if (_cache.TryGetValue(promptName, out prompt)
return prompt;
prompt = base.Translate(type, name);
_cache.Insert(promptName, prompt);
return prompt;
}
}
最后,在 IoC 容器中注册您的实现。
本地化 Views/_layout.cshtml
本地化框架使用控制器/操作作为每个翻译的基础(因此您可以拥有相同的短语,但含义不同)。这在大多数情况下效果很好。
但是,由于框架无法区分文本是来自布局还是视图,您将为所有页面获得布局提示。
简单的解决方案是访问一个页面,然后转到管理区域,翻译所有布局提示并将它们推为通用提示。
如果您有更好的解决方案(也能与 Area 一起工作),请随时留下评论。
关注点
我偶然发现了一篇关于 ASP.NET MVC3 扩展点 的精彩文章,由 Brad Wilson 撰写。必读。
最后的寄语
正如您可能注意到的,英语不是我的母语。我希望您喜欢这篇文章以及它所描述的框架。
Griffin.MvcContrib 还提供了一组可扩展的 HTML 助手,允许您在 HTML 标签输出到 HTML 之前对其进行修改。
还有一个成员资格提供程序,它使用反向控制(服务定位)来定位其依赖项。这使得编写自定义成员资格提供程序的过程更加容易。
请将所有 bug 和功能请求发布到 github,而不是在这里作为评论留下。
历史
- 2012-03-23 文章初版