diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index 9553d4d60d7ce..4369a5549f367 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -113,6 +113,13 @@ + + + + + + + diff --git a/src/Symfony/Component/Translation/Command/IntlConvertCommand.php b/src/Symfony/Component/Translation/Command/IntlConvertCommand.php new file mode 100644 index 0000000000000..745881c634273 --- /dev/null +++ b/src/Symfony/Component/Translation/Command/IntlConvertCommand.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Reader\TranslationReaderInterface; +use Symfony\Component\Translation\Util\IntlMessageConverter; +use Symfony\Component\Translation\Writer\TranslationWriterInterface; + +/** + * Convert to Intl styled message format. + * + * @author Tobias Nyholm + */ +class IntlConvertCommand extends Command +{ + protected static $defaultName = 'translation:convert-to-intl-messages'; + + private $writer; + private $reader; + + public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader) + { + parent::__construct(); + + $this->writer = $writer; + $this->reader = $reader; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDescription('Convert from Symfony 3 plural format to ICU message format.') + ->addArgument('locale', InputArgument::REQUIRED, 'The locale') + ->addArgument('path', null, 'A file or a directory') + ->addOption('domain', null, InputOption::VALUE_OPTIONAL, 'The messages domain') + ->addOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'xlf') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $path = $input->getArgument('path'); + $locale = $input->getArgument('locale'); + $domain = $input->getOption('domain'); + /** @var KernelInterface $kernel */ + $kernel = $this->getApplication()->getKernel(); + + // Define Root Paths + $transPaths = $kernel->getProjectDir().\DIRECTORY_SEPARATOR.'translations'; + if (null !== $path) { + $transPaths = $path; + } + + // load any existing messages from the translation files + $currentCatalogue = new MessageCatalogue($locale); + if (!is_dir($transPaths)) { + throw new \LogicException('The "path" must be a directory.'); + } + $this->reader->read($transPaths, $currentCatalogue); + + $allMessages = $currentCatalogue->all($domain); + if (null !== $domain) { + $allMessages = array($domain => $allMessages); + } + + $updated = array(); + foreach ($allMessages as $messageDomain => $messages) { + foreach ($messages as $key => $message) { + $updated[$messageDomain][$key] = IntlMessageConverter::convert($message); + } + } + + $this->writer->write(new MessageCatalogue($locale, $updated), $input->getOption('output-format'), array('path' => $transPaths)); + } +} diff --git a/src/Symfony/Component/Translation/Tests/Util/IntlMessageConverterTest.php b/src/Symfony/Component/Translation/Tests/Util/IntlMessageConverterTest.php new file mode 100644 index 0000000000000..7aef95df822b8 --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/Util/IntlMessageConverterTest.php @@ -0,0 +1,96 @@ +assertEquals($output, $result); + } + + public function testConvertWithCustomDelimiter() + { + $result = IntlMessageConverter::convert('Foo #var# bar', '#'); + $this->assertEquals('Foo {var} bar', $result); + + $result = IntlMessageConverter::convert('{0} Foo #var# bar | {1} Bar #var# foo', '#'); + $this->assertEquals( + <<expectException(\LogicException::class); + IntlMessageConverter::convert(']-Inf, -2[ Negative|]1,Inf[ Positive'); + } + + public function getTestData() + { + yield array('|', '|'); + yield array( + '{0} There are no apples|{1} There is one apple|]1,Inf[ There %name% are %count% apples', + << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Util; + +/** + * Convert from Symfony 3's plural syntax to Intl message format. + * {@link https://messageformat.github.io/messageformat/page-guide}. + * + * @author Tobias Nyholm + */ +class IntlMessageConverter +{ + public static function convert(string $message, string $variableDelimiter = '%'): string + { + $array = self::getMessageArray($message); + if (empty($array)) { + return $message; + } + + if (1 === \count($array) && isset($array[0])) { + return self::replaceVariables($message, $variableDelimiter); + } + + $icu = self::buildIcuString($array, $variableDelimiter); + + return $icu; + } + + /** + * Get an ICU like array. + */ + private static function getMessageArray(string $message): array + { + if (preg_match('/^\|++$/', $message)) { + // If the message only contains pipes ("|||") + return array(); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $message, $matches)) { + $parts = $matches[0]; + } else { + throw new \LogicException(sprintf('Input string "%s" is not supported.', $message)); + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = array(); + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + // try to match an explicit rule, then fallback to the standard ones + if (preg_match($intervalRegexp, $part, $matches)) { + if ($matches[2]) { + foreach (explode(',', $matches[3]) as $n) { + $standardRules['='.$n] = $matches['message']; + } + } else { + $leftNumber = '-Inf' === $matches['left'] ? -INF : (float) $matches['left']; + $rightNumber = \is_numeric($matches['right']) ? (float) $matches['right'] : INF; + + $leftNumber = ('[' === $matches['left_delimiter'] ? $leftNumber : 1 + $leftNumber); + $rightNumber = (']' === $matches['right_delimiter'] ? 1 + $rightNumber : $rightNumber); + + if ($leftNumber !== -INF && INF !== $rightNumber) { + for ($i = $leftNumber; $i < $rightNumber; ++$i) { + $standardRules['='.$i] = $matches['message']; + } + } else { + // $rightNumber is INF or $leftNumber is -INF + if (isset($standardRules['other'])) { + throw new \LogicException(sprintf('%s does not support converting messages with both "-Inf" and "Inf". Message: "%s"', __CLASS__, $message)); + } + $standardRules['other'] = $matches['message']; + } + } + } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + + return $standardRules; + } + + private static function buildIcuString(array $data, string $variableDelimiter): string + { + $icu = "{ COUNT, plural,\n"; + foreach ($data as $key => $message) { + $message = strtr($message, array('%count%' => '#')); + $message = self::replaceVariables($message, $variableDelimiter); + $icu .= sprintf(" %s {%s}\n", $key, $message); + } + $icu .= '}'; + + return $icu; + } + + private static function replaceVariables(string $message, string $variableDelimiter): string + { + $regex = sprintf('|%s(.*?)%s|s', $variableDelimiter, $variableDelimiter); + + return preg_replace($regex, '{$1}', $message); + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy