This is a continuation to my ASP.NET Core Localization Deep Dive.

Something which was asked here today by Dwayne Hawkins was:

Why can't it just use the SharedResources file, which I made for my views. I don't want 5000 seperate resx files for all my viewmodels ...

A very valid concern. If we have a lot of view models, we would not want to copy paste error message translations like "{0} is required" to hundreds of RESX files. Updates would be a nightmare. So I found a solution for the problem. I am not sure if it is the best solution, but it does work.

Configuring the Data Annotation Localizer Provider

First I noticed there is an overload on AddDataAnnotationsLocalization which takes an Action which can be used to configure options. The options class is pretty simple:

/// <summary>
/// Provides programmatic configuration for DataAnnotations localization in the MVC framework.
/// </summary>
public class MvcDataAnnotationsLocalizationOptions
{
    /// <summary>
    /// The delegate to invoke for creating <see cref="IStringLocalizer"/>.
    /// </summary>
    public Func<Type, IStringLocalizerFactory, IStringLocalizer> DataAnnotationLocalizerProvider;
}

It has only one property. But that is the delegate used to create the localizers! By default, it uses the type of the model to create a localizer with the localizer factory.

Here is what I did:

services.AddMvc().AddDataAnnotationsLocalization(o =>
{
    o.DataAnnotationLocalizerProvider = (type, factory) =>
    {
        return factory.Create(typeof(SharedResource));
    };
});
services.AddLocalization(o =>
{
    o.ResourcesPath = "Resources";
});

SharedResource here is an empty class. I have a corresponding SharedResource.en-US.resx and SharedResource.fi-FI.resx file in my Resources folder. This means any annotation localizations will now come from those files!

The Finnish RESX file contains values like this:

  • Name: Nimi
  • {0} is required: {0} on pakollinen

Then if we have a model, we can do annotations like this:

public class MyViewModel
{
    [Display(Name = "Name")]
    [Required(ErrorMessage = "{0} is required")]
    public string Name { get; set; }
}

And we get localized error messages :)

Shorter article this time, hope this is useful!