Blog Posts

Multilanguage support for Doctrine PHPCR-ODM

Over the last weeks, Dan, Brian and myself worked on adding translation capabilities to Doctrine PHPCR-ODM. PHPCR-ODM is an object - document mapper for the php content repository (PHPCR). Thanks to the Liip Ecostar process, we got funding to do this during work time.

How does it work?

Using persistTranslation($document, $locale) or persist($document) with the @Locale annotation allows to store several copies of the "same" document in different languages. You update the document for the next language and then persist that translation too. Using find() to get a translated document, the LocaleChooserStrategy class will find the best (according to its implementation - might look at session locale, browser preferences, user account settings, ...) available translation for a document. And using findTranslation() you can explicitly specify which language you want.

There are two translation strategies available: Store translated fields in a separate namespace as properties of the same node, and namespaced child nodes per locale with the translated properties in them. You can add your own strategies with the DocumentManager::addTranslationStrategy() method. Translation always happens on a document level, not on individual fields to keep performance reasonable.

Our translation strategies use a namespace to avoid collisions with other attributes resp. child nodes. In order to use multilanguage, set up the console and run

php bin/phpcr doctrine:phpcr:register-system-node-types

.

When using findTranslation, the existing document instance is updated rather than a new one created. Otherwise we could get into non-deterministic situations when you update non-translated fields on two objects.

A complete example how using the translations looks like, using the default configured LocaleChooser:

use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;

/**
 * @PHPCRODM\Document(alias="translation_article", translator="attribute")
 */
class Article
{
    /** @PHPCRODM\Id */
    public $id;

    /**
     * The language this document currently is in
     * @PHPCRODM\Locale
     */
    public $locale = 'en';

    /**
     * Untranslated property
     * @PHPCRODM\Date
     */
    public $publishDate;

    /**
     * Translated property
     * @PHPCRODM\String(translated=true)
     */
    public $topic;

    /**
     * Language specific image
     * @PHPCRODM\Binary(translated=true)
     */
    public $image;
}


$localePrefs = array(
    'en' => array('en', 'fr'),
    'fr' => array('fr', 'en'),
);

$dm = new \Doctrine\ODM\PHPCR\DocumentManager($session, $config);
$dm->setLocaleChooserStrategy(new LocaleChooser($localePrefs, 'en'));

// then to use translations:

$doc = new Article();
$doc->id = '/my_test_node';
$doc->publishedDate = new \DateTime();
$doc->topic = 'An interesting subject';
$doc->image = fopen('english.jpg');

// Persist the document in English
$this->dm->persistTranslation($this->doc, 'en');

// Change the content and persist the document in French
$this->doc->topic = 'Un sujet intéressant';
$doc->image = fopen('english.jpg');
$this->dm->persistTranslation($this->doc, 'fr');

// Flush to write the changes to the phpcr backend
$this->dm->flush();

// Get the document in default language (English if you bootstrapped as in the example)
$doc = $this->dm->find('Doctrine\Tests\Models\Translation\Article', '/my_test_node');
echo $doc->topic;

// Get the document in French (updates the existing document)
$this->dm->find('Doctrine\Tests\Models\Translation\Article', '/my_test_node', 'fr');
echo $doc->topic;

What was added?

The complete overview is in the pull request. Just a summary:

  • New annotation options:
    • translator=attribute|child|your_own is a new attribute for the @Document annotation, to mark a document as being translated and specify how translations are to be stored
    • @Locale makes a class property hold the locale the document is currently translated to.
    • translated=true is a new attribute for all properties to mark the String, Number, Binary and so on as being translated.
  • TranslationStrategy is used for the translator attribute of the document. Default strategies for attribute and child exist.
  • LocaleChooserStrategy is used to determine what language matches best for doing language fallback, with the possibility to do language fallbacks based on your self defined logic. The default strategy is configurable with an ordered list of locales per requested locale
  • DocumentManager::persistTranslation($document, $locale) save document in that language
  • DocumentManager::findTranslation($className, $id, $locale) load a document in the specified locale (instead of the default one)
  • DocumentManager::getLocalesFor($document) (get the list of locales this document currently exists in)
Related Entries:
- Symfony CMF: what is left todo?
- Summer vacation in open source land
- Doctrine PHPCR-ODM now handles versioning
- There and back again
- News for the symfony2 cmf: Second PHPCR implementation, hackday announcement and PHPCR to become "official"

About the author


Find more about him on Twitter, Google+ and his personal site.

Comments [3]

Lukas, 09.02.2012 23:43 CEST

Just FYI we have since changed the API slightly.

Instead of "$this->dm->persistTranslation($this->doc, 'en')" one now needs to call "$this->dm->bindTranslation($this->doc, 'en')". Note that this method only works on a previously persisted document.

Norbert Haigermoser, 06.02.2013 11:31 CEST

I just tried the odm on a testproject. but i have a really strange problem with translations. if i add a document which has children - and then try to add a translation to the children documents - the translation will not be saved .... (i tried the same with the symfony cmf - same result ) - is it possible that there's still a bug ?

david, 06.02.2013 16:36 CEST

hi norbert, sure, it is quite possible we have still bugs. some of them we are currently working on:
https://github.com/doctrine/phpcr-odm/pulls

can you please open an issue on the doctrine phpcr-odm jira (sorry no github issues here) with the details of your code. what calls to phpcr-odm in what order. and how do your documents look, mapping?

if you manage to create a failing test that demoes the problem (if its not exactly the same as https://github.com/doctrine/phpcr-odm/pull/238) then please do a PR for it, that would help a lot to identify the problem.

then to work around the issue for the moment, maybe it helps to flush before you create the children, or to explicitly persist the children as well.

Add a comment

Your email adress will never be published. Comment spam will be deleted!