Globalisation and Localisation

Ok, so I said I wasn’t going to do a post on globalisation and localisation because there’s so much out there already. However, I thought I’d put together some of my thoughts and examples I’ve found.

I want to show how to use a separate Resources folder to keep all localisation files instead of using the global or local resource folders that Visual Studio can create. This will allow us to access the resources at any time, including for testing.

I’m using the Contoso University project that you can download here.

So, create a folder called ‘Resources’ and add a couple of .resx files under the folder. One is to be called Student.resx and one to be Student.fr.resx

Next, set the properties on each file to be an Embedded Resource (if not already). Set the ‘Custom Tool’ property to ‘PublicResxFileCodeGenerator’. Set the ‘Custom Tool Namespace’ to be ‘Resources’. This will give us an easy accessor to the resource files. Make sure the project builds at this point.

We can now add some strings to our resx files. Add the following Name Value pairs to the Student.resx file:

and the following pairs to the Student.fr.resx file

We can now configure the Student model to make use of the resources for our validation. In the Student.cs file under the Models folder, add the following attributes:

        [Display(Name = "StudentName", ResourceType = typeof(Resources.Student))]
        [Required(ErrorMessageResourceType = typeof(Resources.Student), ErrorMessageResourceName = "StudentNameRequired")]
        [StringLength(50, ErrorMessageResourceType = typeof(Resources.Student), ErrorMessageResourceName = "StudentNameTooLong")]
public string LastName { get; set; }

The attributes will set our display name, the error message to show it is required and the maximum length of the field. You should see in Visual Studio that we have the intellisense for the Resources object. Unfortunately, it’s not pretty. Phil Haack has some thoughts on this here. It’s a proof of concept that hasn’t been updated but has some great ideas.

 

Further reading:

We can also use fluent validation to create validation rules on the model. This does mean the rules are somewhere else than on the model. The rules can get overlooked. Jerrie Pelser has a good article here.

 

Datepicker Localisation

I won’t go into web site localisation here. There’s enough on the web about that. Take a look at Scott Allen’s blog for some good stuff there. Here I want to localise just the jQuery datepicker. The jQuery documentation has all the info you need to hard code the language into your datepicker but I wanted to know how to get all that good stuff from the client’s browser or current thread in MVC.

So what they are saying is that if you want to show the datepicker to someone using their browser set to French, then load up the French js file something like this:

<script src="datepicker-fr.js"></script>

I understand that it’s great to have these files as small as possible and loaded only when needed but how do you get to load the correct file if you don’t know where the user is coming from?

Well, I guess you can add the language to the html element. You can do it like this:

<html lang="@System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName">

This will give us a nice lang=”en” property of the html element. However, if you take a look at your windows settings you’ll find there’s a lot more to the languages than just “en”. Here in Blighty we’re well aware that British English is quite different to American English. The default “en” is American English and you get all the American date formats etc. Windows shows us there are 16 english language variants.

So, the country is also important. This is why we need to use the language-COUNTRY setting. However, I don’t see most of those in the jQuery ui svn on google. There are many languages but they don’t hold the variants. The good people at w3c internationalization (i18n) show us how to do it.

We can then use the following – CurrentUICulture.Name – to get our <lang>-<COUNTRY> setting to give the end user the experience they deserve.

<html lang="@System.Threading.Thread.CurrentThread.CurrentUICulture.Name">

However, many of these variants don’t seem to be supported by the URLs at the google svn. So, we need a solution with all the possibilities. This might sound like overkill, but there’s nothing more annoying than being presented with a form to fill in with a strange date format, or a decimal separator that you’re not used to.

You can search the nuget packages for ‘jquery-ui-i18n’ and you’ll find a script that contains all the above variants for the datepicker that we need.

There’s some further reading here by Richard Ishida. I worked at Xerox at the same time as Richard and he was the leading light in everything to do with localisation.

Now we have a script full of translations for the datepicker. We just need to know how to use it.

We can use our immediately called function in contoso.js (see previous post) to grab the language:

    // Grab the language of the html element.
    var lang = $('html')[0].lang;

We can set the datepicker defaults (from the i18n js file) by calling the setDefaults method of the datepicker:

    // default format for the current culture on date pickers
    $.datepicker.setDefaults($.datepicker.regional[lang]);

But, even though we have a better set of localisations in the i18n file, we still have neutral language variants instead of the culture provided by .net

For example, we don’t have fr-FR because French for France is the neutral variant. So, if we can’t find the language-COUNTRY variant in the file, we need to drop back to the neutral one.

    // try to get the full language-COUNTRY variant of the user's settings.
    if ($.datepicker.regional[lang] == undefined) {
        // Change lang to neutral language variant. e.g. instead of fr-FR, use fr.
        lang = lang.substr(0, 2);
    }

Also, we can’t use “en” because it’s the default language. We have to send through an empty string.

    // default format for the current culture on date pickers
    $.datepicker.setDefaults($.datepicker.regional[(lang === 'en' ? '' : lang)]);

If we do all this before we turn the text boxes into datepickers:

    // Turn them all into date pickers.
    $(":input[data-datepicker=true]").datepicker();

Then, we will have the correct localisation for our datepicker.

 

In Internet Explorer, with the language priority setting given to fr-FR:

We get:

 

Localisation is a massive task when creating multi-language web sites. It’s best thought out at the start of the project because it’s a huge effort to come and do it at the end.

As a final note, if you’re using Chrome as the browser you may need to include this fix for it’s validation of localised dates.

Scott Hanselman has some similar thoughts on his blog over here.

 

Use a Custom Template in MVC to Automatically Create Datepicker Text Boxes

I started out on this challenge trying to find a way to automatically setup a jQuery datepicker on each rendered text box that had or needed to contain a date. I wanted to keep the standard Html.EditorFor in the MVC View without having to add special instructions to render the required text box that turned it into a datepicker. This is where the MVC Custom Templates come in.

The standard template for a datetime object is the String template. I wanted to create a DateTime template that would create all the necessary information for the jQuery datepicker.

In the Views, Shard folder, create an EditorTemplates folder.

In the EditorTemplates folder, Create a razor page called DateTime.cshtml

I’m using the ContosoUniversity project for the demo as shown below.

DateTime.cshtml

DateTime.cshtml

Replace the contents of the file with the following code:

@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,
                    new { @class = "text-box single-line", 
                            data_datepicker = "true" })

This will create our TextBox with a data- attribute we can use to hook up the datepicker.

Now, when we load an editor for a model with a DateTime object, MVC will find the DateTime custom template and use that instead of the default String template.

Before it magically appears on every page, we need to get jQuery to attach the datepicker to the new text boxes. We need to include a piece of javascript to do that. I added this line to the contoso.js file that I have in the standard bundle loaded on every page.

$(":input[data-datepicker=true]").datepicker();

An example of editing a student record with an enrollment date is shown below. We haven’t had to modify anything in the view to get this functionality.

Date Picker
Date Picker

The view still looks like this:

        <div class="editor-label">
            @Html.LabelFor(model => model.EnrollmentDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.EnrollmentDate)
            @Html.ValidationMessageFor(model => model.EnrollmentDate)
        </div>

The work here did lead me on to another post which I should write up now while I’m thinking about it. That is the use of the datepicker when used with localisation.