web 2.0

How to Localize an ASP.NET MVC Application

While working on WeBlog this week I decided that I needed to start thinking about localization. If you have never heard the term “localization” before then its just a fancy way of saying that I want my application to be multi-lingual. In order to localize an application in .NET, you generally need to create a separate resource files for each language you want to support. In an ASP.NET MVC application, the resource files should be placed in a folder called App_GlobalResources. The folder can be created by right clicking on your project and selecting

Add –> Add ASP.NET Folder –> App_GlobalResources.

The resource files follow a naming convention. The first part of the name is the user defined part, for WeBlog we called it “Strings” but it could be whatever you want. The second part of the string is the Culture. For English the culture is “en”, for French the culture is “fr”, and so on and so forth. Here are few examples:

Prefix Language Culture Filename
Strings General Spanish es Strings.es.resx
Labels French fr Labels.fr.resx
Text French Canadian fr-ca Text.fr-ca.resx
Strings Spanish (Mexico) es-mex Strings.es-mex.resx

 
In Weblog the default language is English. However, from looking at the screenshot below you will notice that there is no file named Strings.en.resx. That is because when ASP.NET does not have a resource file matching the culture it will use a default file. The default file is the one with no culture specified (Strings.resx). 

image 

Once your resource file has been added to the project you will want to make sure that the properties are correct. First of all, make sure you set the “Access Modifier” to “Public”. This allows the resources to accessible from other assemblies. Internaly, it changes the “Custom Tool” used to generate the code behind from  ResXFileCodeGenerator to “PublicResXFileCodeGenerator”. Also confirm that the “Build Action” is set to “Embedded Resource”.

When you edit the resource file you will see three columns which are Name, Value and comment. The Name field is essentially the variable name and you will use it as a reference in your code. The files in your project should have a 1 to 1 match. Here is a side by side comparison of my Spanish and English resource file.

image 

If you speak Spanish then please don’t laugh at my translations. I have been using Google Translate and I have been told that the results are not very accurate. Luckily, there is a developer on the WeBlog team who does speak Spanish and has been regularly correcting the resource file. Thanks Marc :-)   

Automatic or Manual Mode?

If you want localization to happen automatically based on the user’s culture then you should add the following setting to your web.config file under the system.web section.

<globalization culture="auto" uiCulture="auto" />

 
Alternatively, you could set the language based on a parameter in the URL or by allowing the user to specify the language in a configuration screen. In order for this to work you have to manually set the language for each request. There is a good example of how to do this on codeproject.com.

For WeBlog I opted for the automatic language detection. Once the web.config file was modified it was time to move on to the next step…

Decorating Your Models

In MVC the process of localizing your forms is easy if you use data annotations. In particular you will want to use the Required and LocalizedDisplayName attributes as shown below. I initially tried using the standard DisplayName attribute but apparently it does not support localization. Luckily, Alex Adamyan took on the task of creating the LocalizedDisplayName attribute which fixes the problem. Thanks Alex!

public class CommentModel {
    public Guid? ID { get; set; }
    public Guid? PostID { get; set; }
    public Guid? UserID { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resources.Strings), ErrorMessageResourceName = "Required")]
    [RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", ErrorMessage = "Not a valid email")]
    [LocalizedDisplayName("Email", NameResourceType = typeof(Resources.Strings))]
    public string Email { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resources.Strings), ErrorMessageResourceName = "Required")]
    [LocalizedDisplayName("FullName", NameResourceType= typeof(Resources.Strings))]
    public string Author { get; set; }

    public string IPAddress { get; set; }

    [DataType(DataType.Url)]
    [LocalizedDisplayName("Website", NameResourceType = typeof(Resources.Strings))]
    public string Website { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resources.Strings), ErrorMessageResourceName = "Required")]
    [LocalizedDisplayName("Comments", NameResourceType = typeof(Resources.Strings))]
    public string Content { get; set; }

    public DateTime DateTime { get; set; }
    public CommentStatus Status { get; set; }
}


Once you have decorated your model with attributes you can start working on the view page. To make life easy I used the LabelFor and ValidationMessageFor methods throughout my view. By using these methods, MVC will lookup the values for the validation messages and labels from the Model. Based on the users culture, the ASP.NET runtime will then locate these values in the corresponding resource file and return them to the view:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<WeBlog.Models.CommentModel>" %>

    <% using (Html.BeginForm() ) {%>
        <%: Html.ValidationSummary(true) %>
        
        <fieldset>    
            
            <div class="editor-label">
                <%: Html.LabelFor(model => model.Author) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.Author) %>
                <%: Html.ValidationMessageFor(model => model.Author) %>
            </div>

            <div class="editor-label">
                <%: Html.LabelFor(model => model.Email) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.Email, new { size = 40 })%>
                <%: Html.ValidationMessageFor(model => model.Email) %>
            </div>
                                  
            <div class="editor-label">
                <%: Html.LabelFor(model => model.Website) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.Website, new { size = 40 })%>
                <%: Html.ValidationMessageFor(model => model.Website) %>
            </div>
            
            <div class="editor-label">
                <%: Html.LabelFor(model => model.Content) %>
            </div>
            <div class="editor-field">
                <%: Html.TextAreaFor(model => model.Content, new { cols = 50, rows = 8 } )%>
                <%: Html.ValidationMessageFor(model => model.Content) %>
            </div>
                        
            <p>
                <input type="submit" value=''<%: Resources.Strings.Save %> />
            </p>
        </fieldset>

    <% } %>

 

Testing Localization Using Google Chrome

Once you have everything ready, its time to test. With Google Chrome it is easy to switch between languages. Simply click on the wrench icon, choose options, minor tweaks, Change fonts and language settings. When the Fonts and Languages dialog appears you can add new languages or set the order of preference. In order to view a website in Spanish, simply highlight the Spanish language line item and move it to the top of the list.

image

If you did everything correctly you should now be able to render your form in Spanish and English. Here are some screenshots from Weblog:

image 

image

Related Resources

blog comments powered by Disqus