diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3cc0d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +composer.phar +composer.lock +vendor/ + diff --git a/Command/PhraseAppUpdateCommand.php b/Command/PhraseAppUpdateCommand.php new file mode 100644 index 0000000..28efdc5 --- /dev/null +++ b/Command/PhraseAppUpdateCommand.php @@ -0,0 +1,99 @@ +setName('phraseapp:update')->addOption('locale', null, InputOption::VALUE_REQUIRED); + + $this->addOptionValidator('locale', function ($value) { + if (null === $value) { + return; + } + $this->locales = array_map('trim', explode(',', $value)); + }); + } + + /** + * {@inheritdoc} + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + foreach ($input->getOptions() as $key => $option) { + if (array_key_exists($key, $this->validators)) { + call_user_func_array($this->validators[$key], [ + $input->getOption($key), + $input, + $output + ]); + } + } + } + + /** + * @param string $name + * @param callback $validator + * + * @throws \Exception + */ + protected function addOptionValidator($name, $validator) + { + if (!is_callable($validator)) { + throw new \Exception('Validator is not callable'); + } + + $this->validators[$name] = $validator; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $container = $this->getContainer(); + $this->phraseApp = $container->get('phrase_app.service'); + $this->availableLocales = $this->phraseApp->getLocales(); + + $unsupportedLocales = array_diff($this->locales, array_keys($this->availableLocales)); + if (count($unsupportedLocales)) { + throw new RuntimeException(sprintf('Unsupported locales "%s"', implode(', ', $unsupportedLocales))); + } + if (0 === count($this->locales)) { + $this->locales = array_keys($this->availableLocales); + } + + // fetch and save translations + $this->phraseApp->process($this->locales); + } +} \ No newline at end of file diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php new file mode 100644 index 0000000..58d462e --- /dev/null +++ b/DependencyInjection/Configuration.php @@ -0,0 +1,45 @@ +root('phrase_app'); + + $rootNode + ->children() + ->scalarNode('token')->isRequired()->end() + ->scalarNode('project_id')->isRequired()->end() + ->scalarNode('output_format') + ->defaultValue('yml') + ->end() + ->scalarNode('translations_path')->isRequired()->end() + ->arrayNode('translations') + ->prototype('scalar')->end() + ->end() + ->arrayNode('locales') + ->prototype('scalar')->end() + ->end() + ; + + // Here you should define the parameters that are allowed to + // configure your bundle. See the documentation linked above for + // more information on that topic. + + return $treeBuilder; + } +} diff --git a/DependencyInjection/PhraseAppExtension.php b/DependencyInjection/PhraseAppExtension.php new file mode 100644 index 0000000..3021701 --- /dev/null +++ b/DependencyInjection/PhraseAppExtension.php @@ -0,0 +1,31 @@ +processConfiguration($configuration, $configs); + + $container->setParameter('phrase_app.token', $config['token']); + $container->setParameter('phrase_app.config', $config); + + $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('phraseapp-services.xml'); + } +} diff --git a/PhraseAppBundle.php b/PhraseAppBundle.php new file mode 100644 index 0000000..93dd502 --- /dev/null +++ b/PhraseAppBundle.php @@ -0,0 +1,9 @@ + + + + + + + %phrase_app.token% + + + + + + + %phrase_app.config% + + + + diff --git a/Service/PhraseApp.php b/Service/PhraseApp.php new file mode 100644 index 0000000..a98b18f --- /dev/null +++ b/Service/PhraseApp.php @@ -0,0 +1,200 @@ +client = $client; + $this->translationLoader = $translationLoader; + $this->translationWriter = $translationWriter; + $this->projectId = $config['project_id']; + $this->locales = $config['locales']; + $this->outputFormat = $config['output_format']; + $this->translationsPath = $config['translations_path']; + } + + /** + * Set output format + * + * @param string $outputFormat + * + * @return PhraseApp + */ + public function setOutputFormat($outputFormat) + { + $this->outputFormat = $outputFormat; + + return $this; + } + + /** + * Get Locales + * @return array + */ + public function getLocales() + { + return $this->locales; + } + + /** + * @param $locale + * @param $format + * + * @return array|string + */ + protected function makeDownloadRequest($locale, $format) + { + $response = $this->client->request('locale.download', [ + 'project_id' => $this->projectId, + 'id' => $locale, + 'file_format' => $format, + ]); + + return $response['text']->getContents(); + } + + /** + * @param string $targetLocale + * + * @return string + */ + protected function downloadLocale($targetLocale) + { + $sourceLocale = $this->locales[$targetLocale]; + $tmpFile = $this->getTmpPath() . '/' . 'messages.' . $targetLocale . '.yml'; + + if (true === array_key_exists($sourceLocale, $this->downloadedLocales)) { + // Make copy because operated catalogues must belong to the same locale + copy($this->downloadedLocales[$sourceLocale], $tmpFile); + + return $this->downloadedLocales[$sourceLocale]; + } + + $phraseAppMessage = $this->makeDownloadRequest($sourceLocale, 'yml_symfony2'); + file_put_contents($tmpFile, $phraseAppMessage); + $this->downloadedLocales[$sourceLocale] = $tmpFile; + + return $this->downloadedLocales[$sourceLocale]; + } + + /** + * @param string $targetLocale + */ + protected function dumpMessages($targetLocale) + { + // load downloaded messages + $extractedCatalogue = new MessageCatalogue($targetLocale); + $this->translationLoader->loadMessages($this->getTmpPath(), $extractedCatalogue); + + // load any existing messages from the translation files + $currentCatalogue = new MessageCatalogue($targetLocale); + $this->translationLoader->loadMessages($this->translationsPath, $currentCatalogue); + + $operation = new DiffOperation($currentCatalogue, $extractedCatalogue); + + // Exit if no messages found. + if (0 === count($operation->getDomains())) { + return; + } + + $this->translationWriter->writeTranslations($operation->getResult(), $this->outputFormat, ['path' => $this->translationsPath]); + } + + /** + * @param array $locales + */ + public function process(array $locales) + { + foreach ($locales as $locale) + { + $this->downloadLocale($locale); + $this->dumpMessages($locale); + } + //TODO: clean downloaded files + } + + /** + * Get TmpPath + * @return string + */ + protected function getTmpPath() + { + if (null === $this->tmpPath) { + $this->tmpPath = $this->generateTmpPath(); + } + + return $this->tmpPath; + } + + /** + * @return string + */ + protected function generateTmpPath() + { + $tmpPath = sys_get_temp_dir() . '/' . uniqid('translation', false); + if (!is_dir($tmpPath) && false === @mkdir($tmpPath, 0777, true)) { + throw new RuntimeException(sprintf('Could not create temporary directory "%s".', $tmpPath)); + } + + return $tmpPath; + } +} \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d85ce76 --- /dev/null +++ b/composer.json @@ -0,0 +1,18 @@ +{ + "name": "nediam/phraseapp-bundle", + "minimum-stability": "dev", + "authors": [ + { + "name": "Bartłomiej Kuleszewicz", + "email": "nediam@staramilosna.info" + } + ], + "autoload": { + "psr-4": { + "nediam\\PhraseAppBundle\\": "" + } + }, + "require": { + "nediam/phraseapp-lib": "dev-master" + } +} \ No newline at end of file