web 2.0

ASP.NET MVC 3 Internationalization


ASP.NET MVC 3 Internationalization

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

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:

  1. By using resource strings in all our site views. (See part 2)
  2. By using different set of views for every language and locale.
  3. By mixing between 1 and 2

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.

Globalizing our Web Site

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:

namespace MvcInternationalization.Models
{
    public class Person
    {
        [Required(ErrorMessageResourceType=typeof(MyResources.Resources), 
                  ErrorMessageResourceName="FirstNameRequired")]
        [StringLength(50, ErrorMessageResourceType = typeof(MyResources.Resources), 
                          ErrorMessageResourceName = "FirstNameLong")]       
        public string FirstName { get; set; }

        [Required(ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "LastNameRequired")]
        [StringLength(50, ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "LastNameLong")]
        public string LastName { get; set; }

        [Required(ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "AgeRequired")]        
        [Range(0, 130, ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "AgeRange")]                
        public int Age { get; set; }

        [Required(ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "EmailRequired")]
        [RegularExpression(".+@.+\\..+", ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "EmailInvalid")]        
        public string Email { get; set; }

        public string Biography { get; set; }
    }
}

Localizing Data Annotations Validation Messages

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:

namespace MvcInternationalization.Controllers
{
    public class BaseController : Controller
    {
        
        protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // Is it View ?
            ViewResultBase view = filterContext.Result as ViewResultBase;
            if (view == null) // if not exit
                return;

            string cultureName = Thread.CurrentThread.CurrentCulture.Name; // e.g. "en-US" // filterContext.HttpContext.Request.UserLanguages[0]; // needs validation return "en-us" as default            

            // Is it default culture? exit
            if (cultureName == CultureHelper.GetDefaultCulture())
                return;

            
            // Are views implemented separately for this culture?  if not exit
            bool viewImplemented = CultureHelper.IsViewSeparate(cultureName);
            if (viewImplemented == false)
                return;
            
            string viewName = view.ViewName;

            int i = 0;

            if (string.IsNullOrEmpty(viewName))
                viewName = filterContext.RouteData.Values["action"] + "." + cultureName; // Index.en-US
            else if ((i = viewName.IndexOf('.')) > 0)
            {
                // contains . like "Index.cshtml"                
                viewName = viewName.Substring(0, i + 1) + cultureName + viewName.Substring(i);
            }
            else
                viewName += "." + cultureName; // e.g. "Index" ==> "Index.en-Us"

            view.ViewName = viewName;

            filterContext.Controller.ViewBag._culture = "." + cultureName;

            base.OnActionExecuted(filterContext);
        }


        protected override void ExecuteCore()
        {
            string cultureName = null;
            // Attempt to read the culture cookie from Request
            HttpCookie cultureCookie = Request.Cookies["_culture"];
            if (cultureCookie != null)
                cultureName = cultureCookie.Value;
            else
                cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages

            // Validate culture name
            cultureName = CultureHelper.GetValidCulture(cultureName); // This is safe



            // Modify current thread's culture            
            Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
            Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureName);
            
            
            base.ExecuteCore();
        }



    }
}

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:

namespace MvcInternationalization.Utility
{
    public static class CultureHelper
    {
        // Include ONLY cultures you are implementing as views
        private static readonly  Dictionary<String, bool> _cultures  = new Dictionary<string,bool> {
            {"en-US", true},  // first culture is the DEFAULT
            {"es-CL", true},
            {"ar-JO", true}
        };


        /// <summary>
        /// Returns a valid culture name based on "name" parameter. If "name" is not valid, it returns the default culture "en-US"
        /// </summary>
        /// <param name="name">Culture's name (e.g. en-US)</param>
        public static string GetValidCulture(string name)
        {
            if (string.IsNullOrEmpty(name))
                return GetDefaultCulture(); // return Default culture

            if (_cultures.ContainsKey(name))
                return name;

            // Find a close match. For example, if you have "en-US" defined and the user requests "en-GB", 
            // the function will return closes match that is "en-US" because at least the language is the same (ie English)            
            foreach(var c in _cultures.Keys)
                if (c.StartsWith(name.Substring(0, 2)))
                    return c;

            
            // else             
            return GetDefaultCulture(); // return Default culture as no match found
        }


        /// <summary>
        /// Returns default culture name which is the first name decalared (e.g. en-US)
        /// </summary>
        /// <returns></returns>
        public static string GetDefaultCulture()
        {
            return _cultures.Keys.ElementAt(0); // return Default culture

        }


        /// <summary>
        ///  Returns "true" if view is implemented separatley, and "false" if not.
        ///  For example, if "es-CL" is true, then separate views must exist e.g. Index.es-cl.cshtml, About.es-cl.cshtml
        /// </summary>
        /// <param name="name">Culture's name</param>
        /// <returns></returns>
        public static bool IsViewSeparate(string name)
        {
            return _cultures[name];
        }

    }
}

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:

namespace MvcInternationalization.Controllers
{
    public class HomeController : BaseController
    {
        [HttpGet]
        public ActionResult Index()
        {
          
            return View();
        }

        [HttpPost]
        public ActionResult Index(Person per)
        {
                 return View();
        }

        public ActionResult SetCulture(string culture)
        {
            // Validate input
            culture = CultureHelper.GetValidCulture(culture);

            // Save culture in a cookie
            HttpCookie cookie = Request.Cookies["_culture"];
            if (cookie != null)
                cookie.Value = culture;   // update cookie value
            else
            {

                cookie = new HttpCookie("_culture");
                cookie.HttpOnly = false; // Not accessible by JS.
                cookie.Value = culture;
                cookie.Expires = DateTime.Now.AddYears(1);
            }
            Response.Cookies.Add(cookie);

            return RedirectToAction("Index");
        }

        public ActionResult About()
        {
            
           return View();
        }

        



    }
}

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

Client-Side localization

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:

  1. Creating standalone localized javascript files for every culture and language.
  2. Creating a standard common javascript file for all cultures by sticking to Microsoft Ajax Library.

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 :

<script src="@Url.Content("~/Scripts/myscript" + ViewBag._culture + ".js")" type="text/javascript"></script>

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.

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 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:

  1. 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.
  2. Add a helper class that stores the list of culture names that the site will support.
  3. Create a single view or set of views for every culture and language.
  4. Create resource files that contain translation of all string messages. (e.g. Resources.resx, Resources.es-CL.resx, Resources.ar-JO.resx, etc )
  5. Localize javascript files.

I hope this help!
Any questions or comments are welcome!

Tags: ,

ASP.NET | MVC | Website Optimization

Comments

@nonintanon Thailand, on 1/14/2011 6:41:44 PM Said:

Thanks for sharing. This is the technique I'm using as well. So you give me strong feeling that I'm walking through the right path Laughing

Jorrit Salverda Netherlands, on 1/14/2011 10:31:09 PM Said:

I strongly disagree with your recommendation to use separate views for every locale your site supports instead of using resource strings in all your views. It will be maintainability hell.

Because more often than not html will be exactly the same for each language. So if there are html changes you have to update all the views for every language, instead of one. And you will have to test all languages to see if your change is correct.

Furthermore if you have some 200 views and you add a language you would have to create 200 new views. If you use resource strings you only have to create one new file with the proper translations and your done.

Of course it's nice to see the texts directly in your views, but if you use the resource string in your view Visual Studio shows you it's content if you hover over it. That's what the tooling is for!

So maybe your suggestion is nice for really small sites with few views, but in enterprise level sites with hundreds of views it's just not an option.

Regards, Jorrit

Peter Prikryl Slovakia, on 1/14/2011 11:04:06 PM Said:

Nice article, but I do not like this way of localizing applications, because it results in a mess. I can imagine using this technique only for small sites with static content that will never be updated. I cannot imagine maintaining it at all. You also expect that the localization team knows how to code and design web pages.



You said that resource strings in all our site views will clutter the views. What is wrong with using @Resources.EnterPassword instead of "Enter password" - is it unreadable or messy? I do not think so. The valid approach is to use resource strings, but they should not be stored just in one big resource file for the whole site. Each localized page should have its own resource file dependent on that page (e.g. Index.cshtml should have its Index.sk-SK.resx, Index.sp-CL.resx and so on). I understand it is not so easy to mark one file to be dependent on another one in Visual Studio, but there are plugins that make it simple. This results in a nice structure when you can show/hide the localization files after expanding/collapsing the page view in the solution explorer. If you delete/move the view, VS automatically deletes/moves the resource files for you. You also do not need to create another project with localization resources as they are compiled into satellite assembly anyway.



The only thing that is not handled when using resource strings is right-to-left vs left-to-right layout. If you need to implement this, you can have just two views - one for RTL and another one for LTR. You can also have just one view and use your custom HTML helper methods that will handle both directions. The rest (the resource strings) remains the same. When using the resource strings and something changes, you need your localization team just to translate the strings in the resource files. You also get all bells and whistles that come with resource files - not only the localization tools, but also the possibility to read/store the resources in the database when needed.



Your approach is acceptable only for sites that use exactly two languages - one LTR language and one RTL language. If you want to add new language, you need to add a lot of new localized views. If something changes in the layout, you need to update all localized views - that can be a risky task if you do not understand the language used in the views.



Don't repeat yourselft, it is 2011.


PS: I tried to add a comment in Opera and it did not work ^^

konstantin Latvia, on 1/14/2011 11:29:20 PM Said:

Sorry, but have you heard about DRY principle?

Robin Soenen Belgium, on 1/15/2011 1:10:34 AM Said:

I'm afraid that I agree with most comments.
an extra view for each language is really a hell to maintain.

It is a lot easier to make a LocalizedDisplaynameAttribute that inherits from the DisplayNameAttribute.
That way you have an extra attribute like this:
[LocalizedDisplayName("AgeLabel", NameResourceType = typeof(Resourcefile))]

As state previously the rtl and ltr problem isn't solved in a good way.
But I'm sure you can solve that too with a more elegant and easier to maintain solution.

Nich Overend United Kingdom, on 1/15/2011 1:35:46 AM Said:

Erm... I'm not sure about using multiple Views for all of the reasons mentioned above...

But even if I were to go that route, I certainly wouldn't do my view - culture selection in the Controller... Controllers should be very very lean.

Override the FindView mechanism in the View Engine to keep everything neat. That's the way that I select alternate views for Mobile devices, etc.

Miguel Vitorino United Kingdom, on 1/15/2011 2:07:20 AM Said:

I will have to agree with most of the comments also. The DRY principle is very important. View per culture is going to be hell to maintain. I believe the @Resource.MyResourceName syntax can be as readable as plain text. It is just a matter of choosing a resource name that is descriptive enough.
Also, we have always done it this way since the beginning of ASP.NET. MVC has no reason to be different in this matter.

Robert Maissan Canada, on 1/15/2011 2:40:06 AM Said:

I agree with the comments about using one view only.  Multiple views is just crazy.  I maintain a site at work in MVC2 that has French and English displays.  The site has approx 150 views.  If I had to keep separate views for the French and English it would be a nightmare to fix and update.  Both languages share the exact same HTML markup, why would I duplicate myself?  

Personally, I created an extension method for Html.LabelFor that would automatically pull the correct resource for the label.  Using descriptive enough resource names have @Resources. scattered in your file shouldn't be a problem.  For example if I see - @Resources.SiteMainTitle I know exactly what will be displayed.  just like this @Resources.ViewTitle.  But then I create one Resource file per view.  I have Home.Index.resx, Account.Index.resx, Account.ChangePassword.resx, etc.

Nadeem United States, on 1/15/2011 4:17:18 AM Said:

@Robert @Miguel @konstantin
You don't have to stick to this example. I mentioned above you can use only one view for all cultures and languages. However, for languages from right-to-left i am afraid you will need a separate view.

Nadeem United States, on 1/15/2011 4:23:26 AM Said:

I created separate views to show you a different way. I also didn't use labels because I used separate views, where display labels makes little sense.

Miguel Vitorino United Kingdom, on 1/15/2011 4:40:16 AM Said:

Couldn't you try to support right-to-left languages layout with CSS? I have to admit that is a scenario I have not had to deal with.

I would still use the labels to improve accessibility and semantic connection to the respective input field.

Nadeem Afana United States, on 1/15/2011 2:59:35 PM Said:

@Miguel
Right-to-left layout is implemented via inline CSS in the view.

Nadeem Afana United States, on 1/15/2011 3:25:56 PM Said:

@Jorrit

It's a matter of convenience. With resource strings, if you have 200 views and add a language, you'll need to add probably 2000 entries in the resource file. It's almost the same, with separate views sets, you copy and paste every view and add appropriate text. Copying a file takes only a few seconds only.

Furthermore, because translated text size will not be the same as in original language, you might need to tweak html content based on translated text size. You might have to fix the width, height of a container, panel, span, etc. You can even offer different pieces of content like private ads.

Nadeem Afana United States, on 1/15/2011 3:38:07 PM Said:

@Nich,
I think overriding FindView requires a custom view engine. Even doing so, we'll need to adjust current thread's culture by performing some pre-action.

Peter Prikryl Slovakia, on 1/15/2011 10:45:15 PM Said:

It's NOT almost the same. With separate views, you expect that the localization team knows how to design pages and if you change the layout in one view, the localization team HAS TO update all the related views (although there was NO change in the strings). Also the localization is often done by 3rd parties and you do not want them to modify your code.

With resource files, the common scenario is just to export the resource files to colorful Excel files and pass them to your localization team. After they are done, you just process the Excel files with a tool that automatically creates or modifies the resource files in the solution. Of course, it has not to be Excel - you can use other tools.

The layout should be flexible enough to handle long strings. If it is not the case, it is the problem of the layout. You need to fix just the layout (one file) and with that change you will handle all languages.

Nadeem Afana United States, on 1/16/2011 9:17:39 AM Said:

@Peter
"With separate views, you expect that the localization team knows how to design pages and if you change the layout in one view,"


You can extract strings from all views before sending them to localization team. You can copy strings from views or rendered html directly.

With resource files, the common scenario is just to export the resource files to colorful Excel files and pass them to your localization team. After they are done, you just process the Excel files with a tool that automatically creates or modifies the resource files in the solution. Of course, it has not to be Excel - you can use other tools.

I agree with you if the web site is already using resource strings from the beginning. Many projects have text strings embedded in views directly, these strings must be moved first to resource file.

The layout should be flexible enough to handle long strings. If it is not the case, it is the problem of the layout. You need to fix just the layout (one file) and with that change you will handle all languages.

It is generally true. However, Layout is a battle. It's not always 100% flexible.  For example, once I had to create table headers. In English, header text was short and tight enough that if I increased two pixels, it would break the line. The table size took all space and there was no more space. In Spanish, the translation text was longer, so I had to apply different CSS and tweak a little the source html. Again, if your views look fine using Resource string translations, then it's great - you don't need separate views!!

This does not mean you should choose "separate views" over Resource strings, you should be able to choose what is right or wrong depending on your scenario.

Nadeem Afana United States, on 1/18/2011 2:56:37 PM Said:

@David,
Yes, But it doesn't always work. It depends on the content in your pages. Even if you add it, you might still need to tweak layout. For example, in this sample site, the radio button list does not appear well using HTML dir="RTL". Now if you tested that on your site and worked, great!

Oleksii United Kingdom, on 1/19/2011 12:45:20 AM Said:

I would agree with the comments of a few peops in the begining. DRY is strongly agains using separate views with static text. The maintanance of such a web site is hell on Earth.

I would like to add a few other options, like storing localized strings in:
- database
- XML
- text file

In fact, there is nothing new in ASP.NET MVC 3 in terms of interalization.

Oleksii.

Steve Lydford United Kingdom, on 1/22/2011 11:29:00 AM Said:

I prefer to use the DisplayAttribute on the properties of the model and use the Html.LabelFor helpers. For example:

In the Model:

[Display(Name="Surname", ResourceType=typeof(PersonResources))]
public string Surname { get; set; }

in the View:

@Html.LabelFor(model => model.Surname)

That way the View remains nice and clean and I don't have to have maintain multiple versions of each View.

I have blogged about it here: blog.stevelydford.com/.../

Nadeem Afana United States, on 1/22/2011 3:12:57 PM Said:

@Steve,
The DisplayAttribute would make no sense in the example above as it uses separate views. However, it makes sense when using a single view for all languages and cultures as mentioned above.

Steve Lydford United Kingdom, on 1/23/2011 3:40:05 AM Said:

Yes, that's kinda what I'm saying. The new DisplayAttribute means that you can use a single View for all cultures whilst keeping View neat and uncluttered.

You may still need to provide alternate Views for right-to-left languages though. This depends on your UI design I suppose though.

Korayem Egypt, on 2/5/2011 11:56:37 AM Said:

يا أخى تسلم أيدك و الله

raamshalakha United States, on 2/21/2011 5:12:16 AM Said:

It is great supporting tool, some days ago my development team was asking about that tool , i could not tell this. I bookmarked and mail it, we will use it...thanks for help.

Nadeem Afana United States, on 2/21/2011 12:15:43 PM Said:

@BlueCoder
A global filter won't work as we need to perform actions much earlier than filters.

Cerita Dewasa - 17 Tahun - Video 3gp Bokep - Kisah - Bogel United States, on 3/3/2011 5:13:40 AM Said:

Ahhh - I had to read it twice..     but I finally got it :o)

Car Seats Infants United States, on 3/5/2011 5:45:01 PM Said:

Great site, where did you come up with the info in this posting? Im happy I found it though, ill be checking back soon to see what other articles you have.

NerdGround Czech Republic, on 3/9/2011 2:01:28 AM Said:

I've missed this feature a lot in MVC 2, I used my custom internationalization based on xml with caching.

Sarah George United States, on 3/24/2011 2:36:12 PM Said:

Would love different languages on my site, but this sounds complicated and expensive?

Rais Hussain Islamic Republic of Pakistan, on 5/30/2011 3:03:10 AM Said:

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.

Thank you in advance, I hope you would help me out in this regard.

Nadeem United States, on 6/1/2011 3:28:47 PM Said:

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

I hope this helps!!

gb United Kingdom, on 6/1/2011 8:03:16 PM Said:

Hi,
I think your article is one of the best around about Globalization in asp.net MVC. However as many said the usage of a view for language will never work.We have 500 pages and support 10 languages.

How would you change your baseController if you were todo it using resource files?

Thanks for your sharing.

Nadeem Afana United States, on 6/1/2011 8:54:05 PM Said:

gb,
I don't know why so many people think I tell them to use multiple views. I just showed an example since this has not been done before. You can perfectly use resource files easily as I showed above too. You need to pass FALSE to cultures second parameter. It is easy!

gb United Kingdom, on 6/1/2011 9:59:34 PM Said:

Hi,
Thanks for your prompt reply.Because your code is based on views than people including me think that it only works with views.
And as you say you cater for both

Are you refering to "IsViewSeparate"?
and change your baseController to hardcode a false
bool viewImplemented=false;

if not could you actually point me to the code?
thanks for your time

Nadeem Afana United States, on 6/2/2011 7:32:41 AM Said:

gb,
You just need to modify CultureHelper to add FALSE as the second parameter to the dictionary. That's all you need!! Of course, you need to create resource files for each culture.

One view is really easier than what I did here (multiple views). that's why I decided to show you the difficult part which is multiple views.

jim Russia, on 7/1/2011 12:53:08 AM Said:

Thanks for your prompt reply.Because your code is based on views than people including me think that it only works with views.

Marce; Netherlands, on 7/5/2011 2:51:26 AM Said:

Besides the views I like the approach. What I am missing here is the URL effect.

www.website.com/es for Spain
www.website.com/nl for Netherlands
etc

How do you deal with this?

Nadeem Afana United States, on 7/5/2011 8:05:35 AM Said:

Marce,
As I said. The views is just one way to do it, but it is not the only one. You can use a single view which everybody knows how that´s why I didn´t use it here. Also, for languages from rigt-to-left, separate views might be needed to override default layout.

Anyway, you can do that easily. You can set the cookie value in a base controller based on the URL parameter which you can configure. Or you can set the CurrentCulture and CurrentUICulture directly.

I hope this helps!!

Mike United States, on 7/13/2011 11:56:13 AM Said:

I cannot imagine maintaining it at all. You also expect that the localization team knows how to code and design web pages. You said that resource strings in all our site views will clutter the views. What is wrong with using @Resources.EnterPassword instead of "Enter password" - is it unreadable or messy? I do not think so

Nadeem Afana United States, on 7/13/2011 12:27:10 PM Said:

Mike,
You decide what you like!! Some people don't like resources, they prefer views!! believe it or not! Anyway, In this simple case @Resource.EnterPassword is fine, but there are cases where you need to replace complete paragraphs !! Even worse, some paragraphs contain code which makes it difficult to break in other languages. Even naming these resource variables can be confusing to some people. People who worked in globalizing large projects know what I am talking about.

I hope this helps!!

nab United Kingdom, on 7/24/2011 9:32:24 AM Said:

I think this is a perfectly valid blog. In these times it's quite normal to have a small number of views driven by DHTML. Besides as pointed out by the blogger you can default to using the same view for all your languages except perhaps for that problematic one that requires its own view. Thanks for sharing.

Adrichal United States, on 8/3/2011 11:46:20 PM Said:

I have missed this feature a lot in MVC 2, I used my custom internationalization based on xml with caching.

H. McCreight United Kingdom, on 8/7/2011 1:03:54 AM Said:

Far simpler in ASP.NET Web Forms. Separate views per language in MVC - please understand DRY....or as i say..Write once use many times !!!

Nadeem Afana United States, on 8/7/2011 9:23:29 AM Said:

H. MacCreight,
That was an example only. Check my next post afana.me/.../...c-internationalization-part-2.aspx

logo design United States, on 8/17/2011 7:43:28 AM Said:

Nice work Nadeem. Your post helped me a lot. Thanks!

Nadeem Afana United States, on 8/21/2011 3:58:34 PM Said:

H. McCreight,
See part 2.

tanden bleken United States, on 8/22/2011 5:56:32 AM Said:

I'm building a multi language web application for my daily job atm so this comes in real handy. Thank you for this Nadeem, it solves some questions/problems i had.

Chaussures d'enfant France, on 8/26/2011 3:45:26 AM Said:

Good article.Congratulations to you. I put it in my bookmarks

Chris Netherlands, on 8/28/2011 11:03:47 AM Said:

Does anybody now where I find cues on how to create my own localized version? changing CultureHelper to False is not working for me

Nadeem Afana United States, on 8/28/2011 11:06:10 AM Said:

Chris,
See part 2

Bill Yeager United States, on 11/19/2011 7:34:18 AM Said:

Nice article....

Is there any way to do globalize the validation messages that are coming from a Model which is in a separate dll?

I tried creating a reference to my presentation tier project in my model project, but it just didn't work out. From my view, it seems to be only working if you have the Model in the same project.

Nadeem United States, on 11/19/2011 7:58:45 AM Said:

Bill Yeager,
Yes, of course you can use validation messages from a different library as long as you specify the data annotation correctly.

Bill Yeager United States, on 11/19/2011 8:31:17 AM Said:

I have a separate dll that is a Model and it returns validation messages properly (via DataAnnotations) with no problem when I'm editing on the presentation tier.

However, when I try to implement your section on Internationalizing Validation Messages in my separate model dll, I can't get a reference to the Resources object.

I have your MyResources project in the same solution as my presentation tier project. It compiles just fine. I then add my presentation solution dll to my Model project as a reference.

The Resources object does not show up in the intellisense (in my model class) when I prefix it with my presentation tier project name (MyProjectName) and then the "." (for the intellisense).

What do I need to do to have the Resources show up?

Thanks a lot for your help.

Bill Yeager United States, on 11/19/2011 8:40:42 AM Said:

I have set up only 1 view for any language that is selected. Having multiple views set up for each language is not an option for this specific application.

Is there a way that when the user selects the language, that the application can programatically update the culture in the browser which will then display text in the culture that they selected?

Bill Yeager United States, on 11/19/2011 11:37:03 AM Said:

Nadeem, I successfully globalized the whole site thanks to your project. I was able to set the site to specified resources (via your MyResources project which in turn, turned out the correct language text) based on the selected language.

However, I still can't successfully link validation messages from a model that is contained in a separate dll as per my previous post.

Can you please let me know what I might be missing here?

Bill Yeager United States, on 11/19/2011 11:48:03 AM Said:

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

Bill Yeager United States, on 11/19/2011 1:26:11 PM Said:

Nadeem, I don't see the prior posts I submitted.

Could you please post (or email me back) my previous posts as I'd like to ask them on a forum. I typed a lot of stuff, and it would be great not to have to type it all in again.

Nadeem Afana United States, on 11/19/2011 2:14:41 PM Said:

Bill Yeager,
You need to add a reference of your resources project or DLL to the Model project. If it's not showing up then did you set the resources Access modifier to PUBLIC like above ??

Robert Slaney Australia, on 12/14/2011 1:34:06 PM Said:

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.

This means I cannot reconcile the two approaches.  Any advice ?

Nadeem Afana United States, on 12/15/2011 7:30:45 AM Said:

Robert Slaney
Try and define properties that are not dynamic. Why should your properties be dynamic ?!

Robert Slaney Australia, on 12/15/2011 12:14:16 PM Said:

I don't have a class that has a property for each resource key like the generated resx.

I use classes that inherit from IResourceProvider and ResourceProviderFactory to resolve a resource string from the given culture.

public override IResourceProvider CreateGlobalResourceProvider(string classKey)
public object GetObject(string resourceKey, CultureInfo culture)

These are incompatible with the DataAnnotations validation attributes

Nadeem Afana United States, on 12/15/2011 12:37:51 PM Said:

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

Jason Smith Denmark, on 1/17/2012 9:58:13 PM Said:

Where is the part 2 of this article?

Jason
http://www.wordfeudsnyd.dk

David Hanson Canada, on 1/29/2012 9:15:48 AM Said:

Thank you for writing this article.

I appreciate your insights and your code examples.  I like your strategy.  The code you wrote nicely provides for having common views for several cultures to share and also accommodates occasions where a culture needs to have its own dedicated view.

Your code can be easily adapted to have cultures share common views when grouped by left-to-right and right-to-left with some very basic code modifications.

I love how your approach encourages good architecture.  The separate resource library project approach easily lends itself for multiple projects to take advantage of this powerful approach.  We often have a need to access localized content from both the web layer (MVC) and the back-end services layer - not to mention a handful of independent utility applications.

Excellent article.  Well written.  Easy to follow.

Thank you for sharing.

David

Fayez Tunisia, on 2/27/2012 2:31:46 AM Said:

Hello and Thank you for sharing this article!
I have a strange problem in translation on a production server.
I posted my question here stackoverflow.com/.../translation-different-from-culture-and-ui-culture-in-asp-mvc-3-site

Can you please take a look at it when you have time?
Thank you.
Fayez

Nadeem United States, on 2/27/2012 9:00:23 AM Said:

@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


protected override void OnActionExecuted(ActionExecutedContext filterContext)

        

Fayez Tunisia, on 2/28/2012 5:23:35 AM Said:

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"; }

Nadeem United States, on 2/28/2012 9:31:02 AM Said:

@Fayez:

The same method I gave you above. Ideally you should set the Thread cultures in this method. You can access the host in this way:

string host = filterContext.HttpContext.Request.Url.Host;


Charles Daitem France, on 2/28/2012 9:45:43 PM Said:

Thanks Nadeem, I had the same problem as Fayez and now it's fixed. Smile

Fayez Tunisia, on 2/28/2012 10:04:21 PM Said:

Thank you very much M. Afana! That worked perfectly. Have a nice day.

Jacob United States, on 4/9/2012 4:47:31 AM Said:

What is the most optimum way to incorporate different languages and cultures in ASP.NET MVC 3? Which one most people find the easiest to implement?

Jacob
http://www.governmentauction.com

Abdalla Nayer Egypt, on 5/15/2012 6:11:22 AM Said:

Thank You for your code more than perfect,,, i just have one question i am using views and partialviews and it doesnot work with @html.partial, how can i get it work with @html.partial thank you Smile

Nadeem United States, on 5/20/2012 8:32:34 AM Said:

Abdalla Naye,
Could you please provide more details ??

Alberto Menchaca Mexico, on 6/22/2012 5:38:22 AM Said:

Hi, great work. Very easy to implement and to adapt to projects.
Thanks for sharing this on your blog Smile

Brendan Tscharni United States, on 10/21/2012 8:08:38 AM Said:

Is there any way to implement this using database as backing storage? Have tried looking around and have found the Tausens Localization library at www.tausens.com/using-the-localization-library which translates string keys to localized texts with e.g. database as storage - but that's closed source. Can anyone give an example of how this could be done so that editing our localized texts can be integrated into our administration interface?

Add comment




  Country flag

Click to change captcha
biuquote
  • Comment
  • Preview
Loading



Google+