The Ultimate Symfony Tutorial on Internationalization

Symfony i18n tutorial

Symfony is one of the most popular PHP frameworks, also consisting of reusable components for cases in which we want to use only certain services. When it comes to internationalization, there are a lot of options such as the Translation Component for handling translation messages, the Intl component for displaying locale-aware information and localized Routing configurations for handling URL paths to translated pages. In this Symfony tutorial, we are going to leverage in full the provided abstractions with some advanced examples of those components in action.

There is an excellent introduction to the basics of translating Symfony 3 applications, where you can learn how to use the Translation Component. In this Symfony tutorial, we will step up even further and use this library to the max to show some practical and more advanced examples of i18n. For the purposes of this tutorial, we are using PHP 7 and the Symfony framework with either versions 4 or 5. All the code examples are available on GitHub. The quickest way to run the bundled application is to clone the repository and using docker-compose run the following command:

Localized Routing

Symfony’s latest version offers, among other improvements, the ability to add unique paths per locale. That way we can show a different URL pathname for each language. If you couple that with an annotation-specific configuration, we can have our routes map to each locale without much effort. Let’s see how we can do that…

First, expose some locale parameters in the services.yaml file.

File: phrase-app-symfony4/phrase-app/config/services.yaml

Here, app_locales is a list of supported locales for our app. We also used bind to wire them as parameters in our controllers.

Next, add the annotation configuration to prefix our routes with locale information:

File: phrase-app-symfony4/phrase-app/config/routes/annotations.yaml

With that in place, all our routes need to have a _locale prefix, even for the index route.

We can also further customize each route in the controller.

File: phrase-app-symfony4/phrase-app/src/Controller/DefaultController.php

File: phrase-app-symfony4/phrase-app/templates/index.html.twig

Based on those configuration values, we can visit the following routes for the Greek locale:

  • /el/giasas
  • /el/hello

and have the same page translated into Greek.

Note that if we start the development server, the debug toolbar will show any missing translation strings as depicted in the screenshots below:

Twig Extensions

If you are building web apps using Twig templates, then you will benefit from having to include the Twig extensions library, as it’s bundled with some useful tools that enhance the existing functionality. For example, the i18n extension adds Gettext support and if you are working with .PO  and .MO files, then it’s really handy.

To install them, just use composer first:

And make sure you enable them in the twig_extensions.yaml

File: phrase-app-symfony4/phrase-app/config/packages/twig_extensions.yaml

The i18nExtension adds Gettext support and you may find useful our guide to the Gettext tools.

The IntlExtension has a very useful filter for printing localizable date string from timestamps or DateTime instances, localizable numbers and localizable currencies. Let’s see an example below:

Define a new controller:

File: phrase-app-symfony4/phrase-app/src/Controller/ExampleController.php

Add the following template:

File: phrase-app-symfony4/phrase-app/templates/example.html.twig

Then navigate to a few supported locale routes and see the results. I’ve included some examples for Greek, German and French.

Basic Intl Component

If you want to store and operate on data using a locale-independent binary representation and operations, then your best bet is to use the Intl Component. This is an extension that adds locale-specific tools for special cases.

To install it, invoke the following command:

$ composer require symfony/intl

Some useful use cases for this library are:

  • Writing or reading locale-specific information to files; you can use the, for example, to write PHP resources in a locale-specific format for later use or for archiving.
  • Retrieving locale information: You have a variety of methods to retrieve the list of languages, scripts, locales, country names, currencies or currency symbols for use. Those prove very handy as you won’t have to hardcopy anything in your code.

Let’s see some examples below:

Printing the list of locales based on the current locale:

Printing the list of currencies based on the current locale:

Printing the list of countries based on the current locale:

Database Translations

In most cases, you might be storing your content in a database using Docturine ORM. Typically, if you want to have your content translated to different locales, then you want to have the content stored in a separate column or table where you can reference.

For example, if you have an Entity called Product with following fields:


and you want to give translations for the Description field, you can add a few fields, such as Description_el or Description_de to store the Greek and German translations. 

This solution is suited only if you plan only to support a couple of locales. A more scalable approach is to include a separate table for the model that represent its Translations, such as in that case we would need the ProductTranslation where we can refer to each Product Id with a list of translations. In order to retrieve the model with the relevant translations, you will need to do a LEFT OUTER JOIN on the Product and ProductTranslation entities.

You can either choose to carry out your own repository manager for that or use a bundle that does that for you. There is a well-known bundle called DocturineBehaviors from KnpLabs that adds a few interesting traits and one of them is the translatable trait. Let’s see how we can use that…

First, require the library and add it to the list of loaded bundles:

File: phrase-app-symfony4/phrase-app/config/bundles.php

Create a new entity either from the generator or by hand called Product and add some fields.

File: phrase-app-symfony4/phrase-app/src/Entity/Product.php

File: phrase-app-symfony4/phrase-app/src/Entity/ProductTranslation.php

Create a ProductRepository class as a base manager.

File: phrase-app-symfony4/phrase-app/src/Repository/ProductRepository.php

Now, if you haven’t set up the database, you can do it now…

and then

The last command will create all the necessary migrations for the table schemas.

Now run the migrations to apply them in the database.

If you have the database explorer on, you can see there are two tables created one for the Product and one for the ProductTranslation:

To populate the database, you can use the database explorer itself or use the Product Entity itself:

Now if you can retrieve the Product Entity using the repository manager and pass the current locale in the translate method to retrieve the correct translations for the field:

Integrating Phrase In-Context Editor

If you want to take your internationalization process to the next level, I suggest you take a look at Phrase and specifically its In-Context Editor. It will give you better insights into how the strings get displayed in your software and also help you to manage your translations more easily. Let’s see how can we integrate this into our Symfony app.

I’m using the guidelines from the setup page

Start by creating a new configuration file:

Copy the configuration from the dev package to the newly created translation folder.

Make sure to update the bundles to include the new environment.

File: phrase-app-symfony4/phrase-app/config/bundles.php

Create the PhraseTranslator class as per instructions:

File: phrase-app-symfony4/phrase-app/src/AppBundle/PhraseTranslator.php

Add a process method to the Kernel Class to use the PhraseTanslator on this environment:

File: phrase-app-symfony4/phrase-app/src/Kernel.php

Then, on your templates include the javascript loader as per instructions:

File: phrase-app-symfony4/phrase-app/templates/base.html.twig

Navigate to to get a trial version.

Once you set your account up, you can create a project and navigate to Project Setting to find your projectId key.

Now change the environment and restart the server

When you navigate to the page, you will see the login modal again and once you are authenticated, you will see the translated strings change to include edit buttons next to them. The In-Context Editor panel will also show up.

From there, you can manage your translations more easily.


In this Symfony tutorial, we translated Symfony applications using well-known techniques. We’ve also seen how can we integrate Phrase’s In-Context Editor in our workflow. If you have any other questions left, don’t hesitate to post a comment or drop me a line. Thank you for reading and see you again next time!

4.7 (94%) 30 votes