Browser Language Based Content Negotiation

Are you willing to change sitecore website content language based on user's browser preferences? We can achieve this using Content negotiation. Content negotiation is a mechanism to serve different versions of a page or document based on browser's preferences.

For example, a user wants to see information in German, if possible, else English will do. Browsers indicate their preferences by headers in the request. To request only German representations, the browser would send below headers:

Accept-Language: de

As an example of a more complex request, this browser has been configured to accept German and English, but prefer German. In such situation, browser would send below headers:

Accept-Language: de; q=1.0, en; q=0.5


Users can set these preferences in the browser itself. Below is given example of Firefox.

How Browser Based Language Content Negotiation will work

Sitecore provides LanguageResolver, which first checks that the Http Request contains language in Query String (using sc_lang parameter) or in File Path. If the language is found, it will use that language as a context language, otherwise will consider site's default language as the context language.

To achieve our requirement, if the language is not found in the Query String or File Path, we will add one more check to see Language in User Languages in Request Header (Or Accept-Language request header). We can get multiple languages in the request header. So, we will give first priority to the language which is available to serve from our site.

We can get the browser's preferred languages using below code in .NET.

string[] languages = HttpContext.Current.Request.UserLanguages;

Let's see how to achieve in Sitecore

Step 1: Override LanguageResolver

Do below code to override Sitecore's default LanguageResolver:
namespace SitecoreTactics.HttpRequestPipeline
{
    public class LanguageResolver : Sitecore.Pipelines.HttpRequest.LanguageResolver
    {
        public override void Process(HttpRequestArgs args)
        {
            Assert.ArgumentNotNull((object)args, "args");
            Language languageFromRequest = this.GetLanguageFromRequest(args.Context.Request);
            if (languageFromRequest == (Language)null)
                return;
            Context.Language = languageFromRequest;
        }

        private Language ExtractLanguageFromQueryString(HttpRequest request)
        {
            string name = request.QueryString["sc_lang"];
            if (string.IsNullOrEmpty(name))
                return null;
            Language result;
            if (!Language.TryParse(name, out result))
                return null;
            else
                return result;
        }

        private Language GetLanguageFromRequest(HttpRequest request)
        {
            Language languageFromQueryString = this.ExtractLanguageFromQueryString(request);
            if (languageFromQueryString != null)
                return languageFromQueryString;
            else if (Context.Data.FilePathLanguage != null)
                return Context.Data.FilePathLanguage;
            else
            {
                return GetLanguageFromBrowser(request);
            }
        }

        // Added function to allow browser based language content negotiation
        private Language GetLanguageFromBrowser(HttpRequest request)
        {
            Language result;
            if (HttpContext.Current.Request.UserLanguages!= null && HttpContext.Current.Request.UserLanguages.Length > 0)
            {
                string[] languages = HttpContext.Current.Request.UserLanguages;
                string chosenLanguage = string.Empty;

                foreach (string language in languages)
                {
                    string[] langParts = language.Split(';');
                    if (langParts.Length > 0)
                    {
                        if (Sitecore.Context.Database.GetItem("/sitecore/system/languages/" + langParts[0]) != null)
                        {
                            chosenLanguage = langParts[0];
                            break;
                        }
                    }
                }
                if (!Language.TryParse(chosenLanguage, out result))
                    return (Language)null;
                else
                    return result;
            }
            else
                return (Language)null;
            
        }
    }
}

Step 2: Configure overridden LanguageResolver in web.config

In Web.config, replace the Sitecore's LanguageResolver and add our custom overridden LanguageResolver as below:
<httpRequestBegin>
    <!--<processor type="Sitecore.Pipelines.HttpRequest.LanguageResolver, Sitecore.Kernel" />-->
    <processor type="SitecoreTactics.HttpRequestPipeline.LanguageResolver, SitecoreTactics" />
</httpRequestBegin>

There's more

Same way, we can use many other request parameters to negotiate user experience like.
  • User-Agent: User agent of the browser, which Sitecore uses in DeviceResolver 
  • Accept: Content types the browser prefers, which we can use to provide preferable content types of media or content.
  • Accept-DateTime: It is a header to retrieve content of any specific date-time.

We can use all these locale settings to enable them to provide information in the local format.
  •  What numeric formats does the user expect?
  • How should dates and times be formatted?
  • Should measurements be metric (centimeters, kilometers, liters) or imperial (inches, miles, gallons)?
  • What is the user's time zone?
  • Does the user use Letter size paper, or A4?
  • What shoe and clothing sizing systems should be used?
  • What is the user's physical location?

Comments