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);
+ }
+}