How to best detect browser localization?


Have you ever changed the localization properties of your browser? Well, if you haven’t, don’t worry, because until recently I hadn’t done that either.

However, due to some specific activities and testing I needed to perform, I had to switch my browser’s language (a.k.a. localization) to German (respectively DE-de). I did my job but, three days later, I still have not reverted to the default English language (ah, I’ll use the opportunity I am reminded to do that now) and noticed something pretty interesting – many websites which I visit (LinkedIn for example) automatically changed their interfaces to be displayed in German.

What I realized is that many web services are provided with some magic underneath that does localization of the service for us. But first, what is localization all about?

Firstly, the terms localization, internationalization and i18n mean one and the same thing in the sense of the Internet – automatic displaying of a web service in the language which is set for the browser. The last one – i18n – has actually got pretty interesting roots – just count the characters between the first and the last letter of the word i-n-t-e-r-n-a-t-i-o-n-a-l-i-z-a-t-i-o-n – they are 18, so with time the IT world has become accustomed to using i18n instead of the whole word, since it is really long.

International world flags

Photo credit: www.clickworker.com

Nevertheless, localization is nowadays really important, though. If a given website or service is a truly informational one – like this blog for example – it is not that vital to provide interface in a local language since there are actually no controls provided for the user (don’t mix the terms interface and content – the content is localized by the author and cannot be localized by the browser – we’re speaking of interface here).

However, if it comes to a service with numerous controls like, e.g. Facebook or your Gmail or even the admin panel of WordPress, then localization is pretty vital so that anyone can use all of the provided controls properly and as intended.

So, if we imagine we have a web service and we want to provide internationalization for its interface, there are actually three steps we have to fulfill in order to achieve multi-lingual support:

  • 1) Detect what language is set for the user’s browser.
  • 2) Provide a list of predefined key-value pairs with translations of all interface controls for every language you want to support. Let’s assume your Logout button is programmatically set to retrieve the language value of the key logout-button-text. Then, if you support English and German, you will have two lists with “label-equals-language-value” pairs and in the English one you will have logout-button-text='Logout', while in the German one you will provide logout-button-text='Abmelden' (which means the same).
  • 3) After having detected that, for example, the browser is set to work in German language, check if you have a list present that supports German and then retrieve the language value for the Logout button in German (as well as all other interface controls). Otherwise – fall back to the default language for the service.

What we will cover in this article is the first step – how we can best detect the language of the user’s browser, so that we can “magically” set the language of our web service that exactly fits him or her.

First of all, it is important that we have the knowledge about how local languages are identified or rather “labeled” by the browser. What browsers make use of is the so-called locale, denoting which language the user has selected for the browser. The tricky part here is that the naming convention for locales takes the form language[-region], where the region is optional. What this means is that while en represents English language in general, en-US represents American English, while en-GB stands for Great Britain English. So, what we need to obtain in order to detect the user language preferences is that specific browser locale and, if we support specific regions as well, we have to keep in mind that regions are also provided. However, the article is focused on the different approaches for getting the browser’s language, so let’s go deeper on that topic.

There are three general approaches for informing ourselves about the locale that the user prefers most – on the client side, on the server side or on both sides.

Client Side

Okay, we’re on the client side and, as usual, it is a trouble maker, because there is a well-known discrepancy between browsers and we are always unsure what they don’t support and when exactly they support what they pretend to support. For the case of getting the locale on the client side, we need the navigator browser object which contains information considering the browser itself. Here, however, cross-browser compatibility cannot be fulfilled that easily, since the navigator.language field gives the browser locale properly for Firefox, Chrome, Opera, Safari and newer versions of Internet Explorer (IE > 11.0), but with older versions of IE (10 and below) you have to use the navigator.browserLanguage property. And here, if we do some more research, we find out that, for IE, there is another property available – navigator.userLanguage, which, as stated here, “reflects the setting in the “Your locale (location)” box in the Regional Options of Control Panel” or, in other words, the system locale, not the browser one. So, generally concluded, the (currently) proper way to get the browser locale on the client side is to check what the navigator.language is or, if it is undefined and the browser is IE version less or equal to 10, check what the navigator.browserLanguage is, while keeping in mind that the navigator.userLanguage shows the operating system locale and that navigator.userLanguage and navigator.browserLanguage are undefined for all browsers besides IE. The current proper solution to get the browser locale on the client side can be implemented as follows:

<script>
    //Used for IE < 10 only
    if (navigator.browserLanguage) {
        // outputs en-US
        console.log(navigator.browserLanguage);
    // All other browsers
    } else if (navigator.language) {
        // outputs en-US
        console.log(navigator.language);
    }
</script>

Furthermore, it can be said that checking the locale on the client side for older browsers is, besides what said so far, pretty unreliable. The reason behind that is the information about locale returned by the browsers used to be pretty inconsistent some time ago. In previous browser versions Firefox seemed to be the only browser to respect the locale that was specified in the browser’s language settings. Nowadays, thankfully, this is resolved, but yet again – the client side takes the role of the Dark Side. So, we move to the server-side solution.

Server Side

In most cases, whenever you communicate with a given server, you send HTTP requests, right? All of these requests contain headers and one of them – namely the Accept-Language header, carries the information we need. Furthermore, the Accept-Language header provides a full list of locales, which is beneficial because if you, for example, don’t support the first language in the list, you may support the second one, thus fulfilling at least partially the user’s language desires before falling back to the default language you provide. Here is an example how the browser locale can be retrieved using PHP on the server side:

<?php
    define ('ACCEPT_LANGUAGE_HEADER', 'Accept-Language');

    foreach (getallheaders() as $headerName => $headerValue) {
        // pay attention that PHP's strcmp(str1, str2)
        // returns 0 if strings are equal

        if (! strcmp(ACCEPT_LANGUAGE_HEADER, $headerName)) {
            // $headerValue will be in the form e.g.
            // en-US,en;q=1.0,fr;q=0.8,de;q=0.4
        }
    }
?>

If you have had a careful look or tested the example, you have probably noticed for each locale in the list there is another parameter called q. This parameter represents the quality value of the locale. It is completely optional and defaults to q=1. If it is present in the header for a given locale, it indicates the user’s preference priority for the locale. In other words, the higher the value, the more the user wants this language to be displayed (i.e. the higher it stands in the user’s preference chart). Pretty cool, huh?

When you decide to implement locale retrieval mechanisms on the server side, you have two options – if you provide translated messages directly from the server side, you can do locale processing here and directly pass translated units to the UI. If you, however, do the translation logic on the client side, you can directly pass back the locale to the UI for further language processing. The solution depends on your choice and exact case of implementation.

Some final words

Okay, now you know how you can detect the user’s preferences for displayed languages in your implemented web service. You may still be asking yourself why localization is so important? Well, besides making users feel good and proud that someone has done effort to provide them controls in their native language, don’t forget that, when reading in native language, users will know how to grasp the exact finely-granulated nuances of the language, while their brain will better process the information the UI wants to give them. In other words, you will provide better experience for the users. So, never forget localization, it’s your best friend!

See you,

Alex

Leave a comment

Your email address will not be published. Required fields are marked *