Forest header image

Symfony Finland
Random things on PHP, Symfony and web development

My Symfony translations workflow in 2017

Translations continue to be a challenging topic in web applications. There are many methods, formats and terms for translating. While Symfony has quite a good set of translation tools, there are many formats and some gotchas that are easy to forget if you don't have a routine.

Let's take a look at a workflow that has worked for me with Symfony app localizations 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.


Written by Jani Tarvainen on Sunday April 9, 2017
Permalink -

« A practical introduction to TypeScript for PHP developers - Porting a Symfony 3 application to Flex »