Localizing MVC for ASP.NET views and master pages
Microsoft’s MVC for ASP.NET is still under serious development but at the moment support for localization is a little weak. Here’s one approach that works with the 04/16 source-drop.
LocalizingWebFormViewLocator class
This class helps by trying to identify language-specific versions of views, user controls and master-pages where they exist, falling back to the generic one where necessary.
public class LocalizingWebFormViewLocator : ViewLocator {
public LocalizingWebFormViewLocator() : base() {
ViewLocationFormats = new[] { "~/Views/{1}/{0}.{2}aspx", "~/Views/{1}/{0}.{2}ascx", "~/Views/Shared/{0}.{2}aspx", "~/Views/Shared/{0}.{2}ascx" };
MasterLocationFormats = new[] { "~/Views/{1}/{0}.{2}master", "~/Views/Shared/{0}.{2}master" };
}
protected override string GetPath(RequestContext requestContext, string[] locationFormats, string name) {
string foundView = FindViewLocation(locationFormats, requestContext, name, CultureInfo.CurrentUICulture.Name + ".");
if (String.IsNullOrEmpty(foundView))
foundView = FindViewLocation(locationFormats, requestContext, name, "");
return foundView;
}
protected string FindViewLocation(string[] locationFormats, RequestContext requestContext, string name, string cultureSuffix) {
string controllerName = requestContext.RouteData.GetRequiredString("controller");
foreach (string locationFormat in locationFormats) {
string viewFile = string.Format(CultureInfo.InvariantCulture, locationFormat, name, controllerName, cultureSuffix);
if (HostingEnvironment.VirtualPathProvider.FileExists(viewFile))
return viewFile;
}
return null;
}
}
Using the class
To use the class you must set the ViewLocator on the WebFormViewEngine to a new instance of LocalizingWebFormViewLocator (either in the constructor or in your common controller subclass) and ensure that any master pages are specified on the RenderView calls to ensure the localized version is detected.
public class HomeController : Controller {
public HomeController() {
((WebFormViewEngine)ViewEngine).ViewLocator = new LocalizingWebFormViewLocator();
}
public ActionResult Index() {
return RenderView("Index", "Site");
}
public ActionResult About() {
return RenderView("About", "Site");
}
}
You must also ensure the thread’s current UI culture is set. The easiest way to do this is to specify the following in your web.config file’s system.web section which will pick it up automatically from the user’s browser settings via the HTTP language-accept header.
<globalization responseEncoding="UTF-8" requestEncoding="UTF-8" culture="auto" uiCulture ="auto" />
Then all you need to do is create views and master pages that have the culture name appended between the name and .aspx, e.g:
/Views/Home/Index.aspx
(common fall-back for this view)/Views/Home/Index.ja.aspx
(Japanese view)/Views/Home/Index.en-GB.aspx
(British English view)/Views/Shared/Site.Master
(common fall-back for this masterpage)/Views/Shared/Site.ja.Master
(Japanese masterpage)
Caveats
There are some limitations to this solution:
Only primary language is attempted
Only the user’s primary language specified in their browser is attempted despite browsers having a complete list in order of preference. Ideally we would scan down this entire list before giving up but that would need more code and there is the issue of whether scanning for several languages across several folders could be too much of a performance hit.
Specifying the masterpage on RenderView
It would be nice if you didn’t have to specify the masterpage on render view but if you do not then the ViewLocator never gets called to resolve the actual masterpage address. This may be for backward compatibility within MVC.
Creating files in Visual Studio
Visual Studio 2008 seems to get a little confused if you create a Index.ja.aspx or Site.ja.aspx – whilst the files are created okay the names are not and you will need to adjust the class names to ensure they don’t conflict and make sure the opening declaration on the .aspx file points to the right code-behind page and inherits from the correct name.
Of course the beauty of this approach is you can mix-and-match using dedicated views where required and localising labels in the fall-back view when it isn’t.
[)amien
9 responses