获取构建您的第一个多语言 ASP.NET MVC 5 Web 应用程序的见解
本文解释了如何创建一个简单的多语言 ASP.NET MVC 5 Web 应用程序。我将展示如何翻译文本、本地化图像或整个视图,以及如何处理 URL 路由以支持多种语言。
引言
本文解释了如何创建一个简单的多语言 ASP.NET MVC 5 Web 应用程序。该应用程序将能够处理英语(美国)、西班牙语和法语。英语将是默认语言。当然,扩展解决方案以包含新语言将非常容易。
首先,假设读者对 ASP.NET MVC 5 框架有基本的了解。其他相关技术,如 jQuery,将在文章中略作评论,因为它们已被用于添加新功能。我将重点解释 MVC 框架组件。无论如何,我将尝试解释每个组件的工作原理,或者至少为您提供获取更详细信息的链接。本文的目标不是解释每种技术的每一行代码或重复其他文章中已详细说明的解释。我的目标将是解释我们演示应用程序的主要功能,同时提醒关键问题以获得更好的理解。
话虽如此,我们的演示应用程序将有一个 `HomeController`,其中包含三个基本的典型操作,例如 `Index`、`About` 和 `Contact`。此外,我们稍后将使此控制器从 `BaseController` 派生,以为每个控制器提供通用功能。因此,从 `HomeController` 调用的 `Views` 中呈现的内容将根据默认或用户选择的语言进行本地化。
背景
在我看来,谈到多语言 ASP.NET MVC 应用程序时,至少需要考虑以下关键问题:
- 考虑应用全球化和本地化的组件。
- 配置多语言目的的 URL 路由,尤其要考虑到 SEO 视角。关于这一点,最重要的问题是保持不同 URL 上的不同内容,绝不能从同一个 URL 提供不同的内容。
全球化和本地化
我们必须能够为控制器上正在运行的当前 Thread 上处理的每个请求设置正确的文化。因此,我们将创建一个 `CultureInfo` 对象,并在 `Thread` 上设置 `CurrentCulture` 和 `CurrentUICulture` 属性(在此处查看更多信息),以根据适当的文化提供内容。为此,我们将从 Url Routing 中提取文化信息。
`CurrentCulture` 属性指的是全球化,即如何管理日期、货币或数字。相反,`CurrentUICulture` 管理本地化,即如何翻译或本地化内容和资源。在本文中,我将重点关注本地化。
`CultureInfo` 类是根据每个特定文化的唯一名称实例化的。此代码使用“xx-XX”模式,其中前两个字母代表语言,后两个字母代表子语言、国家或地区(在此处查看更多信息)。在此演示应用程序中,en-US、es-ES 和 fr-FR 代码表示支持语言英语(美国)、西班牙语(西班牙)和法语(法国)。
话虽如此,以下是根据文化需要本地化的元素列表:
- 纯文本。
- 我们将使用 `资源文件` 翻译文本。简而言之,这些文件允许我们根据键/值对的字典保存内容资源,主要是文本和图像。我们只会使用这些文件来存储文本,而不是图像。在 Microsoft 演练 中阅读更多内容。
- 图片。
- 我们将通过扩展 `UrlHelper` 类(包含在 `System.Web.Mvc.dll` 程序集中)来本地化图像。通过插入到此类的扩展方法,我们将在根据支持语言预先创建的文件夹结构中查找图像。简而言之,`UrlHelper` 类包含用于处理 MVC 应用程序中 URL 地址的方法。特别是,我们可以通过使用 `WebViewPage` 类中的 `Url` 内置属性在 `Razor 视图` 中获取 `UrlHelper` 类的引用。有关更多信息,请参阅此处。
- 客户端和服务器代码中的验证消息。
- 为了翻译服务器端验证消息,我们将使用 `资源文件`。
- 为了翻译客户端验证消息,我们将覆盖默认消息。由于我们将使用 jQuery Validation Plugin 1.11.1 来应用客户端验证,因此我们必须覆盖此插件的消息。本地化消息将根据支持的语言保存在单独的文件中。因此,为了访问本地化脚本文件,我们将再次扩展 `UrlHelper` 类。
- 根据应用程序的要求,可能需要本地化整个 `视图`。因此,我们将考虑这个问题。
- 在此演示中,英语(美国)和西班牙语将不使用此选项,但出于演示目的,法语将使用。因此,我们将从默认的 `RazorViewEngine` 派生一个新的 `ViewEngine` 以实现此目标。这个新的视图引擎将通过预先创建的文件夹树查找 `视图`。
- 其他脚本和 CSS 文件。
- 对于大型应用程序,可能需要考虑本地化的脚本和 CSS 文件。可以使用与图像文件相同的策略。我们不会深入探讨这个问题,只是简单地考虑一下。
- 来自后端存储组件(如数据库)的本地化内容。
- 在此演示应用程序中,我们不会使用数据库。这篇文章会太长。相反,我们将假设,如果需要,有关 `Thread` 上设置的当前文化的信息将从数据访问层提供给数据库。这样,应相应地返回相应的翻译文本或本地化图像。至少,如果您计划使用数据库中的本地化内容,请记住这一点。
让我们看看我们演示应用程序的一些屏幕截图
主页 英语 (美国) 版本
主页 西班牙语 版本
这是一个非常简单的应用程序,但足以深入了解多语言的关键问题。
- 主页视图页面包含本地化文本和图像。
- 关于视图页面仅包含本地化文本。
- 联系视图页面还包含本地化文本,但它也包含一个带有表单的局部视图,用于发布数据并对相应的底层模型应用客户端和服务器验证。
- 共享的 错误视图页面 也将被翻译。
- 布局视图页面提供了选择标志的列表。
URL 路由
首先,我们必须实现一个事实,即不从同一个 URL 提供不同的(基于语言的)内容。配置适当的 URL 路由对于根据不同的 URL 提供基于语言的内容是强制性的。因此,我们将配置路由,通过从 URL 路由中提取特定文化来包含多语言支持。
我们的 URL 地址,在调试模式下,将如下所示。我假设我们的演示应用程序正在 localhost 上使用 XXXXX 端口提供服务。
- 英语 (美国) 语言
- https://:XXXXX/Home/Index 或 https://:XXXXX/en-US/Home/Index
- https://:XXXXX/Home/About 或 https://:XXXXX/en-US/Home/About
- https://:XXXXX/Home/Contact 或 https://:XXXXX/en-US/Home/Contact
- 西班牙语
- https://:XXXXX/es-ES/Home/Index
- https://:XXXXX/es-ES/Home/About
- https://:XXXXX/es-ES/Home/Contact
- 法语
- https://:XXXXX/fr-FR/Home/Index
- https://:XXXXX/fr-FR/Home/About
- https://:XXXXX/fr-FR/Home/Contact
此外,我们将在 `Layout View Page` 中向用户提供支持语言列表。因此,用户可以通过单击其中一个来始终获取所需的语言。当用户选择不同的语言时,我们将使用 Cookie 来保存此手动选择。使用 Cookie 可能会引起争议。因此,是否使用它取决于您。这不是文章的关键点。我们将使用它,考虑到我们绝不会根据 URL 路由的内容从服务器端创建 Cookie。因此,如果给定用户从不手动更改语言,他将以他进入我们网站时使用的语言进行导航。下次用户进入我们网站时,如果 cookie 存在,他们将被重定向到根据他们上次语言选择的适当 URL。无论如何,再次记住,永远不要只考虑使用 Cookie、Session State、浏览器客户端用户设置等来从同一个 URL 提供不同的内容。
使用代码
创建多语言应用程序的第一步
我使用 Microsoft Visual Studio 2013 提供的简单 MVC 5 模板开始工作,将身份验证选项更改为“无身份验证”。如下所示,我的演示应用程序的名称是 MultiLanguageDemo。然后,我重新排列了文件夹,如下所示:
- 请注意 Content 目录下的文件夹。就个人而言,我喜欢这种结构来存储 Images、Scripts、Styles 和 Texts。每次您创建新目录并向其中添加类时,默认会添加一个与文件夹名称相同的命名空间。请记住这一点。我修改了 Views 文件夹中的 `web.config` 文件以包含这些新的命名空间。通过这样做,您可以直接从 Razor 视图代码片段访问这些命名空间中的类。
- 由于 en-US 将是默认文化,因此有必要相应地配置 `web.config`。
- 我们将使用自定义的 `GlobalHelper` 类来包含全局通用功能,例如读取 `Thread` 上的当前文化或 `web.config` 中的默认文化。以下是代码:
public class GlobalHelper { public static string CurrentCulture { get { return Thread.CurrentThread.CurrentUICulture.Name; } } public static string DefaultCulture { get { Configuration config = WebConfigurationManager.OpenWebConfiguration("/"); GlobalizationSection section = (GlobalizationSection)config.GetSection("system.web/globalization"); return section.UICulture; } } }
设置 URL 路由
我们将有两个路由,`LocalizedDefault` 和 `Default`。我们将使用 `lang` 占位符来管理文化。以下是 `RouteConfig.cs` 文件中 `RouteConfig` 类中的代码(在此处查看有关 URL 路由的更多信息):
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "LocalizedDefault", url: "{lang}/{controller}/{action}", defaults: new { controller = "Home", action = "Index"}, constraints: new {lang="es-ES|fr-FR|en-US"} ); routes.MapRoute( name: "Default", url: "{controller}/{action}", defaults: new { controller = "Home", action = "Index", lang = en-US } ); } }
一方面,`Default` 路由将用于匹配未明确指定文化的 URL。因此,我们将配置它以使用默认文化。请注意 `defaults` 参数中 `lang` 如何设置为 en-US 文化。
另一方面,`LocalizedDefault` 路由配置为在 URL 上使用特定文化。此外,`lang` 参数被限制为包含在支持的语言 es-ES、fr-FR 或 en-US 中。请注意这是如何通过在 `MapRoute` 方法中设置 `constraints` 参数来配置的。通过这种方式,我们将覆盖所有先前建立的路由。
配置控制器以提供适当的基于语言的内容
正如我之前所说,为了切换文化,需要创建一个 `CultureInfo` 对象来设置处理发送到控制器的每个 http 请求的 `Thread` 上的 `CurrentCulture` 和 `CurrentUICulture` 属性。使用 MVC 5,有几种方法可以实现这一点。在这种情况下,我将创建一个抽象的 `BaseController` 类,其余控制器将从该类派生。`BaseController` 将包含通用功能,并将覆盖 `System.Web.Mvc.Controller` 类中的 `OnActionExecuting` 方法。关于 `OnActionExecuting` 方法的关键点是,它总是在调用控制器方法之前被调用。
最后,简单地说,另一种实现此目的的方法是通过全局操作过滤器而不是使用基类。在此示例中未考虑,但如果您更喜欢,请记住它。
让我们看看我们的 `BaseController` 类代码:
public abstract class BaseController : Controller { private static string _cookieLangName = "LangForMultiLanguageDemo"; protected override void OnActionExecuting(ActionExecutingContext filterContext) { string cultureOnCookie = GetCultureOnCookie(filterContext.HttpContext.Request); string cultureOnURL = filterContext.RouteData.Values.ContainsKey("lang") ? filterContext.RouteData.Values["lang"].ToString() : GlobalHelper.DefaultCulture; string culture = (cultureOnCookie == string.Empty) ? (filterContext.RouteData.Values["lang"].ToString()) : cultureOnCookie; if (cultureOnURL != culture) { filterContext.HttpContext.Response.RedirectToRoute("LocalizedDefault", new { lang=culture, controller = filterContext.RouteData.Values["controller"], action = filterContext.RouteData.Values["action"] }); return; } SetCurrentCultureOnThread(culture); if (culture != MultiLanguageViewEngine.CurrentCulture) { (ViewEngines.Engines[0] as MultiLanguageViewEngine).SetCurrentCulture(culture); } base.OnActionExecuting(filterContext); } private static void SetCurrentCultureOnThread(string lang) { if (string.IsNullOrEmpty(lang)) lang = GlobalHelper.DefaultCulture; var cultureInfo = new System.Globalization.CultureInfo(lang); System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo; System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo; } public static String GetCultureOnCookie(HttpRequestBase request) { var cookie = request.Cookies[_cookieLangName]; string culture = string.Empty; if (cookie != null) { culture= cookie.Value; } return culture; } }
`BaseController` 类覆盖了 `OnActionExecuting` 方法。然后,我们从 URL 路由和 Cookie 中获取特定文化的信息。如果没有 Cookie,`Thread` 上的文化将从 URL 路由设置。否则,如果最终用户手动选择了一种语言并且存在 Cookie,则 http 响应将被重定向到包含 Cookie 中存储的语言的相应路由。
此外,为了在 Thread 上设置当前文化,`BaseController` 使用 `SetCurrentCultureOnThread` 私有函数。首先,根据作为参数传递的特定文化创建一个新的 `CultureInfo` 类。最后,将当前 `Thread` 的 `CurrentUICulture` 和 `CurrentCulture` 属性分配给先前创建的 `CultureInfo` 对象。
处理纯文本
为了翻译纯文本,我们将使用 `资源文件`。这些是存储待翻译文本的绝佳方式。存储基于键/值对的字典,其中键是标识给定资源的字符串,值是翻译后的文本或本地化的图像。在内部,所有这些信息都以 XML 格式保存,并由 Visual Studio Designer 动态编译。
`资源文件` 具有 RESX 扩展名。因此,在此演示中,我们将为默认文化创建三个不同的 `资源文件`。一个用于存储全局文本,`RGlobal.resx`;另一个用于一般错误消息,`RError.resx`;最后一个用于存储与 HomeController 相关的消息,`RHome.resx`。我喜欢在我的项目中创建这种资源文件结构,通常每个控制器包含一个资源文件,但如果您愿意,可以选择其他方式。
对于其他支持语言,我们将创建名为 RGlobal.es-ES.resx、RError.es-ES.resx、RHome.es-ES.resx(西班牙语)和 RGlobal.fr-FR.resx、RError.fr-FR.resx 和 RHome.fr-FR.resx(法语)的资源文件。请注意每个名称的文化代码。以下是我们的 `资源文件` 树:
最重要的一点是
- 当您为默认文化创建资源文件,例如 `RGlobal.resx` 文件时,Visual Studio 会自动生成一个名为 `RGlobal` 的 `internal` 类。使用设计器,您应该将访问修饰符更改为 `public` 以便在解决方案中使用它。让我们看看我们的英语和西班牙语的 `RGlobal` 文件:
- 针对每种特定文化的资源在单独的程序集中编译,根据文化存储在不同的子目录中,并命名为 AssemblyName.resources.dll。在我们的例子中,名称将是 `MultiLanguageDemo.resources.dll`。
- 一旦在 `Thread` 上设置了特定文化,运行时将相应地选择程序集。
- 控制器或其他类可以通过将资源文件名与关键字连接起来来使用单个资源。例如 `RGlobal.About`、`RGlobal.AppName` 等。
- 要在使用 Razor 语法的视图中使用单个资源,您只需添加诸如 `@RHome.Title`、`@RHome.Subtitle` 或 `@RHome.Content` 之类的代码。
处理图像
正如我之前所说,我们将只在 `资源文件` 中存储文本,尽管也可以保存图像。就个人而言,我更喜欢以其他方式保存图像。让我们看看 Content 目录下的 Images 文件夹。
如您所见,已为每种文化创建了一个特定的文件夹。不需要本地化的图像将直接保存在 Images 文件夹中,例如 France.jpg 或 Spain.jpg。这些文件只包含用于显示和选择语言的标志,因此它们不需要本地化。需要本地化的其余图像将单独存储。例如,en-US 子目录下的 welcome.jpg 文件包含一张带有文本“Welcome”的图片,而 es-ES 子目录下的 welcome.jpg 文件包含一张带有文本“Bienvenido”的图片。
话虽如此,让我们继续在 `UrlHelper` 类中扩展 `GetImage` 方法以选择本地化图像。这个静态方法将包含在 Extensions 文件夹下的 `UrlHelperExtensions.cs` 文件中的 `UrlHelperExtensions` 静态类中。以下是代码:
public static class UrlHelperExtensions { public static string GetImage(this UrlHelper helper, string imageFileName, bool localizable=true) { string strUrlPath, strFilePath = string.Empty; if (localizable) { /* Look for current culture */ strUrlPath = string.Format("/Content/Images/{0}/{1}", GlobalHelper.CurrentCulture, imageFileName); strFilePath = HttpContext.Current.Server.MapPath(strUrlPath); if (!File.Exists(strFilePath)) { /* Look for default culture */ strUrlPath = string.Format("/Content/{0}/Images/{1}", GlobalHelper.DefaultCulture, imageFileName); } return strUrlPath; } strUrlPath = string.Format("/Content/Images/{0}", imageFileName); strFilePath = HttpContext.Current.Server.MapPath(strUrlPath); if (File.Exists(strFilePath)) { /* Look for resources in general folder as last option */ return strUrlPath; } return strUrlPath; } }
我们将通过添加一个新的 `GetImage` 方法来扩展 `UrlHelper`。此方法将允许我们在 Images 目录下查找本地化图像。我们只需通过向其传递正确的图像文件名来调用该方法。还有一个布尔参数用于设置图像是否已本地化。如果是,该方法将在根据当前文化对应的子目录中查找结果,如果未找到,则将尝试使用默认文化和一般文件夹,按此顺序。无论如何,如果一切都配置良好,第一次搜索应该就足够了。
模板 `View` 中的典型调用将是
<img src="@Url.GetImage("Welcome.jpg")" alt="@RGlobal.Welcome"/>
`Url` 是 `System.Web.Mvc.WebViewPage` 类的属性,所有 Razor 视图都派生自该类。此属性返回 `UrlHelper` 实例。通过这种方式,我们可以访问我们的 `GetImage` 方法。
处理验证消息
我们将同时考虑服务器端和客户端验证。为了对服务器端验证应用本地化,我们将使用 `资源文件`,而对于客户端验证,我们将创建一个类似于处理图像的目录结构。然后,我们将创建新的脚本文件来根据支持的语言覆盖默认消息,并且我们将扩展 `UrlHelper` 类以访问这些新文件。
服务器验证
服务器验证通常在控制器上对模型执行。如果验证不正确,包含模型状态的模型状态字典对象 `ModelState` 将被设置为不正确。在代码中,这等于将 `ModelState` 的 `IsValid` 属性设置为 `false`。因此,`ModelState` 字典将根据输入字段、全局验证等填充验证消息,这些消息应该被翻译。
在这个例子中,我将展示如何翻译源自 数据注解 的验证消息。在 MVC 项目中,使用 `System.ComponentModel.DataAnnotations` 中包含的类配置服务器验证非常常见。让我们看一个例子。
这是与应用于 `Contact View` 的 `Contact Model` 相关的代码
namespace MultiLanguageDemo.Models { [MetadataType(typeof(ContactModelMetaData))] public partial class ContactModel { public string ContactName { get; set; } public string ContactEmail { get; set; } public string Message { get; set; } } public partial class ContactModelMetaData { [Required(ErrorMessageResourceName = "RequiredField", ErrorMessageResourceType = typeof(RGlobal))] [Display(Name = "ContactName", ResourceType = typeof(RHome))] public string ContactName { get; set; } [Required(ErrorMessageResourceName = "RequiredField", ErrorMessageResourceType = typeof(RGlobal))] [Display(Name = "ContactEmail", ResourceType = typeof(RHome))] [DataType(DataType.EmailAddress)] public string ContactEmail { get; set; } [Required(ErrorMessageResourceName = "RequiredField", ErrorMessageResourceType = typeof(RGlobal))] [Display(Name = "Message", ResourceType = typeof(RHome))] public string Message { get; set; } } }
一方面,我们有一个包含三个简单属性的 `ContactModel` 类。另一方面,我们有一个 `ContactModelMetaData` 类,用于对 `ContactModel` 应用验证并设置更多功能或元数据,以便显示与字段、数据类型等相关的标签。
关于验证,我们将所有模型字段配置为 `Required`。因此,为了强制实现本地化,有必要引用与资源文件关联的自动生成的类。这通过 `ErrorMessageResourceType` 属性完成。我们还必须配置与我们想要显示的相应验证消息相关的关键字名称。这通过使用 `ErrorMessageResourceName` 属性完成。通过这种方式,将相应地返回资源文件中的消息——这些消息将根据文化自动选择。
客户端验证
通过使用客户端验证,可以在客户端执行验证,从而避免对控制器进行不必要的请求。我们将通过 jQuery Validation Plugin 1.11.1 和 jQuery Validation Unobtrusive Plugin 来利用此功能。当您使用 Microsoft Visual Studio MVC 5 模板项目启动新的 MVC 5 项目时,会自动生成对这些文件的引用。您可以在 `web.config` 文件中启用客户端验证,如下图所示:
您还可以通过 `System.Web.Mvc.WebViewPage` 类的继承 `Html` 属性直接从 `Views` 启用/禁用客户端验证。如下图所示,`View` 中的 `Html` 属性返回一个 `HtmlHelper` 对象,该对象包含 `EnableClientValidation` 和 `EnableUnobtrusiveJavaScript` 方法。一旦启用客户端验证,`HtmlHelper` 类就被允许自动编写客户端验证代码。
在我们的演示应用程序中,我们使用 jQuery 验证插件 执行验证。因此,默认消息以英语显示,但我们需要为所有支持的语言提供翻译后的消息。为此,我们将扩展该插件。首先,我们将创建一个目录树,如下图所示。
然后,对于每种支持的语言,我们将创建一个 javascript 文件,以根据当前 `Thread` 上运行的文化覆盖默认消息。以下是与西班牙语相关的代码:
jQuery.extend(jQuery.validator.messages, { required: "Este campo es obligatorio.", remote: "Por favor, rellena este campo.", email: "Por favor, escribe una dirección de correo válida", url: "Por favor, escribe una URL válida.", date: "Por favor, escribe una fecha válida.", dateISO: "Por favor, escribe una fecha (ISO) válida.", number: "Por favor, escribe un número entero válido.", digits: "Por favor, escribe sólo dígitos.", creditcard: "Por favor, escribe un número de tarjeta válido.", equalTo: "Por favor, escribe el mismo valor de nuevo.", accept: "Por favor, escribe un valor con una extensión aceptada.", maxlength: jQuery.validator.format("Por favor, no escribas más de {0} caracteres."), minlength: jQuery.validator.format("Por favor, no escribas menos de {0} caracteres."), rangelength: jQuery.validator.format("Por favor, escribe un valor entre {0} y {1} caracteres."), range: jQuery.validator.format("Por favor, escribe un valor entre {0} y {1}."), max: jQuery.validator.format("Por favor, escribe un valor menor o igual a {0}."), min: jQuery.validator.format("Por favor, escribe un valor mayor o igual a {0}.") });
我假设 jQuery 和 `jQuery 验证插件` 在这些文件之前加载。无论如何,要从需要客户端验证的视图中引用这些文件,我们必须使用以下代码:
@Scripts.Render("~/bundles/jqueryval") @if (this.Culture != GlobalHelper.DefaultCulture) { <script src="@Url.GetScript("jquery.validate.extension.js")" defer></script> }
正如我之前所做,我扩展了 `UrlHelper` 类以添加一个新的 `GetScript` 方法,用于搜索本地化脚本文件。然后,我在加载 `jQuery Validation plugin` 之后立即引用 `jQuery.validate.extension.js` 文件,但仅当当前文化与默认文化不同时。
由于前面提到的所有内容,当我们尝试在不填写任何必填字段的情况下发送 `Contact View` 时,我们将根据英语和西班牙语获得以下验证消息。
英语的验证消息
西班牙语的验证消息
最后,这里是 `_Contact.cshtml` 文件中 `_Contact` 局部视图的代码片段
此局部视图包含一个简单的表单来提交数据。如果此局部视图是从 http 请求的 Get 方法呈现的,则会显示一个表单。相反,如果它是在 Post 请求发送数据后呈现的,则会显示 Post 的结果。除了,如果您想深入研究源代码,我正在使用 Post-Redirect-Get 模式来实现此目的(在此处查看更多信息)。
重点关注验证,我想指出的是,当客户端验证被激活时,`HtmlHelper` 类中的某些方法,例如 `ValidationMessageFor`(见上图),被启用以根据模型元数据类中的注释为每个输入字段编写 html 代码来管理验证。对于简单的验证,您无需做任何其他事情。
处理整个视图的本地化
到目前为止,我们已经完成了非复杂大型应用程序本地化的所有工作。这些应用程序可能需要新功能,例如本地化整个视图。也就是说,`Views` 必须对每种文化都非常不同。因此,我们需要向应用程序添加新功能。我将把这种情况应用于 法语 文化。这种语言的视图将是不同的。我们需要达到这个目标吗?首先,我们需要为此语言创建新的特定 `Views`。其次,当选择 法语 文化时,我们必须能够引用这些 `Views`。最后,所有前面解释的内容也应该运行良好。让我们看看我们如何完成所有这些。
首先,我们将在 Views 目录下创建一个目录树,如下所示:
请注意 Home 目录下的 fr-FR 子目录。它将包含 法语 文化的特定视图。直接在 Home 目录下的视图将用于 默认 和 西班牙语 文化。如果有比 `Home Controller` 更多的控制器,也应该采取相同的策略。
此时,我们必须提供一种基于文化选择模板 `Views` 的方法。为此,我们将创建一个从 `RazorViewEngine` 派生的自定义 `ViewEngine`(在此处查看更多信息)。我们将此引擎称为 `MultiLanguageViewEngine`。简单地说,视图引擎负责搜索、检索和呈现视图、局部视图和布局。默认情况下,当您运行 MVC 5 Web 应用程序时,会预加载两个视图引擎:Razor 和 ASPX View Engine。但是,我们可以删除它们或添加新的自定义视图引擎,通常在 `Global.asax` 文件中的 `Application_Start` 方法中。在这种情况下,我们将卸载预先存在的默认视图引擎以添加我们的 `MultiLanguageViewEngine`。它将执行与 `RazorViewEngine` 相同的功能,但此外,它还将根据文化查找包含本地化整个视图模板的特定子目录。让我们看看 App_code 文件夹下的 `MultiLanguageViewEngine.cs` 文件中存储的代码:
namespace MultiLanguageDemo { public class MultiLanguageViewEngine : RazorViewEngine { private static string _currentCulture = GlobalHelper.CurrentCulture; public MultiLanguageViewEngine() : this(GlobalHelper.CurrentCulture){ } public MultiLanguageViewEngine(string lang) { SetCurrentCulture(lang); } public void SetCurrentCulture(string lang) { _currentCulture = lang; ICollection<string> arViewLocationFormats = new string[] { "~/Views/{1}/" + lang + "/{0}.cshtml" }; ICollection<string> arBaseViewLocationFormats = new string[] { @"~/Views/{1}/{0}.cshtml", @"~/Views/Shared/{0}.cshtml"}; this.ViewLocationFormats = arViewLocationFormats.Concat(arBaseViewLocationFormats).ToArray(); } public static string CurrentCulture { get { return _currentCulture; } } } } </string></string></string>
首先,请注意 `MultiLanguageViewEngine` 如何继承自 `RazorViewEngine`。然后,我添加了一个构造函数以获取支持语言。此构造函数将通过使用新的 `SetCurrentCulture` 方法设置查找本地化整个视图的新位置。此方法根据 `lang` 参数设置查找视图的新位置。此新路径插入到搜索位置数组的第一个位置,并且字符串数组保存在 `ViewLocationFormats` 属性中。此外,`MultiLanguageViewEngine` 将返回用于设置此属性的特定文化。
话虽如此,如何处理 `MultiLanguageViewEngine` 呢?首先,我们将在 `Global.asax` 文件中的 `Application_Start` 方法中创建此视图引擎的新实例。其次,在 `Thread` 上设置文化之后,我们将立即切换自定义视图引擎的当前文化。更详细地说,我们将覆盖 `BaseController` 类中的 `OnActionExecuting` 方法。我提醒您,此方法总是在调用控制器上的任何方法之前调用。
让我们看看 `Global.asax` 文件中的 `Application_Start` 方法
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new MultiLanguageViewEngine()); }
加粗代码显示了如何卸载预加载视图引擎集合以及如何加载我们的新 `MultiLanguageViewEngine`。
现在,让我们再次看看 `BaseController` 类中的 `OnActionExecuting` 方法,重点关注这一点:
protected override void OnActionExecuting(ActionExecutingContext filterContext) { string cultureOnCookie = GetCultureOnCookie(filterContext.HttpContext.Request); string cultureOnURL = filterContext.RouteData.Values.ContainsKey("lang") ? filterContext.RouteData.Values["lang"].ToString() : GlobalHelper.DefaultCulture; string culture = (cultureOnCookie == string.Empty) ? (filterContext.RouteData.Values["lang"].ToString()) : cultureOnCookie; if (cultureOnURL != culture) { filterContext.HttpContext.Response.RedirectToRoute("LocalizedDefault", new { lang=culture, controller = filterContext.RouteData.Values["controller"], action = filterContext.RouteData.Values["action"] }); return; } SetCurrentCultureOnThread(culture); if (culture != MultiLanguageViewEngine.CurrentCulture) { (ViewEngines.Engines[0] as MultiLanguageViewEngine).SetCurrentCulture(culture); } base.OnActionExecuting(filterContext); }
上面加粗的代码显示了如何更新我们的自定义视图引擎。如果 `Thread` 上存储在 `culture` 变量中的当前文化与 `MultiLanguageViewEngine` 中的当前文化不同,则我们的新引擎将更新以与 `Thread` 同步。我们通过 `ViewEngines` 类的 `Engines` 集合属性(索引为零)访问 `MultiLanguageViewEngine`。请注意,我们在 `global.asax` 文件中卸载了预加载的视图引擎,只添加了 `MultiLanguageViewEngine`。因此,它位于第一个位置。
从用户界面切换语言
如前面的截图所示,我们的演示应用程序将有一个位于右下角的标志列表,用于切换语言。因此,此功能将包含在 `_Layout.cshtml` 文件中。此文件将包含项目中每个视图的布局。
一方面,这里是渲染标志的 HTML 代码片段。它是一个简单的选项列表,用于显示代表支持语言的标志。一旦设置了语言,选定的标志将以实心绿色边框突出显示。
为了处理用户选择,我们将包含 javascript 代码。首先,我创建了一个 javascript 文件 `multiLanguageDemo.js`,以包含应用程序的通用功能。基本上,此文件包含读取和写入 cookie 的函数。它基于“命名空间模式”(在此处查看更多信息)。当然,此文件包含在 Scripts 文件夹中。
一旦用户点击一个选项,就会创建一个包含所选语言的 cookie。之后,页面将重新加载,以根据指定的语言导航到相应的 URL。以下是实现此目的的 jQuery 代码:
您必须注意使用 `MultiLanguageDemo.Cookies.getCookie` 读取 cookie 值和 `MultiLanguageDemo.Cookies.SetCookie` 设置 cookie 值。此外,当单击某个标志时,javascript 代码会为选定的标志设置 `active-lang` 类,从 `data-lang` 属性捕获语言并重新加载视图页面。
关注点
我花了很长时间构建这个小型演示应用程序。然而,用非母语详细解释某些内容确实很困难。对此深表歉意。
环境
本演示使用 Microsoft Visual Studio 2013 for the Web,基于 .Net Framework 4.5 和 MVC 5 开发。使用的其他主要组件包括 jQuery JavaScript Library v1.10.2、jQuery Validation Plugin 1.11.1、jQuery Validation Unobtrusive Plugin 和 Bootstrap v3.0.0。
历史
这是本文的第一个版本。在后续的审查中,我希望添加一些改进,例如提供使用全球化问题和来自数据库对象(如表、存储过程等)的本地化内容的应用程序示例。