Plan du site  
pixel
pixel

Articles - Étudiants SUPINFO

Multilingue en ASP.NET CORE

Par Camille RAJOELISOLO Publié le 15/05/2017 à 21:48:52 Noter cet article:
(0 votes)
Avis favorable du comité de lecture

Proposer du contenu dans plusieurs langues est une fonctionnalité non négligeable à l'heure actuelle. Par exemple, avoir un siteweb multilingue permet de toucher davantage de visiteurs, mais peut également procurer une meilleure expérience pour toutes celles et ceux qui, à tout hasard, visiteraient votre site et ne comprendraient pas votre langue. Plutôt que d'écrire en dur vos textes, en faisant un paragraphe en français et un traduit en anglais en dessous (certains le font encore...), il serait préférable d'y instaurer la traduction par un framework.

Nous allons, pour cet article, créer une web application en ASP.NET CORE, et décrire pas à pas les instructions pour y mettre en place l'internationalisation.

Création du projet

Dans Visual Studio 2017, il vous faut d'abord créer un nouveau projet. En supposant que votre installation comporte les composants nécessaires pour supporter le .Net Core (si non, vous pouvez consulter ce lien pour suivre l'installation complète : https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/start-mvc), il faut créer un nouveau projet dans Templates > Visual C# > .Net Core > ASP.NET CORE Web Application (.NET CORE) :

Une fois le projet créé (ainsi que le code de la page principale "allégé"), voici ce à quoi ressemble le site une fois que vous avez lancé votre application dans Visual Studio (la lancer avec IIS Express suffit) :

Le code de l'index :

@{
    ViewData["Title"] = "Home Page";
}

<div class="row">
    <div class="col-md-4">
        <h2>Application uses</h2>
        <ul>
            <li>Sample pages using ASP.NET Core MVC</li>
        </ul>
    </div>
    <div class="col-md-4">
        <h2>Overview</h2>
        <ul>
            <li><a href="https://go.microsoft.com/fwlink/?LinkId=518008">Conceptual overview of what is ASP.NET Core</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkId=699320">Fundamentals of ASP.NET Core such as Startup and middleware.</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkId=398602">Working with Data</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkId=398603">Security</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=699321">Client side development</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=699322">Develop on different platforms</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=699323">Read more on the documentation site</a></li>
        </ul>
    </div>
    <div class="col-md-4">
        <h2>Run & Deploy</h2>
        <ul>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=517851">Run your app</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=517853">Run tools such as EF migrations and more</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=398609">Publish to Microsoft Azure Web Apps</a></li>
        </ul>
    </div>
</div>

Et le code du layout :

<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">KWS_Localizer</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
                </ul>
            </div>
        </div>
    </nav>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2017 - KWS_Localizer</p>
        </footer>
    </div>
</body>

Notez que pour les vues, le Razor est utilisé.

Les textes sont écrits en dur, l'objectif va donc être de tout mettre dans des fichiers de ressources.

Configuration

Configurer le startup.cs

Nous allons commencer par configurer la méthode ConfigureServices en y ajoutant le service de localisation AddLocalization :

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddLocalization(options => options.ResourcesPath = "Resources")
        .AddMvc()
        .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
        .AddDataAnnotationsLocalization();
}

Sans oublier d'importer le namespace :

using Microsoft.AspNetCore.Mvc.Razor;

Puis, dans la méthode Configure, on ajoute la liste des langues (dit aussi cultures) que l'on souhaite utiliser pour notre site :

var supportedCultures = new List<CultureInfo>
{
    new CultureInfo("fr"),
    new CultureInfo("fr-FR"),
    new CultureInfo("en"),
    new CultureInfo("en-US")
};

var localizationOptions = new RequestLocalizationOptions
{
    DefaultRequestCulture = new RequestCulture("en-US"),
    SupportedCultures = supportedCultures,
    SupportedUICultures = supportedCultures
};
app.UseRequestLocalization(localizationOptions);

Avec les namespaces :

using System.Collections.Generic;
using System.Globalization;
using Microsoft.AspNetCore.Localization;

Si vous souhaitez connaître la liste des codes des langues que vous pouvez utiliser avec CultureInfo : https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx

Dans les options (via RequestLocalizationOptions), on précise la langue que l'on souhaite faire apparaître par défaut (pour cet article, ce sera l'anglais) ainsi que la liste des langues supportées (ici anglais et français).

Une fois le Startup.cs modifié, nous pouvons passer au HomeController.

Configurer le HomeController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace KWS_Localizer.Controllers
{
    public class HomeController : Controller
    {

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

        public IActionResult Index()
        {
            return View();
        }

        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";

            return View();
        }

        public IActionResult Contact()
        {
            ViewData["Message"] = "Your contact page.";

            return View();
        }

        public IActionResult Error()
        {
            return View();
        }
    }
}

On y a ajouté :

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

Ce qui va permettre de localiser le texte dans le fichier de ressources en identifiant sa clé.

Nous pouvons passer à la création des fichiers de ressources.

Ajout des fichiers de ressources (.resx)

Nous allons créer le dossier Resources à la racine du projet, dans lequel se trouvera les fichiers .resx de traduction. Pour ce site nous n'allons traduire que la page d'accueil, il n'y aura besoin que de deux fichiers - un pour le français et un autre pour l'anglais.

Nb : Sachez que l'on ne peut créer un fichier de ressources commun contenant tous les textes de toutes les pages du site. Pour chaque page il faudra des fichiers de ressources contenant les textes de cette page ainsi que ceux contenant les traductions correspondantes. Pour un site contenant plusieurs pages et proposant plusieurs langues de traduction, cela fera beaucoup de fichiers .resx à créer.

Commençons par créer un fichier de ressources :

Une fois le dossier Resources créé, il faut faire : Add > New Item > ASP.Net Core > Code > Resources file :

Il existe deux façon de nommer et d'organiser ses fichiers de ressources :

La première est de nommer ses fichiers avec le chemin de leur emplacement dans le projet (tout en rajoutant en suffixe le code de la culture), et de placer tous ces fichiers directement dans Resources :

La seconde est de recréer dans le dossier Resources les dossiers du projet pour y placer les fichiers .resx correspondants aux pages du site :

Cette dernière est sans doute la meilleure façon de faire, étant plus propre et plus pratique sur le plan organisationnel pour se retrouver dans un projet (surtout si l'on a beaucoup de pages à gérer).

Maintenant il nous faut remplir ces fichiers de ressources des textes de l'Index :

Index.en.resx :

Index.fr.resx :

Maintenant il faut injecter les textes dans notre page Index :

On n'oublie pas de spécifier le namespace et l'injection de dépendances :

@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer

Ce qui va permettre d'utiliser Localizer[ ] pour injecter les textes du fichier ressource à partir de leurs clés.

On remplace ainsi les textes de la page Index pour obtenir :

@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer

@{
    ViewData["Title"] = Localizer["TitlePage"];
}

<div class="row">
    <div class="col-md-4">
        <h2>@Localizer["Title1"]</h2>
        <ul>
            <li>@Localizer["SubTitle1"]</li>
        </ul>
    </div>
    <div class="col-md-4">
        <h2>@Localizer["Title2"]</h2>
        <ul>
            <li><a href="https://go.microsoft.com/fwlink/?LinkId=518008">@Localizer["OverviewL1"]</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkId=699320">@Localizer["OverviewL2"]</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkId=398602">@Localizer["OverviewL3"]</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkId=398603">@Localizer["OverviewL4"]</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=699321">@Localizer["OverviewL5"]</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=699322">@Localizer["OverviewL6"]</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=699323">@Localizer["OverviewL7"]</a></li>
        </ul>
    </div>
    <div class="col-md-4">
        <h2>@Localizer["Title3"]</h2>
        <ul>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=517851">@Localizer["RunL1"]</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=517853">@Localizer["RunL2"]</a></li>
            <li><a href="https://go.microsoft.com/fwlink/?LinkID=398609">@Localizer["RunL3"]</a></li>
        </ul>
    </div>
</div>

Nb2 : Notez qu'il est possible de fournir une traduction aussi bien pour des textes situés dans les fichiers html que dans tout autre type de fichiers (notamment les classes).

C'est d'autant plus pratique pour traduire par exemple les messages d'erreurs pour des formulaires :

namespace example.ViewModels
{
    public class ContactViewModel
    {
        [StringLength(100)]
        [Required(ErrorMessage = "ErrorMissingName")]
        [Display(Name = "Name")]
        public string Name { get; set; }

        [StringLength(100)]
        [Required(ErrorMessage = "ErrorMissingSubject")]
        [Display(Name = "Subject")]
        public string Subject { get; set; }
    }
}

Ici, "ErrorMissingName" et "ErrorMissingSubject" correspondent aux clés du fichier de ressources ContactViewModel.[culture].resx.

Accès aux pages traduites

Spécifier la langue dans la Query string

Maintenant, on souhaite accéder à l'Index en fonction de la culture spécifiée. Dans ce projet nous allons spécifier la langue dans la query string, avec ?culture=en-us (ou ?culture=fr-fr). Pour ce faire on ouvre une invite de commande dans le dossier source de notre projet, puis on exécute les commandes suivantes :

dotnet restore
dotnet run

Accéder à l'index en fonction de la culture

Désormais lorsque vous accédez à votre site sans spécifier la culture dans une query string, vous obtenez la langue que vous avez spécifié par défaut :

Avec localhost:5000/ et http://localhost:5000/?culture=en-us :

Avec http://localhost:5000/?culture=fr-fr :

Accès aux pages traduites via une url customisée

Maintenant que notre site est enfin traduit en plusieurs langues, on pourrait aller plus loin en ne spécifiant pas la culture dans une query string mais directement dans l'url, et pouvoir avoir une url de ce style : localhost:5000/en/ et localhost:5000/fr/

Pour ce faire nous allons utiliser un provider que nous allons appeler UrlRequestCultureProvider :

De même, nous allons modifier le Startup.cs pour le rajouter :

var supportedCultures = new List<CultureInfo>
{
    new CultureInfo("fr"),
    new CultureInfo("fr-FR"),
    new CultureInfo("en"),
    new CultureInfo("en-US")
};

var localizationOptions = new RequestLocalizationOptions
{
    DefaultRequestCulture = new RequestCulture("en-US"),
    SupportedCultures = supportedCultures,
    SupportedUICultures = supportedCultures
};
app.UseRequestLocalization(localizationOptions);
localizationOptions.RequestCultureProviders.Insert(0, new UrlRequestCultureProvider());

UrlRequestCultureProvider est une classe qui va donc nous permettre de déterminer la culture en fonction de l'url.

Maintenant modifions-le :

using Microsoft.AspNetCore.Localization;
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using System.Text.RegularExpressions;

namespace KWS_Localizer.Providers
{
    public class UrlRequestCultureProvider : IRequestCultureProvider
    {
        public Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
        {
            var url = httpContext.Request.Path;

            var parts = url.Value
                .Split('/')
                .Where(p => !String.IsNullOrWhiteSpace(p)).ToList();
            if (parts.Count == 0)
            {
                return Task.FromResult<ProviderCultureResult>(null);
            }

            var cultureSegmentIndex = 0;
            var hasCulture = Regex.IsMatch(
                parts[cultureSegmentIndex],
                @"^[a-z]{2}(?:-[A-Z]{2})?$");
            if (!hasCulture)
            {
                return Task.FromResult<ProviderCultureResult>(null);
            }

            var culture = parts[cultureSegmentIndex];
            return Task.FromResult(new ProviderCultureResult(culture));
        }
    }
}

Petites explications :

  • var parts correspond aux parties de l'url.

  • var cultureSegmentIndex indique d'aller chercher en position 0 dans l'url le code de la culture (/en-us/page/).

  • var hasCulture check l'écriture de la culture via une regExp (qui va matcher les cultures écrites en /en/).

  • Si la culture n'a pas été spécifiée dans l'url, la culture spécifiée par un des providers suivants ou la culture par défaut sera retournée.

On modifie le Startup.cs pour rajouter une route, dans laquelle sera identifiée la culture :

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "cultureRoute",
        template: "{culture}/{controller}/{action}/{id?}",
        defaults: new { controller = "Home", action = "Index" },
        constraints: new
        {
            culture = new RegexRouteConstraint("^[a-z]{2}(?:-[A-Z]{2})?$")
        });

    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

En ajoutant le namespace :

using Microsoft.AspNetCore.Routing.Constraints;

On modifie ensuite notre page Index.cshtml pour y rajouter des boutons de changement de langues :

Dans le _Layout.cshtml du projet, nous allons modifier les menus et les remplacer par les boutons "Français" et "English" :

<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">KWS_Localizer</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
                    <li><a style="@(culture == "en" ? "color: blue" : "")" asp-area="" asp-route-culture="en">English</a></li>
                    <li><a style="@(culture == "fr" ? "color: red" : "")" asp-area="" asp-route-culture="fr">Français</a></li>
                </ul>
            </div>
        </div>
    </nav>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2017 - KWS_Localizer</p>
        </footer>
    </div>
</body>


En n'oubliant pas les injections et les namespaces :

@using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer

@{
    var culture = Context.Features.Get<IRequestCultureFeature>
        ().RequestCulture.Culture.TwoLetterISOLanguageName;
}

Nous obtenons alors :

Avec localhost:5000/ et localhost:5000/en/ :

Avec localhost:5000/fr/ :

Conclusion

Vous pouvez désormais changerr de langue sur votre site en .Net Core, et ce avec une url plus propre. Il existe bien d'autres méthodes qui permettent d'utiliser des fichiers de ressources moins "encombrants" que les fichiers .resx en termes de modifications et d'utilisation, en fonction des technologies que vous utilisez (avec par exemple l'utilisation de fichiers .json - pour des projets dont la traduction se fait côté client). Cet article vous permet avant tout de découvrir la méthode de base de traduction en .Net Core.

Sources :

- https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization

- https://damienbod.com/2015/10/21/asp-net-5-mvc-6-localization/

- http://www.dotnetcurry.com/aspnet/1314/aspnet-core-globalization-localization

- https://www.jeffogata.com/asp-net-core-localization-culture/

- https://andrewlock.net/adding-localisation-to-an-asp-net-core-application/

A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Toeic, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels