• Symfony >= v2.3

Use composer

composer require a2lix/i18n-doctrine-bundle

After the successful installation, add in your AppKernel.php file

// in AppKernel::registerBundles()
$bundles = array(
// ...
new A2lix\I18nDoctrineBundle\A2lixI18nDoctrineBundle(),
// ...
);

There is no minimal configuration. Full list:

# app/config/config.yml

# FIRST, update your Doctrine Configuration (http://symfony.com/doc/current/reference/configuration/doctrine.html) as :
doctrine:
    [...]
    orm:
        auto_generate_proxy_classes: %kernel.debug%
#        auto_mapping: true
        entity_managers:
            default:
                auto_mapping: true
                # New custom filter
                filters:
                    oneLocale:
                        class: A2lix\I18nDoctrineBundle\Doctrine\ORM\Filter\OneLocaleFilter
                        enabled: true

# SECOND, add:
a2lix_i18n_doctrine:
    manager_registry: doctrine       # [1]
  • [1] Default to doctrine
'doctrine' is the only one manager_registry available currently.

Example for an existing object Product

namespace Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Product
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column
     */
    private $title;

    /**
     * @ORM\Column(type="text")
     */
    private $description;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
        return $this;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setDescription($description)
    {
        $this->description = $description;
        return $this;
    }

    public function getDescription()
    {
        return $this->description;
    }
}

To internationalize this entity, we need to do some refactors.
First, we move fields, getters and setters concerned by translations into a new object, call the translation object: ObjectTranslation.
Our original object, relieved of some code, is call translatable object.

Our translatable object

namespace Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 */
class Product
{
    use \A2lix\I18nDoctrineBundle\Doctrine\ORM\Util\Translatable;

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @Assert\Valid
     */
    protected $translations;

    public function getId()
    {
        return $this->id;
    }
}
Note that 'use \A2lix\I18nDoctrineBundle\Doctrine\ORM\Util\Translatable;' is only possible if you have PHP5.4+, else you need to copy manually the code of this trait.

Our translation object

namespace Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class ProductTranslation implements \A2lix\I18nDoctrineBundle\Doctrine\Interfaces\OneLocaleInterface
{
    use \A2lix\I18nDoctrineBundle\Doctrine\ORM\Util\Translation;

    /**
     * @ORM\Column
     */
    private $title;

    /**
     * @ORM\Column(type="text")
     */
    private $description;

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
        return $this;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setDescription($description)
    {
        $this->description = $description;
        return $this;
    }

    public function getDescription()
    {
        return $this->description;
    }
}
Note that, here too, 'use \A2lix\I18nDoctrineBundle\Doctrine\ORM\Util\Translation;' is only possible if you have PHP5.4+, else you need to copy manually the code of this trait.

If you use PHP5.4+, you can use some traits provided in Doctrine/O*M/Util

During edition of your forms, you need to use the annotation 'I18nDoctrine'.

use A2lix\I18nDoctrineBundle\Annotation\I18nDoctrine;
...

/**
 * New/Edit object
 *
 * @Route("/new", defaults={"id"=""}, name="backend_product_new")
 * @Route("/{id}/edit", name="backend_product_edit")
 * @Template()
 * @I18nDoctrine
 */
public function editAction($id = null)
{
...

For minimize requests number, I advice to specify the join relation about your translations.

SELECT p, pTrans
    FROM A2lixDemoBundle:Product p
    JOIN p.translations pTrans