Skip to content

Commit

Permalink
Improved detection in accept-language iRail#242
Browse files Browse the repository at this point in the history
Added tests for language, special cases.
Fixed broken tests iRail#253
  • Loading branch information
Bertware committed Jan 15, 2017
1 parent c0c5523 commit aefd3cc
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 6 deletions.
2 changes: 1 addition & 1 deletion app/Http/Controllers/Welcome.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function __construct()

public function index()
{
if (! Session::get('lang')) {
if (Session::get('lang') == null || empty(Session::get('lang'))) {
return View('language');
} else {
return Redirect::to('route');
Expand Down
59 changes: 55 additions & 4 deletions app/Http/Middleware/Language.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Session;
use Locale;

class Language
{
Expand All @@ -29,13 +30,16 @@ class Language
*/
public function handle($request, Closure $next)
{
$browserLanguage = \Locale::lookup(['nl', 'fr', 'en'], $request->server('HTTP_ACCEPT_LANGUAGE'));
$browserLanguage = $this->matchAcceptLanguage($request->server('HTTP_ACCEPT_LANGUAGE'), ['nl', 'fr', 'en']);

// if the user didn't set a language in the cookie, and a browser language is available, use browser language.
if (empty(Session::get('lang') && ! empty($browserLanguage))) {
if (!empty(Input::get('lang'))) {
// if a language is set in the browser, we need to update the language. User may have requested a switch.
$language = Input::get('lang');
} elseif (empty(Session::get('lang')) && !empty($browserLanguage)) {
// if the user didn't set a language in the cookie, and a browser language is available, use browser language.
$language = $browserLanguage;
} else {
$language = (Input::get('lang')) ?: Session::get('lang');
$language = Session::get('lang');
}

$this->setSupportedLanguage($language);
Expand Down Expand Up @@ -63,4 +67,51 @@ private function setSupportedLanguage($lang)
Session::put('lang', $lang);
}
}

/**
* Search a HTTP accept-header for a supported language. Take order (weight, q=) into account. Skip unsupported
* languages.
*
* This method has a big avantage over locale_lookup, as locale_lookup will only look at the first language in accept-language.
* This method will skip unsupported languages and keep searching for a supported, meaning that if only NL and EN are supported,
* da, nl;q=0.5, en;q=0.2 will result in nl being chosen. (vs null return from locale_lookup)
*
* @param string $header the HTTP accept-language header to search
* @param array $supported the list of supported languages
* @return string|null if a language matches, a value from the supported array is returned. If not, null is
* returned.
*/
private function matchAcceptLanguage($header, $supported)
{

// step 1: transform header into array
// source for step 1: http://stackoverflow.com/questions/6168519/transform-accept-language-output-in-array

$values = explode(',', $header);

$accept_language = array();

foreach ($values AS $lang) {
$cnt = preg_match('/([-a-zA-Z]+)\s*;\s*q=([0-9\.]+)/', $lang, $matches);
if ($cnt === 0) {
$accept_language[$lang] = 1;
} else {
$accept_language[$matches[1]] = $matches[2];
}
}

// step 2: match array with supported languages

foreach ($accept_language as $accept_lang => $accept_lang_q) {
foreach ($supported as $supported_lang) {
// use filterMatches to ensure nl-be, nl, nl-nl all match nl, etc..
if (Locale::filterMatches($accept_lang, $supported_lang)) {
return $supported_lang;
};
}
}

// no match found
return null;
}
}
33 changes: 33 additions & 0 deletions tests/App/Http/Middleware/LanguageMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,39 @@ public function testSetLanguageToFrench()
$this->setAssertLanguageTest('fr', 'fr');
}

public function testSetLanguageToFrenchWhileBrowserDutch()
{
// GET parameters should have priority over browser language.
$response = $this->get('/?lang=fr', ['accept-language' => 'nl-be']);
$this->assertEquals('fr', $this->getApplicationLanguage());
}

public function testSetLanguageFromBrowser()
{
$response = $this->get('/', ['accept-language' => 'nl-be']);
$this->assertEquals('nl', $this->getApplicationLanguage());
}

public function testSetLanguageFromBrowser2()
{
$response = $this->get('/', ['accept-language' => 'nl']);
$this->assertEquals('nl', $this->getApplicationLanguage());
}

public function testSetLanguageFromBrowser3()
{
// unsupported languages mixed with supported. Should pick first supported.
$response = $this->get('/', ['accept-language' => 'da, nl;q=0.9, en;q=0.5']);
$this->assertEquals('nl', $this->getApplicationLanguage());
}

public function testSetLanguageFromBrowser4()
{
// unsupported languages. Should fall back to en.
$response = $this->get('/', ['accept-language' => 'da, se;q=0.9, no;q=0.5']);
$this->assertEquals('en', $this->getApplicationLanguage());
}

public function testFallbackToDefaultLanguageWhenLanguageDoesNotExist()
{
$this->setAssertLanguageTest('en', 'NotALanguage');
Expand Down
3 changes: 2 additions & 1 deletion tests/VariousTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ class VariousTest extends TestCase
{
public function testHome()
{
// expect redirect, since call includes "accept-language" header
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
$this->assertEquals(302, $response->status());
}

public function testContributors()
Expand Down

0 comments on commit aefd3cc

Please sign in to comment.