init without trunk
This commit is contained in:
parent
ed24ac4994
commit
bb809e7233
14652 changed files with 177862 additions and 94817 deletions
894
vendor/gedmo/doctrine-extensions/doc/translatable.md
vendored
Normal file
894
vendor/gedmo/doctrine-extensions/doc/translatable.md
vendored
Normal file
|
|
@ -0,0 +1,894 @@
|
|||
# Translatable behavior extension for Doctrine 2
|
||||
|
||||
**Translatable** behavior offers a very handy solution for translating specific record fields
|
||||
in different languages. Further more, it loads the translations automatically for a locale
|
||||
currently used, which can be set to **Translatable Listener** on it`s initialization or later
|
||||
for other cases through the **Entity** itself
|
||||
|
||||
Features:
|
||||
|
||||
- Automatic storage of translations in database
|
||||
- ORM and ODM support using same listener
|
||||
- Automatic translation of Entity or Document fields when loaded
|
||||
- ORM query can use **hint** to translate all records without issuing additional queries
|
||||
- Can be nested with other behaviors
|
||||
- Annotation, Yaml and Xml mapping support for extensions
|
||||
|
||||
**2012-01-28**
|
||||
|
||||
- Created personal translation which maps through real foreign key
|
||||
constraint. This dramatically improves the management of translations
|
||||
|
||||
**2012-01-04**
|
||||
|
||||
- Refactored translatable to be able to persist, update many translations
|
||||
using repository, [issue #224](https://github.com/Atlantic18/DoctrineExtensions/issues/224)
|
||||
|
||||
**2011-12-11**
|
||||
|
||||
- Added more useful translation query hints: Override translatable locale, inner join
|
||||
translations instead left join, override translation fallback
|
||||
|
||||
**2011-11-08**
|
||||
|
||||
- Thanks to [@acasademont](https://github.com/acasademont) Translatable now does not store translations for default locale. It is always left as original record value.
|
||||
So be sure you do not change your default locale per project or per data migration. This way
|
||||
it is more rational and unnecessary to store it additionally in translation table.
|
||||
|
||||
Update **2011-04-21**
|
||||
|
||||
- Implemented multiple translation persistence through repository
|
||||
|
||||
Update **2011-04-16**
|
||||
|
||||
- Made an ORM query **hint** to hook into any select type query, which will join the translations
|
||||
and let you **filter, order or search** by translated fields directly. It also will translate
|
||||
all selected **collections or simple components** without issuing additional queries. It also
|
||||
supports translation fallbacks
|
||||
- For performance reasons, translation fallbacks are disabled by default
|
||||
|
||||
Update **2011-04-04**
|
||||
|
||||
- Made single listener, one instance can be used for any object manager
|
||||
and any number of them
|
||||
|
||||
**Note list:**
|
||||
|
||||
- Public [Translatable repository](http://github.com/Atlantic18/DoctrineExtensions "Translatable extension on Github") is available on github
|
||||
- Using other extensions on the same Entity fields may result in unexpected way
|
||||
- May impact your application performance since it does an additional query for translation if loaded without query hint
|
||||
- Last update date: **2012-02-15**
|
||||
|
||||
**Portability:**
|
||||
|
||||
- **Translatable** is now available as [Bundle](http://github.com/stof/StofDoctrineExtensionsBundle)
|
||||
ported to **Symfony2** by **Christophe Coevoet**, together with all other extensions
|
||||
|
||||
This article will cover the basic installation and functionality of **Translatable** behavior
|
||||
|
||||
Content:
|
||||
|
||||
- [Including](#including-extension) the extension
|
||||
- Entity [example](#entity-domain-object)
|
||||
- Document [example](#document-domain-object)
|
||||
- [Yaml](#yaml-mapping) mapping example
|
||||
- [Xml](#xml-mapping) mapping example
|
||||
- Basic usage [examples](#basic-examples)
|
||||
- [Persisting](#multi-translations) multiple translations
|
||||
- Using ORM query [hint](#orm-query-hint)
|
||||
- Advanced usage [examples](#advanced-examples)
|
||||
- Personal [translations](#personal-translations)
|
||||
|
||||
<a name="including-extension"></a>
|
||||
|
||||
## Setup and autoloading
|
||||
|
||||
Read the [documentation](http://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/annotations.md#em-setup)
|
||||
or check the [example code](http://github.com/Atlantic18/DoctrineExtensions/tree/master/example)
|
||||
on how to setup and use the extensions in most optimized way.
|
||||
|
||||
### Translatable annotations:
|
||||
- **@Gedmo\Mapping\Annotation\Translatable** it will **translate** this field
|
||||
- **@Gedmo\Mapping\Annotation\TranslationEntity(class="my\class")** it will use this class to store **translations** generated
|
||||
- **@Gedmo\Mapping\Annotation\Locale or @Gedmo\Mapping\Annotation\Language** this will identify this column as **locale** or **language**
|
||||
used to override the global locale
|
||||
|
||||
<a name="entity-domain-object"></a>
|
||||
|
||||
## Translatable Entity example:
|
||||
|
||||
**Note:** that Translatable interface is not necessary, except in cases where
|
||||
you need to identify an entity as being Translatable. The metadata is loaded only once when
|
||||
cache is activated
|
||||
|
||||
``` php
|
||||
<?php
|
||||
namespace Entity;
|
||||
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Translatable\Translatable;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="articles")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Article implements Translatable
|
||||
{
|
||||
/** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @Gedmo\Translatable
|
||||
* @ORM\Column(name="title", type="string", length=128)
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* @Gedmo\Translatable
|
||||
* @ORM\Column(name="content", type="text")
|
||||
*/
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @Gedmo\Locale
|
||||
* Used locale to override Translation listener`s locale
|
||||
* this is not a mapped field of entity metadata, just a simple property
|
||||
*/
|
||||
private $locale;
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function setTranslatableLocale($locale)
|
||||
{
|
||||
$this->locale = $locale;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="document-domain-object"></a>
|
||||
|
||||
## Translatable Document example:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
namespace Document;
|
||||
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
|
||||
use Gedmo\Translatable\Translatable;
|
||||
|
||||
/**
|
||||
* @ODM\Document(collection="articles")
|
||||
*/
|
||||
class Article implements Translatable
|
||||
{
|
||||
/** @ODM\Id */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @Gedmo\Translatable
|
||||
* @ODM\Field(type="string")
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* @Gedmo\Translatable
|
||||
* @ODM\Field(type="string")
|
||||
*/
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @Gedmo\Locale
|
||||
* Used locale to override Translation listener`s locale
|
||||
* this is not a mapped field of entity metadata, just a simple property
|
||||
*/
|
||||
private $locale;
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function setTranslatableLocale($locale)
|
||||
{
|
||||
$this->locale = $locale;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="yaml-mapping"></a>
|
||||
|
||||
## Yaml mapping example
|
||||
|
||||
Yaml mapped Article: **/mapping/yaml/Entity.Article.dcm.yml**
|
||||
|
||||
```
|
||||
---
|
||||
Entity\Article:
|
||||
type: entity
|
||||
table: articles
|
||||
gedmo:
|
||||
translation:
|
||||
locale: localeField
|
||||
# using specific personal translation class:
|
||||
# entity: Translatable\Fixture\CategoryTranslation
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
title:
|
||||
type: string
|
||||
length: 64
|
||||
gedmo:
|
||||
- translatable
|
||||
content:
|
||||
type: text
|
||||
gedmo:
|
||||
- translatable
|
||||
```
|
||||
|
||||
<a name="xml-mapping"></a>
|
||||
|
||||
## Xml mapping example
|
||||
|
||||
``` xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">
|
||||
|
||||
<entity name="Mapping\Fixture\Xml\Translatable" table="translatables">
|
||||
|
||||
<id name="id" type="integer" column="id">
|
||||
<generator strategy="AUTO"/>
|
||||
</id>
|
||||
|
||||
<field name="title" type="string" length="128">
|
||||
<gedmo:translatable/>
|
||||
</field>
|
||||
<field name="content" type="text">
|
||||
<gedmo:translatable/>
|
||||
</field>
|
||||
|
||||
<gedmo:translation entity="Gedmo\Translatable\Entity\Translation" locale="locale"/>
|
||||
|
||||
</entity>
|
||||
|
||||
</doctrine-mapping>
|
||||
```
|
||||
|
||||
<a name="basic-examples"></a>
|
||||
|
||||
## Basic usage examples:
|
||||
|
||||
Currently a global locale used for translations is "en_us" which was
|
||||
set in **TranslationListener** globally. To save article with its translations:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$article = new Entity\Article;
|
||||
$article->setTitle('my title in en');
|
||||
$article->setContent('my content in en');
|
||||
$em->persist($article);
|
||||
$em->flush();
|
||||
```
|
||||
|
||||
This inserted an article and inserted the translations for it in "en_us" locale
|
||||
only if **en_us** is not the [default locale](#advanced-examples) in case if default locale
|
||||
matches current locale - it uses original record value as translation
|
||||
|
||||
Now lets update our article in different locale:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
// first load the article
|
||||
$article = $em->find('Entity\Article', 1 /*article id*/);
|
||||
$article->setTitle('my title in de');
|
||||
$article->setContent('my content in de');
|
||||
$article->setTranslatableLocale('de_de'); // change locale
|
||||
$em->persist($article);
|
||||
$em->flush();
|
||||
```
|
||||
|
||||
This updated an article and inserted the translations for it in "de_de" locale
|
||||
To see and load all translations of **Translatable** Entity:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
// reload in different language
|
||||
$article = $em->find('Entity\Article', 1 /*article id*/);
|
||||
$article->setLocale('ru_ru');
|
||||
$em->refresh($article);
|
||||
|
||||
$article = $em->find('Entity\Article', 1 /*article id*/);
|
||||
$repository = $em->getRepository('Gedmo\Translatable\Entity\Translation');
|
||||
$translations = $repository->findTranslations($article);
|
||||
/* $translations contains:
|
||||
Array (
|
||||
[de_de] => Array
|
||||
(
|
||||
[title] => my title in de
|
||||
[content] => my content in de
|
||||
)
|
||||
|
||||
[en_us] => Array
|
||||
(
|
||||
[title] => my title in en
|
||||
[content] => my content in en
|
||||
)
|
||||
)*/
|
||||
```
|
||||
|
||||
As far as our global locale is now "en_us" and updated article has "de_de" values.
|
||||
Lets try to load it and it should be translated in English
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$article = $em->getRepository('Entity\Article')->find(1/* id of article */);
|
||||
echo $article->getTitle();
|
||||
// prints: "my title in en"
|
||||
echo $article->getContent();
|
||||
// prints: "my content in en"
|
||||
```
|
||||
|
||||
<a name="multi-translations"></a>
|
||||
|
||||
## Persisting multiple translations
|
||||
|
||||
Usually it is more convenient to persist more translations when creating
|
||||
or updating a record. **Translatable** allows to do that through translation repository.
|
||||
All additional translations will be tracked by listener and when the flush will be executed,
|
||||
it will update or persist all additional translations.
|
||||
|
||||
**Note:** these translations will not be processed as ordinary fields of your object,
|
||||
in case if you translate a **slug** additional translation will not know how to generate
|
||||
the slug, so the value as an additional translation should be processed when creating it.
|
||||
|
||||
### Example of multiple translations:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
// persisting multiple translations, assume default locale is EN
|
||||
$repository = $em->getRepository('Gedmo\\Translatable\\Entity\\Translation');
|
||||
// it works for ODM also
|
||||
$article = new Article;
|
||||
$article->setTitle('My article en');
|
||||
$article->setContent('content en');
|
||||
|
||||
$repository->translate($article, 'title', 'de', 'my article de')
|
||||
->translate($article, 'content', 'de', 'content de')
|
||||
->translate($article, 'title', 'ru', 'my article ru')
|
||||
->translate($article, 'content', 'ru', 'content ru')
|
||||
;
|
||||
|
||||
$em->persist($article);
|
||||
$em->flush();
|
||||
|
||||
// updating same article also having one new translation
|
||||
|
||||
$repo
|
||||
->translate($article, 'title', 'lt', 'title lt')
|
||||
->translate($article, 'content', 'lt', 'content lt')
|
||||
->translate($article, 'title', 'ru', 'title ru change')
|
||||
->translate($article, 'content', 'ru', 'content ru change')
|
||||
->translate($article, 'title', 'en', 'title en (default locale) update')
|
||||
->translate($article, 'content', 'en', 'content en (default locale) update')
|
||||
;
|
||||
$em->flush();
|
||||
```
|
||||
|
||||
<a name="orm-query-hint"></a>
|
||||
|
||||
## Using ORM query hint
|
||||
|
||||
By default, behind the scenes, when you load a record - translatable hooks into **postLoad**
|
||||
event and issues additional query to translate all fields. Imagine that, when you load a collection,
|
||||
it may issue a lot of queries just to translate those fields. Including array hydration,
|
||||
it is not possible to hook any **postLoad** event since it is not an
|
||||
entity being hydrated. These are the main reasons why **TranslationWalker** was created.
|
||||
|
||||
**TranslationWalker** uses a query **hint** to hook into any **select type query**,
|
||||
and when you execute the query, no matter which hydration method you use, it automatically
|
||||
joins the translations for all fields, so you could use ordering filtering or whatever you
|
||||
want on **translated fields** instead of original record fields.
|
||||
|
||||
And in result there is only one query for all this happiness.
|
||||
|
||||
If you use translation [fallbacks](#advanced-examples) it will be also in the same single
|
||||
query and during the hydration process it will replace the empty fields in case if they
|
||||
do not have a translation in currently used locale.
|
||||
|
||||
Now enough talking, here is an example:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$dql = <<<___SQL
|
||||
SELECT a, c, u
|
||||
FROM Article a
|
||||
LEFT JOIN a.comments c
|
||||
JOIN c.author u
|
||||
WHERE a.title LIKE '%translated_title%'
|
||||
ORDER BY a.title
|
||||
___SQL;
|
||||
|
||||
$query = $em->createQuery($dql);
|
||||
// set the translation query hint
|
||||
$query->setHint(
|
||||
\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER,
|
||||
'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
|
||||
);
|
||||
|
||||
$articles = $query->getResult(); // object hydration
|
||||
$articles = $query->getArrayResult(); // array hydration
|
||||
```
|
||||
|
||||
And even a subselect:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$dql = <<<___SQL
|
||||
SELECT a, c, u
|
||||
FROM Article a
|
||||
LEFT JOIN a.comments c
|
||||
JOIN c.author u
|
||||
WHERE a.id IN (
|
||||
SELECT a2.id
|
||||
FROM Article a2
|
||||
WHERE a2.title LIKE '%something_translated%'
|
||||
AND a2.status = 1
|
||||
)
|
||||
ORDER BY a.title
|
||||
___SQL;
|
||||
|
||||
$query = $em->createQuery($dql);
|
||||
$query->setHint(
|
||||
\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER,
|
||||
'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
|
||||
);
|
||||
```
|
||||
|
||||
**NOTE:** if you use memcache or apc. You should set locale and other options like fallbacks
|
||||
to query through hints. Otherwise the query will be cached with a first used locale
|
||||
|
||||
``` php
|
||||
<?php
|
||||
// locale
|
||||
$query->setHint(
|
||||
\Gedmo\Translatable\TranslatableListener::HINT_TRANSLATABLE_LOCALE,
|
||||
'en' // take locale from session or request etc.
|
||||
);
|
||||
// fallback
|
||||
$query->setHint(
|
||||
\Gedmo\Translatable\TranslatableListener::HINT_FALLBACK,
|
||||
1 // fallback to default values in case if record is not translated
|
||||
);
|
||||
|
||||
$articles = $query->getResult(); // object hydration
|
||||
```
|
||||
|
||||
There's no need for any words anymore.. right?
|
||||
I recommend you to use it extensively since it is a way better performance, even in
|
||||
cases where you need to load single translated entity.
|
||||
|
||||
**Note**: Even in **COUNT** select statements translations are joined to leave a
|
||||
possibility to filter by translated field, if you do not need it, just do not set
|
||||
the **hint**. Also take into account that it is not possible to translate components
|
||||
in **JOIN WITH** statement, example
|
||||
|
||||
```
|
||||
JOIN a.comments c WITH c.message LIKE '%will_not_be_translated%'`
|
||||
```
|
||||
|
||||
**Note**: any **find** related method calls cannot hook this hint automagically, we
|
||||
will use a different approach when **persister overriding feature** will be
|
||||
available in **Doctrine**
|
||||
|
||||
In case if **translation query walker** is used, you can additionally override:
|
||||
|
||||
### Overriding translation fallback
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$query->setHint(\Gedmo\Translatable\TranslatableListener::HINT_FALLBACK, 1);
|
||||
```
|
||||
|
||||
will fallback to default locale translations instead of empty values if used.
|
||||
And will override the translation listener setting for fallback.
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$query->setHint(\Gedmo\Translatable\TranslatableListener::HINT_FALLBACK, 0);
|
||||
```
|
||||
|
||||
will do the opposite.
|
||||
|
||||
### Using inner join strategy
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$query->setHint(\Gedmo\Translatable\TranslatableListener::HINT_INNER_JOIN, true);
|
||||
```
|
||||
|
||||
will use **INNER** joins
|
||||
for translations instead of **LEFT** joins, so that in case if you do not want untranslated
|
||||
records in your result set for instance.
|
||||
|
||||
### Overriding translatable locale
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$query->setHint(\Gedmo\Translatable\TranslatableListener::HINT_TRANSLATABLE_LOCALE, 'en');
|
||||
```
|
||||
|
||||
would override the translation locale used to translate the resultset.
|
||||
|
||||
**Note:** all these query hints lasts only for the specific query.
|
||||
|
||||
<a name="advanced-examples"></a>
|
||||
|
||||
## Advanced examples:
|
||||
|
||||
### Default locale
|
||||
|
||||
In some cases we need a default translation as a fallback if record does not have
|
||||
a translation on globally used locale. In that case Translation Listener takes the
|
||||
current value of Entity. So if **default locale** is specified and it matches the
|
||||
locale in which record is being translated - it will not create extra translation
|
||||
but use original values instead. If translation fallback is set to **false** it
|
||||
will fill untranslated values as blanks
|
||||
|
||||
To set the default locale:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$translatableListener->setDefaultLocale('en_us');
|
||||
```
|
||||
|
||||
To set translation fallback:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$translatableListener->setTranslationFallback(true); // default is false
|
||||
```
|
||||
|
||||
**Note**: Default locale should be set on the **TranslatableListener** initialization
|
||||
once, since it can impact your current records if it will be changed. As it
|
||||
will not store extra record in translation table by default.
|
||||
|
||||
If you need to store translation in default locale, set:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
$translatableListener->setPersistDefaultLocaleTranslation(true); // default is false
|
||||
```
|
||||
|
||||
This would always store translations in all locales, also keeping original record
|
||||
translated field values in default locale set.
|
||||
|
||||
### Translation Entity
|
||||
|
||||
In some cases if there are thousands of records or even more.. we would like to
|
||||
have a single table for translations of this Entity in order to increase the performance
|
||||
on translation loading speed. This example will show how to specify a different Entity for
|
||||
your translations by extending the mapped superclass.
|
||||
|
||||
ArticleTranslation Entity:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
namespace Entity\Translation;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Translatable\Entity\MappedSuperclass\AbstractTranslation;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="article_translations", indexes={
|
||||
* @ORM\Index(name="article_translation_idx", columns={"locale", "object_class", "field", "foreign_key"})
|
||||
* })
|
||||
* @ORM\Entity(repositoryClass="Gedmo\Translatable\Entity\Repository\TranslationRepository")
|
||||
*/
|
||||
class ArticleTranslation extends AbstractTranslation
|
||||
{
|
||||
/**
|
||||
* All required columns are mapped through inherited superclass
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** We specified the repository class to be used from extension.
|
||||
It is handy for specific methods common to the Translation Entity
|
||||
|
||||
**Note:** This Entity will be used instead of default Translation Entity
|
||||
only if we specify a class annotation @Gedmo\TranslationEntity(class="my\translation\entity"):
|
||||
|
||||
``` php
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="articles")
|
||||
* @ORM\Entity
|
||||
* @Gedmo\TranslationEntity(class="Entity\Translation\ArticleTranslation")
|
||||
*/
|
||||
class Article
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Now all translations of Article will be stored and queried from specific table
|
||||
|
||||
<a name="personal-translations"></a>
|
||||
|
||||
## Personal translations
|
||||
|
||||
Translatable has **AbstractPersonalTranslation** mapped superclass, which must
|
||||
be extended and mapped based on your **entity** which you want to translate.
|
||||
Note: translations are not automapped because of user preference based on cascades
|
||||
or other possible choices, which user can make.
|
||||
Personal translations uses foreign key constraint which is fully managed by ORM and
|
||||
allows to have a collection of related translations. User can use it anyway he likes, etc.:
|
||||
implementing array access on entity, using left join to fill collection and so on.
|
||||
|
||||
Note: that [query hint](#orm-query-hint) will work on personal translations the same way.
|
||||
You can always use a left join like for standard doctrine collections.
|
||||
|
||||
Usage example:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
namespace Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @Gedmo\TranslationEntity(class="Entity\CategoryTranslation")
|
||||
*/
|
||||
class Category
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @Gedmo\Translatable
|
||||
* @ORM\Column(length=64)
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* @Gedmo\Translatable
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(
|
||||
* targetEntity="CategoryTranslation",
|
||||
* mappedBy="object",
|
||||
* cascade={"persist", "remove"}
|
||||
* )
|
||||
*/
|
||||
private $translations;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->translations = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getTranslations()
|
||||
{
|
||||
return $this->translations;
|
||||
}
|
||||
|
||||
public function addTranslation(CategoryTranslation $t)
|
||||
{
|
||||
if (!$this->translations->contains($t)) {
|
||||
$this->translations[] = $t;
|
||||
$t->setObject($this);
|
||||
}
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getTitle();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now the translation entity for the Category:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
namespace Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="category_translations",
|
||||
* uniqueConstraints={@ORM\UniqueConstraint(name="lookup_unique_idx", columns={
|
||||
* "locale", "object_id", "field"
|
||||
* })}
|
||||
* )
|
||||
*/
|
||||
class CategoryTranslation extends AbstractPersonalTranslation
|
||||
{
|
||||
/**
|
||||
* Convenient constructor
|
||||
*
|
||||
* @param string $locale
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
*/
|
||||
public function __construct($locale, $field, $value)
|
||||
{
|
||||
$this->setLocale($locale);
|
||||
$this->setField($field);
|
||||
$this->setContent($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Category", inversedBy="translations")
|
||||
* @ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
|
||||
*/
|
||||
protected $object;
|
||||
}
|
||||
```
|
||||
|
||||
Some example code to persist with translations:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
// assumes default locale is "en"
|
||||
$food = new Entity\Category;
|
||||
$food->setTitle('Food');
|
||||
$food->addTranslation(new Entity\CategoryTranslation('lt', 'title', 'Maistas'));
|
||||
|
||||
$fruits = new Entity\Category;
|
||||
$fruits->setParent($food);
|
||||
$fruits->setTitle('Fruits');
|
||||
$fruits->addTranslation(new Entity\CategoryTranslation('lt', 'title', 'Vaisiai'));
|
||||
$fruits->addTranslation(new Entity\CategoryTranslation('ru', 'title', 'rus trans'));
|
||||
|
||||
$em->persist($food);
|
||||
$em->persist($fruits);
|
||||
$em->flush();
|
||||
```
|
||||
|
||||
This would create translations for english and lithuanian, and for fruits, **ru** additionally.
|
||||
|
||||
Easy like that, any suggestions on improvements are very welcome
|
||||
|
||||
|
||||
### Example code to use Personal Translations with (Symfony2 Sonata) i18n Forms:
|
||||
|
||||
Suppose you have a Sonata Backend with a simple form like:
|
||||
|
||||
``` php
|
||||
<?php
|
||||
protected function configureFormFields(FormMapper $formMapper) {
|
||||
$formMapper
|
||||
->with('General')
|
||||
->add('title', 'text')
|
||||
->end()
|
||||
;
|
||||
}
|
||||
```
|
||||
|
||||
Then you can turn it into an i18n Form by providing the following changes.
|
||||
|
||||
``` php
|
||||
<?php
|
||||
protected function configureFormFields(FormMapper $formMapper)
|
||||
{
|
||||
$formMapper
|
||||
->with('General')
|
||||
->add('title', 'translatable_field', array(
|
||||
'field' => 'title',
|
||||
'personal_translation' => 'ExampleBundle\Entity\Translation\ProductTranslation',
|
||||
'property_path' => 'translations',
|
||||
))
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
To accomplish this you can add the following code in your bundle:
|
||||
|
||||
https://gist.github.com/2437078
|
||||
|
||||
<Bundle>/Form/TranslatedFieldType.php
|
||||
<Bundle>/Form/EventListener/addTranslatedFieldSubscriber.php
|
||||
<Bundle>/Resources/services.yml
|
||||
|
||||
Then you can change to your needs:
|
||||
|
||||
``` php
|
||||
'field' => 'title', //you need to provide which field you wish to translate
|
||||
'personal_translation' => 'ExampleBundle\Entity\Translation\ProductTranslation', //the personal translation entity
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Translations field type using Personal Translations with Symfony2:
|
||||
|
||||
You can use [A2lixTranslationFormBundle](https://github.com/a2lix/TranslationFormBundle) to facilitate your translations.
|
||||
Loading…
Add table
Add a link
Reference in a new issue