Mise en place d'une application dot net core MVC multilingue

1. Creation d'un "culture provider"

La première étape consiste à créer un provider qui aura pour but de déterminer la langue du contexte courant.

namespace Site.CultureProviders
{
  public class UrlRequestCultureProvider: IRequestCultureProvider
  {
    public Task 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(null);
      }

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

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

2. Configuration des langues supportées

Nous souhaitons avoir une application en Français/Anglais, il faut donc définir, au niveau du configure dans le startup.cs, les langues supportées, ainsi que celle qui sera utilisée par défaut:

var supportedCultures = new List
{
  new CultureInfo("fr"),
  new CultureInfo("en"),   
};

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

3. Gestion de la langue au niveau du routing

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

Une petite visite sur la page d'accueil de l'application et on a une erreur 404, il va donc falloir rediriger l'utilisaeur sur /fr ou /en, on ajoute donc une redirection:

routes.MapRoute(
        name: "redirect",
        template: "/",
        defaults: new { controller = "Home", action = "Redirect" }                    
);

Et au niveau du controller:

public IActionResult Redirect()
{                        
  var rqf = Request.HttpContext.Features.Get();
  // Culture contains the information of the requested culture
  var culture = rqf.RequestCulture.Culture;

  return Redirect("/"+culture);            
}

Si la langue de l'utilisateur ne correspond pas à une des langues supportées par l'application, il sera redirigé vers /en dans notre exemple.

Nous avons donc une application qui gère 2 langues, mais il faut maintenant gérer les traductions.

4. Fichiers de traductions

Nous souhaitons avoir un seul de fichier de traduction par langue commun à l'ensemble des vues, on crée un répertoire Resources et on ajoute 1 fichier de resource par langue supportée:

  • SharedResources.fr.resx
  • SharedResources.fr.resx

On ajoute la configuration au niveau du configureService:

services.AddLocalization(options => options.ResourcesPath = "Resources");
    services.AddMvc()
        .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
        .AddDataAnnotationsLocalization(o =>
        {
            o.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SharedResources));
        });

Note: on peux voir ici que l'on a activé les traductions par annotations (pour gérer la localisation dans les formulaires) et que nous avons crée le type SharedResources, il nous faut donc le créer:

namespace Multilingual
{
  public class SharedResources
  {}
}

5. Au niveau des vues

Pour afficher les traductions au niveau des vues, il faut injecter notre Localizer:

@using Microsoft.AspNetCore.Mvc.Localization
@inject IHtmlLocalizer SharedLocalizer

et remplacer les textes à traduire par:

@SharedLocalizer["entry.name"]

ou entry.name correspond à une clé des fichiers de traductions.

6. Pour aller plus loin

Nous avons donc une application multilingue fonctionnelle, cependant on peut voir certaines limitations pour les url qui prennent des paramètres (définis dans la route ou en query string) mais pas de panique on vous apporte la solution.

Afin de garder les paramètres de la route ainsi que les paramètres passés en query string, il faut les récupérer au niveau de la vue :

  @{ 
     var routeValues = ViewContext.RouteData.Values;                            
     var controller = routeValues["controller"] as string;
     var action = routeValues["action"] as string;                                              
     var queryString = QueryHelpers.ParseQuery(ViewContext.HttpContext.Request.QueryString.ToString());
   }

Et avec 2 extensions methods, nous sommes en mesure de changer de langue tout en gardant les différents paramètres:

@Url.Action(action, controller, routeValues.MergeWithQueryString(queryString).ReplaceParameter("culture","en"))

Avec les fonctions:

public static RouteValueDictionary ReplaceParameter(this RouteValueDictionary rvd, string key, string value)
{
    rvd.Remove(key);
    rvd.Add(key,value);

    return rvd;
}

public static RouteValueDictionary MergeWithQueryString(this RouteValueDictionary rvd,Dictionary qs)
{            
    foreach(var entry in qs)
    {
        rvd.Remove(entry.Key);
        rvd.Add(entry.Key,entry.Value);
    }

    return rvd;
}

Enjoy !

Documentation officielle

Twitter / LinkedIn / Instagram / Facebook / Meetup / SlideShare