1
1
Fork 0

Merge pull request #7722 from nupplaphil/bug/7718-lang

Fix browser language detection
This commit is contained in:
Tobias Diekershoff 2019-10-10 16:54:32 +02:00 committed by GitHub
commit 50fa119f79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 173 additions and 38 deletions

View file

@ -53,12 +53,12 @@ class L10n
*/ */
private $logger; private $logger;
public function __construct(Configuration $config, Database $dba, LoggerInterface $logger) public function __construct(Configuration $config, Database $dba, LoggerInterface $logger, array $server, array $get)
{ {
$this->dba = $dba; $this->dba = $dba;
$this->logger = $logger; $this->logger = $logger;
$this->loadTranslationTable(L10n::detectLanguage($config->get('system', 'language', 'en'))); $this->loadTranslationTable(L10n::detectLanguage($server, $get, $config->get('system', 'language', 'en')));
} }
/** /**
@ -158,6 +158,11 @@ class L10n
{ {
$lang = Strings::sanitizeFilePathItem($lang); $lang = Strings::sanitizeFilePathItem($lang);
// Don't override the language setting with empty languages
if (empty($lang)) {
return;
}
$a = new \stdClass(); $a = new \stdClass();
$a->strings = []; $a->strings = [];
@ -166,12 +171,12 @@ class L10n
while ($p = $this->dba->fetch($addons)) { while ($p = $this->dba->fetch($addons)) {
$name = Strings::sanitizeFilePathItem($p['name']); $name = Strings::sanitizeFilePathItem($p['name']);
if (file_exists("addon/$name/lang/$lang/strings.php")) { if (file_exists("addon/$name/lang/$lang/strings.php")) {
include "addon/$name/lang/$lang/strings.php"; include __DIR__ . "/../../../addon/$name/lang/$lang/strings.php";
} }
} }
if (file_exists("view/lang/$lang/strings.php")) { if (file_exists(__DIR__ . "/../../../view/lang/$lang/strings.php")) {
include "view/lang/$lang/strings.php"; include __DIR__ . "/../../../view/lang/$lang/strings.php";
} }
$this->lang = $lang; $this->lang = $lang;
@ -184,49 +189,78 @@ class L10n
* @brief Returns the preferred language from the HTTP_ACCEPT_LANGUAGE header * @brief Returns the preferred language from the HTTP_ACCEPT_LANGUAGE header
* *
* @param string $sysLang The default fallback language * @param string $sysLang The default fallback language
* @param array $server The $_SERVER array
* @param array $get The $_GET array
* *
* @return string The two-letter language code * @return string The two-letter language code
*/ */
public static function detectLanguage(string $sysLang = 'en') public static function detectLanguage(array $server, array $get, string $sysLang = 'en')
{ {
$lang_list = []; $lang_variable = $server['HTTP_ACCEPT_LANGUAGE'] ?? null;
if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { $acceptedLanguages = preg_split('/,\s*/', $lang_variable);
// break up string into pieces (languages and q factors)
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
if (count($lang_parse[1])) { if (empty($acceptedLanguages)) {
// go through the list of prefered languages and add a generic language $acceptedLanguages = [];
// for sub-linguas (e.g. de-ch will add de) if not already in array
for ($i = 0; $i < count($lang_parse[1]); $i++) {
$lang_list[] = strtolower($lang_parse[1][$i]);
if (strlen($lang_parse[1][$i]) > 3) {
$dashpos = strpos($lang_parse[1][$i], '-');
if (!in_array(substr($lang_parse[1][$i], 0, $dashpos), $lang_list)) {
$lang_list[] = strtolower(substr($lang_parse[1][$i], 0, $dashpos));
}
}
}
}
} }
if (isset($_GET['lang'])) { // Add get as absolute quality accepted language (except this language isn't valid)
$lang_list = [$_GET['lang']]; if (!empty($get['lang'])) {
$acceptedLanguages[] = $get['lang'];
} }
// check if we have translations for the preferred languages and pick the 1st that has // return the sys language in case there's nothing to do
foreach ($lang_list as $lang) { if (empty($acceptedLanguages)) {
if ($lang === 'en' || (file_exists("view/lang/$lang") && is_dir("view/lang/$lang"))) { return $sysLang;
$preferred = $lang; }
// Set the syslang as default fallback
$current_lang = $sysLang;
// start with quality zero (every guessed language is more acceptable ..)
$current_q = 0;
foreach ($acceptedLanguages as $acceptedLanguage) {
$res = preg_match(
'/^([a-z]{1,8}(?:-[a-z]{1,8})*)(?:;\s*q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?$/i',
$acceptedLanguage,
$matches
);
// Invalid language? -> skip
if (!$res) {
continue;
}
// split language codes based on it's "-"
$lang_code = explode('-', $matches[1]);
// determine the quality of the guess
if (isset($matches[2])) {
$lang_quality = (float)$matches[2];
} else {
// fallback so without a quality parameter, it's probably the best
$lang_quality = 1;
}
// loop through each part of the code-parts
while (count($lang_code)) {
// try to mix them so we can get double-code parts too
$match_lang = strtolower(join('-', $lang_code));
if (file_exists(__DIR__ . "/../../../view/lang/$match_lang") &&
is_dir(__DIR__ . "/../../../view/lang/$match_lang")) {
if ($lang_quality > $current_q) {
$current_lang = $match_lang;
$current_q = $lang_quality;
break; break;
} }
} }
if (isset($preferred)) {
return $preferred; // remove the most right code-part
array_pop($lang_code);
}
} }
// in case none matches, get the system wide configured language, or fall back to English return $current_lang;
return $sysLang;
} }
/** /**

View file

@ -4,6 +4,7 @@ use Dice\Dice;
use Friendica\App; use Friendica\App;
use Friendica\Core\Cache; use Friendica\Core\Cache;
use Friendica\Core\Config; use Friendica\Core\Config;
use Friendica\Core\L10n\L10n;
use Friendica\Core\Lock\ILock; use Friendica\Core\Lock\ILock;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Factory; use Friendica\Factory;
@ -173,4 +174,9 @@ return [
['addRoutes', [include __DIR__ . '/routes.config.php'], Dice::CHAIN_CALL], ['addRoutes', [include __DIR__ . '/routes.config.php'], Dice::CHAIN_CALL],
], ],
], ],
L10n::class => [
'constructParams' => [
$_SERVER, $_GET
],
],
]; ];

View file

@ -0,0 +1,95 @@
<?php
namespace src\Core\L10n;
use Friendica\Core\L10n\L10n;
use Friendica\Test\MockedTest;
class L10nTest extends MockedTest
{
public function dataDetectLanguage()
{
return [
'empty' => [
'server' => [],
'get' => [],
'default' => 'en',
'assert' => 'en',
],
'withGet' => [
'server' => [],
'get' => ['lang' => 'de'],
'default' => 'en',
'assert' => 'de',
],
'withPipe' => [
'server' => ['HTTP_ACCEPT_LANGUAGE' => 'en-gb'],
'get' => [],
'default' => 'en',
'assert' => 'en-gb',
],
'withoutPipe' => [
'server' => ['HTTP_ACCEPT_LANGUAGE' => 'fr'],
'get' => [],
'default' => 'en',
'assert' => 'fr',
],
'withQuality1' => [
'server' => ['HTTP_ACCEPT_LANGUAGE' => 'fr;q=0.5,de'],
'get' => [],
'default' => 'en',
'assert' => 'de',
],
'withQuality2' => [
'server' => ['HTTP_ACCEPT_LANGUAGE' => 'fr;q=0.5,de;q=0.2'],
'get' => [],
'default' => 'en',
'assert' => 'fr',
],
'withLangOverride' => [
'server' => ['HTTP_ACCEPT_LANGUAGE' => 'fr;q=0.5,de;q=0.2'],
'get' => ['lang' => 'it'],
'default' => 'en',
'assert' => 'it',
],
'withQualityAndPipe' => [
'server' => ['HTTP_ACCEPT_LANGUAGE' => 'fr;q=0.5,de;q=0.2,nb-no;q=0.7'],
'get' => [],
'default' => 'en',
'assert' => 'nb-no',
],
'withQualityAndInvalid' => [
'server' => ['HTTP_ACCEPT_LANGUAGE' => 'fr;q=0.5,bla;q=0.2,nb-no;q=0.7'],
'get' => [],
'default' => 'en',
'assert' => 'nb-no',
],
'withQualityAndInvalid2' => [
'server' => ['HTTP_ACCEPT_LANGUAGE' => 'blu;q=0.9,bla;q=0.2,nb-no;q=0.7'],
'get' => [],
'default' => 'en',
'assert' => 'nb-no',
],
'withQualityAndInvalidAndAbsolute' => [
'server' => ['HTTP_ACCEPT_LANGUAGE' => 'blu;q=0.9,de,nb-no;q=0.7'],
'get' => [],
'default' => 'en',
'assert' => 'de',
],
'withInvalidGet' => [
'server' => ['HTTP_ACCEPT_LANGUAGE' => 'blu;q=0.9,nb-no;q=0.7'],
'get' => ['lang' => 'blu'],
'default' => 'en',
'assert' => 'nb-no',
],
];
}
/**
* @dataProvider dataDetectLanguage
*/
public function testDetectLanguage(array $server, array $get, string $default, string $assert)
{
$this->assertEquals($assert, L10n::detectLanguage($server, $get, $default));
}
}