This is a very quick post in response to a comment asking about the state of localisation for the DisplayAttribute in ASP.NET Core 1.1. A while ago I wrote a series of posts about localising your ASP.NET Core application, using the IStringLocalizer abstraction.

  1. Adding Localisation to an ASP.NET Core application
  2. Localising the DisplayAttribute and avoiding magic strings in ASP.NET Core
  3. Url culture provider using middleware as filters in ASP.NET Core 1.1.0
  4. Applying the RouteDataRequest CultureProvider globally with middleware as filters
  5. Using a culture constraint and catching 404s with the url culture provider
  6. Redirecting unknown cultures to the default culture when using the url culture provider

The IStringLocalizer is a new way of localising your validation messages, view templates, and arbitrary strings. If you're not familiar with localisation in ASP.NET Core, I suggest checking out my first post on localisation for the benefits and pitfalls it brings, but I'll give a quick refresher here.

Brief Recap

Localisation is handled in ASP.NET Core through two main abstractions IStringLocalizer and IStringLocalizer<T>. These allow you to retrieve the localised version of a string by essentially using it as the key into a dictionary; if the key does not exist for that resource, or you are using the default culture, the key itself is returned as the resource:

public class ExampleClass  
{
    public ExampleClass(IStringLocalizer<ExampleClass> localizer)
    {
        // If the resource exists, this returns the localised string
        var localisedString1 = _localizer["I exist"]; // "J'existe"

        // If the resource does not exist, the key itself  is returned
        var localisedString2 = _localizer["I don't exist"]; // "I don't exist"
    }
}

Resources are stored in .resx files that are named according to the class they are localising. So for example, the IStringLocalizer<ExampleClass> localiser would look for a file named (something similar to) ExampleClass.fr-FR.resx. Microsoft recommends that the resource keys/names in the .resx files are the localised values in the default culture. That way you can write your application without having to create any resource files - the supplied string will be used as the resource.

As well as arbitrary strings like this, DataAnnotations which derive from ValidationAttribute also have their ErrorMessage property localised automatically.

Finally, you can localise your Views, either providing whole replacements for your View by using filenames of the form Index.fr-FR.cshtml, or by localising specific strings in your view with another abstraction, the IViewLocalizer, which acts as a view-specific wrapper around IStringLocalizer.

Localising the DisplayAttribute in ASP.NET Core 1.0

Unfortunately, in ASP.NET Core 1.0, there was one big elephant in the room… You could localise the ValidationAttributes on your view models, but you couldn't localise the DisplayAttribute which would generate the associated labels!

Can't localise the labels

That's a little unfair - you could localise the DisplayAttribute, it just required jumping through some significant hoops. You had to fall back to the ResourceManager class, use Visual Studio to generate .resx designer files, and move away from the simpler localisation approach adopted everywhere else. Instead of just passing a key to the Name or ErrorMessage property, you had to remember to set the ResourceType too:

public class HomeViewModel  
{
    [Required(ErrorMessage = "The field is required")]
    [EmailAddress(ErrorMessage = "Not a valid email address")]
    [Display(Name = "Your email address", ResourceType = typeof(Resources.ViewModels_HomeViewModel))]
    public string Email { get; set; }
}

Localising the DisplayAttribute in ASP.NET Core 1.1

Luckily, that issue has all gone away now. In ASP.NET Core 1.1 you can now localise the DisplayAttribute in the same way you do your ValidationAttributes:

public class HomeViewModel  
{
    [Required(ErrorMessage = "The field is required")]
    [EmailAddress(ErrorMessage = "Not a valid email address")]
    [Display(Name = "Your email address")]
    public string Email { get; set; }
}

These values will be used by the IStringLocalizer as keys in the resx files for localisation (or as the localised value itself if a value can't be found in the .resx). No fuss, no muss.

Note As an aside, I still really dislike this idea of using the English phrase as the key in the dictionary - it's too fragile for my liking, but at least you can easily fix that.

Summary

Localisation in ASP.NET Core still requires a lot of effort, but it's certainly easier than in the previous version of ASP.NET. With the update to the DisplayAttribute in ASP.NET Core 1.1, one of the annoying differences in behaviour was fixed, so that you localize it the same way you would your other DataAnnotations.

As with most of my blog posts, there's a small sample project demonstrating this on GitHub