One of the project my mate Mic working on is Merino.com, requires multi-languages for the site. The project is built in mvc3 and we are trying to use data annotations for form elements display-names and validations.
It works well if you turn client side validation on. But on server side validation, it always loads up the English resource file. I did a few research, and realize that I need to implement a custom DataAnnotationsModelMetadataProvider and override the CreateMetadata() method.
We create a bunch of resource files, and make sure you set the property Custom Tool to PublicResXFileCodeGenerator.
Also please follow the naming conventions for resource files, [RESOURCE-NAME].[CULTURE].resx
[RESOURCE-NAME] – the name of file. Can be anything you like. It’s used for grouping.
[CULTURE] – word indicating culture of resource file. Cultures are two type: neutral(also called invariant) and concrete. Neutral culture consists from language code only(examples: en, ru, de etc), Concrete culture consists from language code and region code(examples: en-US, en-UK etc).
eg. Resources.en-US.resx -English US
Resources.en-UK.resx – English UK
Resources.en.resx – English neutral (this is also fall back for English)
Resources.fr.resx – French neutral
Resources.resx – Fall back resource file
First, in my example I created two resource files, FormResource.fr.resx (French) and FormResource.resx (English)
Second, I created a model User, contains FirstName, LastName and Email. I assign the Display attribute with Name and ResourceType, and Required attribute with ErrorMessageResourceName and ErrorMessageResourceType. The ResourceType or ErrorMessageResourceType is the resource class name, and display Name or ErrorMessageResourceName is the one of the entries inside resource file.
<pre> public class User { [Display(Name = "FirstName", ResourceType = typeof(FormResource))] public string FirstName { get; set; } [Display(Name = "LastName", ResourceType = typeof(FormResource))] [Required(ErrorMessageResourceName = "RequiredLastName", ErrorMessageResourceType = typeof(FormResource))] public string LastName { get; set; } [Required(ErrorMessageResourceName = "RequiredEmail", ErrorMessageResourceType = typeof(FormResource))] //[Required] [Email(ErrorMessageResourceName = "ValidEmail", ErrorMessageResourceType = typeof(FormResource))] [DataType(DataType.EmailAddress)] [Display(Name = "Email")] public string Email { get; set; } }
Then, I created an Initialize method to override the default one, and set the culture & UI culture based on QueryString. So it allows you to browse a French version form from http://localhost/?culture=fr
public class HomeController : Controller { protected override void Initialize(RequestContext requestContext) { var culture = requestContext.HttpContext.Request.QueryString["culture"]; if (!String.IsNullOrEmpty(culture)) { SetCulture(culture); } base.Initialize(requestContext); } private void SetCulture(string culture) { culture = culture ?? "en"; ViewBag.Culture = culture; Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(culture); Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(culture); } }
At this point, if you have turned on client side validation (in web.config)
<add key="ClientValidationEnabled" value="true"/>
You will find out jquery.validate.unobtrusive handles the validation message just ok. It loads up form labels and validation messages in its language (FR or EN) base on URL you pass in. http://localhost and http://localhost/?culture=fr
But if you turn off the client side validation, the form posting will trigger server side validation. You will notice that form labels are displaying correctly in its corresponding language, but validation messages will fall back to English.
Here is the solution I found out from the a blog, http://haacked.com/archive/2011/07/14/model-metadata-and-validation-localization-using-conventions.aspx
You need to implement custom DataAnnotationsModelMetadataProvider, override the CreateMetadata() method and assign the custom provider to the resource class in Global.asax.
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ModelMetadataProviders.Current = new ConventionalModelMetadataProvider( requireConventionAttribute: false, defaultResourceType: typeof(FormResource) ); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); }
You can download the ConventionalModelMetaProvider.cs and its related classes from Google code, https://model-metadata-localization.googlecode.com/svn/trunk/
It works like charming after you have done the these.