Translations
Edit this pageWarning: You are browsing the documentation for Symfony 2.3, which is no longer maintained.
Read the updated version of this page for Symfony 6.3 (the current stable version).
Translations
The term "internationalization" (often abbreviated i18n) refers to the process of abstracting strings and other locale-specific pieces out of your application into a layer where they can be translated and converted based on the user's locale (i.e. language and country). For text, this means wrapping each with a function capable of translating the text (or "message") into the language of the user:
1 2 3 4 5 6
// text will *always* print out in English
var_dump('Hello World');
// text can be translated into the end-user's language or
// default to English
var_dump($translator->trans('Hello World'));
Note
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 this chapter, you'll learn how to use the Translation component in the Symfony Framework. You can read the Translation component documentation to learn even more. Overall, the process has several steps:
- Enable and configure Symfony's translation service;
- Abstract strings (i.e. "messages") by wrapping them in calls to the
Translator
("Translations"); - Create translation resources/files for each supported locale that translate each message in the application;
- Determine, set and manage the user's locale for the request and optionally on the user's entire session.
Configuration
Translations are handled by a translator
service that uses the
user's locale to lookup and return translated messages. Before using it,
enable the translator
in your configuration:
1 2 3
# app/config/config.yml
framework:
translator: { fallbacks: [en] }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:translator>
<framework:fallback>en</framework:fallback>
</framework:translator>
</framework:config>
</container>
1 2 3 4
// app/config/config.php
$container->loadFromExtension('framework', array(
'translator' => array('fallbacks' => array('en')),
));
See Translations for details on the fallbacks
key
and what Symfony does when it doesn't find a translation.
The locale used in translations is the one stored on the request. This is
typically set via a _locale
attribute on your routes (see Translations).
Basic Translation
Translation of text is done through the translator
service
(Translator). To translate a block
of text (called a message), use the
trans() method. Suppose,
for example, that you're translating a simple message from inside a controller:
1 2 3 4 5 6 7 8 9
// ...
use Symfony\Component\HttpFoundation\Response;
public function indexAction()
{
$translated = $this->get('translator')->trans('Symfony is great');
return new Response($translated);
}
When this code is executed, Symfony will attempt to translate the message
"Symfony is great" based on the locale
of the user. For this to work,
you need to tell Symfony how to translate the message via a "translation
resource", which is usually a file that contains a collection of translations
for a given locale. This "dictionary" of translations can be created in several
different formats, XLIFF being the recommended format:
1 2 3 4 5 6 7 8 9 10 11 12
<!-- messages.fr.xlf -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="symfony_is_great">
<source>Symfony is great</source>
<target>J'aime Symfony</target>
</trans-unit>
</body>
</file>
</xliff>
1 2
# messages.fr.yml
Symfony is great: J'aime Symfony
1 2 3 4
// messages.fr.php
return array(
'Symfony is great' => 'J\'aime Symfony',
);
For information on where these files should be located, see Translations.
Now, if the language of the user's locale is French (e.g. fr_FR
or fr_BE
),
the message will be translated into J'aime Symfony
. You can also translate
the message inside your templates.
The Translation Process
To actually translate the message, Symfony uses a simple process:
- The
locale
of the current user, which is stored on the request is determined; - A catalog (e.g. big collection) of translated messages is loaded from translation
resources defined for the
locale
(e.g.fr_FR
). Messages from the fallback locale are also loaded and added to the catalog if they don't already exist. The end result is a large "dictionary" of translations. - If the message is located in the catalog, the translation is returned. If not, the translator returns the original message.
When using the trans()
method, Symfony looks for the exact string inside
the appropriate message catalog and returns it (if it exists).
Message Placeholders
Sometimes, a message containing a variable needs to be translated:
1 2 3 4 5 6 7 8
use Symfony\Component\HttpFoundation\Response;
public function indexAction($name)
{
$translated = $this->get('translator')->trans('Hello '.$name);
return new Response($translated);
}
However, creating a translation for this string is impossible since the translator will try to look up the exact message, including the variable portions (e.g. "Hello Ryan" or "Hello Fabien").
For details on how to handle this situation, see Using the Translator in the components documentation. For how to do this in templates, see Translations.
Pluralization
Another complication is when you have translations that may or may not be plural, based on some variable:
1 2
There is one apple.
There are 5 apples.
To handle this, use the transChoice()
method or the transchoice
tag/filter in your template.
For much more information, see Using the Translator in the Translation component documentation.
Translations in Templates
Most of the time, translation occurs in templates. Symfony provides native support for both Twig and PHP templates.
Twig Templates
Symfony provides specialized Twig tags (trans
and transchoice
) to
help with message translation of static blocks of text:
1 2 3 4 5
{% trans %}Hello %name%{% endtrans %}
{% transchoice count %}
{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples
{% endtranschoice %}
The transchoice
tag automatically gets the %count%
variable from
the current context and passes it to the translator. This mechanism only
works when you use a placeholder following the %var%
pattern.
Caution
The %var%
notation of placeholders is required when translating in
Twig templates using the tag.
Tip
If you need to use the percent character (%
) in a string, escape it by
doubling it: {% trans %}Percent: %percent%%%{% endtrans %}
You can also specify the message domain and pass some additional variables:
1 2 3 4 5 6 7
{% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %}
{% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %}
{% transchoice count with {'%name%': 'Fabien'} from "app" %}
{0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf[ %name%, there are %count% apples
{% endtranschoice %}
The trans
and transchoice
filters can be used to translate variable
texts and complex expressions:
1 2 3 4 5 6 7
{{ message|trans }}
{{ message|transchoice(5) }}
{{ message|trans({'%name%': 'Fabien'}, "app") }}
{{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}
Tip
Using the translation tags or filters have the same effect, but with
one subtle difference: automatic output escaping is only applied to
translations using a filter. In other words, if you need to be sure
that your translated message is not output escaped, you must apply
the raw
filter after the translation filter:
1 2 3 4 5 6 7 8 9 10
{# text translated between tags is never escaped #}
{% trans %}
<h3>foo</h3>
{% endtrans %}
{% set message = '<h3>foo</h3>' %}
{# strings and variables translated via a filter are escaped by default #}
{{ message|trans|raw }}
{{ '<h3>bar</h3>'|trans|raw }}
Tip
You can set the translation domain for an entire Twig template with a single tag:
1
{% trans_default_domain "app" %}
Note that this only influences the current template, not any "included" template (in order to avoid side effects).
2.1
The trans_default_domain
tag was introduced in Symfony 2.1.
PHP Templates
The translator service is accessible in PHP templates through the
translator
helper:
1 2 3 4 5 6 7
<?php echo $view['translator']->trans('Symfony is great') ?>
<?php echo $view['translator']->transChoice(
'{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
10,
array('%count%' => 10)
) ?>
Translation Resource/File Names and Locations
Symfony looks for message files (i.e. translations) in the following locations:
- the
app/Resources/translations
directory; - the
app/Resources/<bundle name>/translations
directory; - the
Resources/translations/
directory inside of any bundle.
The locations are listed here with the highest priority first. That is, you can override the translation messages of a bundle in any of the top 2 directories.
The override mechanism works at a key level: only the overridden keys need to be listed in a higher priority message file. When a key is not found in a message file, the translator will automatically fall back to the lower priority message files.
The filename of the translation files is also important: each message file
must be named according to the following path: domain.locale.loader
:
- domain: An optional way to organize messages into groups (e.g.
admin
,navigation
or the defaultmessages
) - see The Translation Component; - locale: The locale that the translations are for (e.g.
en_GB
,en
, etc); - loader: How Symfony should load and parse the file (e.g.
xlf
,php
,yml
, etc).
The loader can be the name of any registered loader. By default, Symfony provides many loaders, including:
xlf
: XLIFF file;php
: PHP file;yml
: YAML file.
The choice of which loader to use is entirely up to you and is a matter of
taste. The recommended option is to use xlf
for translations.
For more options, see The Translation Component.
Note
You can also store translations in a database, or any other storage by providing a custom class implementing the LoaderInterface interface. See the The Dependency Injection Tags tag for more information.
Caution
Each time you create a new translation resource (or install a bundle that includes a translation resource), be sure to clear your cache so that Symfony can discover the new translation resources:
1
$ php app/console cache:clear
Fallback Translation Locales
Imagine that the user's locale is fr_FR
and that you're translating the
key Symfony is great
. To find the French translation, Symfony actually
checks translation resources for several locales:
- First, Symfony looks for the translation in a
fr_FR
translation resource (e.g.messages.fr_FR.xlf
); - If it wasn't found, Symfony looks for the translation in a
fr
translation resource (e.g.messages.fr.xlf
); - If the translation still isn't found, Symfony uses the
fallbacks
configuration parameter, which defaults toen
(see Configuration).
Handling the User's Locale
The locale of the current user is stored in the request and is accessible
via the request
object:
1 2 3 4 5 6
use Symfony\Component\HttpFoundation\Request;
public function indexAction(Request $request)
{
$locale = $request->getLocale();
}
To set the user's locale, you may want to create a custom event listener so that it's set before any other parts of the system (i.e. the translator) need it:
1 2 3 4 5 6 7
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
// some logic to determine the $locale
$request->setLocale($locale);
}
Read Making the Locale "Sticky" during a User's Session for more information on making the user's locale "sticky" to their session.
Note
Setting the locale using $request->setLocale()
in the controller
is too late to affect the translator. Either set the locale via a listener
(like above), the URL (see next) or call setLocale()
directly on
the translator
service.
See the Translations section below about setting the locale via routing.
The Locale and the URL
Since you can store the locale of the user in the session, 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:
1 2 3 4 5 6
# app/config/routing.yml
contact:
path: /{_locale}/contact
defaults: { _controller: AppBundle:Contact:index }
requirements:
_locale: en|fr|de
1 2 3 4 5 6 7 8 9 10 11 12
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="contact" path="/{_locale}/contact">
<default key="_controller">AppBundle:Contact:index</default>
<requirement key="_locale">en|fr|de</requirement>
</route>
</routes>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('contact', new Route(
'/{_locale}/contact',
array(
'_controller' => 'AppBundle:Contact:index',
),
array(
'_locale' => 'en|fr|de',
)
));
return $collection;
When using the special _locale
parameter in a route, the matched locale
will automatically be set on the Request and can be retrieved via the
getLocale() method.
In other words, if a user
visits the URI /fr/contact
, the locale fr
will automatically be set
as the locale for the current request.
You can now use the locale to create routes to other translated pages in your application.
Tip
Read How to Use Service Container Parameters in your Routes to learn how to
avoid hardcoding the _locale
requirement in all your routes.
Setting a Default Locale
What if the user's locale hasn't been determined? You can guarantee that a
locale is set on each user's request by defining a default_locale
for
the framework:
1 2 3
# app/config/config.yml
framework:
default_locale: en
1 2 3 4 5 6 7 8 9 10 11 12
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config default-locale="en" />
</container>
1 2 3 4
// app/config/config.php
$container->loadFromExtension('framework', array(
'default_locale' => 'en',
));
2.1
The default_locale
parameter was defined under the session key
originally, however, as of 2.1 this has been moved. This is because the
locale is now set on the request instead of the session.
Translating Constraint Messages
If you're using validation constraints with the Form component, then translating
the error messages is easy: simply create a translation resource for the
validators
domain.
To start, suppose you've created a plain-old-PHP object that you need to use somewhere in your application:
1 2 3 4 5 6 7
// src/AppBundle/Entity/Author.php
namespace AppBundle\Entity;
class Author
{
public $name;
}
Add constraints through any of the supported methods. Set the message option to the
translation source text. For example, to guarantee that the $name
property is
not empty, add the following:
1 2 3 4 5 6 7 8 9 10
// src/AppBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\NotBlank(message = "author.name.not_blank")
*/
public $name;
}
1 2 3 4 5
# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
properties:
name:
- NotBlank: { message: 'author.name.not_blank' }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<!-- src/AppBundle/Resources/config/validation.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="AppBundle\Entity\Author">
<property name="name">
<constraint name="NotBlank">
<option name="message">author.name.not_blank</option>
</constraint>
</property>
</class>
</constraint-mapping>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/AppBundle/Entity/Author.php
// ...
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
class Author
{
public $name;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('name', new NotBlank(array(
'message' => 'author.name.not_blank',
)));
}
}
Create a translation file under the validators
catalog for the constraint
messages, typically in the Resources/translations/
directory of the
bundle.
1 2 3 4 5 6 7 8 9 10 11 12
<!-- validators.en.xlf -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="author.name.not_blank">
<source>author.name.not_blank</source>
<target>Please enter an author name.</target>
</trans-unit>
</body>
</file>
</xliff>
1 2
# validators.en.yml
author.name.not_blank: Please enter an author name.
1 2 3 4
// validators.en.php
return array(
'author.name.not_blank' => 'Please enter an author name.',
);
Translating Database Content
The translation of database content should be handled by Doctrine through the Translatable Extension or the Translatable Behavior (PHP 5.4+). For more information, see the documentation for these libraries.
Summary
With the Symfony Translation component, creating an internationalized application no longer needs to be a painful process and boils down to just a few basic steps:
- Abstract messages in your application by wrapping each in either the trans() or transChoice() methods (learn about this in Using the Translator);
- Translate each message into multiple locales by creating translation message files. Symfony discovers and processes each file because its name follows a specific convention;
- Manage the user's locale, which is stored on the request, but can also be set on the user's session.