]> git.mxchange.org Git - friendica.git/commitdiff
Fix browser language detection (& tests)
authorPhilipp Holzer <admin+github@philipp.info>
Thu, 10 Oct 2019 14:39:04 +0000 (16:39 +0200)
committerPhilipp Holzer <admin+github@philipp.info>
Thu, 10 Oct 2019 14:39:04 +0000 (16:39 +0200)
src/Core/L10n/L10n.php
static/dependencies.config.php
tests/src/Core/L10n/L10nTest.php [new file with mode: 0644]

index f4e14c78e3d140fc0398101e7df42ed35d3cf409..ce930b402011641112de09c428d9bd78a86479fc 100644 (file)
@@ -53,12 +53,12 @@ class L10n
         */
        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->logger = $logger;
 
-               $this->loadTranslationTable(L10n::detectLanguage($config->get('system', 'language', 'en')));
+               $this->loadTranslationTable(L10n::detectLanguage($server, $get, $config->get('system', 'language', 'en')));
        }
 
        /**
@@ -140,7 +140,7 @@ class L10n
                $this->lang    = $this->langSave;
 
                $this->stringsSave = null;
-               $this->langSave = null;
+               $this->langSave    = null;
        }
 
        /**
@@ -158,6 +158,11 @@ class L10n
        {
                $lang = Strings::sanitizeFilePathItem($lang);
 
+               // Don't override the language setting with empty languages
+               if (empty($lang)) {
+                       return;
+               }
+
                $a          = new \stdClass();
                $a->strings = [];
 
@@ -166,12 +171,12 @@ class L10n
                while ($p = $this->dba->fetch($addons)) {
                        $name = Strings::sanitizeFilePathItem($p['name']);
                        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")) {
-                       include "view/lang/$lang/strings.php";
+               if (file_exists(__DIR__ . "/../../../view/lang/$lang/strings.php")) {
+                       include __DIR__ . "/../../../view/lang/$lang/strings.php";
                }
 
                $this->lang    = $lang;
@@ -184,49 +189,78 @@ class L10n
         * @brief Returns the preferred language from the HTTP_ACCEPT_LANGUAGE header
         *
         * @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
         */
-       public static function detectLanguage(string $sysLang = 'en')
+       public static function detectLanguage(array $server, array $get, string $sysLang = 'en')
        {
-               $lang_list = [];
-
-               if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
-                       // 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])) {
-                               // go through the list of prefered languages and add a generic language
-                               // 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));
-                                               }
-                                       }
-                               }
-                       }
+               $lang_variable = $server['HTTP_ACCEPT_LANGUAGE'] ?? null;
+
+               $acceptedLanguages = preg_split('/,\s*/', $lang_variable);
+
+               if (empty($acceptedLanguages)) {
+                       $acceptedLanguages = [];
                }
 
-               if (isset($_GET['lang'])) {
-                       $lang_list = [$_GET['lang']];
+               // Add get as absolute quality accepted language (except this language isn't valid)
+               if (!empty($get['lang'])) {
+                       $acceptedLanguages[] = $get['lang'];
                }
 
-               // check if we have translations for the preferred languages and pick the 1st that has
-               foreach ($lang_list as $lang) {
-                       if ($lang === 'en' || (file_exists("view/lang/$lang") && is_dir("view/lang/$lang"))) {
-                               $preferred = $lang;
-                               break;
-                       }
+               // return the sys language in case there's nothing to do
+               if (empty($acceptedLanguages)) {
+                       return $sysLang;
                }
-               if (isset($preferred)) {
-                       return $preferred;
+
+               // 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;
+                                       }
+                               }
+
+                               // 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 $sysLang;
+               return $current_lang;
        }
 
        /**
index 0a9f1f42e053271fb3b1102ab79ae773f753b255..938b13495b56cd6db6bd8ff9f2a062b600b74433 100644 (file)
@@ -4,6 +4,7 @@ use Dice\Dice;
 use Friendica\App;
 use Friendica\Core\Cache;
 use Friendica\Core\Config;
+use Friendica\Core\L10n\L10n;
 use Friendica\Core\Lock\ILock;
 use Friendica\Database\Database;
 use Friendica\Factory;
@@ -173,4 +174,9 @@ return [
                        ['addRoutes', [include __DIR__ . '/routes.config.php'], Dice::CHAIN_CALL],
                ],
        ],
+       L10n::class => [
+               'constructParams' => [
+                       $_SERVER, $_GET
+               ],
+       ],
 ];
diff --git a/tests/src/Core/L10n/L10nTest.php b/tests/src/Core/L10n/L10nTest.php
new file mode 100644 (file)
index 0000000..1207ceb
--- /dev/null
@@ -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));
+       }
+}