Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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:
            - en

fallbacks 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 fr

The 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.

Thanks for reading!

Donate to the site — we work hard for you.