Localize Web Applications with Microsoft MVC


Localize Web Application with Microsoft MVC

Demo Solution Structure

Solution Structure

Modifications required to localize the application

ModificationsRequired

Presentation

As you can see, this is a standard MVC4 web application. To make it localizable, we have to take these actions.

Right click on the project name, click on Add, click on Add ASP.NET Folder, click on App_GlobalResources, this is required to make the MVC engine think it has global resources.

This action is just for create the folder. Leave it empty.

Modify the file RouteConfig.cs to add support for languages in the QueryString parameters. Leave like this:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{lang}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, lang = "en" }
);
}

We are telling route engine that, if there is not a language in the route, it has to add “en” by default. Otherwise, it has to add the supplied QueryString Param to the route.

We will come back to this project below. Leave it at this moment and let’s go to the Infrastructure layer.

Infrastructure.CrossCutting.Localization

In that layer, there is a project called Infrastructure.CrossCutting.Localization

LocalizationProject

As you can see, in there, there are several classes and 3 resx files. In these resx files we have to write all required strings.

ResourceFiles

The important thing here is to put the Resource Access Modifier to Public as shown.

After that, if you click on the Global (can be of the resx files in the project, and press F4 to see its properties, you can see these properties:

CodeGenerator

The important thing here is that the Build Action is selected as Embedded Resource and the Custom Tool is PublicResXFileCodeGenerator (which is selected when you put Access Modifier to Public at the last point). Another thing to see here is the Custom Tool Namespace used to make it directly readable by the MVC4 application without adding anything else. This is the namespace that will contain the auto generated code with the strings.

The next thing that we have to look at is the CultureHelpers class. This simple static class, only have one static method. This method is the responsible of changing the current culture on the current thread (as it is a web application the thread is regenerated between requests). This is the method we have to invoke to change language. We can see this invocation below.

The next class to read is the LocalizedDisplayName class. This class inherits from the DisplayNameAttribute to override the DisplayName attribute used in the models of the MVC4 application.

public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
private readonly string _resourceName;
public LocalizedDisplayNameAttribute(string resourceName)
{
_resourceName = resourceName;
}
public override string DisplayName
{
get { return Global.ResourceManager.GetString(_resourceName); }
}
}

Thanks to this override, where we were using

[Display(Name = "User name")]

Now we can use

[LocalizedDisplayName("USERNAME")]

And the application will retrieve the translation from the Global resx file based on the current culture.

Putting all together

You can see an example of this behavior on the view Account/Login. The definition of the Login Model, has changed to this (you can see it in Models/AccountModels):

public class LoginModel
{

[Required(ErrorMessageResourceType = typeof(Global), ErrorMessageResourceName = "REQUIRED")]
[LocalizedDisplayName("USERNAME")]
public string UserName { get; set; }

[Required(ErrorMessageResourceType = typeof(Global), ErrorMessageResourceName = "REQUIRED")]
[DataType(DataType.Password)]
[LocalizedDisplayName("PASSWORD")]
public string Password { get; set; }

[LocalizedDisplayName("REMEMBERME")]
public bool RememberMe { get; set; }

}

Note that in the new model class, we are using the Global Resources to translate both DisplayName and the Required Error Message.

We do not have to do anything else in the view, because with the razor helpers, the application will get the correct translation based on the current culture:

<ol>
<li>
@Html.LabelFor(m => m.UserName)
@Html.TextBoxFor(m => m.UserName)
@Html.ValidationMessageFor(m => m.UserName)
</li>
<li>
@Html.LabelFor(m => m.Password)
@Html.PasswordFor(m => m.Password)
@Html.ValidationMessageFor(m => m.Password)
</li>
<li>
@Html.CheckBoxFor(m => m.RememberMe)
@Html.LabelFor(m => m.RememberMe, new { @class = "checkbox" })
</li>
</ol>
<input type="submit" value="@Global.LOGIN" />

Note that we are introducing translations also in the cshtml file directly from the Global resources, right in the value of the input button. To achieve this, look at the first line of the view, it is using the Resources Namespace:

@using Resources

Finally, we have a class called SetCultureFilter. It is a class that inherits from ActionFilterAttribute. We will override the method OnActionExecuting to set the culture from the QueryString Params:

public class SetCultureFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var culture = (string) filterContext.RouteData.Values["lang"];
CultureHelpers.SetCulture(culture);
base.OnActionExecuting(filterContext);
}
}

The job is almost done. We only have to add the attribute to the Controllers that we want to be auto translated and it will works. For example, if you open HomeController, you can see below the class, the attributed we created in the Localization dll:

[SetCultureFilter]
public class HomeController : Controller

This is telling the MVC4 engine to execute the specified culture filter for every action on the Controller (although we can restrict the filter and apply only to selected actions if required).

If you see the HomeController, you can see an action called ChangeLanguage. Well this action is a quick way to change the current language:

public ActionResult ChangeLanguage(int id)
{
switch (id)
{
case 1:
#if DEBUG
CultureHelpers.SetCulture("es"); // Duplicated code due to be used in unit tests as filter not executing in test, to be investigated
#endif
_currentCulture = "es";
break;
default:
#if DEBUG
CultureHelpers.SetCulture("en");
#endif
_currentCulture = "en";
break;
}
return RedirectToAction("Index", new {lang = _currentCulture});
}

Evidently, the duplicated code is not best way to do this, it is written this way just for the prototype.

To execute this action, you can open the shared view _LoginPartial. We had added to this list, two more items:

<li>@Html.ActionLink(@Global.LANGENGLISH, "ChangeLanguage", "Home", new { id = 0 }, null)</li>
<li>@Html.ActionLink(@Global.LANGSPANISH, "ChangeLanguage", "Home", new { id = 1 }, null)</li>

These two items execute the language change, and the method on the controller redirects to the home page.

Tests Driven

Additionally, you can see on the Tests layer, a couple of projects that show how to test the controllers (integration tests) and how to test the method (unit tests).

Conclusion:

After done all this actions, you can simply use

on the views the syntax @Global.KEY,

on the C# classes the syntax Global.KEY and

on the data models the syntax [LocalizedDisplayName(“KEY”)

or, i.e. [Required(ErrorMessageResourceType = typeof(Global), ErrorMessageResourceName = “KEY”)]

to retrieve a localized string.

Take a look also at _Layout.cshtml, it has this line:

<p>&copy; @DateTime.Now.ToShortDateString() – Translation Example</p>

At the footer. Well, if you run the application in Spanish, to demonstrate date format changes the format shown is dd/mm/yyyy but if you run the application in English, the format shown is mm/dd/yyyy. Validation of dates on the client side would require some more investigation.

You can download the source project here: https://github.com/victorxata/TranslatedWeb/.

Anuncios

Un comentario en “Localize Web Applications with Microsoft MVC

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s