Blog post
Working on a variety of products targeting specific locales and looking to expand to new ones, I’ve caught on to the importance of internationalization and localization early in my career.
In this blog post I’d like to share the knowledge acquired and show you why it’s important to translate your application and how you can do it using the Symfony translations package.
What is i18n and l10n?
Before embarking on the adventure of exploring the vast world of the Symfony translations package it is important to understand the terms internationalization (i18n) and localization (l10n). Let us begin.
Internationalization is a process with the intention of designing and developing the software architecture, i.e. product, adaptable to various cultures, regions and languages. The end result should be a product that can cover any number of languages with minimal effort on the engineering level.
Fun fact: the term i18n is coined from the number of letters between “i” and “n”.
Some of the processes internationalization covers:
- Designing placeholders for translations instead of hardcoded values per language
- Cultural formatting for: numbers, time zones, currencies, etc.
- Organizing source code
Localization might be considered the younger brother of i18n. It is a process of adapting the given product to meet the cultural, regional and language needs of a specific target market. The target market is also known as locale. Adaptation for the locale is done through adding resources and translating content.
Fun fact: just as it’s older brother, l10n is coined from the number of letters between “l” and “n”.
Some of the processes localization covers:
- Localizing date and time differences
- Currency, accounting standards
- Culturally appropriate images, symbols and hand gestures
Why should I translate my product?
This is a question worthy of a separate blog post, maybe even a series of blog posts. In order to follow the KISS principle, here are just a few strong points:
- Targeting a larger audience - good for business expansion; costs but has a long-term benefit
- Improve SEO - translated content ranks better on locale specific search engines
- Reality check: the world doesn’t just speak English (internet users by country) - a localized app allows the user to consume its content easier making it more trustworthy in the long run
Enter: Symfony Translation
Symfony has been around for quite some time now, providing the world with i18n capabilities all the way back from version 2.7 (latest stable release is 6.0 at the time of writing).
1. The translation process is pretty simple and consists of 4 steps:
Installation and configuration
2. Abstraction of messages using the Translator component
3. Creating translation resources/files per locale
4. Determining, setting and managing the user’s locale
Installation and configuration
To prepare your translating field, install the symfony/translation package by running:
> composer require symfony/translation
The installation process will create a translation.yaml file where all the configuration is stored. After configuring the original translation.yaml, the file should look similar to this:
The %default_locale% represents your default app supported locale defined in parameters.yaml. Symfony will look for translation files in the default path or Resources/translations of any bundle.
The locale is stored on the request, typically via the _locale attribute set on the route as shown in the example below
In case your Backend needs to provide the client with multiple translations on the same endpoint it is necessary to create a kernel request listener where the locale can be determined before going through with the rest of the request.
The following example resolves the locale using the Accept-Language header to negotiate which translation to provide the client.
Note: if you run `bin/console debug:event kernel.request` you will notice Symfony has a listener for determining the locale from attributes (_locale). Your listener has to be set with a higher priority to run first as illustrated in the example below.
And that’s it! You’re ready to start internationalizing your application. Let’s explore how you can do that.
Supported formats and different approaches
All of the translations are being handled by the Translator class provided. As seen before, it is possible to translate whole URL paths. We will now cover how to translate the content of the application.
Supported formats
First of all, it is necessary to define placeholders for translations. These placeholders are defined in the translation path using the following format for naming files: domain.locale.loader.
Domain
Represents a way to organize messages into groups. The default domain is messages and is usually sufficient unless there is a need for chunking messages.
Example:
- Backend provides translations for a client web page and administration system or CMS
- Client web page uses the domain front
- CMS uses the domain admin
Locale
Covers the locale that the translations are for (e.g. en, de, hr, etc.)
Loader
Tells Symfony how to load and parse the file. Symfony supports a number of loaders where the most commonly used ones are: .yaml (YAML) and .xlf (XLIFF). It is recommended to use YAML for simple projects and use XLIFF where it’s necessary to cooperate with translators using specialized programs.
Approaches
With translations defined in domains, locales and loaders, they can be used in code and/or templates through the Translator class with the trans method.
Use translations in code
To use the translations in code, inject the TranslatorInterface service using Dependency Injection. The trans method provided by the interface takes the following parameters:
- key - the key of the message defined in resources
- parameters - an array of parameters defined in the message under
- key, wrapped with % in the message definition
- domain - group under which the key is stored
- locale - locale determined from Request, can be forced if necessary
Other than parameterized messages, the Translator also supports pluralization. Just pass in the count as a parameter and define the message boundaries for count.
The problem with pluralization might not be obvious in English, but other languages have various forms of words when pluralized and this would have to be resolved programmatically because of the limited support in message formatting. The problem can be resolved using the ICU message format which is described below.
Use translations in templates
Aside from translations in the application code, translations can also be used in Twig (the default rendering engine for Symfony) templates (e.g. email templates sent via Backend). Symfony provides two ways of translating content in templates.
The first one is by using the Twig trans tag:
The other, more commonly used approach, is by using the Twig trans filter:
ICU message format
Starting from Symfony 4.2, a new IntlFormatter class was introduced. It’s goal: resolve the complexity behind pluralization and other variable translation use cases. It takes advantage of the ICU message format and the PHP intl extension which is a wrapper around the International Components for Unicode library.
To use the ICU message format, all the translation resources need to have their domain appended with the +intl-icu suffix, e.g. messages.en.yaml becomes messages+intl-icu.en.yaml.
Pluralization issues are then resolved like so:
Notice the difference in definition of pluralization rules for the hr locale. These rules can be examined on the language plural rules provided by the Unicode organization. Other than pluralization, gender and similar variables can be used to provide correct translations for various locales.
Other examples where ICU message format comes in handy are: numbers, currencies, percentages, date and time, etc.
Conclusion
Internationalization and localization can be hard to achieve, but they are worth every penny. By establishing these processes early on in your application you will be able to represent your product appropriately for the user’s locale - allowing the user to identify with your product in their own language is a game changer.
By using Symfony, you’re already halfway there. The translations package offers support for a wide variety of formats making it possible to include translators easily into the process, including pluralization and similar translation challenges. Additionally, the support for ICU message format covers even the most complex translations and automates a lot of manual labor per locale.
To wrap this up, a warm recommendation for teams: standardize the translation structure - you will reap the benefits in the long run. I believe you will have lots of fun translating your product with Symfony now that you’ve seen some of its superpowers.