Current Path : /var/www/www-root/data/www.catalog.monolith-realty.ru/bitrix/modules/main/lib/localization/ |
Current File : /var/www/www-root/data/www.catalog.monolith-realty.ru/bitrix/modules/main/lib/localization/loc.php |
<?php namespace Bitrix\Main\Localization; use Bitrix\Main; use Bitrix\Main\IO\Path; use Bitrix\Main\Context; use Bitrix\Main\Config\Configuration; use Bitrix\Main\Text\Encoding; final class Loc { private static $currentLang = null; private static $messages = array(); private static $customMessages = array(); private static $userMessages = array(); private static $includedFiles = array(); private static $lazyLoadFiles = array(); private static $triedFiles = array(); /** * Returns translation by message code. * Loc::loadMessages(__FILE__) should be called first once per php file * * @param string $code * @param array $replace e.g. array("#NUM#"=>5) * @param string $language * @return string|null */ public static function getMessage($code, $replace = null, $language = null) { if($language === null) { //function call optimization if(self::$currentLang === null) { $language = self::getCurrentLang(); } else { $language = self::$currentLang; } } if(!isset(self::$messages[$language][$code])) { self::loadLazy($code, $language); } $s = self::$messages[$language][$code] ?? null; if (is_array($replace) && $s !== null) { $s = strtr($s, $replace); } return $s; } /** * Loads language messages for specified file in a lazy way * * @param string $file */ public static function loadMessages($file) { if(($realPath = realpath($file)) !== false) { $file = $realPath; } $file = Path::normalize($file); self::$lazyLoadFiles[$file] = $file; } /** * @return string */ public static function getCurrentLang() { if(self::$currentLang === null) { $context = Context::getCurrent(); if($context !== null) { $language = $context->getLanguage(); if($language !== null) { self::$currentLang = $language; } } } return (self::$currentLang !== null? self::$currentLang : 'en'); } public static function setCurrentLang($language) { self::$currentLang = $language; } /** * Loads language messages for specified file and language. * * @param string $file File path to look for language translation for its. * @param string $language Language code. * @param string $loadedLangFile Certain loaded language file. * * @return array */ private static function includeLangFiles($file, $language, &$loadedLangFile) { static $langDirCache = array(); // open_basedir restriction static $openBasedir = [], $openBasedirRestriction; if ($openBasedirRestriction === null) { $openBasedirTmp = ini_get('open_basedir'); if (!empty($openBasedirTmp)) { // multiple paths split by colon ":" - "/home/bitrix:/var/www/html" // under non windows by semicolon ";" - "c:/www/;c:/www/html" $openBasedirTmp = explode( (strncasecmp(PHP_OS, 'WIN', 3) == 0 ? ';' : ':'), $openBasedirTmp ); foreach ($openBasedirTmp as $testDir) { if (!empty($testDir)) { $testDir = Path::normalize($testDir); if (is_dir($testDir)) { $openBasedir[] = $testDir; } } } } $openBasedirRestriction = !empty($openBasedir); } $path = Path::getDirectory($file); if(isset($langDirCache[$path])) { $langDir = $langDirCache[$path]; $fileName = mb_substr($file, (mb_strlen($langDir) - 5)); } else { //let's find language folder $langDir = $fileName = ''; $filePath = $file; while (($slashPos = mb_strrpos($filePath, '/')) !== false) { $filePath = mb_substr($filePath, 0, $slashPos); if ($openBasedirRestriction === true) { $withinOpenBasedir = false; foreach ($openBasedir as $testDir) { if (stripos($filePath, $testDir) === 0) { $withinOpenBasedir = true; break; } } if (!$withinOpenBasedir) { break; } } $langPath = $filePath. '/lang'; if (is_dir($langPath)) { $langDir = $langPath; $fileName = mb_substr($file, $slashPos); $langDirCache[$path] = $langDir; break; } } } $mess = array(); if($langDir <> '') { //load messages for default lang first $defaultLang = self::getDefaultLang($language); if($defaultLang <> $language) { $langFile = $langDir. '/'. $defaultLang. $fileName; $langFile = Translation::convertLangPath($langFile, $defaultLang); if(file_exists($langFile)) { $mess = self::includeFile($langFile); $loadedLangFile = $langFile; } } //then load messages for specified lang $langFile = $langDir. '/'. $language. $fileName; $langFile = Translation::convertLangPath($langFile, $language); if(file_exists($langFile)) { $mess = array_merge($mess, self::includeFile($langFile)); $loadedLangFile = $langFile; } } return $mess; } /** * Loads language messages for specified file * * @param string $file * @param string $language * @param bool $normalize * @return array */ public static function loadLanguageFile($file, $language = null, $normalize = true) { if($language === null) { $language = self::getCurrentLang(); } if($normalize) { $file = Path::normalize($file); } if(!isset(self::$messages[$language])) { self::$messages[$language] = array(); } //first time call only for lang if(!isset(self::$userMessages[$language])) { self::$userMessages[$language] = self::loadUserMessages($language); } //let's find language folder and include lang files $mess = self::includeLangFiles($file, $language, $langFile); if (!empty($mess)) { [$convertEncoding, $targetEncoding, $sourceEncoding] = Translation::getEncodings($language, $langFile); foreach ($mess as $key => $val) { if (isset(self::$customMessages[$language][$key])) { self::$messages[$language][$key] = $mess[$key] = self::$customMessages[$language][$key]; } else { if ($convertEncoding) { if ($targetEncoding !== 'utf-8' || !preg_match('//u', $val)) { $val = Encoding::convertEncoding($val, $sourceEncoding, $targetEncoding); } $mess[$key] = $val; } self::$messages[$language][$key] = $val; } } } return $mess; } /** * Loads custom messages from the file to overwrite messages by their IDs. * * @param string $file * @param string|null $language */ public static function loadCustomMessages($file, $language = null) { if($language === null) { $language = self::getCurrentLang(); } if(!isset(self::$customMessages[$language])) { self::$customMessages[$language] = array(); } //let's find language folder and include lang files $mess = self::includeLangFiles(Path::normalize($file), $language, $langFile); if (!empty($mess)) { [$convertEncoding, $targetEncoding, $sourceEncoding] = Translation::getEncodings($language, $langFile); foreach ($mess as $key => $val) { if ($convertEncoding) { if ($targetEncoding !== 'utf-8' || !preg_match('//u', $val)) { $val = Encoding::convertEncoding($val, $sourceEncoding, $targetEncoding); } $mess[$key] = $val; } self::$customMessages[$language][$key] = $val; } } } private static function loadLazy($code, $language) { if($code == '') { return; } //control of duplicates if(!isset(self::$triedFiles[$language])) { self::$triedFiles[$language] = []; } $trace = Main\Diag\Helper::getBackTrace(4, DEBUG_BACKTRACE_IGNORE_ARGS); $currentFile = null; for($i = 3; $i >= 1; $i--) { if (isset($trace[$i]) && stripos($trace[$i]["function"], "GetMessage") === 0) { $currentFile = Path::normalize($trace[$i]["file"]); //we suppose there is a language file even if it wasn't registered via loadMessages() self::$lazyLoadFiles[$currentFile] = $currentFile; break; } } if($currentFile !== null && isset(self::$lazyLoadFiles[$currentFile])) { //in most cases we know the file containing the "code" - load it directly if(!isset(self::$triedFiles[$language][$currentFile])) { self::loadLanguageFile($currentFile, $language, false); self::$triedFiles[$language][$currentFile] = true; } unset(self::$lazyLoadFiles[$currentFile]); } if(!isset(self::$messages[$language][$code])) { //we still don't know which file contains the "code" - go through the files in the reverse order $unset = array(); if(($file = end(self::$lazyLoadFiles)) !== false) { do { if(!isset(self::$triedFiles[$language][$file])) { self::loadLanguageFile($file, $language, false); self::$triedFiles[$language][$file] = true; } $unset[] = $file; if(isset(self::$messages[$language][$code])) { if(defined("BX_MESS_LOG") && $currentFile !== null) { file_put_contents(BX_MESS_LOG, 'CTranslateUtils::CopyMessage("'.$code.'", "'.$file.'", "'.$currentFile.'");'."\n", FILE_APPEND); } break; } } while(($file = prev(self::$lazyLoadFiles)) !== false); } foreach($unset as $file) { unset(self::$lazyLoadFiles[$file]); } } if(!isset(self::$messages[$language][$code]) && defined("BX_MESS_LOG")) { file_put_contents(BX_MESS_LOG, $code.": not found for ".$currentFile."\n", FILE_APPEND); } } /** * Reads messages from user defined lang file * * @param string $lang * @return array */ private static function loadUserMessages($lang) { $userMess = array(); $documentRoot = Main\Application::getDocumentRoot(); if(($fname = Main\Loader::getLocal("php_interface/user_lang/".$lang."/lang.php", $documentRoot)) !== false) { $mess = self::includeFile($fname); // typical call is Loc::loadMessages(__FILE__) // __FILE__ can differ from path used in the user file foreach($mess as $key => $val) $userMess[str_replace("\\", "/", realpath($documentRoot.$key))] = $val; } return $userMess; } /** * Reads messages from lang file. * * @param string $path * @return array */ private static function includeFile($path) { self::$includedFiles[$path] = $path; //the name $MESS is predefined in language files $MESS = array(); include($path); //redefine messages from user lang file if(!empty(self::$userMessages)) { $path = str_replace("\\", "/", realpath($path)); //cycle through languages foreach(self::$userMessages as $messages) { if(isset($messages[$path]) && is_array($messages[$path])) { foreach($messages[$path] as $key => $val) { $MESS[$key] = $val; } } } } return $MESS; } /** * Returns default language for specified language. Default language is used when translation is not found. * * @param string $lang * @return string */ public static function getDefaultLang($lang) { static $subst = ['ua' => 'en', 'kz' => 'ru', 'by' => 'ru', 'ru' => 'ru', 'en' => 'en', 'de' => 'en']; if(isset($subst[$lang])) { return $subst[$lang]; } $options = Configuration::getValue("default_language"); if(isset($options[$lang])) { return $options[$lang]; } return 'en'; } /** * Returns previously included lang files. * @return array */ public static function getIncludedFiles() { return self::$includedFiles; } /** * Gets plural message by id and number * @param {string} messageId * @param {number} value * @param {object} [replacements] * @return {?string} */ /** * Returns plural message by message code and number. * Loc::loadMessages(__FILE__) should be called first once per php file * * @param string $code * @param int $value * @param array|null $replace e.g. array("#NUM#"=>5) * @param string|null $language * @return string|null */ public static function getMessagePlural(string $code, int $value, array $replace = null, string $language = null): ?string { $language = (string)$language; if ($language === '') { $language = LANGUAGE_ID; } $result = self::getMessage($code . '_PLURAL_' . self::getPluralForm($value, $language), $replace); if ($result === null) { $result = self::getMessage($code . '_PLURAL_1', $replace); } return $result; } /** * Return language plural form id by number * see http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html * @param {number} value * @param {string} languageId * @return integer */ public static function getPluralForm($value, $language = ''): int { $value = (int)$value; $language = (string)$language; if ($language === '') { $language = LANGUAGE_ID; } if ($value < 0) { $value = (-1) * $value; } switch ($language) { case 'ar': $pluralForm = (($value !== 1) ? 1 : 0); /* if ($value === 0) { $pluralForm = 0; } else if ($value === 1) { $pluralForm = 1; } else if ($value === 2) { $pluralForm = 2; } else if ( $value % 100 >= 3 && $value % 100 <= 10 ) { $pluralForm = 3; } else if ($value % 100 >= 11) { $pluralForm = 4; } else { $pluralForm = 5; } */ break; case 'br': case 'fr': case 'tr': $pluralForm = (($value > 1) ? 1 : 0); break; case 'de': case 'en': case 'hi': case 'it': case 'la': $pluralForm = (($value !== 1) ? 1 : 0); break; case 'ru': case 'ua': if ( ($value % 10 === 1) && ($value % 100 !== 11) ) { $pluralForm = 0; } else if ( ($value % 10 >= 2) && ($value % 10 <= 4) && ( ($value % 100 < 10) || ($value % 100 >= 20) ) ) { $pluralForm = 1; } else { $pluralForm = 2; } break; case 'pl': if ($value === 1) { $pluralForm = 0; } else if ( $value % 10 >= 2 && $value % 10 <= 4 && ( $value % 100 < 10 || $value % 100 >= 20 ) ) { $pluralForm = 1; } else { $pluralForm = 2; } break; case 'id': case 'ja': case 'ms': case 'sc': case 'tc': case 'th': case 'vn': $pluralForm = 0; break; default: $pluralForm = 1; break; } return $pluralForm; } }