Log translation messages to the database in Symfony 2

The Symfony 2 translation service does a fantastic job of translating messages using the file system and translation messages.locale.xliff files.  Gathering all of the i18n source phrases to hand off to a translator or translation service can be a major challenge, especially in a large, dynamic website.  The goal here is to provide a logging mechanism to add un-translated text to a database and allow translations to be previewed from the database only in the dev environment prior to building the .xliff files.

 

There are a few i18n tools available such as the Gedmo Translatable Doctrine extension and the Rosetta Symfony bundle and both have their place but neither allowed me to add un-translated text that came from a database call, a model or controller method, and static twig text.  Before exporting translations to .xliff files, I needed a way to preview translations using the database without affecting production performance.

 

My solution was to override the Symfony Translator in the dev environment only, which gives us access to the all of the source messages sent to the translator and allows us to translate messages from wherever we want, in my case a database.

 

A quick check of the Symfony/Bundle/FrameworkBundle/Resources/translation.xml file shows that the Translator class is defined as a parameter using the key translator.class, meaning it can be overridden by simply adding the following code to the app/config/parameters_dev.yml file.

# app/parameters_dev.yml

parameters:
    translator.class: SymfonyExtension\Bundle\FrameworkBundle\Translation\Translator

Note: if you want this to work in production or only in the beta environment, then add this to parameters.yml or parameters_beta.yml.

 

You can now write your own translator class, use the file path that was defined in the parameters file:
src/SymfonyExtension/Bundle/FrameworkBundle/Translation/Translator

 

To begin, define the Translator class as follows:

# src/SymfonyExtension/Bundle/FrameworkBundle/Translation/Translator

namespace SymfonyExtension\Bundle\FrameworkBundle\Translation;

use Symfony\Bundle\FrameworkBundle\Translation\Translator as BaseTranslator;

class Translator extends BaseTranslator {

    /**
     * trans
     *
     * @author Joe Sexton <joe@webtipblog.com>
     * {@inheritdoc}
     */
    public function trans( $id, array $parameters = array(), $domain = null, $locale = null )
    {
        return parent::trans( $id, $parameters, $domain, $locale );
    }

    /**
     * transChoice
     *
     * @author Joe Sexton <joe@webtipblog.com>
     * {@inheritdoc}
     */
    public function transChoice( $id, $number, array $parameters = array(), $domain = null, $locale = null )
    {
        return parent::transChoice( $id, $number, $parameters, $domain, $locale );
    }
}

 

You can choose to just log translations to the database and use the normal translator by updating the trans() method like this:

# src/SymfonyExtension/Bundle/FrameworkBundle/Translation/Translator

/**
 * trans
 *
 * @author Joe Sexton <joe@webtipblog.com>
 * {@inheritdoc}
 */
public function trans( $id, array $parameters = array(), $domain = null, $locale = null )
{

    if ( null === $locale )
        $locale = $this->getLocale();

    if ( null === $locale )
        $locale = $this->container->get( 'request' )->get( 'locale' );

    $em = $this->container->get( 'doctrine' )->getManager();

    // log translation
    $source = $em->getRepository( 'Entity:TranslationSource' )
                 ->findBySource( $id );
    if ( $source === null ) {
        $source = new TranslationSource();
        $source->setSource( $id );
        $em->persist( $source );
        $em->flush();
    }

    return parent::trans( $id, $parameters, $domain, $locale );
}

I’ve left the implementation of the entities and repositories up to you since it is specific to your project.

 

You can override the translator entirely so you can translate from the database; instead of returning parent::trans(), return the result of a database query.

# src/SymfonyExtension/Bundle/FrameworkBundle/Translation/Translator

/**
 * trans
 *
 * @author Joe Sexton <joe@webtipblog.com>
 * {@inheritdoc}
 */
public function trans( $id, array $parameters = array(), $domain = null, $locale = null )
{

    if ( null === $locale )
        $locale = $this->getLocale();

    if ( null === $locale )
        $locale = $this->container->get( 'request' )->get( 'locale' );

    $em = $this->container->get( 'doctrine' )->getManager();

    // log translation
    $source = $em->getRepository( 'Entity:TranslationSource' )
                 ->findBySource( $id );
    if ( $source === null ) {
        $source = new TranslationSource();
        $source->setSource( $id );
        $em->persist( $source );
        $em->flush();
    }

    // translate from database
    $target = $em->getRepository( 'Entity:TranslationTarget' )
                 ->findBySourceAndLocale( $id, $locale );
    if ( $target === null ) {
        $target = $id;
    }

    return strtr( $target, $parameters );

}

I’ve left the implementation of the entities and repositories up to you since it is specific to your project.

 

This solution is not only quick, but if used only in the dev environment it has no production performance impact.  You can choose to only log phrases to the database or override the translator entirely so all translations are done from the database.

One comment on “Extend Symfony 2 Translator to Log Untranslated Messages to a Database

  1. Krishna Ghodke says:

    Hi Joe Sexton,
    Nice article. Just gone through post “Extend Symfony 2 Translator to Log Untranslated Messages to a Database”.I was also implementing same thing. Everything working fine, but if we checked our SF profiler query count increased. Per page no of $id = no of queires with DB. i.e suppose there are 100 ids on on page then 100 queries will fire to DB? I think it`s not feasible solution. What will be your approach on this? Looking forward.
    Thanks.

Comments are closed.