From c6f0bea51d8bb60b4db47f2fb69f4fbc20120d10 Mon Sep 17 00:00:00 2001
From: Hypolite Petovan <hypolite@mrpetovan.com>
Date: Mon, 22 Oct 2018 00:16:30 -0400
Subject: [PATCH 1/2] Move $strings from App to Core\L10n

- Add L10n::init() call in App constructor
- Improve pushLang/popLang
- Remove no longer needed references to App
---
 bin/worker.php                   |   4 -
 mod/register.php                 |   2 +-
 src/App.php                      |  31 +-----
 src/Core/Console/Maintenance.php |   5 -
 src/Core/L10n.php                | 171 +++++++++++++++++++++++--------
 src/Util/Temporal.php            |   4 +-
 6 files changed, 137 insertions(+), 80 deletions(-)

diff --git a/bin/worker.php b/bin/worker.php
index 3d2fc3fa9..d5cd1f6b4 100755
--- a/bin/worker.php
+++ b/bin/worker.php
@@ -7,7 +7,6 @@
 use Friendica\App;
 use Friendica\Core\Config;
 use Friendica\Core\Worker;
-use Friendica\Core\L10n;
 
 // Get options
 $shortopts = 'sn';
@@ -30,9 +29,6 @@ require_once "boot.php";
 
 $a = new App(dirname(__DIR__));
 
-$lang = L10n::getBrowserLanguage();
-L10n::loadTranslationTable($lang);
-
 // Check the database structure and possibly fixes it
 check_db(true);
 
diff --git a/mod/register.php b/mod/register.php
index 41d39f415..7ee0f0d8f 100644
--- a/mod/register.php
+++ b/mod/register.php
@@ -63,7 +63,7 @@ function register_post(App $a)
 
 	$arr['blocked'] = $blocked;
 	$arr['verified'] = $verified;
-	$arr['language'] = L10n::getBrowserLanguage();
+	$arr['language'] = L10n::detectLanguage();
 
 	try {
 		$result = Model\User::create($arr);
diff --git a/src/App.php b/src/App.php
index bde99bfd2..5a29d55ed 100644
--- a/src/App.php
+++ b/src/App.php
@@ -50,7 +50,6 @@ class App
 	public $argv;
 	public $argc;
 	public $module;
-	public $strings;
 	public $hooks = [];
 	public $timezone;
 	public $interactive = true;
@@ -370,6 +369,8 @@ class App
 
 		$this->loadDefaultTimezone();
 
+		Core\L10n::init();
+
 		$this->page = [
 			'aside' => '',
 			'bottom' => '',
@@ -1663,40 +1664,18 @@ class App
 			Core\Addon::callHooks('init_1');
 		}
 
-		$lang = Core\L10n::getBrowserLanguage();
-
-		Core\L10n::loadTranslationTable($lang);
-
-
 		// Exclude the backend processes from the session management
 		if (!$this->isBackend()) {
 			$stamp1 = microtime(true);
 			session_start();
-			$this->saveTimestamp($stamp1, "parser");
+			$this->saveTimestamp($stamp1, 'parser');
+			Core\L10n::setSessionVariable();
+			Core\L10n::setLangFromSession();
 		} else {
 			$_SESSION = [];
 			Core\Worker::executeIfIdle();
 		}
 
-		/* Language was set earlier, but we can over-ride it in the session.
-		 * We have to do it here because the session was just now opened.
-		 */
-		if (!empty($_SESSION['authenticated']) && empty($_SESSION['language'])) {
-			$_SESSION['language'] = $lang;
-			// we haven't loaded user data yet, but we need user language
-			if (!empty($_SESSION['uid'])) {
-				$user = DBA::selectFirst('user', ['language'], ['uid' => $_SESSION['uid']]);
-				if (DBA::isResult($user)) {
-					$_SESSION['language'] = $user['language'];
-				}
-			}
-		}
-
-		if (!empty($_SESSION['language']) && $_SESSION['language'] !== $lang) {
-			$lang = $_SESSION['language'];
-			Core\L10n::loadTranslationTable($lang);
-		}
-
 		// ZRL
 		if (!empty($_GET['zrl']) && $this->getMode()->isNormal()) {
 			$this->query_string = Model\Profile::stripZrls($this->query_string);
diff --git a/src/Core/Console/Maintenance.php b/src/Core/Console/Maintenance.php
index 212edabcc..c8214b161 100644
--- a/src/Core/Console/Maintenance.php
+++ b/src/Core/Console/Maintenance.php
@@ -68,11 +68,6 @@ HELP;
 			throw new \RuntimeException('Database isn\'t ready or populated yet');
 		}
 
-		Core\Config::load();
-
-		$lang = Core\L10n::getBrowserLanguage();
-		Core\L10n::loadTranslationTable($lang);
-
 		$enabled = intval($this->getArgument(0));
 
 		Core\Config::set('system', 'maintenance', $enabled);
diff --git a/src/Core/L10n.php b/src/Core/L10n.php
index 542590646..05f710cd7 100644
--- a/src/Core/L10n.php
+++ b/src/Core/L10n.php
@@ -12,19 +12,90 @@ require_once 'boot.php';
 require_once 'include/dba.php';
 
 /**
- * Provide Languange, Translation, and Localisation functions to the application
- * Localisation can be referred to by the numeronym L10N (as in: "L", followed by ten more letters, and then "N").
+ * Provide Language, Translation, and Localization functions to the application
+ * Localization can be referred to by the numeronym L10N (as in: "L", followed by ten more letters, and then "N").
  */
 class L10n extends BaseObject
 {
 	/**
-	 * @brief get the prefered language from the HTTP_ACCEPT_LANGUAGE header
+	 * A string indicating the current language used for translation:
+	 * - Two-letter ISO 639-1 code.
+	 * - Two-letter ISO 639-1 code + dash + Two-letter ISO 3166-1 alpha-2 country code.
+	 * @var string
 	 */
-	public static function getBrowserLanguage()
+	private $lang = '';
+	/**
+	 * A language code saved for later after pushLang() has been called.
+	 *
+	 * @var string
+	 */
+	private $langSave = '';
+
+	/**
+	 * An array of translation strings whose key is the neutral english message.
+	 *
+	 * @var array
+	 */
+	private $strings = [];
+	/**
+	 * An array of translation strings saved for later after pushLang() has been called.
+	 *
+	 * @var array
+	 */
+	private $stringsSave = [];
+
+	/**
+	 * Detects the language and sets the translation table
+	 */
+	public static function init()
+	{
+		$lang = self::detectLanguage();
+		self::loadTranslationTable($lang);
+	}
+
+	/**
+	 * Returns the current language code
+	 *
+	 * @return string Language code
+	 */
+	public static function getCurrentLang()
+	{
+		return self::$lang;
+	}
+
+	/**
+	 * Sets the language session variable
+	 */
+	public static function setSessionVariable()
+	{
+		if (!empty($_SESSION['authenticated']) && empty($_SESSION['language'])) {
+			$_SESSION['language'] = self::$lang;
+			// we haven't loaded user data yet, but we need user language
+			if (!empty($_SESSION['uid'])) {
+				$user = DBA::selectFirst('user', ['language'], ['uid' => $_SESSION['uid']]);
+				if (DBA::isResult($user)) {
+					$_SESSION['language'] = $user['language'];
+				}
+			}
+		}
+	}
+
+	public static function setLangFromSession()
+	{
+		if (!empty($_SESSION['language']) && $_SESSION['language'] !== self::$lang) {
+			self::loadTranslationTable($_SESSION['language']);
+		}
+	}
+
+	/**
+	 * @brief Returns the preferred language from the HTTP_ACCEPT_LANGUAGE header
+	 * @return string The two-letter language code
+	 */
+	public static function detectLanguage()
 	{
 		$lang_list = [];
 
-		if (x($_SERVER, 'HTTP_ACCEPT_LANGUAGE')) {
+		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);
 
@@ -59,58 +130,69 @@ class L10n extends BaseObject
 	}
 
 	/**
-	 * @param string $language language
+	 * This function should be called before formatting messages in a specific target language
+	 * different from the current user/system language.
+	 *
+	 * It saves the current translation strings in a separate variable and loads new translations strings.
+	 *
+	 * If called repeatedly, it won't save the translation strings again, just load the new ones.
+	 *
+	 * @see popLang()
+	 * @brief Stores the current language strings and load a different language.
+	 * @param string $lang Language code
 	 */
-	public static function pushLang($language)
+	public static function pushLang($lang)
 	{
-		$a = self::getApp();
+		if (!self::$lang) {
+			self::init();
+		}
 
-		$a->langsave = Config::get('system', 'language');
-
-		if ($language === $a->langsave) {
+		if ($lang === self::$lang) {
 			return;
 		}
 
-		if (isset($a->strings) && count($a->strings)) {
-			$a->stringsave = $a->strings;
+		if (!self::$langSave) {
+			self::$langSave = self::$lang;
+			self::$stringsSave = self::$strings;
 		}
-		$a->strings = [];
-		self::loadTranslationTable($language);
-		Config::set('system', 'language', $language);
+
+		self::loadTranslationTable($lang);
 	}
 
 	/**
-	 * Pop language off the top of the stack
+	 * Restores the original user/system language after having used pushLang()
 	 */
 	public static function popLang()
 	{
-		$a = self::getApp();
-
-		if (Config::get('system', 'language') === $a->langsave) {
+		if (!self::$langSave) {
 			return;
 		}
 
-		if (isset($a->stringsave)) {
-			$a->strings = $a->stringsave;
-		} else {
-			$a->strings = [];
-		}
+		self::$strings = self::$stringsSave;
+		self::$lang = self::$langSave;
 
-		Config::set('system', 'language', $a->langsave);
+		self::$stringsSave = [];
+		self::$langSave = '';
 	}
 
 	/**
-	 * load string translation table for alternate language
+	 * Loads string translation table
 	 *
-	 * first addon strings are loaded, then globals
+	 * First addon strings are loaded, then globals
+	 *
+	 * Uses an App object shim since all the strings files refer to $a->strings
 	 *
 	 * @param string $lang language code to load
 	 */
-	public static function loadTranslationTable($lang)
+	private static function loadTranslationTable($lang)
 	{
-		$a = self::getApp();
+		if ($lang === self::$lang) {
+			return;
+		}
 
+		$a = new \stdClass();
 		$a->strings = [];
+
 		// load enabled addons strings
 		$addons = DBA::select('addon', ['name'], ['installed' => true]);
 		while ($p = DBA::fetch($addons)) {
@@ -123,6 +205,11 @@ class L10n extends BaseObject
 		if (file_exists("view/lang/$lang/strings.php")) {
 			include "view/lang/$lang/strings.php";
 		}
+
+		self::$lang = $lang;
+		self::$strings = $a->strings;
+
+		unset($a);
 	}
 
 	/**
@@ -143,14 +230,16 @@ class L10n extends BaseObject
 	 */
 	public static function t($s, ...$vars)
 	{
-		$a = self::getApp();
-
 		if (empty($s)) {
 			return '';
 		}
 
-		if (x($a->strings, $s)) {
-			$t = $a->strings[$s];
+		if (!self::$lang) {
+			self::init();
+		}
+
+		if (!empty(self::$strings[$s])) {
+			$t = self::$strings[$s];
 			$s = is_array($t) ? $t[0] : $t;
 		}
 
@@ -181,18 +270,18 @@ class L10n extends BaseObject
 	 */
 	public static function tt($singular, $plural, $count)
 	{
-		$a = self::getApp();
-
 		if (!is_numeric($count)) {
 			logger('Non numeric count called by ' . System::callstack(20));
 		}
 
-		$lang = Config::get('system', 'language');
+		if (!self::$lang) {
+			self::init();
+		}
 
-		if (!empty($a->strings[$singular])) {
-			$t = $a->strings[$singular];
+		if (!empty(self::$strings[$singular])) {
+			$t = self::$strings[$singular];
 			if (is_array($t)) {
-				$plural_function = 'string_plural_select_' . str_replace('-', '_', $lang);
+				$plural_function = 'string_plural_select_' . str_replace('-', '_', self::$lang);
 				if (function_exists($plural_function)) {
 					$i = $plural_function($count);
 				} else {
@@ -227,8 +316,6 @@ class L10n extends BaseObject
 		return $n != 1;
 	}
 
-
-
 	/**
 	 * @brief Return installed languages codes as associative array
 	 *
diff --git a/src/Util/Temporal.php b/src/Util/Temporal.php
index 696721e45..0cc759da1 100644
--- a/src/Util/Temporal.php
+++ b/src/Util/Temporal.php
@@ -217,13 +217,13 @@ class Temporal
 		// First day of the week (0 = Sunday)
 		$firstDay = PConfig::get(local_user(), 'system', 'first_day_of_week', 0);
 
-		$lang = substr(L10n::getBrowserLanguage(), 0, 2);
+		$lang = substr(L10n::getCurrentLang(), 0, 2);
 
 		// Check if the detected language is supported by the picker
 		if (!in_array($lang,
 				["ar", "ro", "id", "bg", "fa", "ru", "uk", "en", "el", "de", "nl", "tr", "fr", "es", "th", "pl", "pt", "ch", "se", "kr",
 				"it", "da", "no", "ja", "vi", "sl", "cs", "hu"])) {
-			$lang = Config::get('system', 'language', 'en');
+			$lang = 'en';
 		}
 
 		$o = '';

From e3fe25383c5cee6a6c73793b5a2c0b37e5ab2936 Mon Sep 17 00:00:00 2001
From: Hypolite Petovan <hypolite@mrpetovan.com>
Date: Mon, 22 Oct 2018 08:45:41 -0400
Subject: [PATCH 2/2] Add static keyword for Core\L10n properties

---
 src/Core/L10n.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/Core/L10n.php b/src/Core/L10n.php
index 05f710cd7..45b70e062 100644
--- a/src/Core/L10n.php
+++ b/src/Core/L10n.php
@@ -23,26 +23,26 @@ class L10n extends BaseObject
 	 * - Two-letter ISO 639-1 code + dash + Two-letter ISO 3166-1 alpha-2 country code.
 	 * @var string
 	 */
-	private $lang = '';
+	private static $lang = '';
 	/**
 	 * A language code saved for later after pushLang() has been called.
 	 *
 	 * @var string
 	 */
-	private $langSave = '';
+	private static $langSave = '';
 
 	/**
 	 * An array of translation strings whose key is the neutral english message.
 	 *
 	 * @var array
 	 */
-	private $strings = [];
+	private static $strings = [];
 	/**
 	 * An array of translation strings saved for later after pushLang() has been called.
 	 *
 	 * @var array
 	 */
-	private $stringsSave = [];
+	private static $stringsSave = [];
 
 	/**
 	 * Detects the language and sets the translation table