| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- <?php
- /**
- * Add a missing language key for en or ru.
- *
- * The string is written into the module language XML (skipped when that
- * key is already there) and into the database, then the language cache
- * is recompiled. An existing translation is left unchanged.
- *
- * Usage:
- * php scripts/add_lang_key.php <en|ru> <key> <translation>
- * php scripts/add_lang_key.php <en|ru> <key> - <<'EOF'
- * line one
- * line two
- * EOF
- *
- * A translation of "-" is read from stdin. A single trailing newline on
- * stdin is removed; newlines inside the text are kept.
- *
- * Run inside the php container:
- * docker exec -i una-php-1 php /opt/una/scripts/add_lang_key.php en _sys_example "Hello"
- */
- if (PHP_SAPI !== 'cli') {
- fwrite(STDERR, "CLI only\n");
- exit(1);
- }
- $sLang = isset($argv[1]) ? strtolower($argv[1]) : '';
- $sKey = isset($argv[2]) ? $argv[2] : '';
- $bFromStdin = !isset($argv[3]) || $argv[3] === '-';
- if (!in_array($sLang, array('en', 'ru'), true) || $sKey === '' || isset($argv[4])) {
- fwrite(STDERR, usage());
- exit(2);
- }
- if (strpbrk($sKey, "\r\n") !== false) {
- fwrite(STDERR, "Key must be a single line\n");
- exit(2);
- }
- if ($bFromStdin) {
- $sTranslation = stream_get_contents(STDIN);
- if ($sTranslation === false)
- $sTranslation = '';
- if (substr($sTranslation, -2) === "\r\n")
- $sTranslation = substr($sTranslation, 0, -2);
- else if (substr($sTranslation, -1) === "\n")
- $sTranslation = substr($sTranslation, 0, -1);
- }
- else {
- $sTranslation = $argv[3];
- }
- if ($sTranslation === '') {
- fwrite(STDERR, "Translation is empty\n");
- exit(2);
- }
- if (function_exists('mb_check_encoding') && !mb_check_encoding($sTranslation, 'UTF-8')) {
- fwrite(STDERR, "Translation is not valid UTF-8\n");
- exit(2);
- }
- $sRoot = dirname(__DIR__) . '/';
- $aModules = loadModules($sRoot);
- $aLangModules = loadLanguageModules($sRoot);
- $aModule = findModuleByExistingKey($sRoot, $sKey, $aModules);
- if (!$aModule)
- $aModule = inferModule($sKey, $aModules);
- $sFile = resolveLangFile($sRoot, $sLang, $aModule, $aLangModules);
- $sRel = ltrim(str_replace('\\', '/', substr($sFile, strlen($sRoot))), '/');
- $sExisting = is_file($sFile) ? readLangString($sFile, $sKey) : null;
- if ($sExisting !== null) {
- echo "xml: already present in $sRel\n";
- if ($sExisting !== $sTranslation)
- echo "kept the translation already in the language file\n";
- $sStore = $sExisting;
- }
- else {
- if (!writeLangString($sFile, $sLang, $sKey, $sTranslation)) {
- fwrite(STDERR, "Failed to write $sRel\n");
- exit(1);
- }
- echo "xml: added to $sRel\n";
- $sStore = $sTranslation;
- }
- if (dbAddMissing($sLang, $sKey, $sStore, $aModule['language_category']) === false)
- exit(1);
- exit(0);
- function usage()
- {
- return <<<TXT
- Usage: php add_lang_key.php <en|ru> <key> <translation>
- php add_lang_key.php <en|ru> <key> - (translation from stdin, may span lines)
- Adds the key only when that language does not already have it.
- TXT;
- }
- function loadModules($sRoot)
- {
- $a = array(
- 'system' => array(
- 'name' => 'system',
- 'home_dir' => 'system/',
- 'language_category' => 'System',
- ),
- );
- foreach (glob($sRoot . 'modules/*/*/install/config.php') as $sConfig) {
- $s = file_get_contents($sConfig);
- if ($s === false)
- continue;
- $sName = configValue($s, 'name');
- $sHome = configValue($s, 'home_dir');
- if ($sName === null || $sHome === null || $sName === '')
- continue;
- $sCategory = configValue($s, 'language_category');
- if ($sCategory === null || $sCategory === '')
- $sCategory = $sName;
- $a[$sName] = array(
- 'name' => $sName,
- 'home_dir' => rtrim($sHome, '/') . '/',
- 'language_category' => $sCategory,
- );
- }
- return $a;
- }
- function loadLanguageModules($sRoot)
- {
- $a = array(
- 'en' => array('home_dir' => 'boonex/english/'),
- 'ru' => array('home_dir' => 'boonex/russian/'),
- );
- foreach (glob($sRoot . 'modules/*/*/install/config.php') as $sConfig) {
- $s = file_get_contents($sConfig);
- if ($s === false)
- continue;
- $sUri = configValue($s, 'home_uri');
- $sHome = configValue($s, 'home_dir');
- if (($sUri !== 'en' && $sUri !== 'ru') || $sHome === null)
- continue;
- $a[$sUri] = array('home_dir' => rtrim($sHome, '/') . '/');
- }
- return $a;
- }
- function configValue($sSource, $sKey)
- {
- $sPattern = '/[\'"]' . preg_quote($sKey, '/') . '[\'"]\s*=>\s*[\'"]([^\'"]*)[\'"]/';
- if (!preg_match($sPattern, $sSource, $aMatch))
- return null;
- return $aMatch[1];
- }
- function findModuleByExistingKey($sRoot, $sKey, $aModules)
- {
- $aFound = array();
- $aFiles = array_merge(
- glob($sRoot . 'modules/*/*/install/langs/*.xml') ?: array(),
- glob($sRoot . 'modules/*/*/data/langs/*/*.xml') ?: array()
- );
- foreach ($aFiles as $sFile) {
- if (!fileHasKey($sFile, $sKey))
- continue;
- $sNorm = str_replace('\\', '/', $sFile);
- $aModule = null;
- if (preg_match('#/data/langs/([^/]+)/[^/]+\.xml$#', $sNorm, $aMatch) && isset($aModules[$aMatch[1]]))
- $aModule = $aModules[$aMatch[1]];
- else if (preg_match('#/modules/(.+)/install/langs/[^/]+\.xml$#', $sNorm, $aMatch)) {
- $sHome = $aMatch[1] . '/';
- foreach ($aModules as $aCandidate) {
- if ($aCandidate['home_dir'] === $sHome) {
- $aModule = $aCandidate;
- break;
- }
- }
- }
- if ($aModule)
- $aFound[$aModule['name']] = $aModule;
- }
- if (!$aFound)
- return null;
- if (isset($aFound['system']) && count($aFound) > 1)
- unset($aFound['system']);
- return reset($aFound);
- }
- function inferModule($sKey, $aModules)
- {
- $sBare = ltrim($sKey, '_');
- $aNames = array_keys($aModules);
- usort($aNames, function ($sA, $sB) {
- return strlen($sB) - strlen($sA);
- });
- foreach ($aNames as $sName) {
- if ($sName === 'system')
- continue;
- if ($sBare === $sName || strpos($sBare, $sName . '_') === 0)
- return $aModules[$sName];
- }
- return $aModules['system'];
- }
- function resolveLangFile($sRoot, $sLang, $aModule, $aLangModules)
- {
- $sPack = $sRoot . 'modules/' . $aLangModules[$sLang]['home_dir'] . 'data/langs/' . $aModule['name'] . '/' . $sLang . '.xml';
- if (is_file($sPack))
- return $sPack;
- if ($aModule['name'] !== 'system') {
- $sInstall = $sRoot . 'modules/' . $aModule['home_dir'] . 'install/langs/' . $sLang . '.xml';
- if (is_file($sInstall) || $sLang === 'en')
- return $sInstall;
- }
- return $sPack;
- }
- function fileHasKey($sPath, $sKey)
- {
- return readLangString($sPath, $sKey) !== null;
- }
- function readLangString($sPath, $sKey)
- {
- $s = @file_get_contents($sPath);
- if ($s === false)
- return null;
- $aVariants = array_unique(array($sKey, htmlspecialchars($sKey, ENT_QUOTES | ENT_XML1, 'UTF-8')));
- foreach ($aVariants as $sVariant) {
- $sPattern = '/<string\b[^>]*\bname=(["\'])' . preg_quote($sVariant, '/') . '\\1[^>]*>(.*?)<\/string>/s';
- if (!preg_match($sPattern, $s, $aMatch))
- continue;
- $sInner = $aMatch[2];
- if (preg_match('/^\s*<!\[CDATA\[(.*)\]\]>\s*$/s', $sInner, $aCdata))
- return str_replace(']]]]><![CDATA[>', ']]>', $aCdata[1]);
- return html_entity_decode($sInner, ENT_QUOTES | ENT_XML1, 'UTF-8');
- }
- return null;
- }
- function writeLangString($sFile, $sLang, $sKey, $sTranslation)
- {
- $aMeta = array(
- 'en' => array('flag' => 'gb', 'title' => 'English'),
- 'ru' => array('flag' => 'ru', 'title' => 'Russian'),
- );
- $sNl = "\n";
- if (is_file($sFile)) {
- $sXml = file_get_contents($sFile);
- if ($sXml === false)
- return false;
- if (strpos($sXml, "\r\n") !== false)
- $sNl = "\r\n";
- }
- else {
- $sDir = dirname($sFile);
- if (!is_dir($sDir) && !mkdir($sDir, 0775, true))
- return false;
- $sXml = '<?xml version="1.0" encoding="utf-8"?>' . $sNl
- . '<resources name="' . $sLang . '" flag="' . $aMeta[$sLang]['flag'] . '" title="' . $aMeta[$sLang]['title'] . '">' . $sNl
- . '</resources>' . $sNl;
- }
- $iPos = strrpos($sXml, '</resources>');
- if ($iPos === false)
- return false;
- $sLine = ' <string name="' . htmlspecialchars($sKey, ENT_QUOTES | ENT_XML1, 'UTF-8') . '"><![CDATA[' . cdata($sTranslation) . ']]></string>' . $sNl;
- if ($iPos > 0 && $sXml[$iPos - 1] !== "\n")
- $sLine = $sNl . $sLine;
- $sXml = substr($sXml, 0, $iPos) . $sLine . substr($sXml, $iPos);
- return file_put_contents($sFile, $sXml, LOCK_EX) !== false;
- }
- function cdata($s)
- {
- return str_replace(']]>', ']]]]><![CDATA[>', $s);
- }
- function dbAddMissing($sLang, $sKey, $sTranslation, $sCategory)
- {
- bootUna();
- $oDb = BxDolDb::getInstance();
- $aLang = $oDb->getRow($oDb->prepare("SELECT `ID` AS `id` FROM `sys_localization_languages` WHERE `Name` = ? LIMIT 1", $sLang));
- if (!$aLang) {
- fwrite(STDERR, "Language '$sLang' is not installed in the database\n");
- return false;
- }
- $iLangId = (int)$aLang['id'];
- $aKey = $oDb->getRow($oDb->prepare("SELECT `ID` AS `id` FROM `sys_localization_keys` WHERE `Key` = ? LIMIT 1", $sKey));
- if (!$aKey) {
- $iCategoryId = categoryId($oDb, $sCategory);
- if (!$iCategoryId) {
- fwrite(STDERR, "Could not resolve language category '$sCategory'\n");
- return false;
- }
- $iInserted = (int)$oDb->query($oDb->prepare(
- "INSERT INTO `sys_localization_keys` (`IDCategory`, `Key`) VALUES (?, ?)",
- $iCategoryId,
- $sKey
- ));
- if ($iInserted <= 0) {
- fwrite(STDERR, "Could not insert language key\n");
- return false;
- }
- $iKeyId = (int)$oDb->lastId();
- echo "db: created key $iKeyId in '$sCategory'\n";
- }
- else {
- $iKeyId = (int)$aKey['id'];
- }
- $aString = $oDb->getRow($oDb->prepare(
- "SELECT `IDKey` AS `id` FROM `sys_localization_strings` WHERE `IDKey` = ? AND `IDLanguage` = ? LIMIT 1",
- $iKeyId,
- $iLangId
- ));
- if ($aString) {
- echo "db: already present for $sLang\n";
- return true;
- }
- $iInserted = (int)$oDb->query($oDb->prepare(
- "INSERT INTO `sys_localization_strings` (`IDKey`, `IDLanguage`, `String`) VALUES (?, ?, ?)",
- $iKeyId,
- $iLangId,
- $sTranslation
- ));
- if ($iInserted <= 0) {
- fwrite(STDERR, "Could not insert translation\n");
- return false;
- }
- bx_import('BxDolStudioLanguagesUtils');
- BxDolLanguages::getInstance();
- if (!BxDolStudioLanguagesUtils::getInstance()->compileLanguage($iLangId, true)) {
- fwrite(STDERR, "Translation stored, but the language cache was not recompiled\n");
- return false;
- }
- echo "db: added translation for $sLang and recompiled\n";
- return true;
- }
- function categoryId($oDb, $sCategory)
- {
- $aCategory = $oDb->getRow($oDb->prepare(
- "SELECT `ID` AS `id` FROM `sys_localization_categories` WHERE `Name` = ? LIMIT 1",
- $sCategory
- ));
- if ($aCategory)
- return (int)$aCategory['id'];
- $iInserted = (int)$oDb->query($oDb->prepare(
- "INSERT INTO `sys_localization_categories` (`Name`) VALUES (?)",
- $sCategory
- ));
- if ($iInserted <= 0)
- return 0;
- return (int)$oDb->lastId();
- }
- function bootUna()
- {
- $GLOBALS['bx_profiler_disable'] = true;
- if (!defined('BX_DOL_CRON_EXECUTE'))
- define('BX_DOL_CRON_EXECUTE', '1');
- $_ENV['UNA_SKIP_REDIRECT'] = '1';
- $_ENV['UNA_SKIP_MINIMAL_REQUIREMENTS'] = '1';
- $_ENV['UNA_SKIP_INSTALL_FOLDER_CHECK'] = '1';
- if (empty($_SERVER['HTTP_HOST'])) {
- $_SERVER['HTTP_HOST'] = 'hihi.com';
- $_SERVER['REQUEST_URI'] = '/una/';
- $_SERVER['SERVER_NAME'] = 'hihi.com';
- $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
- }
- require_once dirname(__DIR__) . '/inc/header.inc.php';
- require_once BX_DIRECTORY_PATH_INC . 'design.inc.php';
- }
|