I was trying to be witty and use a VirtualPathProvider to find localized views. It takes the requested view path and modifies it when checking after the file. It returns a localized virtual file if found:
public pseudoclass MyFileProvider : VirtualPathProvider
{
bool FileExists(string requestedPath)
{
if (IsLocalizedView(requestedPath))
return true;
return base.FileExists(requestedPath);
}
bool IsLocalizedView(string requestedPath)
{
var uri = requestedUri.AddLocaleByMagic("sv");
if (FileExistsInternal(uri))
return true;
}
//some more stuff to return the actual file
}
The problem is that I get the following exception:
The VirtualPathProvider returned a VirtualFile object with VirtualPath set to '/Views/Shared/_Layout.sv.cshtml' instead of the expected '/Views/Shared/_Layout.cshtml'.
Sure, I could fake the file path, but that would produce problems with caching and different localizations. Right?
Anyone got a better way to be able to create localized views? I do not want to use the same view but with resource strings instead. Such views are so horrible that they almost makes me cry because they are so hard to read.
If you still haven't understood what I'm looking for:
/Views/User/Details.sv.cshtml
Hejsan @Model.FirstName
Detta är en lite rolig text på svenska.
/Views/User/Details.en.cshtml
Hello @Model.FirstName
This is a test on english.
Controller
public ActionResult Details()
{
return View(new User()); //should automagically use a swedish or english view
}
I want to be able to switch views (to a localized one using CurrentCulture) without having to do anything manually at each request.
You can write a custom ViewEngine which returns views depending on the CurrentCulture.
A nice example of this can be found at Scott Hanselman's blog post, which does return mobile Views if request has been made by a mobile device
Here is my implementation. It could be made more generic, but it fulfills all my requirements.
I looks for the most specialized view first and tries without a language specfication last.
View finding process:
- Details.sv-fi.cshtml
- Details.sv.cshtml
- Details.en.cshtml
- Details.cshtml
public class LocalizedRazorViewEngine : RazorViewEngine
{
public LocalizedRazorViewEngine()
{
DefaultLanguageCode = "en";
}
public string DefaultLanguageCode { get; set; }
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
var controllerName = (string)controllerContext.RouteData.Values["controller"];
var language = GetLanguage(controllerName, viewName);
if (language != "") language = "." + language;
var masterPath = string.Format("~/Views/Shared/_Layout{0}.cshtml", language);
var uri = string.Format("~/Views/{0}/{1}{2}.cshtml", controllerName, viewName, language);
if (VirtualPathProvider.FileExists(uri))
return new ViewEngineResult(CreateView(controllerContext, uri, masterPath), this);
return base.FindView(controllerContext, viewName, masterName, useCache);
}
private string GetLanguage(string controllerName, string actionName)
{
string format = "~/Views/{0}/{1}.{2}.cshtml";
if (VirtualPathProvider.FileExists(string.Format(format, controllerName, actionName, Thread.CurrentThread.CurrentCulture.Name)))
return Thread.CurrentThread.CurrentCulture.Name;
if (VirtualPathProvider.FileExists(string.Format(format, controllerName, actionName, Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName)))
return Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
if (VirtualPathProvider.FileExists(string.Format(format, controllerName, actionName, DefaultLanguageCode)))
return DefaultLanguageCode;
return string.Empty;
}
}
Note that the caching is disabled using this approach and you may need to create your own cache (to get the correct language)
Here is the simplest as can be (I guess) example of switching between views using the following convention:
- MyView.cshtml - default one
- MyView.pl.cshtml - Polish locale
.. and so on
public class LocalizedRazor : RazorViewEngine
{
public LocalizedRazor()
: base()
{
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
var controllerName = (string)controllerContext.RouteData.Values["controller"];
var format = "~/Views/{0}/{1}.{2}.cshtml";
var lang = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
if (VirtualPathProvider.FileExists(string.Format(format, controllerName, viewName, lang)))
return base.FindView(controllerContext, viewName + "." + lang, masterName, useCache);
return base.FindView(controllerContext, viewName, masterName, useCache);
}
}
and in Global.asax:
protected void Application_Start()
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new LocalizedRazor());
}