When creating a multilingual website we need to take in account translating:
- Specific routes per language
- Menus, labels and forms
- The content of the website
- Extra: translate FOSUserBundle
1. Routes
It may be tempting
to use the same URL to display a resource in different languages based on
the user's locale. For example,
http://www.example.com/contact
could show
content in English for one user and French for another user. Unfortunately,
this violates a fundamental rule of the Web: that a particular URL returns
the same resource regardless of the user. To further muddy the problem, which
version of the content would be indexed by search engines?
A better policy is to include the locale in the URL. This is fully-supported
by the routing system using the special
_locale
parameter, see documentation:
The term locale refers roughly to the user's language and country. It can be any string that your application uses to manage translations and other format differences (e.g. currency format). The ISO 639-1 language code, an underscore (
_
), then the ISO 3166-1 alpha-2 country code (e.g. fr_FR
for French/France) is recommended.
In config.yml add the following:
parameters: locale: en_GB app.locales: en_GB|es_ES|fr_FR framework: translator: { fallbacks: ["%locale%"] }
By declaring the list of accepted as parameter you can easily use it in all routes declarations:
/**
* @Route("/{_locale}/product",
* name="product",
* requirements={ "_locale" = "%app.locales%" })
*/
public function productAction(Request $request)
By doing this, a route will look like this : www.mysite.com/en_GB/product
2. Translations - labels, menus etc.
Translation of text is done through the
translator
service
(Translator
). To translate a block
of text (called a message), use the
trans()
method, or in Twig using trans and transchoice tags.
The recommended way is to have message placeholders which will be translated using a translation file (one per each language). An example would be a menu button that in english should show "About". We can create a placeholder named: "menu.about" which will be translated in Twig:
{{ 'menu.about'|trans }}
The translation files can have different formats (like YAML of XLIFF) and they live usually in app/Resources/translations, or under Resources/translations in your bundle.
I will be using YAML, so I will create the files "messages.en_GB.yml" , "messages.es_ES.yml" etc.
Inside I add the placeholder and the translation:
button.product.view_detail : View detail
3. Content translation
One solution would be to create yourself the entities and handle the process. Imagine having a Product entity containing fields that do not need translation, like Id, Price and another entity ProductTranslation containing translatable fields like Name and Description. These two will be in a relation (example: OneToMany).
Another way is to use some community bundles that handle these process for us. I will be using KnpLabs/DoctrineBehaviors and A2Lix TranslationBundle.
Install DoctrineBehaviors and add translatable: true to config.yml :
knp_doctrine_behaviors: ... translatable: trueCreate your entities following the tutorial from documentation. Be careful that there are two different traits to be used in Entity and EntityTranslation classes.Do not add id field to the EntityTranslation class as it will be taken care by the traits. Update database structure: php bin/console doctrine:schema:update --force
I am using DoctrineFixturesBundle to add some data. For adding an apple(fruit) product the code is:
$product = new Product();
$product->translate('en')->setName('Apple ');
$product->translate('es')->setName('Manzana ');
$product->translate('en')->setDescription('Sweet apple');
$product->translate('es')->setDescription('Manzana dulce ');
$product->setImage('apple.jpeg');
$product->setCurrency($currency);
$product->setPrice(1.00);
$product->setActive('1'); $manager->persist($product);
$product->mergeNewTranslations();
In the database, I have a product_translation table where the information is saved. In controller I am retrieving the product object just as usual:
$product = $em->getRepository('AppBundle:Product')->find($id);
In order to display the translatable fields in the current language in Twig I use:
{{ product.translate(app.request.locale).name }}
For handling translation from the website interface you can use A2Lix TranslationBundle which will create a form with tabs for each translation (I've installed the version 3.x)
I've generated a ProductType class and added another line in code after adding my non translatable fields:
use A2lix\TranslationFormBundle\Form\Type\TranslationsType;
...
$builder ->add('active') ->add('price') ));
$builder->add('translations', TranslationsType::class);
4. Extra: translate FOSUserBundle
FOSUserBundle is one of the most popular Symfony bundles. You can easily activate translation by following the instructions from the link below:
https://codereviewvideos.com/course/getting-started-with-fosuserbundle/video/translations-and-internationalisation-in-fosuserbundle