65.9K
CodeProject 正在变化。 阅读更多。
Home

在 ASP.NET MVC 中使用 Griffin.MvcContrib 进行本地化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (10投票s)

2012年3月25日

LGPL3

10分钟阅读

viewsIcon

140336

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 中的一个自述文件,其中包含更多说明。但我们先跳过它,继续。

安装包后,我们需要配置框架并添加一个用于翻译的字符串表。

创建字符串表

  1. 右键单击项目文件
  2. 选择“添加新项”
  3. 向下滚动列表并选择“资源文件”
  4. 将其命名为“LocalizedStrings”
  5. 单击“确定”

配置框架

我们需要用框架中的元数据提供程序替换内置的元数据提供程序。通常在 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; }
}   

这意味着我们应该在字符串表中输入以下条目

String table with our localized strings

请注意,验证属性仅按其名称输入(不带 Attribute 后缀)。

然而,属性名称是常见的名称,您可能需要多次重复 FirstName 的翻译(每个模型一次)。有一个内置的解决方案。将类名替换为“CommonPrompts”

Made the translations available for all view models and added a specialized age prompt for one model

正如您可能注意到的,我们仍然有一个 UserViewModel 的翻译。框架将始终优先选择特定模型的翻译而不是通用翻译。

检测缺失的翻译

该框架使用一个名为 DefaultUICulture 的类来检测默认语言是否处于活动状态。默认语言永远不会显示缺失提示的标签(假定属性为默认语言)。只需将 DefaultUICulture 更改为其他内容即可启用检测。

Showing how missing texts looks like

langCode:[] 环绕所有缺失的翻译。

使用动态源

使用字符串表相当僵化。您无法跟踪更改,也无法让字符串自动插入到数据源中。您也看不到翻译在哪里被使用。一段时间后,您可能会有一个包含许多未使用的字符串的字符串表。 

解决方案是切换到本地化存储库。该框架为视图定义了一个存储库,为类型定义了一个存储库。

该框架包含三种支持的源

  1. 平面文件,使用 JSON 存储翻译
  2. SqlServer(可以轻松适应其他数据库引擎)
  3. 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 使用 ILocalizedStringProviderRepositoryStringProvider 实现)来查找视图翻译。
  • 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 允许您指定描述、水印、空显示文本等。这些元数据字符串默认在管理区域中隐藏,但可以通过切换复选框来显示。

翻译提示的屏幕截图: 

Type translation

视图翻译

视图翻译的工作方式与类型翻译类似,只是整个段落一次性被翻译,并且它们支持字符串格式化(如 string.Format())。

View translation

导出翻译

您可能有一个包含所有翻译并经过验证的测试/开发系统。那么您可能希望将这些翻译也迁移到生产系统。这可以通过导出/导入功能来实现。 

您首先筛选出要导出的视图(或类型)。

Filtering

然后按“预览”按钮查看您将获得哪些提示。

Preview result

如果满意,请按“创建”,系统将提示您下载一个包含所有翻译的 JSON 文件。

Save as

导入翻译

很简单。只需上传 JSON 文件。所有现有提示将被替换,所有新提示将被插入。

Translated prompts

一些技巧

以下部分包含一些可以帮助您进行本地化过程的技巧。

选择语言

该框架内置了一个动作过滤器,可以为您选择语言。设置保存在 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 文章初版
© . All rights reserved.