9

I've put together a small script in PHP that checks for the browser's language settings and redirect them to a language version of the site (WP multisite),

function redirect() {
  $language = substr( $_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2 );
  switch( $language ) {
    case 'sv':
      header( 'Location: http://www.example.com/sv/' );
      break;
    case 'no':
      header( 'Location: http://www.example.com/no/' );
      break;
    case 'da':
      header( 'Location: http://www.example.com/da/' );
      break;
    default:
      header( 'Location: http://www.example.com/' );
      break;
  }
}
if ( strlen($url) < 4 ) {
  session_start();
  if ( empty($_SESSION[ 'language' ]) ) {
    $_SESSION[ 'language' ] = true;
    redirect();
  }
}

When testing with Mobile Safari or Mobile Chrome the redirect doesn't appear to work. Is there any special output for the accept language for mobile browsers that I need to consider?

Update: After some more debugging I found out this:

  • Mobile Safari displays the correct language when echoing HTTP_ACCEPT_LANGUAGE but does not redirect.
  • Mobile Chrome (iOS only, works on Android) doesn't display the correct language (defaults to "en").
  • iOS parses the http header data in a different order, compare iOS Chrome (en-US,en;q=0.8,sv;q=0.6) and OSX Chrome (sv,en-US;q=0.8,en;q=0.6).
Staffan Estberg
  • 6,795
  • 16
  • 71
  • 107
  • Echo the `HTTP_ACCEPT_LANGUAGE` from your mobile device is it there? – chris85 Jun 29 '15 at 03:50
  • Sorry, forgot to mention - echoing this gives me "en" as a result for both Mobile Safar and Mobile Chrome (iOS 8). – Staffan Estberg Jun 29 '15 at 08:37
  • So looks like `HTTP_ACCEPT_LANGUAGE` isnt going to work on mobile devices. You could either ask the user their language or try some other method when the UA is mobile. – chris85 Jun 29 '15 at 12:51
  • 2
    If you 'echo out' your `HTTP_ACCEPT_LANGUAGE` value and then try to redirect, it won't work, as you have already sent output to browser. Use proper log-bases debugging techniques if you don't want to potentially break application logic with spurious output. Also, you really have nothing to go on but what the Accept-Language string sent by the browser gives you. Your iOS Chrome example SHOULD result in `en-US` or `en` as language of choice since `sv` has lower q value. Their is nothing broken here at all. You likely just have different settings for localization across those devices. – Mike Brant Jul 06 '15 at 21:30
  • Can you post the value of $url, why are you checking its length to be less than 4 chars long? thanks – Juank Jul 07 '15 at 15:25

6 Answers6

4

Try this and let us know the output please

function redirect() {
  $language = substr( $_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2 );

  switch( $language ) {
    case 'sv':
      header( 'Location: http://www.example.com/sv/' );
      break;
    case 'no':
      header( 'Location: http://www.example.com/no/' );
      break;
    case 'da':
      header( 'Location: http://www.example.com/da/' );
      break;
    default:
      die('Default location');
   /* if you get this message on mobile devices, then this line  

          $language = substr( $_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2 );

      is faulty. Perhaps as @chris85 mentioned, HTTP_ACCEPT_LANGUAGE is
      not populated so mobile behaves as a default by not redirecting to
      other languages. If this is the case, fix that line
      and remove the die();*/
      header( 'Location: http://www.example.com/' );
      break;
  }
  die(); // leave this one in. It forces the server to flush data to the browser
}
Juank
  • 6,096
  • 1
  • 28
  • 28
2

UPDATE to my previous answer

The HTTP_ACCEPT_LANGUAGE is set via headers and will give different values for everyone. In my case I am in south america on an computer setup in english so my lang headers have english and spanish settings with a bias towards english.

session_start();

function redirectToLang($langCode){
    // use if's instead of switch so that you can
    // check exact matches and presence of a substring
    if ($langCode == 'sv'){
        $langPath = 'sv';
    }else if (strpos($langCode, 'en') !== false){ // this would match en, en-CA, en-US
        $langPath = 'en';
    }else if ($langCode == 'no'){
        $langPath = 'no';
    }else{
        $langPath = 'en';
    }

    // you should have no output from the server before this line!
    // that is no echoes, print_r, var_dumps, headers, etc
    header( 'Location: http://www.example.com/' . $langPath .'/' );
    die();
}

function parseLang(){
    // echo $_SERVER['HTTP_ACCEPT_LANGUAGE']; in my case
    // Chrome Mac OS:        en,es;q=0.8
    // Chrome Android 5.1:   en-US;en;q=0.8,es;q=0.6
    // IE Windows Phone 8.1: en-US,en;q=0.5
    // Safari iOS:           en-US
    // Chrome iOS:           en-US,en;q=0.8

    // get the lang and set a default
    $lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : 'en';

    // parse the lang code. This can be as simple or as complex as you want

    // Simple
    $langCode = substr($lang, 0, 2); // in my case 'en'

    // Semi Complex (credits to http://www.thefutureoftheweb.com/blog/use-accept-language-header)
    $languages = array();
    preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $lang, $parsed);

    if (count($parsed[1])) {
        $languages = array_combine($parsed[1], $parsed[4]);
        foreach ($languages as $lang => $val) {
            if ($val === '') $languages[$lang] = 1;
        }
        arsort($languages, SORT_NUMERIC);
    }
    // var_dump($languages); in my case
    // array (size=2)
    //  'en' => int 1
    //  'es' => string '0.8'


    $langCode = key($languages); // in my case 'en'

    return $langCode;
}


if (!isset($_SESSION['lang'])){
    $langCode = parseLang();
    $_SESSION['lang'] = $langCode;
    redirectToLang($langCode);
}else{
    // we already know the language and it is in $_SESSION
    // no need to parseLang nor redirect
}

In my case, all devices redirect correctly. My guess is that there is something happening on the logic that calls redirect()

// this part
if ( strlen($url) < 4 ) {
  session_start();
  if ( empty($_SESSION[ 'language' ]) ) {
    $_SESSION[ 'language' ] = true;
    redirect();
  }
}

and the session var is bypassing the redirect logic. Try the code above and clear all cookies and sessions from all devices so that the $_SESSION['language'] var you have set during testing wont mess up the results. Let us know what happens on your end.

Juank
  • 6,096
  • 1
  • 28
  • 28
2

I'm quoting.. "A more contemporary method would be to use http_negotiate_language():"

Did you check this one? Using the PHP HTTP_ACCEPT_LANGUAGE server variable

Community
  • 1
  • 1
nmorell
  • 65
  • 6
1

This works fine on my desktop browsers, and mobile devices. I too was experiencing session problems on devices only and most often, I was relying on a session variable being empty to fulfill the requirements of my condition when in fact the variable was still in existence, or there simply was no session_id() instantiated.

?reset will clear the session.

It also will run the redirect if the language has changed.

<?php
    session_start();

    if (isset($_REQUEST['reset'])) {
      unset($_SESSION);
      $_SESSION['PREVIOUS_SESSION'] = '&cleared=1';
    }

    function redirect($loc) {
        $_SESSION[ 'language' ] = true;
        $_SESSION['last_language'] = $language;
        header( 'Location: ?r='.$loc.$_SESSION['PREVIOUS_SESSION']);
    }

    $language = substr( $_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2 );

    if (( empty($_SESSION[ 'language' ]) ) || ($_SESSION['last_language'] != $language)) {
        redirect($language);
    }

    echo '<pre>';
    print_r($_SESSION);
    echo '</pre>';

    if (!empty($_SESSION['PREVIOUS_SESSION'])) {
        unset($_SESSION['PREVIOUS_SESSION']);
    }
?>
pokeybit
  • 1,032
  • 11
  • 18
  • 2
    `substr( $_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2 )` is exactly the same code the OP is using which isn't working for his mobile cases. – Drakes Jun 29 '15 at 14:35
  • 1
    That is not the problem with the redirect, which is the issue. In the OP case the variable $language is assigned 'http://www.example.com/' if the device fails to return substr( $_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2 ). No point removing this. – pokeybit Jun 29 '15 at 16:55
1

You should really give us examples of what is the value of $_SERVER["HTTP_ACCEPT_LANGUAGE"] for the three cases.

Anyway, please note that according to the RFC2616 of HTTP/1.1, the choice of a language is much more complicated than just taking the two first chars of the header.

Each language-range MAY be given an associated quality value which represents an estimate of the user's preference for the languages specified by that range. The quality value defaults to "q=1". For example,

   Accept-Language: da, en-gb;q=0.8, en;q=0.7

would mean: "I prefer Danish, but will accept British English and other types of English."

Nothing says that those headers are sorted, nor that the preffered language of the user is the first one in the list. And the language configuration could also not be configured in the browser or OS.

Ideally, to select the best language, you have to parse this header this way:

  • Split the string on commas
  • Split every substring found on the semicolon character
  • When a numeric value is not given, use the default value of 1.0
  • Sort the result using this numeric value
  • Compare this list to the list of languages that are available on your website and find the best one.
Sebastien C.
  • 4,649
  • 1
  • 21
  • 32
0

You really shouldn't rely on getting first two characters. You really need to rely on inspecting the whole string and understanding what the best language selection should be. Those string values have specific meaning, and for example in one of your cases of the "problem" strings, you would actually be doing most appropriate behavior to show en instead of sv. You can obviously write logic to break apart the accept language, investigate the constituent parts, and take appropriate action, but you also might consider using something like:

http_negotiate_language

to do this for you. There are probably dozens of other scripts available from quick google search to really work with this header in a more appropriate fashion than just looking at the two first characters.

Also, you can check out similar question here: Using the PHP HTTP_ACCEPT_LANGUAGE server variable

Community
  • 1
  • 1
Mike Brant
  • 70,514
  • 10
  • 99
  • 103