Localizing MVC for ASP.NET views and master pages
- 📅
- 📝 643 words
- 🕙 3 minutes
- 📦 .NET
- 🏷️ ASP.NET, globalization, C#
- 💬 9 responses
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 to Localizing MVC for ASP.NET views and master pages
i dont think this is applicable in the preview 5 release.
@Kevin: They are defined and called by the parent class (ViewLocator)
I don’t understand how the variables in the constructor could work. they are never called in the code. The ViewLocationFormats and MasterLocationFormats variables are not passed to the other routines and are not properties of the base class. Am I missing something?
Ah ok. I think this just reflects differences in the application type & development workflow — the apps I worked on consisted of many hundreds of individual views (complex web-based TP application with a huge variety of transactions & screens) so of course a designer was never going to be involved in designing every individual view, just the templates of each general component & the overall style. Resources like images were just pulled in from a potentially localised folder too. Each view was light but when you have this many of them, writing one per language would be insane, and also if localisation was outsourced, it can be done just by editing text files. If you have a small app where a designer will be touching every single view directly then I can see it would be different.
You’ve just hit the nail on the head “when the content is going to be the same except for the labels / text”. Not everything is a input form — think web sites that are mostly content.
Devs might be happy with a big resource file for each language and a bunch of almost-empty views but non-devs won’t be happy trying to maintain these abstract elements. Is it really worth penalising these guys with their regularly-changing content just to make your life a bit easier in the rarer case that the structure of a view needs to change?
Given the use of master pages, user controls and CSS the views themselves can be very lightweight indeed and I’m not convinced that they are complex enough to force designers and translators into working in such a disconnected and disoriented manner.
Of course if you’re app is all repeated forms, labels and buttons then it’s a different story.
Can you give some examples of when string replacement doesn’t work? I’ve never had a problem with determining what labels / text mean when they’re expressed as resource names rather than explicit text, simply because I just name them after the English description so it’s usually pretty obvious. I’ve been involved in quite large systems that had locale support available like this, but the caveat is that I never actually translated any of the text :) Having the option to was important though.
It just seems overkill to have a view page for every language when the content is going to be the same except for the labels / text, seems to violate DRY. I could imagine there being a need for different versions for right-to-left systems or something like that though.
I intentionally spelt it in American format for SEO ;-)
You can replace parts of the page using locale-specific resources without any additional support but in my experience this “string replacement” philosophy works to a point when suddenly it just isn’t suitable any more.
The other problem with that is when you open the page it’s quite difficult to know what you’re looking at when all the language-specific stuff isn’t present.
Given that views are supposed to be very simple and that you can use user-controls within them to perform the logic they make good candidates for having their own file-per-language for small sites.
Oh bugger, I spelt it wrong too at the start of my comment. I blame your title ;)
I’m not a localization expert by any means, but is it common to want to replace an entire view like that? Usually I understand that localised messages / labels and number/date formats are enough, using a common view, and that you’d only need a completely specialised view if there were layout concerns with that particular locale, which is a lot less common.
As regards the structure of the view / controller here, I hope it’s possible to eventually configure this stuff outside of code. When I’ve worked with very large view / controller setups it’s invaluable to be able to simply declare flows in a compact form, rather than having to write code for them — although occasionally being able to write code too is useful (data-sensitive redirects for example). Maybe this example is specific to having to inject localisation though.
Oh, and deep, deep shame on you for spelling localiSation wrong. You’re not a Yank yet! ;)