Files
archived-symfony-docs/translation.rst
Javier Eguiluz 69fe48d637 Merge branch '6.4' into 7.3
* 6.4:
  [Translation] Mention that you need to install the PHP extension
2025-08-14 15:37:57 +02:00

1703 lines
65 KiB
ReStructuredText

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::
// text will *always* print out in English
echo 'Hello World';
// text can be translated into the end-user's language or
// default to English
echo $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.
Translations can be organized into groups, called **domains**. By default, all
messages use the default ``messages`` domain::
echo $translator->trans('Hello World', domain: 'messages');
The translation process has several steps:
#. :ref:`Enable and configure <translation-configuration>` Symfony's
translation service;
#. Abstract strings (i.e. "messages") by :ref:`wrapping them in calls
<translation-basic>` to the ``Translator``;
#. :ref:`Create translation resources/files <translation-resources>`
for each supported locale that translate each message in the application;
#. Determine, :ref:`set and manage the user's locale <translation-locale>`
for the request and optionally
:ref:`on the user's entire session <locale-sticky-session>`.
Installation
------------
First, run this command to install the translator before using it:
.. code-block:: terminal
$ composer require symfony/translation
Symfony includes several internationalization polyfills (``symfony/polyfill-intl-icu``,
``symfony/polyfill-intl-messageformatter``, etc.) that allow you to use translation
features even without the `PHP intl extension`_. However, these polyfills only
support English translations, so you must install the PHP ``intl`` extension
when translating into other languages.
.. _translation-configuration:
Configuration
-------------
The previous command creates an initial config file where you can define the
default locale of the application and the directory where the translation files
are located:
.. configuration-block::
.. code-block:: yaml
# config/packages/translation.yaml
framework:
default_locale: 'en'
translator:
default_path: '%kernel.project_dir%/translations'
.. code-block:: xml
<!-- config/packages/translation.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
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config default-locale="en">
<framework:translator
default-path="%kernel.project_dir%/translations"
/>
</framework:config>
</container>
.. code-block:: php
// config/packages/translation.php
use Symfony\Config\FrameworkConfig;
return static function (FrameworkConfig $framework): void {
// ...
$framework
->defaultLocale('en')
->translator()
->defaultPath('%kernel.project_dir%/translations')
;
};
.. tip::
You can also define the :ref:`enabled_locales option <reference-translator-enabled-locales>`
to restrict the locales that your application is available in.
.. _translation-basic:
Basic Translation
-----------------
Translation of text is done through the ``translator`` service
(:class:`Symfony\\Component\\Translation\\Translator`). To translate a block of
text (called a *message*), use the
:method:`Symfony\\Component\\Translation\\Translator::trans` method. Suppose,
for example, that you're translating a static message from inside a
controller::
// ...
use Symfony\Contracts\Translation\TranslatorInterface;
public function index(TranslatorInterface $translator): Response
{
$translated = $translator->trans('Symfony is great');
// ...
}
.. _translation-resources:
When this code is run, 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:
.. configuration-block::
.. code-block:: yaml
# translations/messages.fr.yaml
Symfony is great: Symfony est génial
.. code-block:: xml
<!-- translations/messages.fr.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<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>Symfony est génial</target>
</trans-unit>
</body>
</file>
</xliff>
.. code-block:: php
// translations/messages.fr.php
return [
'Symfony is great' => 'Symfony est génial',
];
You can find more information on where these files
:ref:`should be located <translation-resource-locations>`.
Now, if the language of the user's locale is French (e.g. ``fr_FR`` or ``fr_BE``),
the message will be translated into ``Symfony est génial``. You can also translate
the message inside your :ref:`templates <translation-in-templates>`.
.. _translation-real-vs-keyword-messages:
Using Real or Keyword Messages
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This example illustrates the two different philosophies when creating
messages to be translated::
$translator->trans('Symfony is great');
$translator->trans('symfony.great');
In the first method, messages are written in the language of the default
locale (English in this case). That message is then used as the "id"
when creating translations.
In the second method, messages are actually "keywords" that convey the
idea of the message. The keyword message is then used as the "id" for
any translations. In this case, translations must be made for the default
locale (i.e. to translate ``symfony.great`` to ``Symfony is great``).
The second method is handy because the message key won't need to be changed
in every translation file if you decide that the message should actually
read "Symfony is really great" in the default locale.
The choice of which method to use is entirely up to you, but the "keyword"
format is often recommended for multi-language applications, whereas for
shared bundles that contain translation resources we recommend the real
message, so your application can choose to disable the translator layer
and you will see a readable message.
Additionally, the ``php`` and ``yaml`` file formats support nested ids to
avoid repeating yourself if you use keywords instead of real text for your
ids:
.. configuration-block::
.. code-block:: yaml
symfony:
is:
# id is symfony.is.great
great: Symfony is great
# id is symfony.is.amazing
amazing: Symfony is amazing
has:
# id is symfony.has.bundles
bundles: Symfony has bundles
user:
# id is user.login
login: Login
.. code-block:: php
[
'symfony' => [
'is' => [
// id is symfony.is.great
'great' => 'Symfony is great',
// id is symfony.is.amazing
'amazing' => 'Symfony is amazing',
],
'has' => [
// id is symfony.has.bundles
'bundles' => 'Symfony has bundles',
],
],
'user' => [
// id is user.login
'login' => 'Login',
],
];
The Translation Process
~~~~~~~~~~~~~~~~~~~~~~~
To actually translate the message, Symfony uses the following process when
using the ``trans()`` method:
#. The ``locale`` of the current user, which is stored on the request is
determined; this is typically set via a ``_locale`` :ref:`attribute on
your routes <translation-locale-url>`;
#. A catalog of translated messages is loaded from translation resources
defined for the ``locale`` (e.g. ``fr_FR``). Messages from the
:ref:`fallback locale <translation-fallback>` and the
:ref:`enabled locales <reference-translator-enabled-locales>` 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.
.. _message-placeholders:
.. _pluralization:
Message Format
--------------
Sometimes, a message containing a variable needs to be translated::
// ...
$translated = $translator->trans('Hello '.$name);
However, creating a translation for this string is impossible since the
translator will try to look up the message including the variable portions
(e.g. *"Hello Ryan"* or *"Hello Fabien"*).
Another complication is when you have translations that may or may not be
plural, based on some variable:
.. code-block:: text
There is one apple.
There are 5 apples.
To manage these situations, Symfony follows the `ICU MessageFormat`_ syntax by
using PHP's :phpclass:`MessageFormatter` class. Read more about this in
:doc:`/reference/formats/message_format`.
.. _translatable-objects:
Translatable Objects
--------------------
Sometimes translating contents in templates is cumbersome because you need the
original message, the translation parameters and the translation domain for
each content. Making the translation in the controller or services simplifies
your templates, but requires injecting the translator service in different
parts of your application and mocking it in your tests.
Instead of translating a string at the time of creation, you can use a
"translatable object", which is an instance of the
:class:`Symfony\\Component\\Translation\\TranslatableMessage` class. This object stores
all the information needed to fully translate its contents when needed::
use Symfony\Component\Translation\TranslatableMessage;
// the first argument is required and it's the original message
$message = new TranslatableMessage('Symfony is great!');
// the optional second argument defines the translation parameters and
// the optional third argument is the translation domain
$status = new TranslatableMessage('order.status', ['%status%' => $order->getStatus()], 'store');
Templates are now much simpler because you can pass translatable objects to the
``trans`` filter:
.. code-block:: html+twig
<h1>{{ message|trans }}</h1>
<p>{{ status|trans }}</p>
.. tip::
The translation parameters can also be a :class:`Symfony\\Component\\Translation\\TranslatableMessage`.
.. tip::
There's also a :ref:`function called t() <reference-twig-function-t>`,
available both in Twig and PHP, as a shortcut to create translatable objects.
.. _translation-in-templates:
Translations in Templates
-------------------------
Most of the time, translation occurs in templates. Symfony provides native
support for both Twig and PHP templates.
.. _translation-filters:
Using Twig Filters
~~~~~~~~~~~~~~~~~~
The ``trans`` filter can be used to translate *variable texts* and complex expressions:
.. code-block:: twig
{{ message|trans }}
{{ message|trans({'%name%': 'Fabien'}, 'app') }}
.. tip::
You can set the translation domain for an entire Twig template with a single tag:
.. code-block:: twig
{% trans_default_domain 'app' %}
Note that this only influences the current template, not any "included"
template (in order to avoid side effects).
By default, the translated messages are output escaped; apply the ``raw``
filter after the translation filter to avoid the automatic escaping:
.. code-block:: html+twig
{% set message = '<h3>foo</h3>' %}
{# strings and variables translated via a filter are escaped by default #}
{{ message|trans|raw }}
{{ '<h3>bar</h3>'|trans|raw }}
.. _translation-tags:
Using Twig Tags
~~~~~~~~~~~~~~~
Symfony provides a specialized Twig tag ``trans`` to help with message
translation of *static blocks of text*:
.. code-block:: twig
{% trans %}Hello %name%{% endtrans %}
.. warning::
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:
.. code-block:: twig
{% trans with {'%name%': 'Fabien'} from 'app' %}Hello %name%{% endtrans %}
{% trans with {'%name%': 'Fabien'} from 'app' into 'fr' %}Hello %name%{% endtrans %}
.. warning::
Using the translation tag has the same effect as the filter, but with one
major difference: automatic output escaping is **not** applied to translations
using a tag.
Global Translation Parameters
-----------------------------
.. versionadded:: 7.3
The global translation parameters feature was introduced in Symfony 7.3.
If the content of a translation parameter is repeated across multiple
translation messages (e.g. a company name, or a version number), you can define
it as a global translation parameter. This helps you avoid repeating the same
values manually in each message.
You can configure these global parameters in the ``translations.globals`` option
of your main configuration file using either ``%...%`` or ``{...}`` syntax:
.. configuration-block::
.. code-block:: yaml
# config/packages/translator.yaml
translator:
# ...
globals:
# when using the '%' wrapping characters, you must escape them
'%%app_name%%': 'My application'
'{app_version}': '1.2.3'
'{url}': { message: 'url', parameters: { scheme: 'https://' }, domain: 'global' }
.. code-block:: xml
<!-- config/packages/translation.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
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:translator>
<!-- ... -->
<!-- when using the '%' wrapping characters, you must escape them -->
<framework:global name="%%app_name%%">My application</framework:global>
<framework:global name="{app_version}" value="1.2.3"/>
<framework:global name="{url}" message="url" domain="global">
<framework:parameter name="scheme">https://</framework:parameter>
</framework:global>
</framework:translator>
</framework:config>
</container>
.. code-block:: php
// config/packages/translator.php
use Symfony\Config\TwigConfig;
return static function (TwigConfig $translator): void {
// ...
// when using the '%' wrapping characters, you must escape them
$translator->globals('%%app_name%%')->value('My application');
$translator->globals('{app_version}')->value('1.2.3');
$translator->globals('{url}')->value(['message' => 'url', 'parameters' => ['scheme' => 'https://']]);
};
Once defined, you can use these parameters in translation messages anywhere in
your application:
.. code-block:: twig
{{ 'Application version: {app_version}'|trans }}
{# output: "Application version: 1.2.3" #}
{# parameters passed to the message override global parameters #}
{{ 'Package version: {app_version}'|trans({'{app_version}': '2.3.4'}) }}
# Displays "Package version: 2.3.4"
Forcing the Translator Locale
-----------------------------
When translating a message, the translator uses the specified locale or the
``fallback`` locale if necessary. You can also manually specify the locale to
use for translation::
$translator->trans('Symfony is great', locale: 'fr_FR');
Extracting Translation Contents and Updating Catalogs Automatically
-------------------------------------------------------------------
The most time-consuming task when translating an application is to extract all
the template contents to be translated and to keep all the translation files in
sync. Symfony includes a command called ``translation:extract`` that helps you
with these tasks:
.. code-block:: terminal
# shows all the messages that should be translated for the French language
$ php bin/console translation:extract --dump-messages fr
# updates the French translation files with the missing strings for that locale
$ php bin/console translation:extract --force fr
# check out the command help to see its options (prefix, output format, domain, sorting, etc.)
$ php bin/console translation:extract --help
The ``translation:extract`` command looks for missing translations in:
* Templates stored in the ``templates/`` directory (or any other directory
defined in the :ref:`twig.default_path <config-twig-default-path>` and
:ref:`twig.paths <config-twig-paths>` config options);
* Any PHP file/class that injects or :doc:`autowires </service_container/autowiring>`
the ``translator`` service and makes calls to the ``trans()`` method;
* Any PHP file/class stored in the ``src/`` directory that creates
:ref:`translatable objects <translatable-objects>` using the constructor or
the ``t()`` method or calls the ``trans()`` method;
* Any PHP file/class stored in the ``src/`` directory that uses
:ref:`Constraints Attributes <validation-constraints>` with ``*message`` named argument(s).
.. tip::
Install the ``nikic/php-parser`` package in your project to improve the
results of the ``translation:extract`` command. This package enables an
`AST`_ parser that can find many more translatable items:
.. code-block:: terminal
$ composer require nikic/php-parser
By default, when the ``translation:extract`` command creates new entries in the
translation file, it uses the same content as both the source and the pending
translation. The only difference is that the pending translation is prefixed by
``__``. You can customize this prefix using the ``--prefix`` option:
.. code-block:: terminal
$ php bin/console translation:extract --force --prefix="NEW_" fr
Alternatively, you can use the ``--no-fill`` option to leave the pending translation
completely empty when creating new entries in the translation catalog. This is
particularly useful when using external translation tools, as it makes it easier
to spot untranslated strings:
.. code-block:: terminal
# when using the --no-fill option, the --prefix option is ignored
$ php bin/console translation:extract --force --no-fill fr
.. versionadded:: 7.2
The ``--no-fill`` option was introduced in Symfony 7.2.
.. _translation-resource-locations:
Translation Resource/File Names and Locations
---------------------------------------------
Symfony looks for message files (i.e. translations) in the following default locations:
* the ``translations/`` directory (at the root of the project);
* the ``translations/`` directory inside of any bundle (and also their
``Resources/translations/`` directory, which is no longer recommended for bundles).
The locations are listed here with the highest priority first. That is, you can
override the translation messages of a bundle in the first directory. Bundles are
processed in the order in which they are listed in the ``config/bundles.php`` file,
so bundles appearing earlier have higher priority.
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**: The translation domain;
* **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``, ``yaml``, etc).
The loader can be the name of any registered loader. By default, Symfony
provides many loaders which are selected based on the following file extensions:
* ``.yaml``: YAML file (you can also use the ``.yml`` file extension);
* ``.xlf``: XLIFF file (you can also use the ``.xliff`` file extension);
* ``.php``: a PHP file that returns an array with the translations;
* ``.csv``: CSV file;
* ``.json``: JSON file;
* ``.ini``: INI file;
* ``.dat``, ``.res``: `ICU resource bundle`_;
* ``.mo``: `Machine object format`_;
* ``.po``: `Portable object format`_;
* ``.qt``: `QT Translations TS XML`_ 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 YAML for simple projects and use XLIFF
if you're generating translations with specialized programs or teams.
.. warning::
Each time you create a *new* message catalog (or install a bundle
that includes a translation catalog), be sure to clear your cache so
that Symfony can discover the new translation resources:
.. code-block:: terminal
$ php bin/console cache:clear
.. note::
You can add other directories with the :ref:`paths <reference-translator-paths>`
option in the configuration:
.. configuration-block::
.. code-block:: yaml
# config/packages/translation.yaml
framework:
translator:
paths:
- '%kernel.project_dir%/custom/path/to/translations'
.. code-block:: xml
<!-- config/packages/translation.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-Instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:translator>
<framework:path>%kernel.project_dir%/custom/path/to/translations</framework:path>
</framework:translator>
</framework:config>
</container>
.. code-block:: php
// config/packages/translation.php
use Symfony\Config\FrameworkConfig;
return static function (FrameworkConfig $framework): void {
$framework->translator()
->paths(['%kernel.project_dir%/custom/path/to/translations'])
;
};
Translations of Doctrine Entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unlike the contents of templates, it's not practical to translate the contents
stored in Doctrine Entities using translation catalogs. Instead, use the
Doctrine `Translatable Extension`_.
Custom Translation Resources
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If your translations use a format not supported by Symfony or you store them
in a special way (e.g. not using files or Doctrine entities), you need to provide
a custom class implementing the :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface`
interface. See the :ref:`dic-tags-translation-loader` tag for more information.
.. _translation-providers:
Translation Providers
---------------------
When using external translators to translate your application, you must send
them the new contents to translate frequently and merge the results back in the
application.
Instead of doing this manually, Symfony provides integration with several
third-party translation services. You can upload and download (called "push"
and "pull") translations to/from these services and merge the results
automatically in the application.
Installing and Configuring a Third Party Provider
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before pushing/pulling translations to a third-party provider, you must install
the package that provides integration with that provider:
====================== ===========================================================
Provider Install with
====================== ===========================================================
`Crowdin`_ ``composer require symfony/crowdin-translation-provider``
`Loco (localise.biz)`_ ``composer require symfony/loco-translation-provider``
`Lokalise`_ ``composer require symfony/lokalise-translation-provider``
`Phrase`_ ``composer require symfony/phrase-translation-provider``
====================== ===========================================================
Each library includes a :ref:`Symfony Flex recipe <symfony-flex>` that will add
a configuration example to your ``.env`` file. For example, suppose you want to
use Loco. First, install it:
.. code-block:: terminal
$ composer require symfony/loco-translation-provider
You'll now have a new line in your ``.env`` file that you can uncomment:
.. code-block:: env
# .env
LOCO_DSN=loco://API_KEY@default
The ``LOCO_DSN`` isn't a *real* address: it's a convenient format that offloads
most of the configuration work to Symfony. The ``loco`` scheme activates the
Loco provider that you installed, which knows all about how to push and
pull translations via Loco. The *only* part you need to change is the
``API_KEY`` placeholder.
This table shows the full list of available DSN formats for each provider:
====================== ==============================================================
Provider DSN
====================== ==============================================================
`Crowdin`_ ``crowdin://PROJECT_ID:API_TOKEN@ORGANIZATION_DOMAIN.default``
`Loco (localise.biz)`_ ``loco://API_KEY@default``
`Lokalise`_ ``lokalise://PROJECT_ID:API_KEY@default``
`Phrase`_ ``phrase://PROJECT_ID:API_TOKEN@default?userAgent=myProject``
====================== ==============================================================
To enable a translation provider, customize the DSN in your ``.env`` file and
configure the ``providers`` option:
.. configuration-block::
.. code-block:: yaml
# config/packages/translation.yaml
framework:
translator:
providers:
loco:
dsn: '%env(LOCO_DSN)%'
domains: ['messages']
locales: ['en', 'fr']
.. code-block:: xml
<!-- config/packages/translation.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
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:translator>
<framework:provider name="loco" dsn="%env(LOCO_DSN)%">
<framework:domain>messages</framework:domain>
<!-- ... -->
<framework:locale>en</framework:locale>
<framework:locale>fr</framework:locale>
<!-- ... -->
</framework:provider>
</framework:translator>
</framework:config>
</container>
.. code-block:: php
# config/packages/translation.php
$container->loadFromExtension('framework', [
'translator' => [
'providers' => [
'loco' => [
'dsn' => env('LOCO_DSN'),
'domains' => ['messages'],
'locales' => ['en', 'fr'],
],
],
],
]);
.. important::
If you use Phrase as a provider you must configure a user agent in your dsn. See
`Identification via User-Agent`_ for reasoning and some examples.
Also make the locale _names_ in Phrase should be as defined in RFC4646 (e.g. pt-BR rather than pt_BR).
Not doing so will result in Phrase creating a new locale for the imported keys.
.. tip::
If you use Crowdin as a provider and some of your locales are different from
the `Crowdin Language Codes`_, you have to set the `Custom Language Codes`_ in the Crowdin project
for each of your locales, in order to override the default value. You need to select the
"locale" placeholder and specify the custom code in the "Custom Code" field.
.. tip::
If you use Lokalise as a provider and a locale format following the `ISO
639-1`_ (e.g. "en" or "fr"), you have to set the `Custom Language Name setting`_
in Lokalise for each of your locales, in order to override the
default value (which follow the `ISO 639-1`_ succeeded by a sub-code in
capital letters that specifies the national variety (e.g. "GB" or "US"
according to `ISO 3166-1 alpha-2`_)).
.. tip::
The Phrase provider uses Phrase's tag feature to map translations to Symfony's translation
domains. If you need some assistance with organising your tags in Phrase, you might want
to consider the `Phrase Tag Bundle`_ which provides some commands helping you with that.
Pushing and Pulling Translations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After configuring the credentials to access the translation provider, you can
now use the following commands to push (upload) and pull (download) translations:
.. code-block:: terminal
# push all local translations to the Loco provider for the locales and domains
# configured in config/packages/translation.yaml file.
# it will update existing translations already on the provider.
$ php bin/console translation:push loco --force
# push new local translations to the Loco provider for the French locale
# and the validators domain.
# it will **not** update existing translations already on the provider.
$ php bin/console translation:push loco --locales fr --domains validators
# push new local translations and delete provider's translations that not
# exists anymore in local files for the French locale and the validators domain.
# it will **not** update existing translations already on the provider.
$ php bin/console translation:push loco --delete-missing --locales fr --domains validators
# check out the command help to see its options (format, domains, locales, etc.)
$ php bin/console translation:push --help
.. code-block:: terminal
# pull all provider's translations to local files for the locales and domains
# configured in config/packages/translation.yaml file.
# it will overwrite completely your local files.
$ php bin/console translation:pull loco --force
# pull new translations from the Loco provider to local files for the French
# locale and the validators domain.
# it will **not** overwrite your local files, only add new translations.
$ php bin/console translation:pull loco --locales fr --domains validators
# check out the command help to see its options (format, domains, locales, intl-icu, etc.)
$ php bin/console translation:pull --help
# the "--as-tree" option will write YAML messages as a tree-like structure instead
# of flat keys
$ php bin/console translation:pull loco --force --as-tree
Creating Custom Providers
~~~~~~~~~~~~~~~~~~~~~~~~~
In addition to using Symfony's built-in translation providers, you can create
your own providers. To do so, you need to create two classes:
#. The first class must implement :class:`Symfony\\Component\\Translation\\Provider\\ProviderInterface`;
#. The second class needs to be a factory which will create instances of the first class. It must implement
:class:`Symfony\\Component\\Translation\\Provider\\ProviderFactoryInterface` (you can extend :class:`Symfony\\Component\\Translation\\Provider\\AbstractProviderFactory` to simplify its creation).
After creating these two classes, you need to register your factory as a service
and tag it with :ref:`translation.provider_factory <reference-dic-tags-translation-provider-factory>`.
.. _translation-locale:
Handling the User's Locale
--------------------------
Translating happens based on the user's locale. The locale of the current user
is stored in the request and is accessible via the ``Request`` object::
use Symfony\Component\HttpFoundation\Request;
public function index(Request $request): void
{
$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::
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
// some logic to determine the $locale
$request->setLocale($locale);
}
.. note::
The custom listener must be called **before** ``LocaleListener``, which
initializes the locale based on the current request. To do so, set your
listener priority to a higher value than ``LocaleListener`` priority (which
you can obtain by running the ``debug:event kernel.request`` command).
Read :ref:`locale-sticky-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 :ref:`translation-locale-url` section below about setting the
locale via routing.
.. _translation-locale-url:
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 using the
:ref:`special _locale parameter <routing-locale-parameter>`:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/ContactController.php
namespace App\Controller;
// ...
class ContactController extends AbstractController
{
#[Route(
path: '/{_locale}/contact',
name: 'contact',
requirements: [
'_locale' => 'en|fr|de',
],
)]
public function contact(): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
contact:
path: /{_locale}/contact
controller: App\Controller\ContactController::index
requirements:
_locale: en|fr|de
.. code-block:: xml
<!-- config/routes.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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="contact" path="/{_locale}/contact">
controller="App\Controller\ContactController::index">
<requirement key="_locale">en|fr|de</requirement>
</route>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\ContactController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return function (RoutingConfigurator $routes): void {
$routes->add('contact', '/{_locale}/contact')
->controller([ContactController::class, 'index'])
->requirements([
'_locale' => 'en|fr|de',
])
;
};
When using the special ``_locale`` parameter in a route, the matched locale
is *automatically set on the Request* and can be retrieved via the
:method:`Symfony\\Component\\HttpFoundation\\Request::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::
Define the locale requirement as a :ref:`container parameter <configuration-parameters>`
to avoid hardcoding its value in all your routes.
.. _translation-default-locale:
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:
.. configuration-block::
.. code-block:: yaml
# config/packages/translation.yaml
framework:
default_locale: en
.. code-block:: xml
<!-- config/packages/translation.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
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config default-locale="en"/>
</container>
.. code-block:: php
// config/packages/translation.php
use Symfony\Config\FrameworkConfig;
return static function (FrameworkConfig $framework): void {
$framework->defaultLocale('en');
};
This ``default_locale`` is also relevant for the translator, as shown in the
next section.
Selecting the Language Preferred by the User
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If your application supports multiple languages, the first time a user visits your
site it's common to redirect them to the best possible language according to their
preferences. This is achieved with the ``getPreferredLanguage()`` method of the
:ref:`Request object <controller-request-argument>`::
// get the Request object somehow (e.g. as a controller argument)
$request = ...
// pass an array of the locales (their script and region parts are optional) supported
// by your application and the method returns the best locale for the current user
$locale = $request->getPreferredLanguage(['pt', 'fr_Latn_CH', 'en_US'] );
Symfony finds the best possible language based on the locales passed as argument
and the value of the ``Accept-Language`` HTTP header. If it can't find a perfect
match between them, Symfony will try to find a partial match based on the language
(e.g. ``fr_CA`` would match ``fr_Latn_CH`` because their language is the same).
If there's no perfect or partial match, this method returns the first locale passed
as argument (that's why the order of the passed locales is important).
.. versionadded:: 7.1
The feature to match locales partially was introduced in Symfony 7.1.
.. _translation-fallback:
Fallback Translation Locales
----------------------------
Imagine that the user's locale is ``es_AR`` and that you're translating the
key ``Symfony is great``. To find the Spanish translation, Symfony actually
checks translation resources for several locales:
#. First, Symfony looks for the translation in a ``es_AR`` (Argentinean
Spanish) translation resource (e.g. ``messages.es_AR.yaml``);
#. If it wasn't found, Symfony looks for the translation in the
parent locale, which is automatically defined only for some locales. In
this example, the parent locale is ``es_419`` (Latin American Spanish);
#. If it wasn't found, Symfony looks for the translation in a ``es``
(Spanish) translation resource (e.g. ``messages.es.yaml``);
#. If the translation still isn't found, Symfony uses the ``fallbacks`` option,
which can be configured as follows. When this option is not defined, it
defaults to the ``default_locale`` setting mentioned in the previous section.
.. configuration-block::
.. code-block:: yaml
# config/packages/translation.yaml
framework:
translator:
fallbacks: ['en']
# ...
.. code-block:: xml
<!-- config/packages/translation.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
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:translator>
<framework:fallback>en</framework:fallback>
<!-- ... -->
</framework:translator>
</framework:config>
</container>
.. code-block:: php
// config/packages/translation.php
use Symfony\Config\FrameworkConfig;
return static function (FrameworkConfig $framework): void {
// ...
$framework->translator()
->fallbacks(['en'])
;
};
.. note::
When Symfony can't find a translation in the given locale, it will
add the missing translation to the log file. For details,
see :ref:`reference-framework-translator-logging`.
.. _locale-switcher:
Switch Locale Programmatically
------------------------------
Sometimes you need to change the application's locale dynamically while running
some code. For example, a console command that renders email templates in
different languages. In such cases, you only need to switch the locale temporarily.
The ``LocaleSwitcher`` class allows you to do that::
use Symfony\Component\Translation\LocaleSwitcher;
class SomeService
{
public function __construct(
private LocaleSwitcher $localeSwitcher,
) {
}
public function someMethod(): void
{
$currentLocale = $this->localeSwitcher->getLocale();
// set the application locale programmatically to 'fr' (French):
// this affects translation, URL generation, etc.
$this->localeSwitcher->setLocale('fr');
// reset the locale to the default one configured via the
// 'default_locale' option in config/packages/translation.yaml
$this->localeSwitcher->reset();
// run some code with a specific locale, temporarily, without
// changing the locale for the rest of the application
$this->localeSwitcher->runWithLocale('es', function() {
// e.g. render templates, send emails, etc. using the 'es' (Spanish) locale
});
// optionally, receive the current locale as an argument:
$this->localeSwitcher->runWithLocale('es', function(string $locale) {
// here, the $locale argument will be set to 'es'
});
// ...
}
}
The ``LocaleSwitcher`` class changes the locale of:
* All services tagged with ``kernel.locale_aware``;
* The default locale set via ``\Locale::setDefault()``;
* The ``_locale`` parameter of the ``RequestContext`` service (if available),
so generated URLs reflect the new locale.
.. note::
The LocaleSwitcher applies the new locale only for the current request,
and its effect is lost on subsequent requests, such as after a redirect.
See :ref:`how to make the locale persist across requests <locale-sticky-session>`.
When using :ref:`autowiring <services-autowire>`, type-hint any controller or
service argument with the :class:`Symfony\\Component\\Translation\\LocaleSwitcher`
class to inject the locale switcher service. Otherwise, configure your services
manually and inject the ``translation.locale_switcher`` service.
.. _translation-debug:
How to Find Missing or Unused Translation Messages
--------------------------------------------------
When you work with many translation messages in different languages, it can be
hard to keep track which translations are missing and which are not used
anymore. The ``debug:translation`` command helps you to find these missing or
unused translation messages templates:
.. code-block:: twig
{# messages can be found when using the trans filter and tag #}
{% trans %}Symfony is great{% endtrans %}
{{ 'Symfony is great'|trans }}
.. warning::
The extractors can't find messages translated outside templates (like form
labels or controllers) unless using :ref:`translatable objects
<translatable-objects>` or calling the ``trans()`` method on a translator
(since Symfony 5.3). Dynamic translations using variables or expressions in
templates are not detected either:
.. code-block:: twig
{# this translation uses a Twig variable, so it won't be detected #}
{% set message = 'Symfony is great' %}
{{ message|trans }}
Suppose your application's default_locale is ``fr`` and you have configured
``en`` as the fallback locale (see :ref:`configuration
<translation-configuration>` and :ref:`fallback <translation-fallback>` for
how to configure these). And suppose you've already set up some translations
for the ``fr`` locale:
.. configuration-block::
.. code-block:: xml
<!-- translations/messages.fr.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<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="1">
<source>Symfony is great</source>
<target>Symfony est génial</target>
</trans-unit>
</body>
</file>
</xliff>
.. code-block:: yaml
# translations/messages.fr.yaml
Symfony is great: Symfony est génial
.. code-block:: php
// translations/messages.fr.php
return [
'Symfony is great' => 'Symfony est génial',
];
and for the ``en`` locale:
.. configuration-block::
.. code-block:: xml
<!-- translations/messages.en.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<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="1">
<source>Symfony is great</source>
<target>Symfony is great</target>
</trans-unit>
</body>
</file>
</xliff>
.. code-block:: yaml
# translations/messages.en.yaml
Symfony is great: Symfony is great
.. code-block:: php
// translations/messages.en.php
return [
'Symfony is great' => 'Symfony is great',
];
To inspect all messages in the ``fr`` locale for the application, run:
.. code-block:: terminal
$ php bin/console debug:translation fr
--------- ------------------ ---------------------- -------------------------------
State Id Message Preview (fr) Fallback Message Preview (en)
--------- ------------------ ---------------------- -------------------------------
unused Symfony is great Symfony est génial Symfony is great
--------- ------------------ ---------------------- -------------------------------
It shows you a table with the result when translating the message in the ``fr``
locale and the result when the fallback locale ``en`` would be used. On top
of that, it will also show you when the translation is the same as the fallback
translation (this could indicate that the message was not correctly translated).
Furthermore, it indicates that the message ``Symfony is great`` is unused
because it is translated, but you haven't used it anywhere yet.
Now, if you translate the message in one of your templates, you will get this
output:
.. code-block:: terminal
$ php bin/console debug:translation fr
--------- ------------------ ---------------------- -------------------------------
State Id Message Preview (fr) Fallback Message Preview (en)
--------- ------------------ ---------------------- -------------------------------
Symfony is great Symfony est génial Symfony is great
--------- ------------------ ---------------------- -------------------------------
The state is empty which means the message is translated in the ``fr`` locale
and used in one or more templates.
If you delete the message ``Symfony is great`` from your translation file
for the ``fr`` locale and run the command, you will get:
.. code-block:: terminal
$ php bin/console debug:translation fr
--------- ------------------ ---------------------- -------------------------------
State Id Message Preview (fr) Fallback Message Preview (en)
--------- ------------------ ---------------------- -------------------------------
missing Symfony is great Symfony is great Symfony is great
--------- ------------------ ---------------------- -------------------------------
The state indicates the message is missing because it is not translated in
the ``fr`` locale but it is still used in the template. Moreover, the message
in the ``fr`` locale equals to the message in the ``en`` locale. This is a
special case because the untranslated message id equals its translation in
the ``en`` locale.
If you copy the content of the translation file in the ``en`` locale to the
translation file in the ``fr`` locale and run the command, you will get:
.. code-block:: terminal
$ php bin/console debug:translation fr
---------- ------------------ ---------------------- -------------------------------
State Id Message Preview (fr) Fallback Message Preview (en)
---------- ------------------ ---------------------- -------------------------------
fallback Symfony is great Symfony is great Symfony is great
---------- ------------------ ---------------------- -------------------------------
You can see that the translations of the message are identical in the ``fr``
and ``en`` locales which means this message was probably copied from English
to French and maybe you forgot to translate it.
By default, all domains are inspected, but it is possible to specify a single
domain:
.. code-block:: terminal
$ php bin/console debug:translation en --domain=messages
When the application has a lot of messages, it is useful to display only the
unused or only the missing messages, by using the ``--only-unused`` or
``--only-missing`` options:
.. code-block:: terminal
$ php bin/console debug:translation en --only-unused
$ php bin/console debug:translation en --only-missing
Debug Command Exit Codes
~~~~~~~~~~~~~~~~~~~~~~~~
The exit code of the ``debug:translation`` command changes depending on the
status of the translations. Use the following public constants to check it::
use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand;
// generic failure (e.g. there are no translations)
TranslationDebugCommand::EXIT_CODE_GENERAL_ERROR;
// there are missing translations
TranslationDebugCommand::EXIT_CODE_MISSING;
// there are unused translations
TranslationDebugCommand::EXIT_CODE_UNUSED;
// some translations are using the fallback translation
TranslationDebugCommand::EXIT_CODE_FALLBACK;
These constants are defined as "bit masks", so you can combine them as follows::
if (TranslationDebugCommand::EXIT_CODE_MISSING | TranslationDebugCommand::EXIT_CODE_UNUSED) {
// ... there are missing and/or unused translations
}
.. _translation-lint:
How to Find Errors in Translation Files
---------------------------------------
Symfony processes all the application translation files as part of the process
that compiles the application code before executing it. If there's an error in
any translation file, you'll see an error message explaining the problem.
If you prefer, you can also validate the syntax of any YAML and XLIFF
translation file using the ``lint:yaml`` and ``lint:xliff`` commands:
.. code-block:: terminal
# lint a single file
$ php bin/console lint:yaml translations/messages.en.yaml
$ php bin/console lint:xliff translations/messages.en.xlf
# lint a whole directory
$ php bin/console lint:yaml translations
$ php bin/console lint:xliff translations
# lint multiple files or directories
$ php bin/console lint:yaml translations path/to/trans
$ php bin/console lint:xliff translations/messages.en.xlf translations/messages.es.xlf
The linter results can be exported to JSON using the ``--format`` option:
.. code-block:: terminal
$ php bin/console lint:yaml translations/ --format=json
$ php bin/console lint:xliff translations/ --format=json
When running these linters inside `GitHub Actions`_, the output is automatically
adapted to the format required by GitHub, but you can force that format too:
.. code-block:: terminal
$ php bin/console lint:yaml translations/ --format=github
$ php bin/console lint:xliff translations/ --format=github
.. tip::
The Yaml component provides a stand-alone ``yaml-lint`` binary allowing
you to lint YAML files without having to create a console application:
.. code-block:: terminal
$ php vendor/bin/yaml-lint translations/
The ``lint:yaml`` and ``lint:xliff`` commands validate the YAML and XML syntax
of the translation files, but not their contents. Use the following command
to check that the translation contents are also correct:
.. code-block:: terminal
# checks the contents of all the translation catalogues in all locales
$ php bin/console lint:translations
# checks the contents of the translation catalogues for Italian (it) and Japanese (ja) locales
$ php bin/console lint:translations --locale=it --locale=ja
.. versionadded:: 7.2
The ``lint:translations`` command was introduced in Symfony 7.2.
Pseudo-localization translator
------------------------------
.. note::
The pseudolocalization translator is meant to be used for development only.
The following image shows a typical menu on a webpage:
.. image:: /_images/translation/pseudolocalization-interface-original.png
:alt: A menu showing multiple items nicely aligned next to eachother.
This other image shows the same menu when the user switches the language to
Spanish. Unexpectedly, some text is cut and other contents are so long that
they overflow and you can't see them:
.. image:: /_images/translation/pseudolocalization-interface-translated.png
:alt: In Spanish, some menu items contain more letters which result in them being cut.
These kind of errors are very common, because different languages can be longer
or shorter than the original application language. Another common issue is to
only check if the application works when using basic accented letters, instead
of checking for more complex characters such as the ones found in Polish,
Czech, etc.
These problems can be solved with `pseudolocalization`_, a software testing method
used for testing internationalization. In this method, instead of translating
the text of the software into a foreign language, the textual elements of an
application are replaced with an altered version of the original language.
For example, ``Account Settings`` is *translated* as ``[!!! Àççôûñţ
Šéţţîñĝš !!!]``. First, the original text is expanded in length with characters
like ``[!!! !!!]`` to test the application when using languages more verbose
than the original one. This solves the first problem.
In addition, the original characters are replaced by similar but accented
characters. This makes the text highly readable, while allowing to test the
application with all kinds of accented and special characters. This solves the
second problem.
Full support for pseudolocalization was added to help you debug
internationalization issues in your applications. You can enable and configure
it in the translator configuration:
.. configuration-block::
.. code-block:: yaml
# config/packages/translation.yaml
framework:
translator:
pseudo_localization:
# replace characters by their accented version
accents: true
# wrap strings with brackets
brackets: true
# controls how many extra characters are added to make text longer
expansion_factor: 1.4
# maintain the original HTML tags of the translated contents
parse_html: true
# also translate the contents of these HTML attributes
localizable_html_attributes: ['title']
.. code-block:: xml
<!-- config/packages/translation.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
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:translator>
<!-- accents: replace characters by their accented version -->
<!-- brackets: wrap strings with brackets -->
<!-- expansion_factor: controls how many extra characters are added to make text longer -->
<!-- parse_html: maintain the original HTML tags of the translated contents -->
<framework:pseudo-localization
accents="true"
brackets="true"
expansion_factor="1.4"
parse_html="true"
>
<!-- also translate the contents of these HTML attributes -->
<framework:localizable-html-attribute>title</framework:localizable-html-attribute>
</framework:pseudo-localization>
</framework:translator>
</framework:config>
</container>
.. code-block:: php
// config/packages/translation.php
use Symfony\Config\FrameworkConfig;
return static function (FrameworkConfig $framework) {
// ...
$framework
->translator()
->pseudoLocalization()
// replace characters by their accented version
->accents(true)
// wrap strings with brackets
->brackets(true)
// controls how many extra characters are added to make text longer
->expansionFactor(1.4)
// maintain the original HTML tags of the translated contents
->parseHtml(true)
// also translate the contents of these HTML attributes
->localizableHtmlAttributes(['title'])
;
};
That's all. The application will now start displaying those strange, but
readable, contents to help you internationalize it. See for example the
difference in the `Symfony Demo`_ application. This is the original page:
.. image:: /_images/translation/pseudolocalization-symfony-demo-disabled.png
:alt: The Symfony demo login page.
:class: with-browser
And this is the same page with pseudolocalization enabled:
.. image:: /_images/translation/pseudolocalization-symfony-demo-enabled.png
:alt: The Symfony demo login page with pseudolocalization.
:class: with-browser
Summary
-------
With the Symfony Translation component, creating an internationalized application
no longer needs to be a painful process and boils down to these steps:
* Abstract messages in your application by wrapping each in the
:method:`Symfony\\Component\\Translation\\Translator::trans` method;
* 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.
Learn more
----------
.. toctree::
:maxdepth: 1
reference/formats/message_format
reference/formats/xliff
.. _`i18n`: https://en.wikipedia.org/wiki/Internationalization_and_localization
.. _`ICU MessageFormat`: https://unicode-org.github.io/icu/userguide/format_parse/messages/
.. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes
.. _`ISO 639-1`: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
.. _`PHP intl extension`: https://php.net/book.intl
.. _`Translatable Extension`: https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/translatable.md
.. _`Custom Language Name setting`: https://docs.lokalise.com/en/articles/1400492-uploading-files#custom-language-codes
.. _`ICU resource bundle`: https://github.com/unicode-org/icu-docs/blob/main/design/bnf_rb.txt
.. _`Portable object format`: https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
.. _`Machine object format`: https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
.. _`QT Translations TS XML`: https://doc.qt.io/qt-5/linguist-ts-file-format.html
.. _`GitHub Actions`: https://docs.github.com/en/free-pro-team@latest/actions
.. _`pseudolocalization`: https://en.wikipedia.org/wiki/Pseudolocalization
.. _`Symfony Demo`: https://github.com/symfony/demo
.. _`Crowdin Language Codes`: https://developer.crowdin.com/language-codes
.. _`Custom Language Codes`: https://support.crowdin.com/project-settings/#languages
.. _`Identification via User-Agent`: https://developers.phrase.com/api/#overview--identification-via-user-agent
.. _`Phrase Tag Bundle`: https://github.com/wickedOne/phrase-tag-bundle
.. _`Crowdin`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Translation/Bridge/Crowdin/README.md
.. _`Loco (localise.biz)`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Translation/Bridge/Loco/README.md
.. _`Lokalise`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Translation/Bridge/Lokalise/README.md
.. _`Phrase`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Translation/Bridge/Phrase/README.md
.. _`AST`: https://en.wikipedia.org/wiki/Abstract_syntax_tree