Utilisation de l’entête “Accept-Language” dans un middleware pour définir la langue de l’utilisateur

L’entête Accept-Language

L’entête Accept-Language est un entête présente dans les standards du Web définis par le W3C et permet de préciser quelles sont les langues “préférées” par l’utilisateur pour le contenu de la réponse. La documentation est définie ici (https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4) et apporte toutes les précisions nécessaires sur cet entête.
Cet entête est automatiquement transmis par les navigateurs avec les langues définies dans le profil de l’utilisateur, ces langues sont classées par préférence avec une pondération pour chacune voici ce que mon navigateur envoie : “Accept-Language: fr,en-US;q=0.8,en;q=0.6,fr-FR;q=0.4,it-IT;q=0.2,it;q=0.2”. Ma langue principale est donc le français, suivie de l’anglais US, l’anglais, …

Dans le cadre d’une application Web développée en ASP.NET Core devant être multilingue, nous pouvons utiliser ce header pour définir la meilleure langue à afficher pour l’utilisateur. Pour ce faire, nous allons tirer parti des middlewares mis à notre disposition dans cette dernière mouture d’ASP.NET et qui permettent d’exécuter du code à chaque appel vers notre application Web. Pour plus d’informations sur comment fonctionnent les middlewares, je vous invite a lire l’article de William qui explique le fonctionnement de ceux-ci.

 

Implémentation dans un middleware

Dans un middleware et plus particulièrement dans la méthode Invoke, nous avons accès au HttpContext qui expose les entêtes de la requête entrante et bien sûr le AcceptLanguage. Le code suivant va nous permettre d’obtenir une liste de code de langue et de pondération (https://docs.asp.net/projects/api/en/latest/autoapi/Microsoft/Net/Http/Headers/StringWithQualityHeaderValue/) :

List<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue> acceptedLanguages = context.Request.GetTypedHeaders().AcceptLanguage;

Une fois cette liste obtenue, nous allons classer ces résultats par pondération et dans notre cas obtenir la première langue de l’utilisateur et avoir le code langue associé.

List<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue> acceptedLanguagesOrdered = acceptedLanguages.OrderBy(l => l.Quality.HasValue).ThenByDescending(l => l.Quality).ToList();
string codeLang = acceptedLanguagesOrdered.First().Value;

Attention, le code de la langue peut être de deux formats différents :

  • un langage général avec deux lettres (fr, en, …),
  • un langage plus précis sur plusieurs lettes (en-us, en-gb, …).

Dans notre cas, nous allons forcer ce code de langue pour avoir dans tous les cas, un code sur deux lettres (code ISO 639-1), car notre application ne gère que ce type de langue. Puis nous allons définir la langue courante de l’application utilisant le CurrentThread et ses propriétés : CurrentCulture et CurrentUICulture.

if (codeLang.Contains("-"))
{
    // La première partie ("en", "fr")
    codeLang = name.Split('-')[0];
}

Microsoft.AspNetCore.Localization.RequestCulture requestCulture = new RequestCulture(codeLang);
System.Threading.Thread.CurrentThread.CurrentCulture = requestCulture.Culture;
System.Threading.Thread.CurrentThread.CurrentUICulture = requestCulture.UICulture;

Notre application est maintenant paramétrée pour utiliser la langue principale définie par l’utilisateur. Il peut être cependant utile de filtrer les langues acceptées par l’application et dans ce cas-là d’avoir une langue par défaut si aucune des langues de l’utilisateur ne correspond.
La langue étant définie dans le thread courant, nous pouvons l’obtenir et l’utiliser n’importe où dans notre application. Ceci peut-être intéressant si nous gérons nous même notre globalisation pour obtenir la bonne traduction en fonction de la langue demandée.

 

Voici le code complet de ce middleware, ainsi que l’extension pour l’ajouter directement dans la méthode Configure du Startup de votre application web en ASP.NET Core (si vous souhaitez plus d’informations sur où et comment ajouter des middlewares dans votre application, vous pouvez consulter le début de l’article de William qui traite de ce sujet).

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace MyFirstApplication.Extensions
{
    public class AcceptLanguageSetCultureMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        public AcceptLanguageSetCultureMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
        {
            _next = next;
            _logger = loggerFactory.CreateLogger<AcceptLanguageSetCultureMiddleware>();
        }

        public async Task Invoke(HttpContext context)
        {
            var acceptLanguageHeader = context.Request.GetTypedHeaders().AcceptLanguage;

            if (acceptLanguageHeader != null && acceptLanguageHeader.Any())
            {
                var twoLetterISOLanguageName = GetNeutralCultureName(acceptLanguageHeader.OrderBy(l => l.Quality.HasValue).ThenByDescending(l => l.Quality).First().Value);
                _logger.LogInformation("Accept-Language defined, use culture : " + twoLetterISOLanguageName);
                SetCurrentThreadCulture(new RequestCulture(twoLetterISOLanguageName));
            }
            else
            {
                _logger.LogInformation("No culture defined, use default culture");
                SetCurrentThreadCulture(new RequestCulture("en"));
            }

            
            await _next.Invoke(context);
        }

        private static void SetCurrentThreadCulture(RequestCulture requestCulture)
        {
            Thread.CurrentThread.CurrentCulture = requestCulture.Culture;
            Thread.CurrentThread.CurrentUICulture = requestCulture.UICulture;
        }

        public static string GetNeutralCultureName(string name)
        {
            if (!name.Contains("-"))
            {
                return name;
            }

            // Read first part only. E.g. "en", "es"
            return name.Split('-')[0];
        }
    }

    public static class AcceptLanguageSetCultureMiddlewareExtension
    {
        public static IApplicationBuilder UseAcceptLanguageSetCulture(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<AcceptLanguageSetCultureMiddleware>();
        }
    }
}
Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus