Rendre localisable le contenu d’une application ASP.NET Core

Par Hisham Bin Ateya, Damien Bowden, Bart Calixto et Nadeem Afana

L’une des tâches de la localisation d’une application consiste à envelopper le contenu localisable avec un code qui facilite le remplacement de ce contenu pour différentes cultures.

IStringLocalizer

IStringLocalizer et IStringLocalizer<T> ont été conçus pour améliorer la productivité lors du développement d’applications localisées. IStringLocalizer utilise ResourceManager et ResourceReader pour fournir des ressources spécifiques à la culture au moment de l’exécution. L’interface possède un indexeur et un IEnumerable pour retourner des chaînes localisées. IStringLocalizer n’exige pas de stocker les chaînes de langue par défaut dans un fichier de ressources. Vous pouvez développer une application ciblée pour la localisation sans avoir besoin de créer des fichiers de ressources au tout début du développement.

Le code ci-dessous montre comment envelopper la chaîne « About Title » à des fins de localisation.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace Localization.Controllers;

[Route("api/[controller]")]
public class AboutController : Controller
{
    private readonly IStringLocalizer<AboutController> _localizer;

    public AboutController(IStringLocalizer<AboutController> localizer)
    {
        _localizer = localizer;
    }

    [HttpGet]
    public string Get()
    {
        return _localizer["About Title"];
    }
}

Dans le code précédent, l’implémentation de IStringLocalizer<T> vient de l’injection de dépendances. Si la valeur localisée de « About Title » est introuvable, alors la clé de l’indexeur est retournée, autrement dit, la chaîne « About Title ».

Vous pouvez laisser les chaînes littérales de la langue par défaut dans l’application et les inclure dans un wrapper dans le localisateur, afin de pouvoir vous concentrer sur le développement de l’application. Vous développez une application avec votre langue par défaut et vous la préparez à l’étape de localisation sans créer au préalable un fichier de ressources par défaut.

Vous pouvez également utiliser l’approche traditionnelle et fournir une clé pour récupérer la chaîne de la langue par défaut. Pour de nombreux développeurs, le nouveau workflow qui n’inclut pas de fichier .resx de langue par défaut et qui consiste à simplement inclure dans un wrapper les littéraux de chaîne permet de réduire la surcharge de la localisation d’une application. D’autres développeurs préfèrent le workflow traditionnel, car il facilite l’utilisation de longs littéraux de chaîne ainsi que la mise à jour des chaînes localisées.

IHtmlLocalizer

Utilisez l’implémentation de IHtmlLocalizer<TResource> pour les ressources qui contiennent du HTML. IHtmlLocalizer encode en HTML les arguments formatés dans la chaîne de ressource, mais pas la chaîne de ressource elle-même. Dans le code mis en surbrillance suivant, seule la valeur du paramètre name est encodée au format HTML.

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;

namespace Localization.Controllers;

public class BookController : Controller
{
    private readonly IHtmlLocalizer<BookController> _localizer;

    public BookController(IHtmlLocalizer<BookController> localizer)
    {
        _localizer = localizer;
    }

    public IActionResult Hello(string name)
    {
        ViewData["Message"] = _localizer["<b>Hello</b><i> {0}</i>", name];

        return View();
    }

Remarque : En règle générale, localisez uniquement le texte et non le code HTML.

IStringLocalizerFactory

Au niveau le plus bas, IStringLocalizerFactory peut être récupéré à partir de l’Injection de dépendances :

public class TestController : Controller
{
    private readonly IStringLocalizer _localizer;
    private readonly IStringLocalizer _localizer2;

    public TestController(IStringLocalizerFactory factory)
    {
        var type = typeof(SharedResource);
        var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
        _localizer = factory.Create(type);
        _localizer2 = factory.Create("SharedResource", assemblyName.Name);
    }       

    public IActionResult About()
    {
        ViewData["Message"] = _localizer["Your application description page."] 
            + " loc 2: " + _localizer2["Your application description page."];

        return View();
    }

Le code ci-dessus illustre chacune des deux méthodes de création.

Ressources partagées

Vous pouvez partitionner vos chaînes localisées par contrôleur, par zone, ou avoir un seul conteneur. Dans l’exemple d’application, un marqueur avec nom de la classe SharedResource est utilisée pour les ressources partagées. La classe du marqueur n’est jamais appelée :

// Dummy class to group shared resources

namespace Localization;

public class SharedResource
{
}

Dans l’exemple ci-dessous, les localiseurs InfoController et SharedResource sont utilisés :

public class InfoController : Controller
{
    private readonly IStringLocalizer<InfoController> _localizer;
    private readonly IStringLocalizer<SharedResource> _sharedLocalizer;

    public InfoController(IStringLocalizer<InfoController> localizer,
                   IStringLocalizer<SharedResource> sharedLocalizer)
    {
        _localizer = localizer;
        _sharedLocalizer = sharedLocalizer;
    }

    public string TestLoc()
    {
        string msg = "Shared resx: " + _sharedLocalizer["Hello!"] +
                     " Info resx " + _localizer["Hello!"];
        return msg;
    }

Localisation de l’affichage

Le service IViewLocalizer fournit des chaînes localisées à une vue. La classe ViewLocalizer implémente cette interface et recherche l’emplacement de la ressource à partir du chemin du fichier de la vue. Le code suivant montre comment utiliser l’implémentation par défaut de IViewLocalizer :

@using Microsoft.AspNetCore.Mvc.Localization

@inject IViewLocalizer Localizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>@Localizer["Use this area to provide additional information."]</p>

L’implémentation par défaut de IViewLocalizer recherche le fichier de ressources en fonction du nom de fichier de l’affichage. Il n’existe aucune option pour utiliser un fichier de ressources partagées globales. ViewLocalizer implémente le localiseur en utilisant IHtmlLocalizer, si bien que Razor n’encode pas en HTML la chaîne localisée. Vous pouvez paramétrer des chaînes de ressources, et IViewLocalizer encode en HTML les paramètres, mais pas les chaînes de ressources. Examinons le balisage Razor suivant :

@Localizer["<i>Hello</i> <b>{0}!</b>", UserManager.GetUserName(User)]

Un fichier de ressources en français peut contenir les valeurs suivantes :

Clé Valeur
<i>Hello</i> <b>{0}!</b> <i>Bonjour</i> <b>{0} !</b>

L’affichage contient le balisage HTML provenant du fichier de ressources.

En règle générale, localisez uniquement le texte et non le code HTML.

Pour utiliser un fichier de ressources partagées dans un affichage, injectez IHtmlLocalizer<T> :

@using Microsoft.AspNetCore.Mvc.Localization
@using Localization.Services

@inject IViewLocalizer Localizer
@inject IHtmlLocalizer<SharedResource> SharedLocalizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>

<h1>@SharedLocalizer["Hello!"]</h1>

Localisation de DataAnnotations

Les messages d’erreur DataAnnotations sont localisés avec IStringLocalizer<T>. En utilisant l’option ResourcesPath = "Resources", les messages d’erreur inclus dans RegisterViewModel peuvent être stockés dans les chemins suivants :

  • Resources/ViewModels.Account.RegisterViewModel.fr.resx
  • Resources/ViewModels/Account/RegisterViewModel.fr.resx
using System.ComponentModel.DataAnnotations;

namespace Localization.ViewModels.Account;

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

    [Required(ErrorMessage = "The Password field is required.")]
    [StringLength(8, ErrorMessage = "The {0} must be at least {2} characters long.",
                                                                 MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage =
                            "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

Les attributs hors validation sont localisés.

Comment utiliser une seule chaîne de ressource pour plusieurs classes

Le code suivant montre comment utiliser une seule chaîne de ressource pour les attributs de validation avec plusieurs classes :

    services.AddMvc()
        .AddDataAnnotationsLocalization(options => {
            options.DataAnnotationLocalizerProvider = (type, factory) =>
                factory.Create(typeof(SharedResource));
        });

Dans le code précédent, SharedResource est la classe correspondant au fichier .resx où sont stockés les messages de validation. Cette approche permet à DataAnnotations d’utiliser uniquement SharedResource, au lieu de la ressource de chaque classe.

Configurer les services de localisation

Les services de localisation sont configurés dans Program.cs :

builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");

builder.Services.AddMvc()
    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
    .AddDataAnnotationsLocalization();
  • AddLocalization ajoute les services de localisation au conteneur de services, y compris les implémentations pour IStringLocalizer<T> et IStringLocalizerFactory. Le code ci-dessus affecte également la valeur « Resources » au chemin des ressources.

  • AddViewLocalization ajoute la prise en charge des fichiers de vues localisées. Dans cet exemple de vue, la localisation se base sur le suffixe du fichier de la vue. Par exemple, « fr » dans le fichier Index.fr.cshtml.

  • AddDataAnnotationsLocalization ajoute la prise en charge des messages de validation DataAnnotations localisés par le biais d’abstractions IStringLocalizer.

Remarque

Vous ne pourrez peut-être pas entrer de virgules décimales dans les champs décimaux. Pour prendre en charge la validation jQuery pour les paramètres régionaux autres que l’anglais qui utilisent une virgule (« , ») comme décimale et des formats de date autres que l’anglais des États-Unis, vous devez effectuer des étapes pour localiser votre application. Consultez ce commentaire GitHub 4076 pour savoir comment ajouter une virgule décimale.

Étapes suivantes

La localisation d’une application implique également les tâches suivantes :

Ressources supplémentaires

Par Rick Anderson, Damien Bowden, Bart Calixto, Nadeem Afana et Hisham Bin Ateya

L’une des tâches de la localisation d’une application consiste à envelopper le contenu localisable avec un code qui facilite le remplacement de ce contenu pour différentes cultures.

IStringLocalizer

IStringLocalizer et IStringLocalizer<T> ont été conçus pour améliorer la productivité lors du développement d’applications localisées. IStringLocalizer utilise ResourceManager et ResourceReader pour fournir des ressources spécifiques à la culture au moment de l’exécution. L’interface possède un indexeur et un IEnumerable pour retourner des chaînes localisées. IStringLocalizer n’exige pas de stocker les chaînes de langue par défaut dans un fichier de ressources. Vous pouvez développer une application ciblée pour la localisation sans avoir besoin de créer des fichiers de ressources au tout début du développement.

Le code ci-dessous montre comment envelopper la chaîne « About Title » à des fins de localisation.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace Localization.Controllers
{
    [Route("api/[controller]")]
    public class AboutController : Controller
    {
        private readonly IStringLocalizer<AboutController> _localizer;

        public AboutController(IStringLocalizer<AboutController> localizer)
        {
            _localizer = localizer;
        }

        [HttpGet]
        public string Get()
        {
            return _localizer["About Title"];
        }
    }
}

Dans le code précédent, l’implémentation de IStringLocalizer<T> vient de l’injection de dépendances. Si la valeur localisée de « About Title » est introuvable, alors la clé de l’indexeur est retournée, autrement dit, la chaîne « About Title ».

Vous pouvez laisser les chaînes littérales de la langue par défaut dans l’application et les inclure dans un wrapper dans le localisateur, afin de pouvoir vous concentrer sur le développement de l’application. Vous développez une application avec votre langue par défaut et vous la préparez à l’étape de localisation sans créer au préalable un fichier de ressources par défaut.

Vous pouvez également utiliser l’approche traditionnelle et fournir une clé pour récupérer la chaîne de la langue par défaut. Pour de nombreux développeurs, le nouveau workflow qui n’inclut pas de fichier .resx de langue par défaut et qui consiste à simplement inclure dans un wrapper les littéraux de chaîne permet de réduire la surcharge de la localisation d’une application. D’autres développeurs préfèrent le workflow traditionnel, car il facilite l’utilisation de longs littéraux de chaîne ainsi que la mise à jour des chaînes localisées.

IHtmlLocalizer

Utilisez l’implémentation de IHtmlLocalizer<T> pour les ressources qui contiennent du HTML. IHtmlLocalizer encode en HTML les arguments formatés dans la chaîne de ressource, mais pas la chaîne de ressource elle-même. Dans le code mis en surbrillance suivant, seule la valeur du paramètre name est encodée au format HTML.

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;

namespace Localization.Controllers
{
    public class BookController : Controller
    {
        private readonly IHtmlLocalizer<BookController> _localizer;

        public BookController(IHtmlLocalizer<BookController> localizer)
        {
            _localizer = localizer;
        }

        public IActionResult Hello(string name)
        {
            ViewData["Message"] = _localizer["<b>Hello</b><i> {0}</i>", name];

            return View();
        }

Remarque

En règle générale, localisez uniquement le texte et non le code HTML.

IStringLocalizerFactory

Au niveau le plus bas, vous pouvez sortir IStringLocalizerFactory de l’injection de dépendances :

{
    public class TestController : Controller
    {
        private readonly IStringLocalizer _localizer;
        private readonly IStringLocalizer _localizer2;

        public TestController(IStringLocalizerFactory factory)
        {
            var type = typeof(SharedResource);
            var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
            _localizer = factory.Create(type);
            _localizer2 = factory.Create("SharedResource", assemblyName.Name);
        }       

        public IActionResult About()
        {
            ViewData["Message"] = _localizer["Your application description page."] 
                + " loc 2: " + _localizer2["Your application description page."];

Le code ci-dessus illustre chacune des deux méthodes de création.

Ressources partagées

Vous pouvez partitionner vos chaînes localisées par contrôleur, par zone, ou avoir un seul conteneur. Dans l’exemple d’application, une classe fictive nommée SharedResource est utilisée pour les ressources partagées.

// Dummy class to group shared resources

namespace Localization
{
    public class SharedResource
    {
    }
}

Certains développeurs utilisent la classe Startup pour contenir des chaînes globales ou partagées. Dans l’exemple ci-dessous, les localiseurs InfoController et SharedResource sont utilisés :

public class InfoController : Controller
{
    private readonly IStringLocalizer<InfoController> _localizer;
    private readonly IStringLocalizer<SharedResource> _sharedLocalizer;

    public InfoController(IStringLocalizer<InfoController> localizer,
                   IStringLocalizer<SharedResource> sharedLocalizer)
    {
        _localizer = localizer;
        _sharedLocalizer = sharedLocalizer;
    }

    public string TestLoc()
    {
        string msg = "Shared resx: " + _sharedLocalizer["Hello!"] +
                     " Info resx " + _localizer["Hello!"];
        return msg;
    }

Localisation de l’affichage

Le service IViewLocalizer fournit des chaînes localisées à une vue. La classe ViewLocalizer implémente cette interface et recherche l’emplacement de la ressource à partir du chemin du fichier de la vue. Le code suivant montre comment utiliser l’implémentation par défaut de IViewLocalizer :

@using Microsoft.AspNetCore.Mvc.Localization

@inject IViewLocalizer Localizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>@Localizer["Use this area to provide additional information."]</p>

L’implémentation par défaut de IViewLocalizer recherche le fichier de ressources en fonction du nom de fichier de l’affichage. Il n’existe aucune option pour utiliser un fichier de ressources partagées globales. ViewLocalizer implémente le localiseur en utilisant IHtmlLocalizer, si bien que Razor n’encode pas en HTML la chaîne localisée. Vous pouvez paramétrer des chaînes de ressources, et IViewLocalizer encode en HTML les paramètres, mais pas les chaînes de ressources. Examinons le balisage Razor suivant :

@Localizer["<i>Hello</i> <b>{0}!</b>", UserManager.GetUserName(User)]

Un fichier de ressources en français peut contenir les valeurs suivantes :

Clé Valeur
<i>Hello</i> <b>{0}!</b> <i>Bonjour</i> <b>{0} !</b>

L’affichage contient le balisage HTML provenant du fichier de ressources.

Remarque

En règle générale, localisez uniquement le texte et non le code HTML.

Pour utiliser un fichier de ressources partagées dans un affichage, injectez IHtmlLocalizer<T> :

@using Microsoft.AspNetCore.Mvc.Localization
@using Localization.Services

@inject IViewLocalizer Localizer
@inject IHtmlLocalizer<SharedResource> SharedLocalizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>

<h1>@SharedLocalizer["Hello!"]</h1>

Localisation de DataAnnotations

Les messages d’erreur DataAnnotations sont localisés avec IStringLocalizer<T>. En utilisant l’option ResourcesPath = "Resources", les messages d’erreur inclus dans RegisterViewModel peuvent être stockés dans les chemins suivants :

  • Resources/ViewModels.Account.RegisterViewModel.fr.resx
  • Resources/ViewModels/Account/RegisterViewModel.fr.resx
public class RegisterViewModel
{
    [Required(ErrorMessage = "The Email field is required.")]
    [EmailAddress(ErrorMessage = "The Email field is not a valid email address.")]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required(ErrorMessage = "The Password field is required.")]
    [StringLength(8, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

Dans ASP.NET Core MVC 1.1.0 et version ultérieure, les attributs de non-validation sont localisés.

Comment utiliser une seule chaîne de ressource pour plusieurs classes

Le code suivant montre comment utiliser une seule chaîne de ressource pour les attributs de validation avec plusieurs classes :

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
        .AddDataAnnotationsLocalization(options => {
            options.DataAnnotationLocalizerProvider = (type, factory) =>
                factory.Create(typeof(SharedResource));
        });
}

Dans le code précédent, SharedResource est la classe correspondant au fichier .resx où sont stockés les messages de validation. Cette approche permet à DataAnnotations d’utiliser uniquement SharedResource, au lieu de la ressource de chaque classe.

Configurer les services de localisation

Les services de localisation sont configurés dans la méthode Startup.ConfigureServices :

services.AddLocalization(options => options.ResourcesPath = "Resources");

services.AddMvc()
    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
    .AddDataAnnotationsLocalization();
  • AddLocalization ajoute les services de localisation au conteneur de services, y compris les implémentations pour IStringLocalizer<T> et IStringLocalizerFactory. Le code ci-dessus affecte également la valeur « Resources » au chemin des ressources.

  • AddViewLocalization ajoute la prise en charge des fichiers de vues localisées. Dans cet exemple de vue, la localisation se base sur le suffixe du fichier de la vue. Par exemple, « fr » dans le fichier Index.fr.cshtml.

  • AddDataAnnotationsLocalization ajoute la prise en charge des messages de validation DataAnnotations localisés par le biais d’abstractions IStringLocalizer.

Remarque

Vous ne pourrez peut-être pas entrer de virgules décimales dans les champs décimaux. Pour prendre en charge la validation jQuery pour les paramètres régionaux autres que l’anglais qui utilisent une virgule (« , ») comme décimale et des formats de date autres que l’anglais des États-Unis, vous devez effectuer des étapes pour localiser votre application. Consultez ce commentaire GitHub 4076 pour savoir comment ajouter une virgule décimale.

Étapes suivantes

La localisation d’une application implique également les tâches suivantes :

Ressources supplémentaires