Wednesday, November 6, 2013

ASP.NET MVC 5 Internationalization-5

How to store culture in the URL instead of a cookie?

There can be different reasons why you want the culture to be part of your website url (such as search engine indexing). Anyway. First let's fist define the culture to be part of our routes. Edit RouteConfig.cs like below:

1
2
3
4
5
6
7
8
9
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapRoute(
        name: "Default",
        url: "{culture}/{controller}/{action}/{id}",
        defaults: new {culture = CultureHelper.GetDefaultCulture(), controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

Notice the we used the default culture in case it is missing. Now modify the base controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
    string cultureName = RouteData.Values["culture"] as string;
    // Attempt to read the culture cookie from Request
    if (cultureName == null)              
        cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ? Request.UserLanguages[0] : null; // obtain it from HTTP header AcceptLanguages
    // Validate culture name
    cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
    if (RouteData.Values["culture"] as string != cultureName) {
         
        // Force a valid culture in the URL
        RouteData.Values["culture"] = cultureName.ToLowerInvariant(); // lower case too
        // Redirect user
        Response.RedirectToRoute(RouteData.Values);               
    }
   
    // Modify current thread's cultures           
    Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
    Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
    return base.BeginExecuteCore(callback, state);
}

The final step is to modify the SetCulture action in HomeController.cs

1
2
3
4
5
6
7
8
9
public ActionResult SetCulture(string culture)
{
    // Validate input
    culture = CultureHelper.GetImplementedCulture(culture);
    RouteData.Values["culture"] = culture;  // set culture
    
    return RedirectToAction("Index");
}    
NOTE: To force the default culture appear in the URL, simply set the default value for culture in RouteConfig.cs to string.Empty

Summary

Building a multilingual web application is not an easy task. but it's worth it especially for web applications targeting users from all over the world, something which many sites do. It is true that globalization is not the first priority in site development process, however, it should be well planned early in the stage of development so it can be easily implemented in the future. Luckily, ASP.NET supports globalization and there are plenty of .NET classes that are handy. We have seen how to create an ASP.NET MVC application that supports 3 different languages, including a right-to-left one, which requires a different UI layout. Anyway, here is a summary of how to globalize a site in ASP.NET MVC:

  1. Add a base controller from which all controllers inherit. This controller will intercept the view names returned and will adjust them depending on the current culture set.
  2. Add a helper class that stores the list of culture names that the site will support.
  3. Create resource files that contain translation of all string messages. (e.g. Resources.resx, Resources.es.resx, Resources.ar.resx, etc )
  4. Update views to use localized text.
  5. Localize javascript files.

I hope this helps!
Any questions or comments are welcome!

1 comment:

  1. Thank you for this tutorial. I have impemented it in my web application and it is work fine. But I faced the problem that application always started with default culture defined in CultureHelper class. And did not use browser language settings. I solved it by modifying this code "if (cultureName == null)" in BeginExecuteCore to "if (cultureName == null) || cultureName == string.Empty".

    ReplyDelete