Translating the interface
For a site to work in several languages, interface texts move out of templates into translation files. The template keeps a key, and the Translation component supplies the string in the visitor's language.
Translation files
Translations live in the translations folder, one file per language:
# translations/messages.en.yaml
nav:
home: Home
blog: Blog
welcome: 'Welcome, %name%!'# translations/messages.fr.yaml
nav:
home: Accueil
blog: Blog
welcome: 'Bienvenue, %name% !'Printing in a template
<a href="{{ path('home') }}">{{ 'nav.home'|trans }}</a>
<h1>{{ 'welcome'|trans({'%name%': user.name}) }}</h1>The trans filter finds a string by its key. Nested keys are written with dots, and values replace the words in percent signs.
Translating in code
use Symfony\Contracts\Translation\TranslatorInterface;
public function publish(TranslatorInterface $translator): Response
{
$this->addFlash('success', $translator->trans('post.published'));
// …
}The default language
# config/packages/translation.yaml
framework:
default_locale: en
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- enfallbacks is the language a string is taken from when a translation is missing.
The language in the address
The special _locale route parameter switches the language of the request:
#[Route('/{_locale}/blog', name: 'blog_index', requirements: ['_locale' => 'en|fr'])]
public function index(): Response
{
// …
}The address /fr/blog shows the page in French. Links pick up the current language by themselves, and a language switcher names it explicitly:
<a href="{{ path('blog_index', {_locale: 'fr'}) }}">Français</a>Plurals
Word forms are described in the ICU format. The name of such a file contains +intl-icu:
# translations/messages+intl-icu.en.yaml
posts_count: '{count, plural, =0 {No posts} one {# post} other {# posts}}'{{ 'posts_count'|trans({count: posts|length}) }}For 1 you get "1 post", for 3 — "3 posts", for 0 — "No posts".
Validation messages and forms
Form field labels and constraint messages are translated automatically: put a key in them and add it to a translation file. Validation messages are looked up in the validators.en.yaml file.
Finding missing strings
php bin/console debug:translation fr
php bin/console translation:extract --force frThe first command shows keys without a translation and unused strings; the second gathers keys from templates and code into a file.
Common mistake
When a translation is missing, Symfony reports no error and prints the key itself — nav.home appears on the page. Such spots are easy to miss on rarely opened pages. Run debug:translation before deploying, and do not glue phrases together from pieces: word order differs between languages, so translate the whole sentence with placeholders.