Documentation
You are currently viewing documentation for version 3.0 which is not a long-term support release. The latest long-term support release is version 2.6

Translations and Localizations

There are 3 ways to translate content displayed in Oro applications to a user. You can use:

This topic explains when to use each of the three approaches and provides implementation examples.

Standard Symfony Translator

Pros Cons
Does not require additional implementation and can be used in Symfony framework out-of-the-box. Cannot be applied to translate dynamic content in the application.

The application you are developing is highly likely to contain some static content that is independent of any dynamic application data, is always displayed in the same place, and never changes. Examples of such content are labels of field forms, static text in the interface, flash messages, etc. Keep in mind this translation approach is used only for static content that does not have impact on any entity (entity field values).

To translate labels, use the Translation component, which is one of the main Symfony framework components.

Oro application adds the translation functionality on top of Symfony’s standard approach which enables modification of translations via UI.

To use this approach, add the translation file to the bundle: Resources/translations/messages.en.yml

1
2
3
4
 oro:
    translation:
        some_field:
            label: Localized value

Use the Symfony translator to translate a label in the twig template: Resources/views/Form/form.html.twig

1
 {{ ‘oro.translation.some_field.label’|trans }}

or in the php code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 <?php

 namespace Oro\Bundle\AcmeBundle\Controller;

 use Symfony\Bundle\FrameworkBundle\Controller\Controller;

 class AcmeController extends Controller
 {
     /**
      * @return array
      */
     public function viewAction()
     {
         return [
             'label' => $this->get('translator')->trans('oro.translation.some_field.label')
         ];
     }
 }

More information on how to use it is available in Symfony Documentation.

Translatable Doctrine Extension

Pros Cons
  • Dynamic content in the application can be easily translated.
  • The translatable fields have value related to the actual language of the application.
  • The user must switch the current language into the required language in the system configuration to fill in the fields with the required values.
  • Translatable fields can have values only for some languages but not for Localizations.

Dynamic content is another type of content used in Oro applications. What is displayed in the UI is based on data loaded from fixtures into the database and entered by users in the UI. As a rule, this data is based on dictionaries used in certain entities.

Examples of such data are the Country and Region fields which are used in the Address entity. The application has dictionaries for each of these entities with all available translations for all translatable fields of these entities (into all available languages). For instance, these fields must take into account the language selected for the interface in cases when users must be able to filter and sort data by Country and Region in a grid with addresses. In this case, use Gedmo/Translatable. Such fields are displayed in the UI in the selected language. All requests to the database will change in order for the translations grid to retrieve data based on the current locale.

Bellow is an example of an entity which must work with Gedmo/Translatable for the name field of this entity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php

namespace Oro\Bundle\AcmeBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\Translatable\Translatable;

/**
 * @ORM\Table("oro_acme_country")
 * @ORM\Entity()
 * @Gedmo\TranslationEntity(class="Oro\Bundle\AcmeBundle\Entity\CountryTranslation")
 */
class Country implements Translatable
{
    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     * @Gedmo\Translatable
     */
    private $name;

    /**
     * @var string
     *
     * @Gedmo\Locale
     */
    private $locale;

    /**
     * @param string $name
     */
    public function setName(string $name)
    {
        $this->name = $name;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param string $locale
     */
    public function setLocale(string $locale)
    {
        $this->locale = $locale;
    }

    /**
     * @return string
     */
    public function getLocale()
    {
        return $this->locale;
    }
}

Also, Gedmo/Translatable requires a dictionary with all translations for the original entity fields:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
 <?php

 namespace Oro\Bundle\AcmeBundle\Entity;

 use Doctrine\ORM\Mapping as ORM;
 use Gedmo\Translatable\Entity\MappedSuperclass\AbstractTranslation;

 /**
  * @ORM\Table(name="oro_acme_country_trans")
  * @ORM\Entity()
  */
 class CountryTranslation extends AbstractTranslation
 {
     /**
      * @var string
      *
      * @ORM\Column(type="string", length=255)
      */
     private $content;
 }

For the grid to have working translations for entities with Gedmo fields, add the HINT_TRANSLATABLE hint: Resources/config/oro/datagrids.yml

1
2
3
4
5
6
7
8
 datagrids:
    acme-grid:
        source:
            type: orm
            query:
                ...
            hints:
                - HINT_TRANSLATABLE

Below is a simple example of a grid configuration which uses the hint: Resources/config/oro/datagrids.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 datagrids:
    acme-grid:
        source:
            type: orm
            query:
                select:
                    - country.id
                    - country.name
                from:
                    - { table: Oro\Bundle\AcmeBundle\Entity\Country, alias: country }
            hints:
                - HINT_TRANSLATABLE

        columns:
            name:
                label: Country Name

        sorters:
            columns:
                name:
                    data_name: country.name

        filters:
            columns:
                name:
                    type: string
                    data_name: country.name

In this case, the values in the name field are displayed in the required language, and filtering and sorting for the values happens in the selected language.

LocalizedFallbackValue Entity from OroLocaleBundle

Pros Cons
  • The translatable fields can be translated for each Localization available in the application.
  • It is easy to provide values for the Localizations in the entity form without changing the actual UI language.
  • Translated values cannot be used in the datagrids for filtering and sorting out-of-the-box.
  • Additional implementation is required to render translated values for the actual Localization.

UI language is incorporated into the localization entity. You can have several localizations in the application with the same interface language. However, data for various localizations may differ. In addition, if the current localization is assigned a parent localization then in cases when a field value does not exist, it is taken from the parent. This allows for setting up a flexible translation tree via the UI.

To implement this approach, use the LocalizedFallbackValue.

To use LocalizedFallbackValue for fields into the entity, make it is extendable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 <?php

 namespace Oro\Bundle\AcmeBundle\Entity;

 use Doctrine\ORM\Mapping as ORM;
 use Oro\Bundle\AcmeBundle\Model\ExtendAcme;

 /**
  * @ORM\Table(name="oro_acme")
  * @ORM\Entity()
  */
 class Acme extends ExtendAcme
 {
     /**
      * @ORM\ManyToMany(
      *      targetEntity="Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue",
      *      cascade={"ALL"},
      *      orphanRemoval=true
      * )
      * @ORM\JoinTable(
      *      name="oro_acme_name",
      *      joinColumns={
      *          @ORM\JoinColumn(name="acme_id", referencedColumnName="id", onDelete="CASCADE")
      *      },
      *      inverseJoinColumns={
      *          @ORM\JoinColumn(name="localized_value_id", referencedColumnName="id", onDelete="CASCADE", unique=true)
      *      }
      * )
      */
     protected $names;
 }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 <?php

 namespace Oro\Bundle\AcmeBundle\Model;

 use Oro\Bundle\LocaleBundle\Entity\Localization;
 use Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue;

 /**
  * @method LocalizedFallbackValue getName(Localization $localization = null)
  * @method LocalizedFallbackValue getDefaultName()
  * @method void setDefaultName(string $value)
  */
 class ExtendAcme
 {
     public function __construct()
     {
     }
 }

Enable OroBundleLocaleBundleDependencyInjectionCompilerDefaultFallbackExtensionPass for the entity and the field inside the bundle class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 <?php

 namespace Oro\Bundle\AcmeBundle;

 use Oro\Bundle\AcmeBundle\Entity\Acme;
 use Oro\Bundle\LocaleBundle\DependencyInjection\Compiler\DefaultFallbackExtensionPass;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\HttpKernel\Bundle\Bundle;

 class OroAcmeBundle extends Bundle
 {
     /**
      * @param ContainerBuilder $container
      */
     public function build(ContainerBuilder $container)
     {
         parent::build($container);

         $container->addCompilerPass(
             new DefaultFallbackExtensionPass(
                 [
                     Acme::class => [
                         'name' => 'names',
                     ]
                 ]
             )
         );
     }
 }

As the result, a proxy class is generated in the application cache: cache/prod/oro_entities/Extend/Entity/EX_OroAcmeBundle_Acme.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 <?php

 namespace Extend\Entity;

 use Oro\Bundle\LocaleBundle\Entity\Localization;
 use Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue;

 abstract class EX_OroAcmeBundle_Acme extends \Oro\Bundle\LocaleBundle\Model\ExtendFallback implements \Oro\Bundle\EntityExtendBundle\Entity\ExtendEntityInterface
 {
   /**
    * @param Localization|null $localization
    * @return LocalizedFallbackValue|null
    */
    public function getName(\Oro\Bundle\LocaleBundle\Entity\Localization $localization = NULL)
    {
        return $this->getFallbackValue($this->names, $localization);
    }
 }

To be able to provide translations in the UI, use the following example of the form type:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <?php

 namespace Oro\Bundle\AcmeBundle\Form\Type;

 use Oro\Bundle\LocaleBundle\Form\Type\LocalizedFallbackValueCollectionType;
 use Symfony\Component\Form\AbstractType;
 use Symfony\Component\Form\FormBuilderInterface;

 class AcmeType extends AbstractType
 {
     /**
      * {@inheritdoc}
      */
     public function buildForm(FormBuilderInterface $builder, array $options)
     {
         $builder->add(
             'names',
             LocalizedFallbackValueCollectionType::class,
             ['label' => 'oro.acme.names.label']
         );
     }
 }

To retrieve a name for the Localization, it is enough to use the getName() method.

More Sources on Translations

Bundle Documentation

Admin Guide Documentation

Media Library

SlideShare Translation and Localization Slides

You will be redirected to [title]. Would you like to continue?

Yes No
sso for www.magecore.comsso for oroinc.desso for oroinc.fr