ACCEPT_LANGUAGE goes before default_langauge

See https://github.com/nextcloud/server/issues/970

Before we had

1. Users settings in personal settings
2. Admins default language settings
3. Accept-Language settings of the browser

However this is not in line with
https://www.w3.org/International/questions/qa-lang-priorities

So this changes the order to

1. Users settings in personal settings
3. Accept-Language settings of the browser
2. Admins default language settings
This commit is contained in:
Roeland Jago Douma 2016-09-04 13:15:46 +02:00
parent e7f618ef14
commit 0228bc6e66
No known key found for this signature in database
GPG Key ID: 1E152838F164D13B
3 changed files with 161 additions and 34 deletions

View File

@ -1,6 +1,7 @@
<?php <?php
/** /**
* @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2016, ownCloud, Inc.
* @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl>
* *
* @author Bart Visscher <bartv@thisnet.nl> * @author Bart Visscher <bartv@thisnet.nl>
* @author Joas Schilling <coding@schilljs.com> * @author Joas Schilling <coding@schilljs.com>
@ -8,6 +9,7 @@
* @author Morris Jobke <hey@morrisjobke.de> * @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl> * @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk> * @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* *
* @license AGPL-3.0 * @license AGPL-3.0
* *
@ -147,18 +149,23 @@ class Factory implements IFactory {
} }
} }
$defaultLanguage = $this->config->getSystemValue('default_language', false); try {
// Try to get the language from the Request
if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) { $lang = $this->getLanguageFromRequest($app);
return $defaultLanguage;
}
$lang = $this->setLanguageFromRequest($app);
if ($userId !== null && $app === null && !$userLang) { if ($userId !== null && $app === null && !$userLang) {
$this->config->setUserValue($userId, 'core', 'lang', $lang); $this->config->setUserValue($userId, 'core', 'lang', $lang);
} }
return $lang; return $lang;
} catch (LanguageNotFoundException $e) {
// Finding language from request failed fall back to default language
$defaultLanguage = $this->config->getSystemValue('default_language', false);
if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) {
return $defaultLanguage;
}
}
// We could not find any language so fall back to english
return 'en';
} }
/** /**
@ -227,10 +234,11 @@ class Factory implements IFactory {
} }
/** /**
* @param string|null $app App id or null for core * @param string|null $app
* @return string * @return string
* @throws LanguageNotFoundException
*/ */
public function setLanguageFromRequest($app = null) { private function getLanguageFromRequest($app) {
$header = $this->request->getHeader('ACCEPT_LANGUAGE'); $header = $this->request->getHeader('ACCEPT_LANGUAGE');
if ($header) { if ($header) {
$available = $this->findAvailableLanguages($app); $available = $this->findAvailableLanguages($app);
@ -245,9 +253,6 @@ class Factory implements IFactory {
foreach ($available as $available_language) { foreach ($available as $available_language) {
if ($preferred_language === strtolower($available_language)) { if ($preferred_language === strtolower($available_language)) {
if ($app === null && !$this->requestLanguage) {
$this->requestLanguage = $available_language;
}
return $available_language; return $available_language;
} }
} }
@ -255,19 +260,31 @@ class Factory implements IFactory {
// Fallback from de_De to de // Fallback from de_De to de
foreach ($available as $available_language) { foreach ($available as $available_language) {
if (substr($preferred_language, 0, 2) === $available_language) { if (substr($preferred_language, 0, 2) === $available_language) {
if ($app === null && !$this->requestLanguage) {
$this->requestLanguage = $available_language;
}
return $available_language; return $available_language;
} }
} }
} }
} }
if ($app === null && !$this->requestLanguage) { throw new LanguageNotFoundException();
$this->requestLanguage = 'en';
} }
return 'en'; // Last try: English
/**
* @param string|null $app App id or null for core
* @return string
*/
public function setLanguageFromRequest($app = null) {
try {
$requestLanguage = $this->getLanguageFromRequest($app);
} catch (LanguageNotFoundException $e) {
$requestLanguage = 'en';
}
if ($app === null && !$this->requestLanguage) {
$this->requestLanguage = $requestLanguage;
}
return $requestLanguage;
} }
/** /**

View File

@ -0,0 +1,26 @@
<?php
/**
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\L10N;
class LanguageNotFoundException extends \Exception {
}

View File

@ -9,6 +9,10 @@
namespace Test\L10N; namespace Test\L10N;
use OC\L10N\Factory; use OC\L10N\Factory;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
use Test\TestCase; use Test\TestCase;
/** /**
@ -18,13 +22,13 @@ use Test\TestCase;
*/ */
class FactoryTest extends TestCase { class FactoryTest extends TestCase {
/** @var \OCP\IConfig|\PHPUnit_Framework_MockObject_MockObject */ /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
protected $config; protected $config;
/** @var \OCP\IRequest|\PHPUnit_Framework_MockObject_MockObject */ /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
protected $request; protected $request;
/** @var \OCP\IUserSession|\PHPUnit_Framework_MockObject_MockObject */ /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
protected $userSession; protected $userSession;
/** @var string */ /** @var string */
@ -33,17 +37,15 @@ class FactoryTest extends TestCase {
public function setUp() { public function setUp() {
parent::setUp(); parent::setUp();
/** @var \OCP\IConfig $request */ $this->config = $this->getMockBuilder(IConfig::class)
$this->config = $this->getMockBuilder('OCP\IConfig')
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
/** @var \OCP\IRequest $request */ $this->request = $this->getMockBuilder(IRequest::class)
$this->request = $this->getMockBuilder('OCP\IRequest')
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
$this->userSession = $this->getMockBuilder('\OCP\IUserSession') $this->userSession = $this->getMockBuilder(IUserSession::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
@ -56,7 +58,7 @@ class FactoryTest extends TestCase {
*/ */
protected function getFactory(array $methods = []) { protected function getFactory(array $methods = []) {
if (!empty($methods)) { if (!empty($methods)) {
return $this->getMockBuilder('OC\L10N\Factory') return $this->getMockBuilder(Factory::class)
->setConstructorArgs([ ->setConstructorArgs([
$this->config, $this->config,
$this->request, $this->request,
@ -111,7 +113,7 @@ class FactoryTest extends TestCase {
->method('getSystemValue') ->method('getSystemValue')
->with('installed', false) ->with('installed', false)
->willReturn(true); ->willReturn(true);
$user = $this->getMockBuilder('\OCP\IUser') $user = $this->getMockBuilder(IUser::class)
->getMock(); ->getMock();
$user->expects($this->once()) $user->expects($this->once())
->method('getUID') ->method('getUID')
@ -145,7 +147,7 @@ class FactoryTest extends TestCase {
->method('getSystemValue') ->method('getSystemValue')
->with('installed', false) ->with('installed', false)
->willReturn(true); ->willReturn(true);
$user = $this->getMockBuilder('\OCP\IUser') $user = $this->getMockBuilder(IUser::class)
->getMock(); ->getMock();
$user->expects($this->once()) $user->expects($this->once())
->method('getUID') ->method('getUID')
@ -188,7 +190,7 @@ class FactoryTest extends TestCase {
->method('getSystemValue') ->method('getSystemValue')
->with('installed', false) ->with('installed', false)
->willReturn(true); ->willReturn(true);
$user = $this->getMockBuilder('\OCP\IUser') $user = $this->getMockBuilder(IUser::class)
->getMock(); ->getMock();
$user->expects($this->once()) $user->expects($this->once())
->method('getUID') ->method('getUID')
@ -234,7 +236,7 @@ class FactoryTest extends TestCase {
->method('getSystemValue') ->method('getSystemValue')
->with('installed', false) ->with('installed', false)
->willReturn(true); ->willReturn(true);
$user = $this->getMockBuilder('\OCP\IUser') $user = $this->getMockBuilder(IUser::class)
->getMock(); ->getMock();
$user->expects($this->once()) $user->expects($this->once())
->method('getUID') ->method('getUID')
@ -460,4 +462,86 @@ class FactoryTest extends TestCase {
$fn = $factory->createPluralFunction($function); $fn = $factory->createPluralFunction($function);
$this->assertEquals($expected, $fn($count)); $this->assertEquals($expected, $fn($count));
} }
public function dataFindLanguage() {
return [
// Not logged in
[false, [], 'en'],
[false, ['fr'], 'fr'],
[false, ['de', 'fr'], 'de'],
[false, ['nl', 'de', 'fr'], 'de'],
[true, [], 'en'],
[true, ['fr'], 'fr'],
[true, ['de', 'fr'], 'de'],
[true, ['nl', 'de', 'fr'], 'nl'],
];
}
/**
* @dataProvider dataFindLanguage
*
* @param bool $loggedIn
* @param array $availableLang
* @param string $expected
*/
public function testFindLanguage($loggedIn, $availableLang, $expected) {
$userLang = 'nl';
$browserLang = 'de';
$defaultLang = 'fr';
$this->config->expects($this->any())
->method('getSystemValue')
->will($this->returnCallback(function($var, $default) use ($defaultLang) {
if ($var === 'installed') {
return true;
} else if ($var === 'default_language') {
return $defaultLang;
} else {
return $default;
}
}));
if ($loggedIn) {
$user = $this->getMockBuilder(IUser::class)
->getMock();
$user->expects($this->any())
->method('getUID')
->willReturn('MyUserUid');
$this->userSession
->expects($this->any())
->method('getUser')
->willReturn($user);
$this->config->expects($this->any())
->method('getUserValue')
->with('MyUserUid', 'core', 'lang', null)
->willReturn($userLang);
} else {
$this->userSession
->expects($this->any())
->method('getUser')
->willReturn(null);
}
$this->request->expects($this->any())
->method('getHeader')
->with($this->equalTo('ACCEPT_LANGUAGE'))
->willReturn($browserLang);
$factory = $this->getFactory(['languageExists', 'findAvailableLanguages']);
$factory->expects($this->any())
->method('languageExists')
->will($this->returnCallback(function ($app, $lang) use ($availableLang) {
return in_array($lang, $availableLang);
}));
$factory->expects($this->any())
->method('findAvailableLanguages')
->will($this->returnCallback(function ($app) use ($availableLang) {
return $availableLang;
}));
$lang = $factory->findLanguage(null);
$this->assertSame($expected, $lang);
}
} }