If your website targets users from different parts of the world, these users might like to see your website content in their own language. Creating a multilingual website is not an easy task, but it will certainly allow your site to reach more audience. Fortunately, the .NET Framework already has components that support different languages and cultures.
We will build an ASP.NET MVC 3 web application that contains the following features:
Globalization and Localization in ASP.NET
Internationalization involves Globalization and Localization. Globalization is the process of designing applications that support different cultures. Localization is the process of customizing an application for a given culture.
The format for the culture name is "<languagecode2>-<country/regioncode2>", where <languagecode2> is the language code and <country/regioncode2> is the subculture code. Examples include es-CL for Spanish (Chile) and en-US for English (United States).
ASP.NET keeps track of two culture values, the Culture and UICulture. The culture value determines the results of culture-dependent functions, such as the date, number, and currency formatting. The UICulture determines which resources are to be loaded for the page by the ResourceManager. The ResourceManager simply looks up culture-specific resources that is determined by CurrentUICulture. Every thread in .NET has CurrentCulture and CurrentUICulture objects. So ASP.NET inspects these values when rendering culture-dependent functions. For example, if current thread's culture (CurrentCulture) is set to "en-US" (English, United States), DateTime.Now.ToLongDateString() shows "Saturday, January 08, 2011", but if CurrentCulture is set to "es-CL" (Spanish, Chile) the result will be "sábado, 08 de enero de 2011".
How to Support Different Languages in ASP.NET MVC 3
There are two ways to incorporate different languages and cultures in ASP.NET MVC 3:
Which one is the best?
It depends on you. It is a matter of convenience. Some people prefer to use a single view for all languages because it is more maintainable. While others think replacing views content with code like "@Resources.Something" might clutter the views and will become unreadable. Recall that views have to be as simple as possible. If your views look fine with a lot of inline code, it's fine. But sometimes you have no choice in languages where layout has to be different like right-to-left languages. Perhaps, a mix of the two is the best. Anyway, in this example we will use the 2nd approach just to show a different way than the usual resource strings.
Views Naming Conventions
In order to create different views for every culture, we will append the culture name to the name of the view. For example, Index.cshtml (the default view), Index.es-CL.cshtml (Spanish, Chile), Index.ar-JO.cshtml (Arabic, Jordan). The view name that has no ending is considered the default culture. A default culture view will be rendered if the requested culture name is not implemented explicitly.
We will create a new ASP.NET MVC 3 web application and globalize it step by step.
Click "File->New Project" menu command within Visual Studio to create a new ASP.NET MVC 3 Project. We'll create a new project using the "Internet Application" template.
Creating the Model
We'll need a model to create our web application. Add a class named "User" to the "Models" folder:
Internationalizing Validation Messages
Our model presented above contains no validation logic, and this is not the case in normal applications nowadays. We can use data annotation attributes to add some validation logic to our model. However, in order to globalize validation messages, we need to specify a few extra parameters. The "ErrorMessageResourceType" indicates the type of resource to look up the error message. "ErrorMessageResourceName" indicates the resource name to lookup the error message. Resource manager will pick the correct resource file based on the current culture.
Now modify the "Person" class and add the following attributes:
Because we need to perform data validation on our model using Data Annotations, we will have to add translated resource strings for every culture our site will support. In this case, English, Spanish, and Arabic.
We will store resource files in a separate assembly, so we can reference them in other project types in the future.
Right click on the Solution and then choose the "Add->New Project" context menu command. Choose "Class Library" project type and name it "MyResources".
Now right click on "MyResources" project and then choose "Add->New Item" context menu command. Choose "Resource File" and name it "Resources.resx". This will be our default culture (en-US) since it has no special endings. Add the following names and values to the file like below:
Remember to mark the resource's access modifier property to "public", so it will be accessible from other projects.
Now create a new resource file and name it "Resources.es-CL.resx" and add the following names and values like below:
Now, do the same for the Arabic version. You may not be able to enter the correct strings by keyboard because your OS may not be configured to accept Arabic. However, you can download the files from the link at the top. Anyway, the resource file is included for reference:
We need to reference "MyResources" project from our web application, so that we can read the resource strings right from our web site. Right click on "References" under our web project "MvcInternationalization", and choose the "MyResources" project from Projects tab.
Determining Culture
How do we determine which version of a view to return to the end user?
How do we know which culture does the user want?
There is a header field called "Accept-Language" that the browser sends on every request. This field contains a list of culture names (language-country) that the user has configured in their browser. The problem is that this culture may not reflect the real user's preferred language, such as a computer in a cybercafé. We should allow the user to choose a language explicitly and allow them even to change it. In order to do this sort of things, we need to store the user's preferred language in a store, which can be perfectly a cookie.
We will create a base controller that inspects the cookie contents first, if there is no cookie, we will use the "Accept-Language" field sent by their browser. Create a controller and name it "BaseController" like below:
The base controller checks if the cookie exists, and sets the current thread culture to that cookie value. Of course, because cookie content can be manipulated on the client side, we should always validate its value using a helper class called "CultureHelper". If the culture name is not valid, the helper class returns the default culture. After that, when we call "Return" in controller action methods, the view name is modified by the base controller behind the scenes to reflect the correct culture in cookie or header field. This way we make sure that the user gets the right content based on their culture.
CultureHelper Class
The CultureHelper is basically a utility that allows us to store culture names we are implementing in our site:
We should populate "_cultures" manually. The "_cultures" dictionary stores the list of culture names our site supports. The first parameter indicates culture name (e.g. en-US), the second parameter indicates whether we are implementing separate views for that culture. If the second parameter is false, the default view is used (ie the one that has no special ending).
The nice part of this utility class is that it serves similar languages. For example, if a user is visiting our site from Argentina (es-ar), a culture which is not implemented in our site, he or she will see our site in Spanish using "es-cl" (Spanish, Chile) instead of English language. This way, you don't have to implement all cultures unless you really care about currency, date format, etc.
Controllers
Visual Studio has created a controller named "HomeCotnroller" for us, so we'll use it for simplicity. Modify the "HomeController.cs" so that it looks like below:
The "SetCulture" action allows the user to change their current culture and stores it in a cookie called "_culture". We are not restricted to cookies, we could instead save the culture name in Session or elsewhere, but cookies are really lightweight since they do not take any type of space on server side.
Creating a View Template
Now we will implement the View associated with the HomeController's Index action. First delete the existing Index.cshtml file under "Views/Home" folder. Now to implement the view right-click within the "HomeController.Index()" method and select the "Add View" command to create a view template for our home page:
We will need to modify some of the settings above. Choose the option "Create a strongly-typed view" and choose the "Person" class we created before. Also, choose the "Create" menu item from the "Scaffold template" drop-down box.
When we click the "Add" button, a view template of our "Create" view (which renders the form) is created. Modify it so it looks like below:
The javascript code simply post back the form to set the culture. The "selected" helper is used to mark the appropriate culture radio button as checked.
Now make two copies of "Index.cshtml" and rename them to "Index.es-CL.cshtml" and "Index.ar-JO.cshtml". These latter views represent the localized versions of Index.cshtml for two different cultures, so we can add whatever is necessary inside them. Make them look like below:
Spanish view
Arabic view
Of course, for simple partial views like "_LogOnPartial.cshtml" and which are not referenced by controllers, we can use resource strings perfectly.
Arabic view
Try It Out
Run the website now. Notice that client side validation is working nicely. Click on radio buttons to switch between cultures, and notice how right-to-left language is showing correctly. Using separate views allowed us to control how to position elements, and have made our views clean and readable.
English
Spanish
Arabic
What about client-side scripts?
For client-side, we should worry mainly about numbers, date and time, and messages since these change from culture to culture. There are many ways to implement client-side localization. But here are two common options:
For (1), we follow the same convention for views and resource files. For example, for the file "myscript.js", you need to create "myscript.es-CL.js", "myscript.ar-JO.js", etc. We can reference the javascript files easily from our views by appending culture name to the javascript file :
The variable "_culture" is already defined in the base controller and works nicely by ignoring default culture (returns null in this case).
Even if you want to use Microsoft Ajax Library, you may still need separate javascript files that define text messages to the end user. You can define a literal object that contains the list of messages, or if you are using separate views for every culture, you can use inline javascript instead of separate javascript files.
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 3 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 3:
I hope this help!
Any questions or comments are welcome!
Trackback: