X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FConsole%2FPoToPhp.php;h=e178e80e72807758c22184dbbc473f954ca1a5c8;hb=4faf08c0643d3e6bbe2a0a77be2ff8c1dbea4d5c;hp=c4ba42ccc4fec74f7808eab60ea69e4d06eb975e;hpb=4dcdc56e8173d6db3410014fa4c4ae3445a2436d;p=friendica.git diff --git a/src/Console/PoToPhp.php b/src/Console/PoToPhp.php index c4ba42ccc4..e178e80e72 100644 --- a/src/Console/PoToPhp.php +++ b/src/Console/PoToPhp.php @@ -1,11 +1,31 @@ . + * + */ namespace Friendica\Console; +use Geekwright\Po\PoFile; +use Geekwright\Po\PoTokens; + /** * Read a messages.po file and create strings.php in the same directory - * - * @author Hypolite Petovan */ class PoToPhp extends \Asika\SimpleConsole\Console { @@ -30,7 +50,7 @@ HELP; return $help; } - protected function doExecute() + protected function doExecute(): int { if ($this->getOption('v')) { $this->out('Class: ' . __CLASS__); @@ -67,132 +87,156 @@ HELP; $this->out('Out to ' . $outfile); - $out = "poFile2Php($lang, $pofile); - $infile = file($pofile); - $k = ''; - $v = ''; - $arr = false; - $ink = false; - $inv = false; - $escape_s_exp = '|[^\\\\]\$[a-z]|'; - - foreach ($infile as $l) { - $l = str_replace('\"', self::DQ_ESCAPE, $l); - $len = strlen($l); - if ($l[0] == "#") { - $l = ""; - } + if (!file_put_contents($outfile, $out)) { + throw new \RuntimeException('Unable to write to ' . $outfile); + } - if (substr($l, 0, 15) == '"Plural-Forms: ') { - $match = []; - preg_match("|nplurals=([0-9]*); *plural=(.*)[;\\\\]|", $l, $match); - $cond = str_replace('n', '$n', $match[2]); - // define plural select function if not already defined - $fnname = 'string_plural_select_' . $lang; - $out .= 'if(! function_exists("' . $fnname . '")) {' . "\n"; - $out .= 'function ' . $fnname . '($n){' . "\n"; - $out .= ' $n = intval($n);' . "\n"; - $out .= ' return ' . $cond . ';' . "\n"; - $out .= '}}' . "\n"; - } + return 0; + } - if ($k != '' && substr($l, 0, 7) == 'msgstr ') { - if ($ink) { - $ink = false; - $out .= '$a->strings["' . $k . '"] = '; - } + private function poFile2Php($lang, $infile): string + { + $poFile = new PoFile(); + $poFile->readPoFile($infile); - if ($inv) { - $out .= '"' . $v . '"'; - } + $out = "getHeaderEntry()->getHeader('plural-forms'); - $inv = true; - } + if (!$pluralForms) { + throw new \RuntimeException('No Plural-Forms header detected'); + } - if ($k != "" && substr($l, 0, 7) == 'msgstr[') { - if ($ink) { - $ink = false; - $out .= '$a->strings["' . $k . '"] = '; - } - if ($inv) { - $inv = false; - $out .= '"' . $v . '"'; - } - - if (!$arr) { - $arr = true; - $out .= "[\n"; - } - - $match = []; - preg_match("|\[([0-9]*)\] (.*)|", $l, $match); - $out .= "\t" - . preg_replace_callback($escape_s_exp, [$this, 'escapeDollar'], $match[1]) - . ' => ' - . preg_replace_callback($escape_s_exp, [$this, 'escapeDollar'], $match[2]) - . ",\n"; - } + $regex = 'nplurals=([0-9]*); *plural=(.*?)[\\\\;]'; - if (substr($l, 0, 6) == 'msgid_') { - $ink = false; - $out .= '$a->strings["' . $k . '"] = '; - } + if (!preg_match('|' . $regex . '|', $pluralForms, $match)) { + throw new \RuntimeException('Unexpected Plural-Forms header value, expected "' . $regex . '", found ' . $pluralForms); + } - if ($ink) { - $k .= trim($l, "\"\r\n"); - $k = preg_replace_callback($escape_s_exp, [$this, 'escapeDollar'], $k); - } + $out .= $this->createPluralSelectFunctionString($match[2], $lang); - if (substr($l, 0, 6) == 'msgid ') { - if ($inv) { - $inv = false; - $out .= '"' . $v . '"'; - } - - if ($k != "") { - $out .= ($arr) ? "];\n" : ";\n"; - } - - $arr = false; - $k = str_replace("msgid ", "", $l); - if ($k != '""') { - $k = trim($k, "\"\r\n"); - } else { - $k = ''; - } - - $k = preg_replace_callback($escape_s_exp, [$this, 'escapeDollar'], $k); - $ink = true; + foreach ($poFile->getEntries() as $entry) { + if (!implode('', $entry->getAsStringArray(PoTokens::TRANSLATED))) { + // Skip completely untranslated entries + continue; } - if ($inv && substr($l, 0, 6) != "msgstr") { - $v .= trim($l, "\"\r\n"); - $v = preg_replace_callback($escape_s_exp, [$this, 'escapeDollar'], $v); + $out .= '$a->strings[' . self::escapePhpString($entry->getAsString(PoTokens::MESSAGE)) . '] = '; + + $msgid_plural = $entry->get(PoTokens::PLURAL); + if (empty($msgid_plural)) { + $out .= self::escapePhpString($entry->getAsString(PoTokens::TRANSLATED)) . ';' . "\n"; + } else { + $out .= '[' . "\n"; + foreach($entry->getAsStringArray(PoTokens::TRANSLATED) as $key => $msgstr) { + $out .= "\t" . $key . ' => ' . self::escapePhpString($msgstr) . ',' . "\n"; + }; + + $out .= '];' . "\n"; } } - if ($inv) { - $out .= '"' . $v . '"'; - } + return $out; + } - if ($k != '') { - $out .= ($arr ? "];\n" : ";\n"); + private function createPluralSelectFunctionString(string $pluralForms, string $lang): string + { + $return = $this->convertCPluralConditionToPhpReturnStatement( + $pluralForms + ); + + $fnname = 'string_plural_select_' . $lang; + $out = 'if(! function_exists("' . $fnname . '")) {' . "\n"; + $out .= 'function ' . $fnname . '($n){' . "\n"; + $out .= ' $n = intval($n);' . "\n"; + $out .= ' ' . $return . "\n"; + $out .= '}}' . "\n"; + + return $out; + } + + private static function escapePhpString($string): string + { + return "'" . strtr($string, ['\'' => '\\\'']) . "'"; + } + + /** + * Converts C-style plural condition in .po files to a PHP-style plural return statement + * + * Adapted from https://github.com/friendica/friendica/issues/9747#issuecomment-769604485 + * Many thanks to Christian Archer (https://github.com/sunchaserinfo) + * + * @param string $cond + * @return string + */ + private function convertCPluralConditionToPhpReturnStatement(string $cond) + { + $cond = str_replace('n', '$n', $cond); + + $tree = []; + self::parse($cond, $tree); + + return is_string($tree) ? "return intval({$tree});" : self::render($tree); + } + + /** + * Parses the condition into an array if there's at least a ternary operator, to a string otherwise + * + * Warning: Black recursive magic + * + * @param string $string + * @param array|string $node + */ + private static function parse(string $string, &$node = []) + { + // Removes extra outward parentheses + if (strpos($string, '(') === 0 && strrpos($string, ')') === strlen($string) - 1) { + $string = substr($string, 1, -1); } - $out = str_replace(self::DQ_ESCAPE, '\"', $out); - if (!file_put_contents($outfile, $out)) { - throw new \RuntimeException('Unable to write to ' . $outfile); + $q = strpos($string, '?'); + $s = strpos($string, ':'); + + if ($q === false && $s === false) { + $node = $string; + return; } - return 0; + if ($q === false || $s < $q) { + list($then, $else) = explode(':', $string, 2); + $node['then'] = $then; + $parsedElse = []; + self::parse($else, $parsedElse); + $node['else'] = $parsedElse; + } else { + list($if, $thenelse) = explode('?', $string, 2); + $node['if'] = $if; + self::parse($thenelse, $node); + } } - private function escapeDollar($match) + /** + * Renders the parsed condition tree into a return statement + * + * Warning: Black recursive magic + * + * @param $tree + * @return string + */ + private static function render($tree): string { - return str_replace('$', '\$', $match[0]); + if (is_array($tree)) { + $if = trim($tree['if']); + $then = trim($tree['then']); + $else = self::render($tree['else']); + + return "if ({$if}) { return {$then}; } else {$else}"; + } + + $tree = trim($tree); + + return " { return {$tree}; }"; } }