Scott Hanselman

Globalization, Internationalization and Localization in ASP.NET MVC 3, JavaScript and jQuery - Part 1

May 26, '11 Comments [35] Posted in ASP.NET | ASP.NET MVC | Internationalization | Javascript
Sponsored By

There are several books worth of information to be said about Internationalization (i18n) out there, so I can't solve it all in a blog post. Even 9 pages of blog posts. I like to call it Iñtërnâtiônàlizætiøn, actually.

There's a couple of basic things to understand though, before you create a multilingual ASP.NET application. Let's agree on some basic definitions as these terms are often used interchangeably.

  • Internationalization (i18n) - Making your application able to support a range of languages and locales
  • Localization (L10n) - Making your application support a specific language/locale.
  • Globalization - The combination of Internationalization and Localization
  • Language - For example, Spanish generally. ISO code "es"
  • Locale - Mexico. Note that Spanish in Spain is not the same as Spanish in Mexico, e.g. "es-ES" vs. "es-MX"

Culture and UICulture

The User Interface Culture is a CultureInfo instance from the .NET base class library (BCL). It lives on Thread.CurrentThread.CurrentUICulture and if you felt like it, you could set it manually like this:

Thread.CurrentThread.CurrentUICulture = new CultureInfo("es-MX");

The CurrentCulture is used for Dates, Currency, etc.

Thread.CurrentThread.CurrentCulture = new CultureInfo("es-MX"); 

However, you really ought to avoid doing this kind of stuff unless you know what you're doing and you really have a good reason.

The user's browser will report their language preferences in the Accept-Languages HTTP Header like this:

GET http://www.hanselman.com HTTP/1.1
Connection: keep-alive
Cache-Control: max-age=0
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8

See how I prefer en-US and then en? I can get ASP.NET to automatically pass those values and setup the threads with with the correct culture. I need to set my web.config like this:



...snip...

That one line will do the work for me. At this point the current thread and current UI thread's culture will be automatically set by ASP.NET.

The Importance of Pseudointernationalization

Back in 2005 I updated John Robbin's Pseudoizer (and misspelled it then!) and I've just ported it over to .NET 4 and used it for this application. I find this technique for creating localizable sites really convenient because I'm effectively changing all the strings within my app to another language which allows me to spot strings I missed with the tedium of translating strings.

You can download the .NET Pseudoizer here.

UPDATE: I've put the source for Pseudoizer up on GitHub. You are welcome to fork/clone it and send pull requests or make your own versions.

Here's an example from that earlier post before I run it through Pseudointernationalization:


Transaction Download


View Statement


Select an account below to view or download your available online statements.

I can convert these resources with the pseudoizer like this:

PsuedoizerConsole examplestrings.en.resx examplestrings.xx.resx

and here's the result:


[Ŧřäʼnşäčŧįőʼn Đőŵʼnľőäđ !!! !!!]


[Vįęŵ Ŝŧäŧęmęʼnŧ !!! !!!]


[Ŝęľęčŧ äʼn äččőūʼnŧ þęľőŵ ŧő vįęŵ őř đőŵʼnľőäđ yőūř äväįľäþľę őʼnľįʼnę şŧäŧęmęʼnŧş. !!! !!! !!! !!! !!!]

Cool, eh? If you're working with RESX files a lot, be sure to familiarize yourself with the resgen.exe command-line tool that is included with Visual Studio and the .NET SDK. You have this on your system already. You can move easily between the RESX XML-based file format and a more human- (and translator-) friendly text name=value format like this:

resgen /compile examplestrings.xx.resx,examplestrings.xx.txt

And now they are a nice name=value format, and as I said, I can move between them.

Accounts.Download.Title=[Ŧřäʼnşäčŧįőʼn Đőŵʼnľőäđ !!! !!!]
Accounts.Statements.Action.ViewStatement=[Vįęŵ Ŝŧäŧęmęʼnŧ !!! !!!]
Accounts.Statements.Instructions=[Ŝęľęčŧ äʼn äččőūʼnŧ þęľőŵ ŧő vįęŵ őř đőŵʼnľőäđ yőūř äväįľäþľę őʼnľįʼnę şŧäŧęmęʼnŧş. !!! !!! !!! !!! !!!]

During development time I like to add this Pseudoizer step to my Continuous Integration build or as a pre-build step and assign the resources to a random language I'm NOT going to be creating, like Polish (with all due respect to the Poles) so I'd make examplestrings.pl.resx and the then we can test our fake language by changing our browser's UserLanguages to prefer pl-PL over en-US.

Localization Fallback

Different languages take different amounts of space. God bless the Germans but their strings will take an average of 30% more space than English phrases. Chinese will take 30% less. The Pseudoizer pads strings in order to illustrate these differences and encourage you to take them into consideration in your layouts.

Localization within .NET (not specific to ASP.NET Proper or ASP.NET MVC) implements a standard fallback mechanism. That means it will start looking for the most specific string from the required locale, then fallback continuing to look until it ends on the neutral language (whatever that is). This fallback is handled by convention-based naming. Here is an older, but still excellent live demo of Resource Fallback at ASPAlliance.

For example, let's say there are three resources. Resources.resx, Resources.es.resx, and Resources.es-MX.resx.

Resources.resx:
HelloString=Hello, what's up?
GoodbyeString=See ya!
DudeString=Duuuude!

Resources.es.resx:
HelloString=¿Cómo está?
GoodbyeString=Adiós!

Resources.es-MX.resx:
HelloString=¿Hola, qué tal?

Consider these three files in a fallback scenario. The user shows up with his browser requesting es-MX. If we ask for HelloString, he'll get the most specific one. If we ask for GoodbyeString, we have no "es-MX" equivalent, so we move up one to just "es." If we ask for DudeString, we have no es strings at all, so we'll fall all the way back to the neutral resource.

Using this basic concept of fallback, you can minimize the numbers of strings you localize and provide users with not only language specific strings (Spanish) but also local (Mexican Spanish) strings. And yes, I realize this is a silly example and isn't really representative of Spaniards or Mexican colloquial language.

Views rather than Resources

If you don't like the idea of resources, while you will still have to deal with some resources, you could also have difference views for different languages and locales. You can structure your ~/Views folders like Brian Reiter and others have. It's actually pretty obvious once you have bought into the idea of resource fallback as above. Here's Brian's example:

/Views
/Globalization
/ar
/Home
/Index.aspx
/Shared
/Site.master
/Navigation.aspx
/es
/Home
/Index.aspx
/Shared
/Navigation.aspx
/fr
/Home
/Index.aspx
/Shared
/Home
/Index.aspx
/Shared
/Error.aspx
/Footer.aspx
/Navigation.aspx
/Site.master

Just as you can let ASP.NET change the current UI culture based on UserLanguages or a cookie, you can also control the way that Views are selected by a small override of your favorite ViewEngine. Brian includes a few lines to pick views based on a language cookie on his blog.

He also includes some simple jQuery to allow a user to override their language with a cookie like this:

var mySiteNamespace = {}

mySiteNamespace.switchLanguage = function (lang) {
$.cookie('language', lang);
window.location.reload();
}

$(document).ready(function () {
// attach mySiteNamespace.switchLanguage to click events based on css classes
$('.lang-english').click(function () { mySiteNamespace.switchLanguage('en'); });
$('.lang-french').click(function () { mySiteNamespace.switchLanguage('fr'); });
$('.lang-arabic').click(function () { mySiteNamespace.switchLanguage('ar'); });
$('.lang-spanish').click(function () { mySiteNamespace.switchLanguage('es'); });
});

I'd probably make this a single client event and use data-language or an HTML5 attribute (brainstorming) like this:

$(document).ready(function () {
$('.language').click(function (event) {
$.cookie('language', $(event.target).data('lang'));
})
});

But you get the idea. You can set override cookies, check those first, then check the UserLanguages header. It depends on the experience you're looking for and you need to hook it up between the client and server

Globalized JavaScript Validation

If you're doing a lot of client-side work using JavaScript and jQuery, you'll need to get familiar with the jQuery Global plugin. You may also want the localization files for things like the DatePicker and jQuery UI on NuGet via "install-package jQuery.UI.i18n."

Turns out the one thing you can't ask your browser via JavaScript is what languages it prefers. That is sitting inside an HTTP Header called "Accept-Language" and looks like this, as it's a weighted list.

en-ca,en;q=0.8,en-us;q=0.6,de-de;q=0.4,de;q=0.2

We want to tell jQuery and friends about this value, so we need access to it from the client side in a different way, so I propose this.

This is Cheesy - use Ajax

We could do this, with a simple controller on the server side:

public class LocaleController : Controller {
public ActionResult CurrentCulture() {
return Json(System.Threading.Thread.Current.CurrentUICulture.ToString(), JsonRequestBehavior.AllowGet);
}
}

And then call it from the client side. Ask jQuery to figure it out, and be sure you have the client side globalization libraries you want for the cultures you'll support. I downloaded all 700 jQuery Globs from GitHub. Then I could make a quick Ajax call and get that info dynamically from the server. I also include the locales I want to support as scripts like  /Scripts/globinfo/jquery.glob.fr.js. You could also build a dynamic parser and load these dynamically also, or load them ALL when they show up on the Google or Microsoft CDNs as a complete blob.

But that is a little cheesy because I have to make that little JSON call. Perhaps this belongs somewhere else, like a custom META tag.

Slightly Less Cheesy - Meta Tag

Why not put the value of this header in a META tag on the page and access it there? It means no extra AJAX call and I can still use jQuery as before. I'll create an HTML helper and use it in my main layout page. Here's the HTML Helper. It uses the current thread, which was automatically set earlier by the setting we added to the web.config.

namespace System.Web.Mvc
{
public static class LocalizationHelpers
{
public static IHtmlString MetaAcceptLanguage(this HtmlHelper html)
{
var acceptLanguage = HttpUtility.HtmlAttributeEncode(Threading.Thread.CurrentThread.CurrentUICulture.ToString());
return new HtmlString(String.Format("",acceptLanguage));
}
}
}

I use this helper like this on the main layout page:







   


@Html.MetaAcceptLanguage()

...

And the resulting HTML looks like this. Note that this made-up META tag would be semantically different from the Content-Language or the lang= attributes as it's part of the the parsed HTTP Header that ASP.NET decided was our current culture, moved into the client.







   



Now I can access it with similar code from the client side. I hope to improve this and support dynamic loading of the JS, however preferCulture isn't smart and actually NEEDS the resources loaded in order to make a decision. I would like a method that would tell me the preferred culture so that I might load the resources on-demand.

So what? Now when I am on the client side, my validation and JavaScript is a little smarter. Once jQuery on the client knows about your current preferred culture, you can start being smart with your jQuery. Make sure you are moving around non-culture-specific data values on the wire, then convert them as they become visible to the user.

var price = $.format(123.789, "c");
jQuery("#price").html('12345');
var date = $.format(new Date(1972, 2, 5), "D");
jQuery("#date").html(date);
var units = $.format(12345, "n0");
jQuery("#unitsMoved").html(units);

Now, you can apply these concepts to validation within ASP.NET MVC.

Globalized jQuery Unobtrusive Validation 

Adding onto the code above, we can hook up the globalization to validation, so that we'll better understand how to manage values like 5,50 which is 5.50 for the French, for example. There are a number of validation methods you can hook up, here's number parsing.

$(document).ready(function () {
//Ask ASP.NET what culture we prefer, because we stuck it in a meta tag
var data = $("meta[name='accept-language']").attr("content")
//Tell jQuery to figure it out also on the client side.
$.global.preferCulture(data);

//Tell the validator, for example,
// that we want numbers parsed a certain way!
$.validator.methods.number = function (value, element) {
if ($.global.parseFloat(value)) {
return true;
}
return false;
}
});

If I set my User Languages to prefer French (fr-FR) as in this screenshot:

Language Preference Dialog preferring French

Then my validation realizes that and won't allow 5.50 as a value, but will allow 5,50, given this model:

public class Example
{
public int ID { get; set; }
[Required]
[StringLength(30)]
public string First { get; set; }
[Required]
[StringLength(30)]
public string Last { get; set; }
[Required]
public DateTime BirthDate { get; set; }
[Required]
[Range(0,100)]
public float HourlyRate { get; set; }
}

I'll see this validation error, as the client side knows our preference for , as a decimal separator.

NOTE: It seems to me that the [Range] attribute that talks to jQuery Validation doesn't support globalization and isn't calling into the localized methods so it won't work with the , and . decimal problem. I was able to fix this problem by overriding the range method in jQuery like this, forcing it to use the global implementation of parseFloat. Thanks to Kostas in the comments on this post for this info.

jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Use the Globalization plugin to parse the value
var val = $.global.parseFloat(value);
return this.optional(element) || (val >= param[0] && val <= param[1]);
}
});
Here it is working with validity... 

The Value 4.5 is not valid for Hourly Rate

And here it is in a Danish culture working with [range]:

Localized Range

 

I can also set the Required Attribute to use specific resources and names and localized them from an ExampleResources.resx file like this:

public class Example
{
public int ID { get; set; }
[Required(ErrorMessageResourceType=typeof(ExampleResources),
ErrorMessageResourceName="RequiredPropertyValue")]
[StringLength(30)]
public string First { get; set; }
...snip...

And see this:

image

NOTE: I'm looking into how to set new defaults for all fields, rather than overriding them individually. I've been able to override some with a resource file that has keys called "PropertyValueInvalid" and "PropertyValueRequired" then setting these values in the Global.asax, but something isn't right.

DefaultModelBinder.ResourceClassKey = "ExampleResources";
ValidationExtensions.ResourceClassKey = "ExampleResources";

I'll continue to explore this.

Dynamically Localizing the jQuery DatePicker

Since I know what the current jQuery UI culture is, I can use it to dynamically load the resources I need for the DatePicker. I've installed the "MvcHtml5Templates" NuGet library from Scott Kirkland so my input type is "datetime" and I've added this little bit of JavaScript that says, do we support dates? Are we non-English? If so, go get the right DatePicker script and set it's info as the default for our DatePicker by getting the regional settings given the current global culture.

//Setup datepickers if we don't support it natively!
if (!Modernizr.inputtypes.date) {
if ($.global.culture.name != "en-us" && $.global.culture.name != "en") {
var datepickerScriptFile = "/Scripts/globdatepicker/jquery.ui.datepicker-" + $.global.culture.name + ".js";
//Now, load the date picker support for this language
// and set the defaults for a localized calendar
$.getScript(datepickerScriptFile, function () {
$.datepicker.setDefaults($.datepicker.regional[$.global.culture.name]);
});
}
$("input[type='datetime']").datepicker();
}

Then we set all input's with type=datetime. You could have used a CSS class if you like as well.

image

Now our jQuery DatePicker is French.

Right to Left (body=rtl)

For languages like Arabic and Hebrew that read Right To Left (RTL) you'll need to change the dir= attribute of the elements you want flipped. Most often you'll change the root element to or change it with CSS like:

div {
direction:rtl;
}

The point is to have a general strategy, whether it be a custom layout file for RTL languages or just flipping your shared layout with either CSS or an HTML Helper. Often folks put the direction in the resources and pull out the value ltr or rtl depending.

Conclusion

Globalization is hard and requires actual thought and analysis. The current JavaScript offerings are in flux and that's kind.

A lot of this stuff could be made boilerplate or automatic, but much of it is a moving target. I'm currently exploring either a NuGet package that sets stuff up for you OR a "File | New Project" template with all the best practices already setup and packaged into one super-package. What's your preference, Dear Reader?

The Complete Script

Here's my current "complete" working script that could then be moved into its own file. This is a work in progress, to be sure. Please forgive any obvious mistakes as I'm still learning JavaScript.

    <script>
        $(document).ready(function () {
            //Ask ASP.NET what culture we prefer, because we stuck it in a meta tag
            var data = $("meta[name='accept-language']").attr("content")

            //Tell jQuery to figure it out also on the client side.
            $.global.preferCulture(data);

            //Tell the validator, for example,
            // that we want numbers parsed a certain way!
            $.validator.methods.number = function (value, element) {
                if ($.global.parseFloat(value)) {
                    return true;
                }
                return false;
            }

            //Fix the range to use globalized methods
            jQuery.extend(jQuery.validator.methods, {
                range: function (value, element, param) {
                    //Use the Globalization plugin to parse the value
                    var val = $.global.parseFloat(value);
                    return this.optional(element) || (val >= param[0] && val <= param[1]);
                }
            });

            //Setup datepickers if we don't support it natively!
            if (!Modernizr.inputtypes.date) {
                if ($.global.culture.name != 'en-us' && $.global.culture.name != 'en') {

                    var datepickerScriptFile = "/Scripts/globdatepicker/jquery.ui.datepicker-" + $.global.culture.name + ".js";
                    //Now, load the date picker support for this language
                    // and set the defaults for a localized calendar
                    $.getScript(datepickerScriptFile, function () {
                        $.datepicker.setDefaults($.datepicker.regional[$.global.culture.name]);
                    });
                }
                $("input[type='datetime']").datepicker();
            }

        });
    </script>

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Thursday, May 26, 2011 6:16:06 AM UTC
This is a very thorough and well written guide Scott. And it will definitely be put to good use in my projects.
Thursday, May 26, 2011 6:21:12 AM UTC
NuGet or File-->New Project? My first vote goes to New Project.

Don't get me wrong, I love NuGet, and recently used your totally unsupported package to upgrade an existing Web Forms intranet app to a MVC3 site. I felt that was safe, because I saw your talk on video and read the blog post. But I bet someone noticed the upgrade in production when the default route displayed your pretty standard landing page. ;-) One thing that creeped in the back of my mind though; did I understand it all well enough?

So during your talk when you also applied that same package, with magic happening in the Solution Explorer, I wondered what is actually happening there? And I think that may be a disadvantage of a NuGet package for Globalization examples.

A clean project with exactly the best practices implemented is easy to follow and understand. Although it probably needs some documentation/comments in the code. So that's why it has my first vote.

My second vote still goes to NuGet, since all that magic, well, it just saves me a lot of time. :-)
Thursday, May 26, 2011 8:15:50 AM UTC
In regards to:

NOTE: I'm looking into how to set new defaults for all fields, rather than overriding them individually. I've been able to override some with a resource file that has keys called "PropertyValueInvalid" and "PropertyValueRequired" then setting these values in the Global.asax, but something isn't right.


These messages come from the default MVC's resx file as i understand. I dont think there is a way to add your own localized version of that resx unless it is inside the mvc project when building it?

So what i did was download the MVC source, pulled out the Validator code and replaced the references to the resx file with a localized version. Then i have to use my custom validator methods instead of the default ones. (So @Html.MyValidationMessageFor). The extra benefit is that i could also make sure the html markup for serverside validation is the same as the jquery validation ;)

In another project i just did a very dirty javascript replace, for instance the default must be a number message:



$(function () {

$("input[data-val-number]").each(function (index, el) {

var re = new RegExp("The field (.*?) must be a number");
var string = $(this).attr("data-val-number");
var m = string.match(re);
if (m != null && m.length > 1) {

$(this).attr("data-val-number", string.replace(m[0], "Het veld " + m[1] + " moet een getal zijn."));
}
});
});


which is wrapped up in an if statement based on server side language settings in the _Layout page.. Note that it not possible to even provide a custom message for the 'must be a number' message, as this is not handled the same way as the Required annotation for instance. (As you explained on a previous blog)

My thoughts at the time where that the MVC team should probably include localized versions of the Resx file, or provide a hook to supply a custom resx file for these properties (which they do for some other properties, just not these ones as they are hardcoded to the resx file).

Michel
Thursday, May 26, 2011 8:22:42 AM UTC
NuGet package!
That way you can add it to existing projects.
Filip
Thursday, May 26, 2011 3:09:09 PM UTC
Scott,

Is it really a good idea to change the "Thread.CurrentThread.CurrentCulture" on the server (through code or through the web.config) based on the user's preference?

We've seen issues where SQL Commands will fail when the culture has been switched to Turkish and there is an upper case "I" in a SQL statement (e.g. "select CITY_ID from sometable). This link describes this particular issue: http://www.pcreview.co.uk/forums/ado-net-1-1-issue-stored-proc-params-missing-but-only-if-culture-turkish-t2878032.html

I realize the Turkish example might be a rare case but it has made me wonder whether it's a good idea to change the CurrentCulture on the server at all based on the user's browser preference. I have no problem with the "UI Culture", though.
Thursday, May 26, 2011 4:07:23 PM UTC
http://authors.aspalliance.com/aspxtreme/webapps/demos/resourcefallback.aspx is broken!
Thursday, May 26, 2011 4:15:27 PM UTC
Mo - Works here? I just visited it in Chrome.

Hector - I've seen those problems rarely in older versions of .NET. Alternatively you can avoid changing that culture and instead just change the NumberFormat on the current culture, or do DateTime calculations with a separate CultureInfo you keep around for such things.
Friday, May 27, 2011 9:08:44 AM UTC
But I visited with IE9/FF4 and 500 error occurred.
Sunday, May 29, 2011 10:14:11 AM UTC
Hi Scott,
overriding the parseFloat javascript immediately make globalization available to all numeric validation attributes. However, in complex application containing other javascript libraries for sure will cause problems with other functions that relies onbehaviour of the "standard" parseFloat.
Unluckly I think the only way is to redefine the client side part of the range attribute to use the parsing functions of the global library...A little bit more work but it is safer. I have done it in my Mvc Controls Toolkit
Sunday, May 29, 2011 9:56:44 PM UTC
Its been a while since I did ASP.NET, but I recall that changing one of either the Culture, or the UICulture, meant that exceptions get logged in that culture, meaning developers (who speak English) end up having to deal with error logs in French...
Does this ring a bell for anyone?
Monday, May 30, 2011 11:46:34 AM UTC
Regarding "enableClientBasedCulture": Why do you use this and what does it help with? The documentation says that "This attribute is not in use at this time." (see here) Also, I've never used this attribute before and the functionality works without it (by setting only the culture and uiculture attributes) I may be missing something, so clarification would be really appreciated.
Tuesday, May 31, 2011 10:16:39 AM UTC
Very useful info here, thanks a lot! The Pseudoizer is especially nifty. We'll be translating our website (LogMyTime Zeiterfassung) the from German to English, but nevertheless it is very useful.
Tuesday, May 31, 2011 8:00:05 PM UTC
Very helpful as always.
Lately I am trying to understand jQuery in conjuction with javascript.. I don't really understand, if there is any compelling reason to override the range method via the extend utility function and not directly like:

jQuery.validator.methods.range = function(value, element, params)
{
var val = $.global.parseFloat(value);
return this.optional(element) || (val >= param[0] && val <= param[1]);
}

as you did with the number method.

Thanks
Wednesday, June 01, 2011 7:09:42 AM UTC
Thank you for the examples, I was researching just this last week.

And by the way:

Resources.es.resx:
HelloString=¿Cómo está?
GoodbyeString=Adiós!

Since you use it as a example, you could do it right :P
HelloString=¿Qué pasa?
GoodbyeString=¡Adiós!
(In Spanish, every "!" have a open exclamation "¡" mark before)
Thursday, June 02, 2011 6:40:09 AM UTC
Hi Scott,
Winform guy moving to web and learning asp.net mvc3. I was looking for a proper way to handle globalization issues and sample asp.net mvc3 on how to do it "properly".In asp.net we have base methods to override and hook the threading.
Despite few articles here and there on the web some using a view for each language which will be a big NO where I work,can you imagine 200 web pages in 10 languages.

I was wondering if you could spare the time and put together a small example where you change language at run time using a drop down or flag etc... with some raccomended practices.

Thanks a lot

mark
Thursday, June 02, 2011 9:53:07 AM UTC
Great post, as usual. I found this (which I believe is a little typo):
var price = $.format(123.789, "c");
jQuery("#price").html('12345'price);


Friday, June 03, 2011 6:25:07 AM UTC
Is there some way of having the resources in a database instead of resource-files?
Friday, June 03, 2011 6:25:53 AM UTC
Is there some way of having the resources in database instead of files? Or does that not work with dataannotations?
Wednesday, June 15, 2011 4:22:51 AM UTC
Hey,

I'm not too sure on this line:

<script src="@Url.Content("~/Scripts/globinfo/jquery.glob.fr.js")" type="text/javascript"></script>


How did you "know" to use the 'fr' file?
Or should you use:

<script src="@Url.Content("~/Scripts/globinfo/jquery.glob." + Request.Userlanguages[0] + ".js")" type="text/javascript"></script>
Wednesday, June 15, 2011 12:06:18 PM UTC
Embedding the jquery.glob.fr.js simply tells jquery-global that there is a localization for french and not that it should be used. You have to tell jquery-global by setting jQuery.global.culture tu use "fr". Thats why there is a "jquery.glob.all.js" containing all localizations for all available languages.
Friday, June 17, 2011 11:11:24 AM UTC
Your validator.number function has a little flaw, because it will return false for a zero value. You could use this:

$.validator.methods.number = function (value, element) {
return !isNaN($.global.parseFloat(value));
}

Tuesday, June 21, 2011 1:14:29 PM UTC
hi,
i have some serious trouble to get that damn validation working with another locale than english on the client:

1) all this validation stuff is absolutly awfully described for mvc if "really" documented at all! i'm total confused. i'm trying to get it running FOR DAYS without success (or still getting english error message with unobstrusive validation enabled - or do i really have to set data annotation with the german descriptions on [Required] text for every damn property?). trying to create a site in 'de-DE' locale only (without language switching! neither serverside nor clientside switching.).

you have to incluce so many .js files - at least there are so damn many files to (possibly) include:

SquishIt.Framework.Bundle.JavaScript()
.Add(Url.Content("~/Scripts/jquery-1.6.1.js")) //must have
.Add(Url.Content("~/Scripts/jquery-ui-1.8.13.js")) //must have

.Add(Url.Content("~/Scripts/jquery.ui.datepicker-de.js"))
.Add(Url.Content("~/Scripts/jquery.validate.js"))
.Add(Url.Content("~/Scripts/jquery.validate.unobtrusive.js")) //i'm also commenting out this line when i try to get jquery validation to work with UnobtrusiveJavaScriptEnabled set to false!!
.Add(Url.Content("~/Scripts/messages_de.js")) //this is even described wrong on msdn - there it says some times messages_de.js and sometimes methods_de.js - http://msdn.microsoft.com/en-us/library/gg674880%28v=VS.98%29.aspx

.Add(Url.Content("~/Scripts/jquery.global.js"))
.Add(Url.Content("~/Scripts/globinfo/jquery.glob.de-DE.js")) //Globalization.de-DE.js OR jquery.glob.de-DE.js !? BOTH?
//.Add(Url.Content("~/Scripts/globinfo/Globalization.de-DE.js")) //Globalization.de-DE.js OR jquery.glob.de-DE.js !? BOTH?

//more Microsoft*.js files

plus you must have to call $.global.preferCulture(data); //my data contains "de-DE" fix!
and overwrite and/or extend the validation methods ($.validator.methods) to get jquery.validate to really working with jquery.global.

2) when you set in web.config:
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="false" />

and also comment out jquery.validate.unobtrusive.js client validation completely stops working!

will it work at all in this combination (<add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="false" />)?

is ClientValidationEnabled (true) alone with UnobtrusiveJavaScriptEnabled false using maybe microsoft*.js files for validation?

how to first get validation running with de-DE (without server and client side without language switching possibility) WITH jquery (jquery validation) but WITHOUT unobtrusive?

*angry again*
tobi
Tuesday, June 21, 2011 1:40:12 PM UTC
i did something wrong:

.Add(Url.Content("~/Scripts/messages_de.js")) //this is even described wrong on msdn - there it says some times messages_de.js and sometimes methods_de.js

and read http://msdn.microsoft.com/en-us/library/gg674880%28v=VS.98%29.aspx again.

now i include the methods_de.js (not messages_de.js) and it "ALMOST" works:

1) if you do NOT use [Required] for a nullable double - example:
[Display(Name = "Stunden")]
[Range(0.0, 24.0)]
public double? Hours { get; set; }

due to the methods_de.js file the client miss-validates the empty field for hours. it says "The field Stunden must be a number." but "" - no value should be ok for a double? without [Required]!

2) all messages are still in english!
do i really have to "overwrite" every single data annotation message for every single property in my project? are there no default messages for other locales?
tobi
Monday, June 27, 2011 8:35:24 AM UTC
How do you handle differing urls for the default error path that is set in web.config. A method we use is to have an IIS application for each language and have a web.config in there with the authentication section, purely to specify the error page url for the correct country.

This does not seem correct, how do you handle this?
Tuesday, June 28, 2011 1:24:04 PM UTC
the overwritten $.validator.methods.number method should take care of optional values, too!
tobi
Wednesday, June 29, 2011 1:28:28 PM UTC
What makes this even more confusing is that the jQuery "global" project was changed to "Globalize" and doesn't work quote the same way anymore. This stuff all seems to be very much in flux at the moment. The upside is that it looks like jQuery is working on making it so that the core methods will be able to understand globalization without all the hacks.

The downside is that it doesn't yet, and the constantly changing nature of it has just made things all the more confusing. I did get it working by using the Globalize project and changing the code to suit that, as well as needing to update jQuery UI and then use the jQuery UI i18n project, loading the proper javascript from Globalize, setting it to use that, setting the server side culture, etc.

Man. Someone tell Microsoft to bake this stuff in next time around, because it's not an optional thing.
Wednesday, June 29, 2011 6:17:14 PM UTC
Tridus - Ya, I hear you. It's not us, it's jQuery. They just haven't gotten attention paid to the globalization stuff in jQuery yet.
Wednesday, July 06, 2011 3:11:36 PM UTC
marred only by the fact that the ControllerActionInvoker uses CultureInfo.InvariantCulture to parse dates :( we either need to step in with some javascript to reformat the dates to yyyy-MM-dd when sending culture formatted dates to the server (though jquery datepicker supports 1 altField/altFormat afaik) or additional string parameters on the controller for the dates which we can then parse using the current culture
Sunday, August 14, 2011 11:49:24 PM UTC
Just a shout out to molgan above...

We also use a custom database driven resource provider as the same set of resources are used in websites, application tier services hosted by WCF, windows services, console applications, SSIS packages and scheduled tasks.

All the DataAnnotations based attributes REQUIRE the use of a resx compiled into an assembly because internally the validation attribute base class reflects over the internal and public static properties.

If one my developers ever depended on the use of reflection in this manner I would send the code back for rework, most likely to add a provider model. You cannot work around this behaviour as you cannot even intercept the call using the DLR, the only thing you can do is subclass every ValidationAttribute and override FormatErrorMessage or roll your own. But this causes downstream problems, particularly with Unobtrusive js

I understand that System.ComponentModel.DataAnnotation doesn't have a reference to the System.Web so it can't directly use the ResourceProviderFactory, IResourceProvider or IResourceReader types, but surely is can expose some sort of provider model to allow a dynamic resource engine to be used instead of a static based resx, defaulting back to reflection based lookup.
Thursday, September 08, 2011 3:10:46 AM UTC
RE: Slightly Less Cheesy - Meta Tag

Did you consider the "lang" attribute on the "html" tag? With some clever jQuery you could have multiple languages on the page (since lang can be applied to any html tag and there are inheritence rules).
Monday, September 19, 2011 8:37:39 AM UTC
Great. Very nice post.Please let know about other aspects of Localization Testing Services apart from functional and cosmetic testing.
Tuesday, September 27, 2011 1:25:44 PM UTC
In answer to the cannot change numeric message: try calling this from Application_Start

NumericValidatorHelper.Apply();

you are then free to use your own resource string whose key is in the code below. This does not require any javascript code to support and no extra code fixup.

Regards

J



public static class NumericValidatorHelper
{
public static void Apply()
{
ClientDataTypeModelValidatorProvider provider = (ClientDataTypeModelValidatorProvider)(from p in ModelValidatorProviders.Providers where p.GetType() == typeof(ClientDataTypeModelValidatorProvider) select p).SingleOrDefault();

if (provider != null)
{
var index = ModelValidatorProviders.Providers.IndexOf(provider);
ModelValidatorProviders.Providers.Insert(index, new CustomClientDataTypeModelValidatorProvider());
ModelValidatorProviders.Providers.Remove(provider);
}
}

private class CustomClientDataTypeModelValidatorProvider : ClientDataTypeModelValidatorProvider
{
public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
{
var validator = base.GetValidators(metadata, context).FirstOrDefault();

if (validator != null)
{
yield return new CustomNumericModelValidator(metadata, context);
}
}
}

private class CustomNumericModelValidator : ModelValidator
{
public CustomNumericModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
: base(metadata, controllerContext)
{
}

public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
ModelClientValidationRule rule = new ModelClientValidationRule()
{
ValidationType = "number",
ErrorMessage = MakeErrorString(Metadata.GetDisplayName())
};

return new ModelClientValidationRule[] { rule };
}

private static string MakeErrorString(string displayName)
{
// use CurrentCulture since this message is intended for the site visitor
return String.Format(CultureInfo.CurrentCulture, App_GlobalResources.Strings.ClientDataTypeModelValidatorProvider_FieldMustBeNumeric, displayName);
}

public override IEnumerable<ModelValidationResult> Validate(object container)
{
// this is not a server-side validator
return Enumerable.Empty<ModelValidationResult>();
}
}
}
Monday, November 14, 2011 6:09:35 PM UTC
.net mvc (2,3) has a bug.

take a look

http://stackoverflow.com/questions/8124147/net-dataannotaion-ang-globalization-issue-server-side
Monday, November 14, 2011 6:22:24 PM UTC


Microsoft has a bug

http://stackoverflow.com/questions/8124147/net-dataannotaion-ang-globalization-issue-server-side
Tuesday, January 17, 2012 6:33:40 PM UTC
You know, you can't use "xx" like you have in there, it's not supported.
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.