From 458aac8dd8bd7a57d1952d196c24ccd2c53f5499 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Mon, 25 Nov 2013 15:58:55 +0100 Subject: [PATCH] Add JMS translation tools --- bin/developer | 2 + composer.json | 3 +- composer.lock | 115 +++++++++++++++++- lib/Alchemy/Phrasea/CLI.php | 2 + .../Command/Developer/TranslationDumper.php | 51 ++++++++ .../Developer/Utils/ConstraintExtractor.php | 83 +++++++++++++ .../TranslationExtractorServiceProvider.php | 96 +++++++++++++++ templates/web/prod/index.html.twig | 2 +- .../prod/{thesaurus.js => thesaurus.js.twig} | 0 9 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Command/Developer/TranslationDumper.php create mode 100644 lib/Alchemy/Phrasea/Command/Developer/Utils/ConstraintExtractor.php create mode 100644 lib/Alchemy/Phrasea/Core/CLIProvider/TranslationExtractorServiceProvider.php rename templates/web/prod/{thesaurus.js => thesaurus.js.twig} (100%) diff --git a/bin/developer b/bin/developer index a7342b659e..d52174170c 100755 --- a/bin/developer +++ b/bin/developer @@ -75,6 +75,8 @@ if ($cli['configuration.store']->isSetup()) { } } +$cli->command(new \Alchemy\Phrasea\Command\Developer\TranslationDumper()); + $cli->command(new InstallAll()); $cli->command(new BowerInstall()); $cli->command(new ComposerInstall()); diff --git a/composer.json b/composer.json index 8d3abf9ba7..aafbe7fbfb 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,8 @@ "behat/mink-extension" : "~1.0", "behat/mink-goutte-driver" : "~1.0", "behat/mink-selenium2-driver" : "~1.0", - "fabpot/goutte" : "~1.0" + "fabpot/goutte" : "~1.0", + "jms/translation-bundle" : "~1.1" }, "repositories": [ { diff --git a/composer.lock b/composer.lock index 699b1c8c98..033f4cf230 100644 --- a/composer.lock +++ b/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "bdd3d36f118f024ac51b2466a8e11d09", + "hash": "daac732f5ff830bcaf81da6ecf16f9e2", "packages": [ { "name": "alchemy-fr/tcpdf-clone", @@ -3745,6 +3745,119 @@ ], "time": "2013-10-04 15:03:51" }, + { + "name": "jms/translation-bundle", + "version": "1.1.0", + "target-dir": "JMS/TranslationBundle", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/JMSTranslationBundle.git", + "reference": "6f03035a38badaf8c48767c7664c3196df1eebdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/JMSTranslationBundle/zipball/6f03035a38badaf8c48767c7664c3196df1eebdf", + "reference": "6f03035a38badaf8c48767c7664c3196df1eebdf", + "shasum": "" + }, + "require": { + "nikic/php-parser": "0.9.1", + "symfony/console": "*", + "symfony/framework-bundle": "~2.1" + }, + "conflict": { + "twig/twig": "1.10.2" + }, + "require-dev": { + "jms/di-extra-bundle": ">=1.1", + "sensio/framework-extra-bundle": "*", + "symfony/browser-kit": "*", + "symfony/class-loader": "*", + "symfony/css-selector": "*", + "symfony/finder": "*", + "symfony/form": "*", + "symfony/process": "*", + "symfony/security": "*", + "symfony/twig-bundle": "*", + "symfony/validator": "*", + "symfony/yaml": "*" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-0": { + "JMS\\TranslationBundle": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Puts the Symfony2 Translation Component on steroids", + "homepage": "http://jmsyst.com/bundles/JMSTranslationBundle", + "keywords": [ + "extract", + "extraction", + "i18n", + "interface", + "multilanguage", + "translation", + "ui", + "webinterface" + ], + "time": "2013-06-08 14:08:19" + }, + { + "name": "nikic/php-parser", + "version": "v0.9.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "b1cc9ce676b4350b23d0fafc8244d08eee2fe287" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/b1cc9ce676b4350b23d0fafc8244d08eee2fe287", + "reference": "b1cc9ce676b4350b23d0fafc8244d08eee2fe287", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "PHPParser": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2012-04-23 22:52:11" + }, { "name": "phpunit/php-code-coverage", "version": "1.2.13", diff --git a/lib/Alchemy/Phrasea/CLI.php b/lib/Alchemy/Phrasea/CLI.php index f20960fc87..680acb556e 100644 --- a/lib/Alchemy/Phrasea/CLI.php +++ b/lib/Alchemy/Phrasea/CLI.php @@ -12,6 +12,7 @@ namespace Alchemy\Phrasea; use Alchemy\Phrasea\Command\CommandInterface; +use Alchemy\Phrasea\Core\CLIProvider\TranslationExtractorServiceProvider; use Symfony\Component\Console; use Alchemy\Phrasea\Core\CLIProvider\CLIDriversServiceProvider; use Alchemy\Phrasea\Core\CLIProvider\ComposerSetupServiceProvider; @@ -58,6 +59,7 @@ class CLI extends Application $this->register(new LessBuilderServiceProvider()); $this->register(new SignalHandlerServiceProvider()); $this->register(new TaskManagerServiceProvider()); + $this->register(new TranslationExtractorServiceProvider()); $this->bindRoutes(); } diff --git a/lib/Alchemy/Phrasea/Command/Developer/TranslationDumper.php b/lib/Alchemy/Phrasea/Command/Developer/TranslationDumper.php new file mode 100644 index 0000000000..42fdc59f48 --- /dev/null +++ b/lib/Alchemy/Phrasea/Command/Developer/TranslationDumper.php @@ -0,0 +1,51 @@ +setDescription('Dump translation files'); + } + + /** + * {@inheritdoc} + */ + protected function doExecute(InputInterface $input, OutputInterface $output) + { + $builder = new ConfigBuilder(); + $config = $builder->setLocale('fr') + ->setOutputFormat('po') + ->setTranslationsDir(__DIR__ . '/../../../../../translations') + ->setScanDirs(array( + $this->container['root.path'].'/lib', + $this->container['root.path'].'/templates', + $this->container['root.path'].'/bin', + $this->container['root.path'].'/www', + )) + ->getConfig(); + + $this->container['translation-extractor.updater']->process($config); +// var_dump($this->container['translation-extractor.updater']->getChangeSet($config)); + + return 0; + } +} diff --git a/lib/Alchemy/Phrasea/Command/Developer/Utils/ConstraintExtractor.php b/lib/Alchemy/Phrasea/Command/Developer/Utils/ConstraintExtractor.php new file mode 100644 index 0000000000..d4862916c7 --- /dev/null +++ b/lib/Alchemy/Phrasea/Command/Developer/Utils/ConstraintExtractor.php @@ -0,0 +1,83 @@ +app = $app; + $this->traverser = new \PHPParser_NodeTraverser(); + $this->traverser->addVisitor($this); + } + + public function enterNode(\PHPParser_Node $node) + { + if ($node instanceof \PHPParser_Node_Stmt_Namespace) { + $this->namespace = implode('\\', $node->name->parts); + + return; + } + + if (!$node instanceof \PHPParser_Node_Stmt_Class) { + return; + } + + $name = '' === $this->namespace ? $node->name : $this->namespace.'\\'.$node->name; + + if (!class_exists($name)) { + return; + } + + if (!is_a($name, 'Symfony\Component\Validator\Constraint', true)) { + return; + } + + $constraint = $name::create($this->app); + $ref = new \ReflectionClass($name); + + foreach ($this->messageProperties as $prop) { + if ($ref->hasProperty($prop)) { + $message = new Message($constraint->$prop, 'validators'); + $this->catalogue->add($message); + } + } + } + + public function visitPhpFile(\SplFileInfo $file, MessageCatalogue $catalogue, array $ast) + { + $this->file = $file; + $this->namespace = ''; + $this->catalogue = $catalogue; + $this->traverser->traverse($ast); + } + + public function beforeTraverse(array $nodes) { } + public function leaveNode(\PHPParser_Node $node) { } + public function afterTraverse(array $nodes) { } + public function visitFile(\SplFileInfo $file, MessageCatalogue $catalogue) { } + public function visitTwigFile(\SplFileInfo $file, MessageCatalogue $catalogue, \Twig_Node $ast) { } +} diff --git a/lib/Alchemy/Phrasea/Core/CLIProvider/TranslationExtractorServiceProvider.php b/lib/Alchemy/Phrasea/Core/CLIProvider/TranslationExtractorServiceProvider.php new file mode 100644 index 0000000000..4e884d865d --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/CLIProvider/TranslationExtractorServiceProvider.php @@ -0,0 +1,96 @@ +share(function (Application $app) { + return $app['monolog']; + }); + $app['translation-extractor.doc-parser'] = $app->share(function () { + $parser = new DocParser(); + $parser->addNamespace("JMS\TranslationBundle\Annotation"); + + return $parser; + }); + $app['translation-extractor.node-visitors'] = $app->share(function (Application $app) { + return array( + new ConstraintExtractor($app), + new ValidationExtractor($app['validator']->getMetadataFactory()), + new DefaultPhpFileExtractor($app['translation-extractor.doc-parser']), + new TwigFileExtractor($app['twig']), + new FormExtractor($app['translation-extractor.doc-parser']), + ); + }); + $app['translation-extractor.file-extractor'] = $app->share(function (Application $app) { + return new FileExtractor($app['twig'], $app['translation-extractor.logger'], $app['translation-extractor.node-visitors']); + }); + $app['translation-extractor.extractor-manager'] = $app->share(function (Application $app) { + return new ExtractorManager($app['translation-extractor.file-extractor'], $app['translation-extractor.logger']); + }); + + $app['translation-extractor.writer'] = $app->share(function (Application $app) { + return new FileWriter($app['translation-extractor.writers']); + }); + + $app['translation-extractor.writers'] = $app->share(function () { + return array('po' => new SymfonyDumperAdapter(new PoFileDumper(), 'po')); + }); + + $app['translation-extractor.loader-manager'] = $app->share(function (Application $app) { + return new LoaderManager($app['translation-extractor.loaders']); + }); + $app['translation-extractor.loaders'] = $app->share(function () { + return array( + 'po' => new SymfonyLoaderAdapter(new PoFileLoader()) + ); + }); + + $app['translation-extractor.updater'] = $app->share(function (Application $app) { + AnnotationRegistry::registerAutoloadNamespace('JMS\TranslationBundle\Annotation', $app['root.path'].'/vendor/jms/translation-bundle'); + + return new Updater($app['translation-extractor.loader-manager'], $app['translation-extractor.extractor-manager'], $app['translation-extractor.logger'], $app['translation-extractor.writer']); + }); + } + + public function boot(Application $app) + { + } +} diff --git a/templates/web/prod/index.html.twig b/templates/web/prod/index.html.twig index a4c0375ecb..6daef76b43 100644 --- a/templates/web/prod/index.html.twig +++ b/templates/web/prod/index.html.twig @@ -1008,7 +1008,7 @@