/** * Dumps a given PHP variable to a YAML string. * * @param mixed $value The PHP variable to convert * * @return string The YAML string representing the PHP array * * @throws DumpException When trying to dump PHP resource */ public static function dump($value) { switch (true) { case is_resource($value): throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); case is_object($value): return '!!php/object:' . serialize($value); case is_array($value): return self::dumpArray($value); case null === $value: return 'null'; case true === $value: return 'true'; case false === $value: return 'false'; case ctype_digit($value): return is_string($value) ? "'{$value}'" : (int) $value; case is_numeric($value): return is_string($value) ? "'{$value}'" : (is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : $value); case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): return Escaper::escapeWithSingleQuotes($value); case '' == $value: return "''"; case preg_match(self::getTimestampRegex(), $value): case in_array(strtolower($value), array('null', '~', 'true', 'false')): return "'{$value}'"; default: return $value; } }
public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false) { switch (true) { case is_resource($value): if ($exceptionOnInvalidType) { throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); } return 'null'; case is_object($value): if ($objectSupport) { return '!!php/object:' . serialize($value); } if ($exceptionOnInvalidType) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } return 'null'; case is_array($value): return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport); case null === $value: return 'null'; case true === $value: return 'true'; case false === $value: return 'false'; case ctype_digit($value): return is_string($value) ? "'{$value}'" : (int) $value; case is_numeric($value): $locale = setlocale(LC_NUMERIC, 0); if (false !== $locale) { setlocale(LC_NUMERIC, 'C'); } $repr = is_string($value) ? "'{$value}'" : (is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : strval($value)); if (false !== $locale) { setlocale(LC_NUMERIC, $locale); } return $repr; case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): return Escaper::escapeWithSingleQuotes($value); case '' == $value: return "''"; case preg_match(self::getTimestampRegex(), $value): case in_array(strtolower($value), array('null', '~', 'true', 'false')): return "'{$value}'"; default: return $value; } }
/** * Find all twig templates and bolt php code, extract translatables * strings, merge with existing translations, return */ function gatherTranslatableStrings($locale = null, $translated = array()) { global $app; $isPhp = function ($fname) { return pathinfo(strtolower($fname), PATHINFO_EXTENSION) == 'php'; }; $isTwig = function ($fname) { return pathinfo(strtolower($fname), PATHINFO_EXTENSION) == 'twig'; }; $ctypes = $app['config']->get('contenttypes'); // function that generates a string for each variation of contenttype/contenttypes $genContentTypes = function ($txt) use($ctypes) { $stypes = array(); if (strpos($txt, '%contenttypes%') !== false) { foreach ($ctypes as $key => $ctype) { $stypes[] = str_replace('%contenttypes%', $ctype['name'], $txt); } } if (strpos($txt, '%contenttype%') !== false) { foreach ($ctypes as $key => $ctype) { $stypes[] = str_replace('%contenttype%', $ctype['singular_name'], $txt); } } return $stypes; }; // Step one: gather all translatable strings $finder = new Finder(); $finder->files()->ignoreVCS(true)->name('*.twig')->name('*.php')->notName('*~')->exclude(array('cache', 'config', 'database', 'resources', 'tests'))->in(dirname($app['paths']['themepath']))->in($app['paths']['apppath']); // regex from: stackoverflow.com/questions/5695240/php-regex-to-ignore-escaped-quotes-within-quotes $re_dq = '/"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"/s'; $re_sq = "/'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'/s"; $nstr = 0; $strings = array(); foreach ($finder as $file) { $s = file_get_contents($file); // Scan twig templates for __('...' and __("..." if ($isTwig($file)) { // __('single_quoted_string'... if (preg_match_all("/\\b__\\(\\s*'([^'\\\\]*(?:\\\\.[^'\\\\]*)*)'(?U).*\\)/s", $s, $matches)) { //print_r($matches[1]); foreach ($matches[1] as $t) { $nstr++; if (!in_array($t, $strings) && strlen($t) > 1) { $strings[] = $t; sort($strings); } } } // __("double_quoted_string"... if (preg_match_all('/\\b__\\(\\s*"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"(?U).*\\)/s', $s, $matches)) { //print_r($matches[1]); foreach ($matches[1] as $t) { $nstr++; if (!in_array($t, $strings) && strlen($t) > 1) { $strings[] = $t; sort($strings); } } } } // php : /** all translatables strings have to be called with: * __("text", $params=array(), $domain='messages', locale=null) // $app['translator']->trans() * __("text", count, $params=array(), $domain='messages', locale=null) // $app['translator']->transChoice() */ if ($isPhp($file)) { $tokens = token_get_all($s); $num_tokens = count($tokens); for ($x = 0; $x < $num_tokens; $x++) { $token = $tokens[$x]; if (is_array($token) && $token[0] == T_STRING && $token[1] == '__') { $token = $tokens[++$x]; if ($x < $num_tokens && is_array($token) && $token[0] == T_WHITESPACE) { $token = $tokens[++$x]; } if ($x < $num_tokens && !is_array($token) && $token == '(') { // in our func args... $token = $tokens[++$x]; if ($x < $num_tokens && is_array($token) && $token[0] == T_WHITESPACE) { $token = $tokens[++$x]; } if (!is_array($token)) { // give up continue; } if ($token[0] == T_CONSTANT_ENCAPSED_STRING) { $t = substr($token[1], 1, strlen($token[1]) - 2); $nstr++; if (!in_array($t, $strings) && strlen($t) > 1) { $strings[] = $t; sort($strings); } // TODO: retrieve domain? } } } } // end for $x } } // Add fields name|label for contenttype (forms) foreach ($ctypes as $ckey => $contenttype) { foreach ($contenttype['fields'] as $fkey => $field) { if (isset($field['label'])) { $t = $field['label']; } else { $t = ucfirst($fkey); } if (!in_array($t, $strings) && strlen($t) > 1) { $strings[] = $t; } } // Relation name|label if exists if (array_key_exists('relations', $contenttype)) { foreach ($contenttype['relations'] as $fkey => $field) { if (isset($field['label'])) { $t = $field['label']; } else { $t = ucfirst($fkey); } if (!in_array($t, $strings) && strlen($t) > 1) { $strings[] = $t; } } } } // Add name + singular_name for taxonomies foreach ($app['config']->get('taxonomy') as $txkey => $value) { foreach (array('name', 'singular_name') as $key) { $t = $value[$key]; if (!in_array($t, $strings)) { $strings[] = $t; } } } // Return the previously translated string if exists, // Return an empty string otherwise $getTranslated = function ($key) use($app, $translated) { if (($trans = $app['translator']->trans($key)) == $key) { if (is_array($translated) && array_key_exists($key, $translated) && !empty($translated[$key])) { return $translated[$key]; } return ''; } return $trans; }; // Step 2: find already translated strings sort($strings); if (!$locale) { $locale = $app['request']->getLocale(); } $msg_domain = array('translated' => array(), 'not_translated' => array()); $ctype_domain = array('translated' => array(), 'not_translated' => array()); foreach ($strings as $idx => $key) { $key = stripslashes($key); $raw_key = $key; $key = Escaper::escapeWithDoubleQuotes($key); if (($trans = $getTranslated($raw_key)) == '' && ($trans = $getTranslated($key)) == '') { $msg_domain['not_translated'][] = $key; } else { $trans = Escaper::escapeWithDoubleQuotes($trans); $msg_domain['translated'][$key] = $trans; } // Step 3: generate additionals strings for contenttypes if (strpos($raw_key, '%contenttype%') !== false || strpos($raw_key, '%contenttypes%') !== false) { foreach ($genContentTypes($raw_key) as $ctypekey) { $key = Escaper::escapeWithDoubleQuotes($ctypekey); if (($trans = $getTranslated($ctypekey)) == '' && ($trans = $getTranslated($key)) == '') { // Not translated $ctype_domain['not_translated'][] = $key; } else { $trans = Escaper::escapeWithDoubleQuotes($trans); $ctype_domain['translated'][$key] = $trans; } } } } sort($msg_domain['not_translated']); ksort($msg_domain['translated']); sort($ctype_domain['not_translated']); ksort($ctype_domain['translated']); return array($msg_domain, $ctype_domain); }
/** * Dumps a given PHP variable to a YAML string. * * @param mixed $value The PHP variable to convert * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string * * @return string The YAML string representing the PHP array * * @throws DumpException When trying to dump PHP resource */ public static function dump($value, $flags = 0) { if (is_bool($flags)) { @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED); if ($flags) { $flags = Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE; } else { $flags = 0; } } if (func_num_args() >= 3) { @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', E_USER_DEPRECATED); if (func_get_arg(2)) { $flags |= Yaml::DUMP_OBJECT; } } switch (true) { case is_resource($value): if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); } return 'null'; case $value instanceof \DateTimeInterface: return $value->format('c'); case is_object($value): if (Yaml::DUMP_OBJECT & $flags) { return '!php/object:' . serialize($value); } if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { return self::dumpArray((array) $value, $flags); } if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } return 'null'; case is_array($value): return self::dumpArray($value, $flags); case null === $value: return 'null'; case true === $value: return 'true'; case false === $value: return 'false'; case ctype_digit($value): return is_string($value) ? "'{$value}'" : (int) $value; case is_numeric($value): $locale = setlocale(LC_NUMERIC, 0); if (false !== $locale) { setlocale(LC_NUMERIC, 'C'); } if (is_float($value)) { $repr = (string) $value; if (is_infinite($value)) { $repr = str_ireplace('INF', '.Inf', $repr); } elseif (floor($value) == $value && $repr == $value) { // Preserve float data type since storing a whole number will result in integer value. $repr = '!!float ' . $repr; } } else { $repr = is_string($value) ? "'{$value}'" : (string) $value; } if (false !== $locale) { setlocale(LC_NUMERIC, $locale); } return $repr; case '' == $value: return "''"; case self::isBinaryString($value): return '!!binary ' . base64_encode($value); case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): case preg_match('{^[0-9]+[_0-9]*$}', $value): case preg_match(self::getHexRegex(), $value): case preg_match(self::getTimestampRegex(), $value): return Escaper::escapeWithSingleQuotes($value); default: return $value; } }
/** * Builds the translations file data with added translations. * * @param array $newTranslations New translation data to write * @param array $savedTranslations Translation data read from file * @param array $hinting Translation data that can be used as hinting * * @return string */ private function buildNewContent($newTranslations, $savedTranslations, $hinting = []) { // Presort $unusedTranslations = $savedTranslations; $transByType = ['Unused' => [' unused messages', []], 'TodoReal' => [' untranslated messages', []], 'TodoKey' => [' untranslated keyword based messages', []], 'DoneReal' => [' translations', []], 'DoneKey' => [' keyword based translations', []]]; foreach ($newTranslations as $key => $translation) { $set = ['trans' => $translation]; if (preg_match('%^[a-z0-9-]+\\.[a-z0-9-.]+$%', $key)) { $type = 'Key'; $set['key'] = preg_split('%\\.%', $key); } else { $type = 'Real'; } $done = $translation === '' ? 'Todo' : 'Done'; $transByType[$done . $type][1][$key] = $set; if (isset($unusedTranslations[$key])) { unset($unusedTranslations[$key]); } } foreach ($unusedTranslations as $key => $translation) { $transByType['Unused'][1][$key] = ['trans' => $translation]; } // Build List $indent = ' '; $status = '# ' . $this->relPath . ' – generated on ' . date('Y-m-d H:i:s e') . "\n\n" . '# Warning: Translations are in the process of being moved to a new keyword-based translation' . "\n" . '# at the moment. This is an ongoing process. Translations currently in the ' . "\n" . '# repository are automatically mapped to the new scheme. Be aware that there ' . "\n" . '# can be a race condition between that process and your PR so that it\'s ' . "\n" . '# eventually necessary to remap your translations. If you\'re planning on ' . "\n" . '# updating your translations, it\'s best to ask on IRC to time your contribution' . "\n" . '# in order to prevent merge conflicts.' . "\n\n"; $content = ''; // Set this to true to get nested output. $nested = false; foreach ($transByType as $type => $transData) { list($text, $translations) = $transData; // Header $count = count($translations) > 0 ? sprintf('%3s', count($translations)) : ' no'; $status .= '# ' . $count . $text . "\n"; if (count($translations) > 0) { $content .= "\n" . '#--- ' . str_pad(ltrim($count) . $text . ' ', 74, '-') . "\n\n"; } // List $lastKey = []; $linebreak = ''; // We want an empty line before each 1st level key foreach ($translations as $key => $tdata) { // Key if ($type == 'DoneKey') { if ($nested) { $differs = false; for ($level = 0, $end = count($tdata['key']) - 1; $level < $end; $level++) { if ($differs || $level >= count($lastKey) - 1 || $lastKey[$level] != $tdata['key'][$level]) { $differs = true; if ($level === 0) { $content .= $linebreak; $linebreak = "\n"; } $content .= str_repeat($indent, $level) . $tdata['key'][$level] . ':' . "\n"; } } $lastKey = $tdata['key']; $content .= str_repeat($indent, $level) . $tdata['key'][$level] . ': '; } else { $key2 = $tdata['key'][0] . (isset($tdata['key'][1]) ? '.' . $tdata['key'][1] : ''); if ($key2 !== $lastKey) { $content .= $linebreak; $linebreak = "\n"; $lastKey = $key2; } $content .= $key . ': '; } } else { $content .= Escaper::escapeWithDoubleQuotes($key) . ': '; } // Value if ($tdata['trans'] === '') { $thint = Trans::__($key); if ($thint === $key) { $thint = isset($hinting[$key]) ? $hinting[$key] : ''; } $content .= '#' . ($thint ? ' ' . Escaper::escapeWithDoubleQuotes($thint) : '') . "\n"; } else { $content .= Escaper::escapeWithDoubleQuotes($tdata['trans']) . "\n"; } } } return $status . $content; }
/** * Dumps a given PHP variable to a YAML string. * * @param mixed $value The PHP variable to convert * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return string The YAML string representing the PHP array * * @throws DumpException When trying to dump PHP resource */ public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false) { switch (true) { case is_resource($value): if ($exceptionOnInvalidType) { throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); } return 'null'; case is_object($value): if ($objectSupport) { return '!php/object:' . serialize($value); } if ($exceptionOnInvalidType) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } return 'null'; case is_array($value): return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport); case null === $value: return 'null'; case true === $value: return 'true'; case false === $value: return 'false'; case ctype_digit($value): return is_string($value) ? "'{$value}'" : (int) $value; case is_numeric($value): $locale = setlocale(LC_NUMERIC, 0); if (false !== $locale) { setlocale(LC_NUMERIC, 'C'); } if (is_float($value)) { $repr = (string) $value; if (is_infinite($value)) { $repr = str_ireplace('INF', '.Inf', $repr); } elseif (floor($value) == $value && $repr == $value) { // Preserve float data type since storing a whole number will result in integer value. $repr = '!!float ' . $repr; } } else { $repr = is_string($value) ? "'{$value}'" : (string) $value; } if (false !== $locale) { setlocale(LC_NUMERIC, $locale); } return $repr; case '' == $value: return "''"; case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): case preg_match(self::getHexRegex(), $value): case preg_match(self::getTimestampRegex(), $value): return Escaper::escapeWithSingleQuotes($value); default: return $value; } }
/** * Dumps a given PHP variable to a YAML string. * * @param mixed $value The PHP variable to convert * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string * * @return string The YAML string representing the PHP array * * @throws DumpException When trying to dump PHP resource */ public static function dump($value, $exceptionOnInvalidType = false, $flags = 0) { if (is_bool($flags)) { @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', E_USER_DEPRECATED); $flags = (int) $flags; } switch (true) { case is_resource($value): if ($exceptionOnInvalidType) { throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); } return 'null'; case is_object($value): if (Yaml::DUMP_OBJECT & $flags) { return '!php/object:' . serialize($value); } if ($exceptionOnInvalidType) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } return 'null'; case is_array($value): return self::dumpArray($value, $exceptionOnInvalidType, $flags); case null === $value: return 'null'; case true === $value: return 'true'; case false === $value: return 'false'; case ctype_digit($value): return is_string($value) ? "'{$value}'" : (int) $value; case is_numeric($value): $locale = setlocale(LC_NUMERIC, 0); if (false !== $locale) { setlocale(LC_NUMERIC, 'C'); } if (is_float($value)) { $repr = (string) $value; if (is_infinite($value)) { $repr = str_ireplace('INF', '.Inf', $repr); } elseif (floor($value) == $value && $repr == $value) { // Preserve float data type since storing a whole number will result in integer value. $repr = '!!float ' . $repr; } } else { $repr = is_string($value) ? "'{$value}'" : (string) $value; } if (false !== $locale) { setlocale(LC_NUMERIC, $locale); } return $repr; case '' == $value: return "''"; case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): case preg_match(self::getHexRegex(), $value): case preg_match(self::getTimestampRegex(), $value): return Escaper::escapeWithSingleQuotes($value); default: return $value; } }
/** * Find all twig templates and bolt php code, extract translatables strings, merge with existing translations * * @param array $translated * @param array $getMessages True returns translation datat for messages, false for contenttypes * @return array */ private function gatherTranslatableStrings($translated, $getMessages) { // Step 1: Gather all translatable strings $this->translatables = array(); $this->scanTwigFiles(); $this->scanPhpFiles(); $this->scanContenttypeFields(); $this->scanContenttypeRelations(); $this->scanTaxonomies(); // Build lists $msgTranslated = array(); $msgUntranslated = array(); foreach ($this->translatables as $key) { $keyRaw = stripslashes($key); $keyEsc = Escaper::escapeWithDoubleQuotes($keyRaw); if ($getMessages) { // Step 2: Find already translated strings if (($trans = $this->getTranslated($keyRaw, $translated)) == '' && ($trans = $this->getTranslated($keyEsc, $translated)) == '') { $msgUntranslated[] = $keyEsc; } else { $trans = Escaper::escapeWithDoubleQuotes($trans); $msgTranslated[$keyEsc] = $trans; } } else { // Step 3: Generate additional strings for contenttypes if (strpos($keyRaw, '%contenttype%') !== false || strpos($keyRaw, '%contenttypes%') !== false) { foreach ($this->genContentTypes($keyRaw) as $ctypekey) { $keyEsc = Escaper::escapeWithDoubleQuotes($ctypekey); if (($trans = $this->getTranslated($ctypekey, $translated)) == '' && ($trans = $this->getTranslated($keyEsc, $translated)) == '') { $msgUntranslated[] = $keyEsc; // Not translated } else { $msgTranslated[$keyEsc] = Escaper::escapeWithDoubleQuotes($trans); } } } } } sort($msgUntranslated); ksort($msgTranslated); return array($msgTranslated, $msgUntranslated); }