Created
June 27, 2013 11:27
-
-
Save kalmanolah/5875746 to your computer and use it in GitHub Desktop.
Symfony2 console command for merging translation files from multiple bundles and copying the merged files over to the app/Resources/translations directory, all the while preserving any existing key/value pairs of any existing translation files in the app/Resources/translations directory. This is effectively safe(r) versioning.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace KalmanOlah\ExampleBundle\Command; | |
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; | |
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\Yaml\Parser; | |
use Symfony\Component\Yaml\Dumper; | |
/** | |
* Generic and extremely basic Symfony console command that merges translation files in src/<bundle>/Resources/translations/ directories | |
* with translation files in the app/Resources/translations/ directory. | |
* | |
* Expected behaviour: | |
* 1 - You have some translation files in src/<bundle>/Resources/translations/, but no translation files in app/Resources/translations/: | |
* ---- This command will create a copy of translation files in src/<bundle>/Resources/translations/ in app/Resources/translations/. | |
* Having translation files with identical filenames in multiple bundle folders will result in a merged copy, with duplicate key/value | |
* pairs being overwritten by one another. | |
* | |
* 2 - You have some translation files in src/<bundle>/Resources/translations/ as well as translation files in app/Resources/translations/: | |
* ---- This command will merge the translation files in app/Resources/translations/ with a copy of translation files in | |
* src/<bundle>/Resources/translations/. Key/value pairs from the translation files in app/Resources/translations/ will have priority, | |
* so no edited translations should be lost. The behaviour from (1) still applies. | |
* | |
* @author Kalman Olah <hello _AT_ kalmanolah _DOT_ net> | |
*/ | |
class TranslationsMergeCommand extends ContainerAwareCommand | |
{ | |
private $container; | |
private $input; | |
private $output; | |
private $yaml_parser; | |
private $yaml_dumper; | |
private $source_only; | |
private $base_dir; | |
private $source_dir; | |
private $vendor_dir; | |
private $target_dir; | |
private $translations_dir; | |
private $cache; | |
private $cache_merged; | |
const LN_INFO = 'info'; | |
const LN_ERROR = 'error'; | |
const LN_COMMENT = 'comment'; | |
const LN_QUESTION = 'question'; | |
const DS = DIRECTORY_SEPARATOR; | |
const YAML_INLINE = 10; | |
protected function configure() | |
{ | |
$this | |
->setName('translations:merge') | |
->setDescription('Merges translation files in the src/<bundle>/Resources/translations/ folder with translation files in the app/Resources/translations/ folder') | |
->addOption( | |
'source-only', | |
null, | |
InputOption::VALUE_OPTIONAL, | |
'Should this command only look for bundles in the src/ folder? [TRUE/false]', | |
true | |
); | |
} | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$this->container = $this->getContainer(); | |
$this->input = $input; | |
$this->output = $output; | |
$this->yaml_parser = new Parser(); | |
$this->yaml_dumper = new Dumper(); | |
$this->source_only = $this->getOption('source-only') == 'true'; | |
$this->base_dir = $this->container->get('kernel')->getRootDir().self::DS.'..'.self::DS; | |
$this->source_dir = $this->base_dir.'src'.self::DS; | |
$this->vendor_dir = $this->base_dir.'vendor'.self::DS; | |
$this->target_dir = $this->base_dir.'app'.self::DS; | |
$this->translations_dir = 'Resources'.self::DS.'translations'.self::DS; | |
$this->cache = array(); | |
$this->cache_merged = array(); | |
$this->performCommand(); | |
} | |
private function println($string = '', $style = null, $indent = false) { | |
if ($indent) { | |
if (is_bool($indent)) { | |
$indent = 4; | |
} | |
$indenting = ''; | |
while ($indent > 0) { | |
$indenting .= ' '; | |
$indent--; | |
} | |
$string = $indenting.$string; | |
} | |
if ($style != null) { | |
$string = '<'.$style.'>'.$string.'</'.$style.'>'; | |
} | |
$this->output->writeln($string); | |
} | |
private function getArgument($name) { | |
return $this->input->getArgument($name); | |
} | |
private function getOption($name) { | |
return $this->input->getOption($name); | |
} | |
private function performCommand() { | |
$this->println(); | |
$this->println('Beginning the merging of translation files..', self::LN_INFO); | |
$this->println(); | |
$this->println('Searching for bundles..', self::LN_COMMENT, 2); | |
$bundles = $this->getBundles(); | |
$this->println('Found <comment>'.count($bundles).'</comment> bundle(s). Attempting to loop..', null, 2); | |
$this->println(); | |
if (count($bundles) == 0) { | |
$this->println('No bundles were found, not looping.', null, 4); | |
$this->println(); | |
} else { | |
foreach($bundles as $bundle) { | |
if ($this->source_only && !$bundle[1]) { | |
$this->println('Skipping bundle <question>'.$bundle[0].'</question> since it\'s not part of the source folder.', null, 4); | |
$this->println(); | |
} else { | |
$this->println('Looking for translation files in bundle <comment>'.$bundle[0].'</comment>..', self::LN_INFO, 4); | |
$bundle_trans_dir = $bundle[0].$this->translations_dir; | |
$this->println('Searching in <comment>'.$bundle_trans_dir.'</comment>..', null, 4); | |
$bundle_trans_path = $bundle[1] ? $this->source_dir : $this->vendor_dir; | |
$bundle_trans_path .= $bundle_trans_dir; | |
if (!is_dir($bundle_trans_path)) { | |
$this->println('The directory does not exist. Skipping.', null, 4); | |
$this->println(); | |
} else { | |
$translations = $this->getTranslationFiles($bundle_trans_path); | |
$this->println('Found <comment>'.count($translations).'</comment> translation file(s). Attempting to loop..', null, 4); | |
$this->println(); | |
if (count($translations) == 0) { | |
$this->println('No translation files were found, not looping.', null, 6); | |
$this->println(); | |
} else { | |
foreach($translations as $translation) { | |
$this->cacheTranslationFile($bundle[0], $bundle_trans_path, $translation); | |
$this->println('Added parsed content of <info>'.$translation.'</info> to cache.', null, 6); | |
} | |
$this->println(); | |
} | |
} | |
} | |
} | |
$this->println('Attempting to merge cached translation file content..', self::LN_COMMENT, 2); | |
$count = $this->getCachedTranslationFileCount(); | |
if ($count == 0) { | |
$this->println('No cached translation files found. Skipping.', null, 2); | |
$this->println(); | |
} else { | |
$this->println('Merging <comment>'.$count.'</comment> translation file(s)..', null, 2); | |
$this->println(); | |
foreach($this->cache as $file => $variants) { | |
$this->mergeAndCacheTranslationFiles($file, array_values($variants)); | |
$this->println('Successfully merged <info>'.count($variants).'</info> variant(s) of <info>'.$file.'</info>.', null, 4); | |
} | |
$this->println(); | |
} | |
$this->println('Attempting to write merged translation files..', self::LN_COMMENT, 2); | |
$count = count($this->cache_merged); | |
if ($count == 0) { | |
$this->println('No merged translation files found. Skipping.', null, 2); | |
$this->println(); | |
} else { | |
$this->println('Writing <comment>'.$count.'</comment> merged translation file(s)..', null, 2); | |
$write_path = $this->target_dir.$this->translations_dir; | |
$this->println('Writing to <comment>'.$write_path.'</comment>..', null, 2); | |
if (!is_dir($write_path)) { | |
$this->println('The directory does not exist. Attempting to create it.', null, 2); | |
mkdir($write_path, 0777, true); | |
} | |
$this->println(); | |
foreach($this->cache_merged as $file => $content) { | |
$this->println('Trying to write <info>'.$file.'</info>..', null, 4); | |
if (file_exists($write_path.$file)) { | |
$this->println('The output file already exists. Merging contents..', null, 6); | |
$original_content = $this->getParsedTranslationFileContent($write_path, $file); | |
$this->mergeAndCacheTranslationFiles($file, array($content, $original_content)); | |
} | |
$this->writeMergedTranslationFile($write_path, $file); | |
$this->println('Done!', self::LN_INFO, 6); | |
} | |
$this->println(); | |
} | |
} | |
$this->println('Merging of translation files was completed successfully.', self::LN_INFO); | |
$this->println(); | |
} | |
private function getBundles() { | |
$return = array(); | |
$bundles = $this->container->getParameter('kernel.bundles'); | |
foreach($bundles as $bundle) { | |
$bundle_split = explode('\\', $bundle); | |
$bundle_file = array_values($bundle_split); | |
$bundle_file = end($bundle_file).'.php'; | |
$bundle_path = implode(self::DS, array_slice($bundle_split, 0, -1)).self::DS; | |
$bundle_in_source = file_exists($this->source_dir.$bundle_path.$bundle_file); | |
array_push($return, array($bundle_path, $bundle_in_source)); | |
} | |
return $return; | |
} | |
private function getTranslationFiles($path) { | |
$return = array(); | |
if ($handle = opendir($path)) { | |
while (($file = readdir($handle)) !== false) { | |
// We're only going to read files ending in .yml | |
if (preg_match('/.*\.yml$/', $file)) { | |
array_push($return, $file); | |
} | |
} | |
closedir($handle); | |
} | |
return $return; | |
} | |
private function getCachedTranslationFileCount() { | |
$count = 0; | |
foreach($this->cache as $tmp) { | |
foreach($tmp as $tmp_tmp) { | |
$count++; | |
} | |
} | |
return $count; | |
} | |
private function getParsedTranslationFileContent($path, $file) { | |
$content = file_get_contents($path.$file); | |
$content_parsed = $this->yaml_parser->parse($content); | |
return $content_parsed; | |
} | |
private function cacheTranslationFile($bundle, $path, $file) { | |
if (!array_key_exists($file, $this->cache)) { | |
$this->cache[$file] = array(); | |
} | |
$content_parsed = $this->getParsedTranslationFileContent($path, $file); | |
$this->cache[$file][$bundle] = $content_parsed; | |
} | |
private function mergeAndCacheTranslationFiles($file, $variants) { | |
$merged = array(); | |
for($i = 0; $i < count($variants); $i++) { | |
$merged = $this->mergeDistinctlyAndRecursively($merged, $variants[$i]); | |
} | |
$this->cache_merged[$file] = $merged; | |
} | |
private function mergeDistinctlyAndRecursively(&$array1, &$array2) { | |
$return = $array1; | |
foreach($array2 as $key => &$value) { | |
if (is_array ($value) && isset($return[$key]) && is_array($return[$key])) { | |
$return[$key] = $this->mergeDistinctlyAndRecursively($return[$key], $value); | |
} else { | |
$return[$key] = $value; | |
} | |
} | |
return $return; | |
} | |
private function writeMergedTranslationFile($path, $file) { | |
$content = $this->cache_merged[$file]; | |
$content_dumped = $this->yaml_dumper->dump($content, self::YAML_INLINE); | |
file_put_contents($path.$file, $content_dumped); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment