My Symfony translations workflow in 2017
First bootstrap a new Symfony 3.2 project with the Symfony installer and start the built in web server:
symfony new translations cd translations ./bin/console server:run
Now we've got the basic app running, next let's enable translations and set our locale to Finnish (fi) via app/config/config.yml:
parameters: locale: fi framework: translator: { fallbacks: ['%locale%'] }
Next let's add a basic valid HTML template to views/default/index.html.twig:
<!DOCTYPE html> <title>Translation demo</title> <meta charset="utf-8"> <h1>Translation demo</h1> <p>Translations can be difficult to handle, and it's worth remembering that even London is not always London.</p> <footer>© Acme corp 2017</footer>
This is a good start, but next up let's add translations tags, but instead of using english strings I'll use period separated namespaces:
<!DOCTYPE html> <title>{% trans %}title.index{% endtrans %}</title> <meta charset="utf-8"> <h1>{% trans %}main.title{% endtrans %}</h1> {% trans %}main.body{% endtrans %} <footer>© {% trans %}footer.copyright{% endtrans %} 2017</footer>
To provide the translation for London to the body text via the backend, let's use the translation service in the controller:
return $this->render('default/index.html.twig', [ 'translated_london' => $this->get('translator')->trans('city.london'), ]);
To pass this to the template we'll use the with operator in our template tag:
{% trans with {'%city.london%': translated_london} %}main.body{% endtrans %}
Next up is generating the translations. Still in Symfony 3.2 built-in translation extraction function does not extract strings from PHP code, so we wouldn't get translation for "city.london" at all.
To get translations from PHP code as well as Twig templates, we'll use the JMSTranslationBundle, which provides this capability. Unfortunately there is currently no Symfony3 compatible release, but a fix is merged and we can use it by specifying the exact commit:
composer require jms/translation-bundle dev-master#c14c4d800ac611e4779243af74d923b63aba9f57
Once you've done the usual installation dance, we can move forward with generating the translations:
php bin/console translation:extract fi \ --dir=./app/Resources/views/ \ --output-dir=./app/Resources/translations \ --output-format=yml \ --keep
In the command we set some options in addition to the locale:
- dir: the directory which to scan
- output-dir: target directory on where to place translations
- output-format: translation format
- keep: keep translations even if they're no longer in the code
To scan the PHP code, you'll need to repeat the above script with the dir option:
--dir=./src
Once you've ran these, then we'll have our YAML translation file in our app directory. You may choose another format, but for me personally the YAML format is clean and fast to edit. The period separated strings providing a nice indented structure:
city: london: city.london footer: copyright: footer.copyright main: body: main.body title: main.title title: index: title.index
From here on you can proceed with translating strings to produce a complete translation file:
city: london: Lontoo footer: copyright: Oy Acme Ab main: title: &document_title Käännösdemo body: | <p> Käännökset voivat olla aika hankalia ja on myös hyvä muistaa, että edes %london% ei ole aina %london%. </p> <p> Saattaa olla, että riipaisen kovan kännin tänään. </p> title: index: *document_title
In the above translations you can see a few noteworthy things:
- The parameter parameter london passed to the body text can be accessed with %london%
- For multi-line text you can use the literal style denoted by the pipe character ( | )
- To reduce repetition you can use reference variables within the file using *document_title and &document_title
Conclusion
With the above steps I've managed to create a maintainable workflow for managing workflows. YAML might be a format that is not compatible with translation tools, but for me the flexibility and simplicity weight over the complexity of XLIFF, etc.
This workflow demostrates the Symfony translation capabilities for a classic server powered web application. I have good experiences also with using Symfony as a backend for API driven JavaScript Applications, where Symfony also manages the front end translations.
For JavaScript apps I recommend exposing your translations using the BazingaJsTranslationBundle. Nowadays there is also a TypeScript type definition file for the front end library, which makes it nice to work with.
In addition to the basic items covered in this article there's plenty more things such as pluralization, dynamic values and more that need to be tackled. For the Symfony project itself I'll put native PHP and JavaScript string extraction to my wishlist, but at the end of the day I'm pretty happy with how Symfony's translation mechanizations work.