Localising an ASP.NET MVC application

I once had the misfortune of having to retrofit internationalisation into an application.  It was a tedious and error-prone process.  Since then, whenever I have the opportunity with a green field project, I always build in localisation from the beginning.  In this way, it does not really require additional development time and has additional benefits - project sponsors will often request  textual changes, these can be made in a single location and flow through the application easily, the resource files can be sent to editorial resources for review and updating without delaying development activities.

So here are the steps for localising an MVC application built using MS Visual Studio:

Step 1:
Create a resource directory and resource files in your project.  Something like this:

Remember, the language cascade is from specific to general eg. 'en-GB' -> 'en' -> none.  Most of the time I have not needed to distinguish between two different cultures using the same language (eg. en-US and en-GB).

Step 2:
View the Properties of the resource file and change the Custom Tool property to be 'PublicResXFileCodeGenerator', so it can be accessed by the view.  It is helpful to add a Custom Tool Namespace eg. 'TextResources'

Change the HTML in your view from something like this:
<h1>My Application</h1>

To something like this:
<h1><%=TextResources.common.APPLICATION_NAME %></h1>

That is:
<%=CustomToolNamespace.FileName.ResourceKey %>

Step 3:
We need to read the user's selected culture from their browser settings.  Fortunately this is normally sent with the request information.  So add a method like this to Global.asax.cs:

        private static CultureInfo GetBrowserCulture()
        {
            Regex _invalidLanguage = new Regex(":+|;+", RegexOptions.Compiled);
            if (HttpContext.Current != null && HttpContext.Current.Request.UserLanguages != null && HttpContext.Current.Request.UserLanguages.Length > 0)
            {
                string language = HttpContext.Current.Request.UserLanguages[0];
                if (_invalidLanguage.IsMatch(language))
                {
                    string[] langs = language.Split(new char[] { ';', ':' }, StringSplitOptions.RemoveEmptyEntries);
                    language = langs[0];
                }
                return new CultureInfo(language);
            }
            return CultureInfo.CurrentUICulture;
        }

Step 4:
Then we need to use this method to set the culture of the executing thread.  So add this method to Global as well:

        protected void Application_AcquireRequestState(object sender, EventArgs e)
        {
            CultureInfo ci = new CultureInfo(GetBrowserCulture().TwoLetterISOLanguageName);
            System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
            System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
        }

So now we are reading the user's selected browser culture, using this to set the culture of the current executing thread and then cascading to pick up the appropriate resource file and getting the value of the supplied resource key from this.  If no matching resource file is found, the resource file with no language suffix will be loaded and used instead.

Comments