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

Comments

topsy.com , on 5/9/2010 10:23:27 PM Said:

pingback

Pingback from topsy.com

Twitter Trackbacks for
        
        Code Capers | How to Localize an ASP.NET MVC Application
        [codecapers.com]
        on Topsy.com

Sanjeev Agarwal , on 5/10/2010 6:18:15 AM Said:

trackback

Daily tech links for .net and related technologies - May 10-12, 2010

Daily tech links for .net and related technologies - May 10-12, 2010 Web Development jQuery Templates

DotNetShoutout , on 5/10/2010 8:21:55 AM Said:

trackback

How to Localize an ASP.NET MVC Application

Thank you for submitting this cool story - Trackback from DotNetShoutout

DotNetKicks.com , on 5/10/2010 8:23:17 AM Said:

trackback

How to Localize an ASP.NET MVC Application

You've been kicked (a good thing) - Trackback from DotNetKicks.com

cook careers United States, on 5/20/2010 9:34:16 PM Said:

cook careers

great tip on using labelfor and validationmessage it really helped with pointing out errors

Community Blogs , on 5/21/2010 2:06:44 AM Said:

trackback

May 20th Links: ASP.NET MVC, ASP.NET, .NET 4, VS 2010, Silverlight

Here is the latest in my link-listing series .  Also check out my VS 2010 and .NET 4 series and

shinguyen.net , on 5/21/2010 2:36:15 AM Said:

pingback

Pingback from shinguyen.net

May 20th Links: ASP.NET MVC, ASP.NET, .NET 4, VS 2010, Silverlight | OOP - Object Oriented Programing

37.tvshowzone.com , on 5/21/2010 4:27:20 AM Said:

pingback

Pingback from 37.tvshowzone.com

Engine 2006 Mazda Tribute, Radiator 2003 Mazda Tribute

nitrix-reloaded.com , on 5/22/2010 5:06:37 AM Said:

pingback

Pingback from nitrix-reloaded.com

ScottGu&#8217;s May 20th Links: ASP.NET MVC, ASP.NET, .NET 4, VS 2010, Silverlight « Code Name "NitRiX Reloaded"

tweetland.wordpress.com , on 5/26/2010 11:22:33 PM Said:

pingback

Pingback from tweetland.wordpress.com

May 20th Links: ASP.NET MVC, ASP.NET, .NET 4, VS 2010, Silverlight | Tweetland's Adventures

metaglossia.wordpress.com , on 5/27/2010 2:10:15 AM Said:

pingback

Pingback from metaglossia.wordpress.com

Metaglossia

BusinessRx Reading List , on 5/29/2010 2:49:13 PM Said:

trackback

May 20th Links: ASP.NET MVC, ASP.NET, .NET 4, VS 2010, Silverlight

Here is the latest in my link-listing series .  Also check out my VS 2010 and .NET 4 series and

Stéphan Parrot Canada, on 7/19/2010 9:10:59 AM Said:

Stéphan Parrot

Nice one!
But what if my localized string has {0} tokens in it so I can pass dynamic values?

Comments are closed