Skip to content

Instantly share code, notes, and snippets.

@kalmanolah
Created June 27, 2013 11:27
Show Gist options
  • Save kalmanolah/5875746 to your computer and use it in GitHub Desktop.
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.
<?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