Software Development

Introduction

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 5 web application that contains the following features:

It can display contents in different languages.

It autodetects the language from the user's browser.

It allows the user to override the language of their browser.

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).

Anyway, Internationalization is often abbreviated to "I18N". The abbreviation takes the first and last letters and the number of letters between them, so 18 stands for the number of letters between the first "I" and the last "N". The same applies to Globalization (G11N), and Localization (L10N).

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".

Now, let’s review the terms used so far:

Globalization (G11N): The process of making an application support different languages and regions.

Localization (L10N): The process of customizing an application for a given language and region.

Neutral culture: A culture that has a specified language, but not a region. (e.g. "en", "es")

Specific culture: A culture that has a specified language and region. (e.g. "en-US", "en-GB", "es-CL")

Why do we need a region? Isn’t a language alone enough?

You might not need a region at all. It is true that English in the United States is not the same as English in the United Kingdom but if your application just shows English text readable to people from these English-speaking countries, you will not need a region. The problem arises when you need to deal with numbers, dates, and currencies. For example, compare the following output for two different Spanish-speaking regions (Chile, Mexico):

You can notice the difference in date and currency format. The decimal separator in each region is different and can confuse people in the other region. If a Mexican user types one thousand in their culture "1,000", it will be interpreted as 1 (one) in a Chilean culture website. We mainly need regions for this type of reasons and not much for the language itself.

How to Support Different Languages in ASP.NET MVC

There are two ways to incorporate different languages and cultures in ASP.NET MVC:

By using resource strings in all our site views.

By using different set of views for every language and locale.

By mixing between 1 and 2

Which one is the best? 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. Some project requirements force developers to implement different views per language. But sometimes you have no choice where layout has to be different like right-to-left languages. Even if you set dir="rtl", this may not be enough in real applications unless the project’s UI layout is really simple. Perhaps, a mix of the two is the best. Anyway, for this example, it makes sense to use resources since we won’t have any issue with the layout for the Spanish, English, and Arabic languages that we will use.

How can ASP.NET guess the user’s language?

On each HTTP request, there is a header field called Accept-Language which determines which languages the user’s browser supports:

Accept-Language: en-us,en;q=0.5

This means that my browser prefers English (United States), but it can accept other types of English. The "q" parameter indicates an estimate of the user’s preference for that language. You can control the list of languages using your web browser.

Internet Explorer

Firefox

Globalizing our Web Site

We will create a new ASP.NET MVC web application and globalize it step by step.

Click "File->New Project" menu command within Visual Studio to create a new ASP.NET MVC 5 Project. We'll create a new project using the "MVC" template.

Creating the Model

We'll need a model to create our web application. Add a class named "Person" to the "Models" folder:

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.

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 "Resources".

Now right click on "Resources" 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.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 "Resources" 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 "Resources" project from Projects tab.

Views

We need to extract the English text from all the views and move it to the resource files. Here is a trick, instead of typing the namespace name each time (e.g. "@Resources.Resources.LogOn "), we can add this namespace to the views Web.config and type "@Resources.LogOn" instead. Open the Web.config file under the views folder.

Determining Culture

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 public place. 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:

Make sure all controllers in this project inherit from this BaseController. The base controller checks if the cookie exists, and sets the current thread cultures 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.

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 nice part of this utility class is that it serves similar cultures. For example, if a user is visiting our site from the United Kingdom (en-GB), a culture which is not implemented in our site, he or she will see our site in English using "en-US" (English, United States). This way, you don't have to implement all cultures unless you really care about currency, date format, etc. One important thing to mention is that we added all specific cultures for the Spanish language. For example, we are implementing "es" Spanish in general without any region where a date looks like this "30/07/2011". Now suppose a nerd is coming from Chile (es-CL), it would be very nice display dates in their culture format (30-07-2011) instead of the neutral one. It does not matter if we don’t have a resource file for any of these cultures. ResourceManager can pick a neutral culture when it cannot find a specific culture file, this automatic mechanism is called fallback.

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:

Click OK and replace the existing view if any. 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:

Now we need to provide basically two sets of CSS and JS files: One for left-to-right languages and one for right-to-left languages. Because MVC default template is using Bootstrap, we need to install the RTL version of bootstrap files. For this, open the package manager console (aka Nuget) by choosing Tools -> Library Package Manager -> Package Manager Console and type:

Install-Package Twitter.Bootstrap.RTL

Make sure you have the two files bootstrap-rtl.js and bootstrap-rtl.css

We need to create two bundles: one for RTL and one for LTR. Modify the file BundleConfig.cs and add the following:

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

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:

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:

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.

Add a helper class that stores the list of culture names that the site will support.

Nice article, I would be thankful to you if you would post the article about ASP.NET MVC 3 Internationalization using Database, because if I would need more languages and website like fifa then what technique should I use.

@Rais Hussain,
You can implement as many languages as you want without using databases, you can use resource files. Of course, you can use databases but I do not recommend this approach since you might run into scalability issues.

After setting the reference in my separate model solution, the only namespaces that come up for "MyProject" with the "." intellisense are:

App_Start
Classes
Controllers
Models
Utility

The "MyResources" project does not show up in the intellisense (which is compiled into the "MyProject" project.

On the presenation tier where I globalized the site (as I said before), I simply put a "using" statement in the Views and the intellisense would pop up for me to place the specific resource text object. This is not happening with my dll. I've got to be missing something....

We use a custom resource provider to load our resource strings from a database, using the globalization element to define the resourceProviderFactoryType.

The ErrorMessageResourceName attribute REQUIRES that an ErrorMessageResourceType be defined otherwise it throws an exception.

Further more, the type supplied MUST be a resx as internally the Validation attribute it uses REFLECTION to enumerate all the STATIC properties. I can't even create a dynamic object and pretend that static properties exist as this doesn't work with reflection calls.

Robert Slaney,
I know that Data Annotations localization is pretty limited. One way would be using alternate validation mechanisms like custom validation. Or maybe inheriting from standard validation attributes like here http://bit.ly/sZRMH9

@Fayez,
I think that CurrentUICulture is overwritten with a different value from ASP.NET due to some other configuration. I also think the event Application_AuthenticateRequest is not the appropriate one for setting the CurrentUICulture, use another one preferrably. Try to override the

Thank you for your answer.
If I would like to set the culture according to the DNS host.
Where can I set it in the code? (asp mvc 3)
Suppose I have this method:
string getLang() {return Request.Url.Host.Contains(".co.uk") ? "en-GB" : "fr-FR"; }

Thank you very much Nadeem, everything is work fantastic One more question, the user could change the languages on any page, but after change of language the user is redirected to Index page, is it possible redirect the user on a same page? I.e. if the user change the language on About page, just the language is changing, but he still see the About page.

Salam aleikum Nadeem!
First of all I think this article is the best on the Internet that deals with Internationalization and how to create a multilingual website. I have searched and searched but have not been able to find an article that deals with the subject as good as you have done. You even managed to get the seo optimized url handling, nice As opposed to others I believe using multiple views in this scenario is the correct way to go, even if it go against the dry principle. As I see it using multiple views will make it possible to style rtl and ltr oriented languages. This would in practice be impossible to achieve in a perfect manner when using one view. Its better to ditch the dry principle and get the job done correctly then to stick with a principle that does not fit the reality in this case.

Again thanks for the best article on the internet that shows how to create a multilingual web site

This works fine but how can I load resource files dynamicly ?
I want to provide original resource files instead of the compiled version to a client so he can modify any text with a simple resource editor.
Is there a way to do that ?

Great article! I was able to use a lot of the techniques you described and they work great except for one small problem...

Has anyone had a problem with the culture not being set on the login page in an MVC4 app? I've stepped trhough the code a dozen times, and it reads/writes the cookie but for some reason the language doesn't change on that page. Every other page is fine after I log in.

There is some missing code which is there in the downloadable code. It is in the resources.resx.cs file where some properties are defined which are not shown in the article this was throwing me off with errors. If you can include all the code steps required would be helpful. Otherwise thanks a lot for the article. Also by any chance can you show the same or similar example for MVC 4 VS 2012 and with some database action going on to save multinational data in SQL Server using ADO.NET entity framework 5.
Thanks a lot for the article it helps but more would help even more

Interesting article Nadeem. I'm confused by a lot of the earlier comments, however, disagreeing with multiple views as opposed to resource strings. This approach uses resource strings for localization, did you change the article or something? It's even stated early on when discussing the 3 options that it will use resources. Am I missing something?

Thank you so much for providing this example! It is extremely helpful.

I do have a question about setting the culture in BeginExecuteCore of the base controller. Why did you not do this in the Initialize method? The MS docs for Initialize even mention that as the appropriate place to set the culture. (msdn.microsoft.com/.../...itialize(v=vs.100).aspx)

Hi Nadeem, thank you for your good stuff. For cookie in URL, I am encountering a problem when I apply [Authorize] on a controller, the language code cannot be set properly (404 error), I tried to correct authentication node in web.config, still no luck, please advise.
Changes made in web.config.
<authentication mode="Forms">
<forms loginUrl="~/Default/Account/Login" timeout="2880" />
</authentication>

Hi Nadeem Afana
thanks for sharing this great tut
but when In the second step I add the :
[Display(Name......
and Error messageType......
the file all filled with Error and when I Build the app all person Calss is Error
and in next step when I right-click on Refrences I don't see any Choose menu to choose the Resources
Please help me additional tut about this greate web app and register form
Thanks again
whit the best regard :
Raha

@Raha,
Did you make sure that the access modifier of the resources is set to Public. You also need to add a reference to the project that contains the resources. Make sure to select the Projects tab first.

Hi Nadeem, sorry for confusing you, 'For cookie in URL' should be corrected to 'How to store culture in the URL instead of a cookie?', I tried to store culture in URL, I am encountering a problem when I apply [Authorize] on a controller, if a unauthenticated user tried to visit a page needs authentication, the user will be taken to '~/Account/Login' page which cause 404 due to default route has been changed, I tried to correct authentication node in web.config, still no luck, I believe there is a better solution, please advise.
Changes made in web.config.
<authentication mode="Forms">
<forms loginUrl="~/Default/Account/Login" timeout="2880" />
</authentication>

Hi @Nadeem
And really thanks for you're reply
I downloaded you're complete project and when I open it in visual studio it begin to download some additonal package .but you didn't add any additional package into you're project in first step of you're post
what are them that we must add to our MvcInternationalization project in first step
please help me by there name ?
so I can add them to my project
thanks Dear Nadeem

Thanks for the article.
I included the code step by step in my project. But the website uses the strings from the Resources.resx (standard culture) file every time. If I switch the language button the page is reloading with exactly the same strings. The cookie includes the selected correct culture but the strings are the default ones. Where could be the error? The modifiers are public, the reference of the web project to the resource Project is set.

Yes, that wasn't the problem.
I named my files resource.resx (with german strings) and resource.en.resx (with english strings) and set the default culture to german "de-DE". Because he could't find a resource file with he used every time the german one. that the switch of the language wasn't working I don't know.
But I fixed my Problem and renamed my resource files to resources.resx (english and fallback) and resources.de.resx (german) and sets the default culture in the culture helper to "de-DE". Now it's working.

I have a model that put in different project from web project i.e:
Project.Library.Data -> used for define model and table (using Entity Framework)
Project.Mvc.Web -> This is a web application project, and I add "Project.Library.Data" to this web application project to references,

If I want to change display name, what I have to do is create partial class,
and create metadata class inside of it. And for internalization case I use your method.

But when I run the web application, display name of my property doesn't change to internalization case. It's just default display name. Is that any problem to put the defined model in another project not in same project?

Hello Nadeem I've implemented your solution several times and I have a problem on my login page.
On the login page the languages are not changing, but if I log in everything works fine.
This is surely due to the authorizations in my web.config.
I haven't found a solution to it.

@Nadeen
Thank you for this very usefull article. I've applied it to my project and it works good. But I faced with the same [Authorize] problem like @Leon. The changing web.config as you stated does not work for me. I found that in my case login url can be modified in Startup.Auth.cs. I Changed it to "LoginPath = new PathString("/en-US/Account/Login")" and now it works.
But there is stiil one problem. If a user first select a defferent culture and then go to authorized page he will be redirected to login page in default (en-US) culture, not culture he is selected before. It is not user friendly. User of course can authorize by link "Log in" and it will be in proper culture. But I would like the automate redirection work the same way. Could you advise how to fix it?

Hi Nadeem, great article you wrote! Thanks.
I've changed Age from int to decimal in order to test decimal validation in pt-br culture (using "," as decimal separator) and i got "The field age must be a number" validation message.
How do i get this validation working? Am i missing something?
Best Regards.

Thanks Nadeem for your answer.
I dont't want to change the message. I think the message should not be shown because the number i entered used a comma as decimal separator (standard in pt-br culture) like 12,5. I did everything the same way your article explained, the culture is set in the right way and everything works good except the message that is still shown when i enter numbers like the one above.

but i ran into a problem, when i dont specify the culture on the url( eg: server:port/controller) i get redirected to the home controller.
is there anyway i can change it to redirect to the specified controller with the default culture?
my RouteConfig is the same as yours

Hello. Very useful tutorial. I've implement this on my project and it's working well. As for database internationalization, it would make sense if we want to implement it on our contents. For static labels, using resources is faster.

Hello. I have a problem on form internationalization. The display name used for each field always using default language (en-US) even though I set the language (the one on the cookie) to id-ID (it still show "Remember Me?" instead of "Ingat Saya?"). Any idea why?

Well ... I'm not sure if I'm understand. Where do exactly I need to update the _cultures?

If I'm not mistaken, it was a list that contain all languages I want to support with the top most is the default, right? So if I never change that set, will I need to update it?

I've try to match your example on above post. Any html text were successfully translated to loaded resources, except for Model specific text like LabelFor, etc (which always use default resources). I wonder where I went wrong

Am trying to add Persian support I have added a resource file called Resources-fa.resx and added option in _culture called "fa", but when I click on the option it does not read resex the layout changes to rtl though.

I have the exact same problem as Evgeny and Leon:
Thank you for this very usefull article. I've applied it to my project and it works good. But I faced with the same [Authorize] problem like @Leon. The changing web.config as you stated does not work for me. I found that in my case login url can be modified in Startup.Auth.cs. I Changed it to "LoginPath = new PathString("/en-US/Account/Login")" and now it works.
But there is still one problem. If a user first select a different culture and then go to an authorized page he will be redirected to login page in default (en-US) culture, not culture he has selected before. It is not user friendly. User of course can authorize by link "Log in" and it will be in proper culture. But I would like the automate redirection to work the same way. Could you advise how to fix it?

As stated above, I can define my login page path in Startup.Auth.cs as I am using ASP.NET Identity with OWIN/KATANA. The thing is I don't want to specify a login url containing an hardcoded culture (the default one) because I updated your code to enable prefered language autodetection if no culture is specified by the user yet. I want the login page to have this feature too. So I can't hardcode the culture in the login path.

I know this is asking a lot. Your article already helped me a lot. Thanks for that. But if you have any idea I'd like to hear it.^^

I think i found a solution to my problem, I can use the OnApplyRedirect method of the OWIN cookie authentication middleware (in the Startup.Auth.cs) to intercept the redirection to the login page (this method only deals with redirections comming from the OWIN cookie middleware). I define the login path with a token that I replace dynamically in this method. For this I use the actual thread culture that was set right in your BaseController just before the login redirection happens :

now if i use this url: http://localhost:xxx/
all work fine with the default language
if i use http://localhost:xx/it
the site doesn't work and return this message:

The view 'Index' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Home/Index.aspx
~/Views/Home/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
~/Views/Home/Index.cshtml
~/Views/Home/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.vbhtml

Can you help me to configure routing with Areas and Internazionalization ?
Thank you in advantage
lluthus