Why isn’t WMPL correctly redirecting users based on browser language? [closed]

I’ve found what was going on, so I’m going to answer it for future reference, as it’s a common issue without clear solution.

TL;DR: If the WPML language which’s not redirecting has a country code (eg. en-US instead of en) then you probably have the same bug. Jump to section “How to fix it”.


The Problem

The problem arises at the redirection javascript provided by WPML (browser-redirect.js):

[...]

1  // Get page language and browser language
2  var pageLanguage = wpml_browser_redirect_params.pageLanguage;
3  var browserLanguage = navigator.language? navigator.language : navigator.userLanguage;
4  browserLanguage = browserLanguage.substr(0,2); // browser language may have double code (i.e. en-us)

[...]

5  // Compare page language and browser language
6  if (pageLanguage != browserLanguage) {
7     var redirectUrl;
8     // First try to find the redirect url from parameters passed to javascript
9     var languageUrls = wpml_browser_redirect_params.languageUrls;
10    if (languageUrls[browserLanguage] != undefined) {
11            redirectUrl = languageUrls[browserLanguage];
12    }
13    // Finally do the redirect
14    if (redirectUrl != undefined) {
15            window.location = redirectUrl;
16    }    
17 }

As you can see at line 4, browserLanguage is trimmed, in a way that pt-br becomes pt, en-us becomes en, and so on. That’s probably there so users from different locations but with same language would both be redirected.

The actual problem is at line 10. languageUrls is an array which contains all languages codes with their respective urls, but the codes aren’t trimmed!

languageUrls[]
en: "http://sitehere.com"
es: "http://sitehere.com?lang=es"
pt-br: "http://sitehere.com?lang=pt-br"

So, if browserLanguage was trimmed, the if condition at line 10 returns false, and then you won’t get redirected:

if (languageUrls[pt] != undefined ) {
    // this part doesn't runs
}

Also, even if browserLanguage wasn’t trimmed at all, it still wouldn’t work completely, because some browsers pass browserLanguage with capital letters, like pt_BR, and it just wouldn’t match.

My fix is to use two browserLanguage variables, one trimmed and one in lowercase. Then you compare pageLanguage against both of them, so the behaviour for ‘different location, same language’ users will likely stay the same and you fix the bug.


The Solution

Replace the content of /wp-content/plugins/sitepress-multilingual-cms/res/js/browser-redirect.js with this:

jQuery(document).ready(function(){
    if(jQuery.cookie != undefined) {
        // Check if cookie are enabled
        jQuery.cookie('wpml_browser_redirect_test', '1');
        var cookie_enabled = jQuery.cookie('wpml_browser_redirect_test') == 1;
        jQuery.removeCookie('wpml_browser_redirect_test');

        if (cookie_enabled) {
            var cookie_params = wpml_browser_redirect_params.cookie
            var cookie_name = cookie_params.name;
            // Check if we already did a redirect

            if (!jQuery.cookie(cookie_name)) {
                // Get page language and browser language
                var pageLanguage = wpml_browser_redirect_params.pageLanguage;
                var browserLanguage = navigator.language? navigator.language : navigator.userLanguage;
                browserLanguage = browserLanguage.toLowerCase()
                browserLanguageTrim = browserLanguage.substr(0,2); // browser language may have double code (i.e. en-us)

                // Build cookie options
                var cookie_options = {
                    expires: cookie_params.expiration / 24,
                    path: cookie_params.path? cookie_params.path : "https://wordpress.stackexchange.com/",
                    domain: cookie_params.domain? cookie_params.domain : ''
                };

                // Set the cookie so that the check is made only on the first visit
                jQuery.cookie(cookie_name, browserLanguage, cookie_options);

                // Compare page language and browser language
                if ((pageLanguage != browserLanguage) && (pageLanguage != browserLanguageTrim)) {
                    var redirectUrl;
                    // First try to find the redirect url from parameters passed to javascript
                    var languageUrls = wpml_browser_redirect_params.languageUrls;
                    if (languageUrls[browserLanguage] != undefined) {
                        redirectUrl = languageUrls[browserLanguage];
                    } else if (languageUrls[browserLanguageTrim] != undefined) {
                        redirectUrl = languageUrls[browserLanguageTrim];
                    }
                    // Finally do the redirect
                    if (redirectUrl != undefined) {
                        window.location = redirectUrl;
                    }    
                }
            }
        }
    }
});

Bonus Solution

Users will get redirected only once. This is proposital (and horrible) behaviour. If you’re not satisfied with that, you can try another alternative. Just keep in mind this is a hack that will redirect all users based on their browser language no matter what language they choose, so you MUST have all pages translated and avoid showing a language switcher, as it wont work.

jQuery(document).ready(function(){
    if(jQuery.cookie != undefined) {
        // Check if cookies are enabled
        jQuery.cookie('wpml_browser_redirect_test', '1');
        var cookie_enabled = jQuery.cookie('wpml_browser_redirect_test') == 1;
        jQuery.removeCookie('wpml_browser_redirect_test');

        if (cookie_enabled) {
            var cookie_params = wpml_browser_redirect_params.cookie
            var cookie_name = cookie_params.name;
            var pageLanguage = wpml_browser_redirect_params.pageLanguage;
            var browserLanguage = navigator.language? navigator.language : navigator.userLanguage;
            var languageUrls = wpml_browser_redirect_params.languageUrls;
            browserLanguage = browserLanguage.toLowerCase()
            browserLanguageTrim = browserLanguage.substr(0,2);

            // First time cookie gets set
            if (!jQuery.cookie(cookie_name)) {
                // Build cookie options
                var cookie_options = {
                    expires: cookie_params.expiration / 24,
                    path: cookie_params.path? cookie_params.path : "https://wordpress.stackexchange.com/",
                    domain: cookie_params.domain? cookie_params.domain : ''
                };
                // Sets the cookie in a way that matches avaible languageUrls
                if (languageUrls[browserLanguage] != undefined) {
                    jQuery.cookie(cookie_name, browserLanguage, cookie_options);
                } else if (languageUrls[browserLanguageTrim] != undefined) {
                    jQuery.cookie(cookie_name, browserLanguageTrim, cookie_options);
                }  
            }
            /* If cookie language != page language then redirect
               !! WARNING: THIS DOESN'T ALLOWS LANGUAGE SWITCHING,
               !! BUT GUARANTEES ALL USERS ALWAYS GET REDIRECTED,
               !! ASSUMING BROWSERLANGUAGE IS THE RIGHT LANGUAGE.
               For better behaviour you should make the language switcher
               update the cookie. Bad, bad hack, but I like it better. */
            if (jQuery.cookie(cookie_name) != pageLanguage) {
                var redirectUrl;
                // Sets the url for redirection
                if (languageUrls[jQuery.cookie(cookie_name)] != undefined) {
                    redirectUrl = languageUrls[jQuery.cookie(cookie_name)];
                }
                // Finally do the redirect
                if (redirectUrl != undefined) {
                    window.location = redirectUrl;
                }
            }
        }
    }
});