Enhancing our CMS with multi-lingual features in Symfony

We’ve just completed our first sprint on internationlising Havells Sylvania’s corporate website. Havells are a global company that previously had multiple websites across brands, languages and countries and their new website aims to consolidate all of that information under one umbrella site, with the same content across lanaguages.

We love the Symfony 2 Framework at UVd, it’s our ‘go-to’ framework for the back-end of our web application development and our Jellybean CMS is built on top of it. We take an agile approach with all the work we do, and until now Jellybean has not required any localisation functionality, fortunately Symfony comes with some really powerful localisation/internationalisation features out of the box and together with Doctrine2 has made it simple to deliver.

Symfony routing and the locale component

Changes like this really demonstrate the power of the Symfony2 routing and localisation components - the route for all of our Jellybean pages were updated as this snippet demonstrates.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# Before
my_route:
pattern: /my-route/{my_pattern}
defaults:
_controller: MyBundle:Default:index
 
# After
my_route:
pattern: /{_locale}/my-route/{my_pattern}
defaults:
_controller: MyBundle:Default:index
_locale: en
requirements:
# You can restrict the lanugages you support here
_locale: ^en|fr|it$
view raw routing.yml hosted with ❤ by GitHub

The special {_locale} variable in Symfony sets the locale for the whole application and allows us to serve the appropriate content to the user based on their preferred langage. You can access the current locale for a user anywhere you have the request object. Under the hood Symfony 2 updates all your internal links to use the current locale, which is very handy!

1 2 3 4 5 6 7 8 9 10 11 12 13
<?php
 
// namespaces etc...
class DefaultController
{
indexAction(Request $request)
{
// returns en at http://mywebsite.com/en/my-route
// returns it at http://mywebsite.com/it/my-route
$request->getLocale();
}
}

Gedmo doctrine extensions

If you use Doctrine for your applications and have not come across the Gedmo set of Doctrine extensions (and Stof’s Symfony2 bundle for them), then I strongly suggest you check them out!

The translatable extension (docs) hooks into Symfony via Stofs bundle, and takes the locale set by the framework to find the corresponding translation for a piece of content, all with some minor updates the models in our application (and very little else!).

It is as simple as installing the packages with composer, and configuring them in the config.yml in your symfony project

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
default_locale: "en"
 
doctrine:
orm:
mappings:
gedmo_translatable:
type: annotation
alias: GedmoTranslatable
prefix: Gedmo\Translatable\Entity
# make sure vendor library location is correct
dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translatable/Entity"
is_bundle: false
 
stof_doctrine_extensions:
default_locale: "en"
translation_fallback: true
orm:
default:
translatable: true
 
a2lix_translation_form:
locales: ["en", "fr", "it"] # [optional] Array of the translation locales (The default locale have to be excluded). Can also be specified in the form builder.
default_required: false
view raw config.yml hosted with ❤ by GitHub

And then configuring your entities like so, (omitted is the configuration for the ArticleTranslation entity, but that is well documented in the docs for the gedmo extension)

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
MyBundle\Entity\Article:
type: entity
gedmo:
translation:
locale: locale
entity: MyBundle\Entity\ArticleTranslation
id:
id:
type: integer
generator: { strategy: AUTO }
 
fields:
title:
type: string
gedmo:
- translatable
content:
type: text
gedmo:
- translatable
# This is the personal translation type
oneToMany:
translations:
targetEntity: MyBundle\Entity\ArticleTranslation
mappedBy: object
cascade: [ persist, remove ]
view raw MyEntity.orm.yml hosted with ❤ by GitHub

Thats it! Your application will now be serving translated content (if it exists) based on the locale you have made available, with no other effort.

a2lix translation form bundle

The real challenge came with updating the user interface for content editors to translate their content. Jellybean CMS features live-edit functionality allowing users to see and edit their content as it looks on the page. For that we have the a2lix translation form bundle; Symfony already handles mapping and validating forms to our business entities (in this case our pages) very nicely. The translation form bundle enhances our existing forms adding fields for each supported language, and handling the (seemingly) magic mapping of the translated content to our database by coordinating with the gedmo extensions. Aside from front-end effort, we only had to add this one liner to our existing form:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<?php
 
// namespaces etc...
 
class ArticleType
{
// ...
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
// old definitions
// ...
$builder->add('translations', 'a2lix_translations');
}
view raw ArticleType.php hosted with ❤ by GitHub

Staying friendly with the search engines

Google advise that you set up unique urls for each translation, often sites set up confusing cookie/session based techniques for setting your preferred languages, however this prevents people from sharing links in their native language, and has other usability issues – not least it doesn’t give search engines anything to crawl! We currently fall back to English for content that hasn’t been translated, and this can cause issues with duplicate content. We therefore use the canonical link property, and hreflang links that hint to search engines that different language versions of the content is available. There is a great article on google webmaster central explaining some of the different options available.

So what?

Well I believe there are still a dearth of sites out there providing a good experience for non-english speakers. With frameworks like Symfony 2 putting i8n/internationisation at the core of their functionality, and with great community support for the bundles we have utilised, there is really very little excuse not to do a good job. Indeed the speed these packages afforded us allowed us to concentrate on UX/UI and other features which is a win all round! Providing your content in different languages can also have the added benefit of making your site more discoverable & competitive through perhaps under-utilised channels (i.e. language specific search engine sites, google.de, bing.fr etc).

Links

This post was tagged with: , , , .

  • Pingback: How to manage translations for your object using SonataAdminBundle ← Bookmarks

  • gollum

    Hi, I am in the process of learning symfony and I found these extensions. Only one question. Which difference there is between Entity Translation and Personal Transalation? I think it is not clear in documentation. Could you point me into the right direction?

  • Alvaro Marcos

    Could you please post the ArticleTranslation orm.yml?
    I fell sure it would be useful for some of us.
    Thank you in advance