Convert your translation files using a Symfony command (English)

2013-09-10

  symfony    english    internationalization 

In this article, I will give you a Symfony command to convert translations files from a format to another.

This command can also be used to create translations files from csv files (for instance) that can be easily generated using a XLS format, often chosen by your customers to manage their translations.

The Symfony command

Here is the command I've written to do that. It used the Loader and Dumper classes for each format available in Symfony using the Translation component.


namespace Eko\\MiscBundle\\Command;

use Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand;

use Symfony\\Component\\Console\\Input\\InputOption;
use Symfony\\Component\\Console\\Input\\InputInterface;
use Symfony\\Component\\Console\\Output\\OutputInterface;
use Symfony\\Component\\Filesystem\\Filesystem;
use Symfony\\Component\\Finder\\Finder;
use Symfony\\Component\\Translation\\Writer\\TranslationWriter;

/**
 * Translation import command
 *
 * @author Vincent Composieux 
 */
class TranslationImportCommand extends ContainerAwareCommand
{
    /**
     * @var Finder
     */
    protected $finder;

    /**
     * @var Filesystem
     */
    protected $filesystem;

    /**
     * {@inheritDoc}
     */
    protected function configure()
    {
        parent::configure();

        $this
            ->setName('eko:translation:convert')
            ->setDescription('Translation convert command from an input format to another format')
            ->setHelp('You must specify a path using the --path option.')
            ->addOption('path', null, InputOption::VALUE_REQUIRED, 'Specify a path of files')
            ->addOption('input', null, InputOption::VALUE_REQUIRED, 'Specifiy a input translation format')
            ->addOption('output', null, InputOption::VALUE_OPTIONAL, 'Specifiy an output translation format (default: xliff)')
        ;
    }

    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $path = $input->getOption('path');
        $inputFormat = $input->getOption('input');
        $outputFormat = $input->getOption('output') ?: 'xliff';

        if (!$inputFormat) {
            throw new \\InvalidArgumentException('You must specify a --input format option.');
        }

        if (!$path || !$this->getFilesystem()->exists($path)) {
            throw new \\InvalidArgumentException('You must specify a valid --path option.');
        }

        $dumper = $this->getDumper($outputFormat);
        $this->getTranslationWriter()->addDumper($outputFormat, $dumper);

        $files = $this->getFinder()->files()->name('/[a-z]+\\.[a-z]{2}\\.[a-z]+/')->in($path);

        foreach ($files as $file) {
            list($domain, $language) = explode('.', $file->getFilename());

            $output->writeln(sprintf('Starts importing file %s', $file->getRealPath()));

            $file = $this->getLoader($inputFormat)->load($file->getRealPath(), $language);
            $messages = $file->all($domain);

            if (!$messages) {
                $output->writeln('No translations found in this file.');

                continue;
            }

            try {
                $this->getTranslationWriter()->writeTranslations($file, $outputFormat, array(
                    'path' => $this->getTranslationPath())
                );

                $output->writeln('? Translation file saved.');
            } catch (\\Exception $e) {
                $output->writeln(sprintf('An error has occured while trying to write translations: %s', $e->getMessage()));
            }
        }
    }

    /**
     * Returns Symfony Filesystem component
     *
     * @return Filesystem
     */
    protected function getFilesystem()
    {
        if (null === $this->filesystem) {
            $this->filesystem = new Filesystem();
        }

        return $this->filesystem;
    }

    /**
     * Returns Symfony Finder component
     *
     * @return Finder
     */
    protected function getFinder()
    {
        if (null === $this->finder) {
            $this->finder = new Finder();
        }

        return $this->finder;
    }

    /**
     * Returns Symfony translation writer service
     *
     * @return TranslationWriter
     */
    protected function getTranslationWriter()
    {
        return $this->getContainer()->get('translation.writer');
    }

    /**
     * Returns Symfony requested format loader
     *
     * @param string $format
     *
     * @return \\Symfony\\Component\\Translation\\Loader\\LoaderInterface
     *
     * @throws \\InvalidArgumentException
     */
    protected function getLoader($format)
    {
        $service = sprintf('translation.loader.%s', $format);

        if (!$this->getContainer()->has($service)) {
            throw new \\InvalidArgumentException(sprintf('Unable to find Symfony Translation loader for format "%s"', $format));
        }

        return $this->getContainer()->get($service);
    }

    /**
     * Returns Symfony requested format dumper
     *
     * @param string $format
     *
     * @return \\Symfony\\Component\\Translation\\Dumper\\DumperInterface
     *
     * @throws \\InvalidArgumentException
     */
    protected function getDumper($format)
    {
        $service = sprintf('translation.dumper.%s', $format);

        if (!$this->getContainer()->has($service)) {
            throw new \\InvalidArgumentException(sprintf('Unable to find Symfony Translation dumper for format "%s"', $format));
        }

        return $this->getContainer()->get($service);
    }

    /**
     * Returns translation path
     *
     * @return string
     */
    protected function getTranslationPath()
    {
        return $this->getContainer()->get('kernel')->getRootDir() . '/Resources/translations';
    }
}

This command quite simple to understand also uses the Finder and Filesystem components to search for files in the given path and check if it exists.

Let's have a look on how to use this command.

Prepare your files

You just need to create a directory with files named as follow: messages.[lang].csv, like that:


|-- var
|   |-- www
|       |-- translations
|   |       |-- messages.de.csv
|   |       |-- messages.en.csv
|   |       |-- messages.fr.csv

Run the command

Now, let's execute our Symfony command to convert our .csv translations files to .xliff files in our Symfony project:


$ php app/console eko:translation:convert --path=/var/www/translations --input=csv --output=xliff
Starts importing file /var/www/translations/messages.de.csv
→ Translation file saved.
Starts importing file /var/www/translations/messages.en.csv
→ Translation file saved.
Starts importing file /var/www/translations/messages.fr.csv
→ Translation file saved.

It's done. Your translations are now available in the app/Resources/translations Symfony directory in the output format requested.

Hope it can help you in your translations files management.

Comments