From 57aa3d51a79db9c7dda2683fa74f3f25cfca5265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Mon, 11 Mar 2013 13:57:32 +0100 Subject: [PATCH] [FrameworkBundle] adds --clean option translation:update command [FrameworkBundle] moved operation classes to component [FrameworkBundle] fixed catalogue operation classes [Translation] renamed catalogue operation classes [Translation] renamed operation classes --- .../Command/TranslationUpdateCommand.php | 43 ++++-- .../Catalogue/AbstractOperation.php | 146 ++++++++++++++++++ .../Translation/Catalogue/DiffOperation.php | 49 ++++++ .../Translation/Catalogue/MergeOperation.php | 45 ++++++ .../Catalogue/OperationInterface.php | 63 ++++++++ .../Tests/Catalogue/AbstractOperationTest.php | 74 +++++++++ .../Tests/Catalogue/DiffOperationTest.php | 60 +++++++ .../Tests/Catalogue/MergeOperationTest.php | 60 +++++++ 8 files changed, 526 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Component/Translation/Catalogue/AbstractOperation.php create mode 100644 src/Symfony/Component/Translation/Catalogue/DiffOperation.php create mode 100644 src/Symfony/Component/Translation/Catalogue/MergeOperation.php create mode 100644 src/Symfony/Component/Translation/Catalogue/OperationInterface.php create mode 100644 src/Symfony/Component/Translation/Tests/Catalogue/AbstractOperationTest.php create mode 100644 src/Symfony/Component/Translation/Tests/Catalogue/DiffOperationTest.php create mode 100644 src/Symfony/Component/Translation/Tests/Catalogue/MergeOperationTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index e5e1e8a264265..8f5a95c58a514 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -12,6 +12,8 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Translation\Catalogue\DiffOperation; +use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; @@ -26,12 +28,6 @@ */ class TranslationUpdateCommand extends ContainerAwareCommand { - /** - * Compiled catalogue of messages. - * @var MessageCatalogue - */ - protected $catalogue; - /** * {@inheritDoc} */ @@ -57,6 +53,10 @@ protected function configure() new InputOption( 'force', null, InputOption::VALUE_NONE, 'Should the update be done' + ), + new InputOption( + 'clean', null, InputOption::VALUE_NONE, + 'Should clean not found messages' ) )) ->setDescription('Updates the translation file') @@ -100,26 +100,41 @@ protected function execute(InputInterface $input, OutputInterface $output) $bundleTransPath = $foundBundle->getPath().'/Resources/translations'; $output->writeln(sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $foundBundle->getName())); - // create catalogue - $catalogue = new MessageCatalogue($input->getArgument('locale')); - // load any messages from templates + $extractedCatalogue = new MessageCatalogue($input->getArgument('locale')); $output->writeln('Parsing templates'); $extractor = $this->getContainer()->get('translation.extractor'); $extractor->setPrefix($input->getOption('prefix')); - $extractor->extract($foundBundle->getPath().'/Resources/views/', $catalogue); + $extractor->extract($foundBundle->getPath().'/Resources/views/', $extractedCatalogue); // load any existing messages from the translation files + $currentCatalogue = new MessageCatalogue($input->getArgument('locale')); $output->writeln('Loading translation files'); $loader = $this->getContainer()->get('translation.loader'); - $loader->loadMessages($bundleTransPath, $catalogue); + $loader->loadMessages($bundleTransPath, $currentCatalogue); + + // process catalogues + $operation = $input->getOption('clean') + ? new DiffOperation($currentCatalogue, $extractedCatalogue) + : new MergeOperation($currentCatalogue, $extractedCatalogue); // show compiled list of messages if ($input->getOption('dump-messages') === true) { - foreach ($catalogue->getDomains() as $domain) { + foreach ($operation->getDomains() as $domain) { $output->writeln(sprintf("\nDisplaying messages for domain %s:\n", $domain)); - $output->writeln(Yaml::dump($catalogue->all($domain), 10)); + $newKeys = array_keys($operation->getNewMessages($domain)); + $allKeys = array_keys($operation->getMessages($domain)); + foreach (array_diff($allKeys, $newKeys) as $id) { + $output->writeln($id); + } + foreach ($newKeys as $id) { + $output->writeln(sprintf('%s', $id)); + } + foreach (array_keys($operation->getObsoleteMessages($domain)) as $id) { + $output->writeln(sprintf('%s', $id)); + } } + if ($input->getOption('output-format') == 'xliff') { $output->writeln('Xliff output version is 1.2'); } @@ -128,7 +143,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // save the files if ($input->getOption('force') === true) { $output->writeln('Writing files'); - $writer->writeTranslations($catalogue, $input->getOption('output-format'), array('path' => $bundleTransPath)); + $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath)); } } } diff --git a/src/Symfony/Component/Translation/Catalogue/AbstractOperation.php b/src/Symfony/Component/Translation/Catalogue/AbstractOperation.php new file mode 100644 index 0000000000000..9346e950db006 --- /dev/null +++ b/src/Symfony/Component/Translation/Catalogue/AbstractOperation.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Base catalogues binary operation class. + * + * @author Jean-François Simon + */ +abstract class AbstractOperation implements OperationInterface +{ + /** + * @var MessageCatalogueInterface + */ + protected $source; + + /** + * @var MessageCatalogueInterface + */ + protected $target; + + /** + * @var MessageCatalogue + */ + protected $result; + + /** + * @var null|array + */ + private $domains; + + /** + * @var array + */ + protected $messages; + + /** + * @param MessageCatalogueInterface $source + * @param MessageCatalogueInterface $target + * + * @throws \LogicException + */ + public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target) + { + if ($source->getLocale() !== $target->getLocale()) { + throw new \LogicException('Operated catalogues must belong to the same locale.'); + } + + $this->source = $source; + $this->target = $target; + $this->result = new MessageCatalogue($source->getLocale()); + $this->domains = null; + $this->messages = array(); + } + + /** + * {@inheritdoc} + */ + public function getDomains() + { + if (null === $this->domains) { + $this->domains = array_values(array_unique(array_merge($this->source->getDomains(), $this->target->getDomains()))); + } + + return $this->domains; + } + + /** + * {@inheritdoc} + */ + public function getMessages($domain) + { + if (!in_array($domain, $this->getDomains())) { + throw new \InvalidArgumentException('Invalid domain: '.$domain.'.'); + } + + if (!isset($this->messages[$domain]['all'])) { + $this->processDomain($domain); + } + + return $this->messages[$domain]['all']; + } + + /** + * {@inheritdoc} + */ + public function getNewMessages($domain) + { + if (!in_array($domain, $this->getDomains())) { + throw new \InvalidArgumentException('Invalid domain: '.$domain.'.'); + } + + if (!isset($this->messages[$domain]['new'])) { + $this->processDomain($domain); + } + + return $this->messages[$domain]['new']; + } + + /** + * {@inheritdoc} + */ + public function getObsoleteMessages($domain) + { + if (!in_array($domain, $this->getDomains())) { + throw new \InvalidArgumentException('Invalid domain: '.$domain.'.'); + } + + if (!isset($this->messages[$domain]['obsolete'])) { + $this->processDomain($domain); + } + + return $this->messages[$domain]['obsolete']; + } + + /** + * {@inheritdoc} + */ + public function getResult() + { + foreach ($this->getDomains() as $domain) { + if (!isset($this->messages[$domain])) { + $this->processDomain($domain); + } + } + + return $this->result; + } + + /** + * @param string $domain + */ + abstract protected function processDomain($domain); +} diff --git a/src/Symfony/Component/Translation/Catalogue/DiffOperation.php b/src/Symfony/Component/Translation/Catalogue/DiffOperation.php new file mode 100644 index 0000000000000..1672d121da413 --- /dev/null +++ b/src/Symfony/Component/Translation/Catalogue/DiffOperation.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +/** + * Diff operation between two catalogues. + * + * @author Jean-François Simon + */ +class DiffOperation extends AbstractOperation +{ + /** + * {@inheritdoc} + */ + protected function processDomain($domain) + { + $this->messages[$domain] = array( + 'all' => array(), + 'new' => array(), + 'obsolete' => array(), + ); + + foreach ($this->source->all($domain) as $id => $message) { + if ($this->target->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->result->add(array($id => $message), $domain); + } else { + $this->messages[$domain]['obsolete'][$id] = $message; + } + } + + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $this->result->add(array($id => $message), $domain); + } + } + } +} diff --git a/src/Symfony/Component/Translation/Catalogue/MergeOperation.php b/src/Symfony/Component/Translation/Catalogue/MergeOperation.php new file mode 100644 index 0000000000000..0052363efeaf7 --- /dev/null +++ b/src/Symfony/Component/Translation/Catalogue/MergeOperation.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +/** + * Merge operation between two catalogues. + * + * @author Jean-François Simon + */ +class MergeOperation extends AbstractOperation +{ + /** + * {@inheritdoc} + */ + protected function processDomain($domain) + { + $this->messages[$domain] = array( + 'all' => array(), + 'new' => array(), + 'obsolete' => array(), + ); + + foreach ($this->source->all($domain) as $id => $message) { + $this->messages[$domain]['all'][$id] = $message; + $this->result->add(array($id => $message), $domain); + } + + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $this->result->add(array($id => $message), $domain); + } + } + } +} diff --git a/src/Symfony/Component/Translation/Catalogue/OperationInterface.php b/src/Symfony/Component/Translation/Catalogue/OperationInterface.php new file mode 100644 index 0000000000000..d72378a3b3c17 --- /dev/null +++ b/src/Symfony/Component/Translation/Catalogue/OperationInterface.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Represents an operation on catalogue(s). + * + * @author Jean-François Simon + */ +interface OperationInterface +{ + /** + * Returns domains affected by operation. + * + * @return array + */ + public function getDomains(); + + /** + * Returns all valid messages after operation. + * + * @param string $domain + * + * @return array + */ + public function getMessages($domain); + + /** + * Returns new messages after operation. + * + * @param string $domain + * + * @return array + */ + public function getNewMessages($domain); + + /** + * Returns obsolete messages after operation. + * + * @param string $domain + * + * @return array + */ + public function getObsoleteMessages($domain); + + /** + * Returns resulting catalogue. + * + * @return MessageCatalogueInterface + */ + public function getResult(); +} diff --git a/src/Symfony/Component/Translation/Tests/Catalogue/AbstractOperationTest.php b/src/Symfony/Component/Translation/Tests/Catalogue/AbstractOperationTest.php new file mode 100644 index 0000000000000..78023e1bc8f3a --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/Catalogue/AbstractOperationTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test\Catalogue; + +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +abstract class AbstractOperationTest extends TestCase +{ + public function testGetEmptyDomains() + { + $this->assertEquals( + array(), + $this->createOperation( + new MessageCatalogue('en'), + new MessageCatalogue('en') + )->getDomains() + ); + } + + public function testGetMergedDomains() + { + $this->assertEquals( + array('a', 'b', 'c'), + $this->createOperation( + new MessageCatalogue('en', array('a' => array(), 'b' => array())), + new MessageCatalogue('en', array('b' => array(), 'c' => array())) + )->getDomains() + ); + } + + public function testGetMessagesFromUnknownDomain() + { + $this->setExpectedException('InvalidArgumentException'); + $this->createOperation( + new MessageCatalogue('en'), + new MessageCatalogue('en') + )->getMessages('domain'); + } + + public function testGetEmptyMessages() + { + $this->assertEquals( + array(), + $this->createOperation( + new MessageCatalogue('en', array('a' => array())), + new MessageCatalogue('en') + )->getMessages('a') + ); + } + + public function testGetEmptyResult() + { + $this->assertEquals( + new MessageCatalogue('en'), + $this->createOperation( + new MessageCatalogue('en'), + new MessageCatalogue('en') + )->getResult() + ); + } + + abstract protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target); +} diff --git a/src/Symfony/Component/Translation/Tests/Catalogue/DiffOperationTest.php b/src/Symfony/Component/Translation/Tests/Catalogue/DiffOperationTest.php new file mode 100644 index 0000000000000..b2e852d9c2f0b --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/Catalogue/DiffOperationTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test\Catalogue; + +use Symfony\Component\Translation\Catalogue\DiffOperation; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +class DiffOperationTest extends AbstractOperationTest +{ + public function testGetMessagesFromSingleDomain() + { + $operation = $this->createOperation( + new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))), + new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c'))) + ); + + $this->assertEquals( + array('a' => 'old_a', 'c' => 'new_c'), + $operation->getMessages('messages') + ); + + $this->assertEquals( + array('c' => 'new_c'), + $operation->getNewMessages('messages') + ); + + $this->assertEquals( + array('b' => 'old_b'), + $operation->getObsoleteMessages('messages') + ); + } + + public function testGetResultFromSingleDomain() + { + $this->assertEquals( + new MessageCatalogue('en', array( + 'messages' => array('a' => 'old_a', 'c' => 'new_c') + )), + $this->createOperation( + new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))), + new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c'))) + )->getResult() + ); + } + + protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target) + { + return new DiffOperation($source, $target); + } +} diff --git a/src/Symfony/Component/Translation/Tests/Catalogue/MergeOperationTest.php b/src/Symfony/Component/Translation/Tests/Catalogue/MergeOperationTest.php new file mode 100644 index 0000000000000..fa5118a7ca026 --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/Catalogue/MergeOperationTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test\Catalogue; + +use Symfony\Component\Translation\Catalogue\MergeOperation; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +class MergeOperationTest extends AbstractOperationTest +{ + public function testGetMessagesFromSingleDomain() + { + $operation = $this->createOperation( + new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))), + new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c'))) + ); + + $this->assertEquals( + array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c'), + $operation->getMessages('messages') + ); + + $this->assertEquals( + array('c' => 'new_c'), + $operation->getNewMessages('messages') + ); + + $this->assertEquals( + array(), + $operation->getObsoleteMessages('messages') + ); + } + + public function testGetResultFromSingleDomain() + { + $this->assertEquals( + new MessageCatalogue('en', array( + 'messages' => array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c') + )), + $this->createOperation( + new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))), + new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c'))) + )->getResult() + ); + } + + protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target) + { + return new MergeOperation($source, $target); + } +}