Symfony 8.1 brings several improvements to the translation system: broader XLIFF format support, a more flexible enabled_locales configuration, a better translated placeholder for expanded choice fields, and a new helper class to compute locale fallback chains.

Using Environment Variables in Enabled Locales

Nicolas Grekas
Contributed by Nicolas Grekas in #63121

The framework.enabled_locales configuration option used to require a hard-coded list of locales. In Symfony 8.1 you can use environment variables in this list directly. Empty values are filtered out automatically, so you can declare a fixed number of slots and leave some of them empty:

1
2
3
4
5
6
7
# config/packages/translation.yaml
framework:
    enabled_locales:
        - '%env(LOCALE_1)%'
        - '%env(LOCALE_2)%'
        - '%env(LOCALE_3)%'
        - '%env(LOCALE_4)%'

This is useful when the list of enabled locales depends on the deployment environment. For example, different tenants may enable different subsets of languages without requiring a separate configuration file per environment.

Translated Placeholder on Expanded Choice Fields

Eyüp Can Akman
Contributed by Eyüp Can Akman in #63727

When a ChoiceType is rendered as a group of radio buttons or checkboxes (expanded: true), its placeholder label used the choice_translation_domain option for translation.

This caused problems with EntityType, whose choice_translation_domain defaults to false. As a result, TranslatableMessage placeholders were never translated even when an explicit translation_domain was configured.

In Symfony 8.1, placeholders for expanded choice fields now use translation_domain, matching the behavior of non-expanded <select> fields:

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;

$form = $this->createForm(EntityType::class, null, [
    'class' => Country::class,
    'expanded' => true,
    // this placeholder is now translated using the 'forms' domain.
    'placeholder' => new TranslatableMessage('select_a_country', [], 'forms'),
    'translation_domain' => 'forms',
]);

New LocaleFallbackProvider Class

Matthias Pigulla
Contributed by Matthias Pigulla in #45553

Applications with many regional locales often need to compute locale fallback chains consistently across different services and components.

Symfony resolves these fallback chains by following ICU parent mappings, shortening locale sub-tags, and finally appending any configured fallback locales. Until now, this logic lived inside the Translator class.

In Symfony 8.1, this logic is extracted into a dedicated reusable LocaleFallbackProvider class:

1
2
3
4
5
use Symfony\Component\Translation\LocaleFallbackProvider;

$provider = new LocaleFallbackProvider(['en']);
$fallbacks = $provider->computeFallbackLocales('es_AR');
// ['es_419', 'es', 'en']

The class also provides a static helper to validate locale strings. It throws an InvalidArgumentException when the locale contains invalid characters:

1
LocaleFallbackProvider::validateLocale($locale);

Support for XLIFF Versions 2.1 and 2.2

Alexandre Daubois
Contributed by Alexandre Daubois in #63455

Symfony previously accepted only XLIFF 1.2 and 2.0 files. Translation files declaring version="2.1" or version="2.2" were rejected even though their structure is fully compatible with XLIFF 2.0.

This could become an issue when using newer localization platforms or translation tooling that already generate XLIFF 2.1 or 2.2 files by default. Symfony 8.1 now accepts these newer version numbers transparently:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8" ?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0"
    version="2.2" srcLang="en" trgLang="fr">
    <file id="f1">
        <unit id="welcome">
            <segment>
                <source>Welcome</source>
                <target>Bienvenue</target>
            </segment>
        </unit>
    </file>
</xliff>

Support for XLIFF PGS Module

Alexandre Daubois
Contributed by Alexandre Daubois in #63465

XLIFF 2.2 introduces the PGS module (Plural, Gender and Select), which allows translators to express plural, gender, and select variations directly inside XLIFF files.

Symfony 8.1 understands these attributes, converts them internally to ICU MessageFormat, and registers the result in the +intl-icu translation domain. This means the translations work transparently with trans():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8" ?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0"
    xmlns:pgs="urn:oasis:names:tc:xliff:pgs:1.0"
    version="2.2" srcLang="en" trgLang="fr">
    <file id="f1">
        <unit id="file_deleted" pgs:switch="plural:file_count">
            <segment id="s1" pgs:case="0">
                <source>You deleted no file.</source>
                <target>Vous n'avez supprimé aucun fichier.</target>
            </segment>
            <segment id="s2" pgs:case="1">
                <source>You deleted one file.</source>
                <target>Vous avez supprimé un fichier.</target>
            </segment>
            <segment id="s3" pgs:case="other">
                <source>You deleted <ph id="1" disp="file_count"/> files.</source>
                <target>Vous avez supprimé <ph id="1" disp="file_count"/> fichiers.</target>
            </segment>
        </unit>
    </file>
</xliff>

The pgs:switch attribute defines the variable name and switch type (plural, gender, or select), while each <segment> uses pgs:case to match a value.

You can also combine multiple switches in a single attribute. For example, pgs:switch="gender:host_gender plural:guest_count". Symfony automatically nests the generated ICU structures as needed.

Published in #Living on the edge