How to properly manage translations in Symfony?
We already wrote about our Symfony translation workflow some years ago. But since 2015, lots of things have evolved and it was time to update this workflow.
The aim stays the same, keeping app translation simple and fluent for all stakeholders of the project. To achieve this, we had chosen an external tool: Loco, which centralizes translation data, and a piece of code written to synchronize it with Symfony translation files.
Many people had the same problem, and since then the php-translation GitHub organization appeared. It consists in simplifying translation keys management for developers, facilitates the work of the translator, and allows product owner to check and correct translations.
Here is how we translate, at JoliCode, a Symfony application in 2017.
Section intitulée the-birth-of-php-translation-organizationThe birth of PHP Translation organization
Until now, there was 2 major bundles used to handle translations in Symfony applications: JMSTranslationBundle and HappyrTranslation. Those bundles provide many interesting features like key extraction, data storing in an external services, dedicated WebUI to allow authorized users to edit translations, commands for key extractions…
But there was some missing features for us: adapters to others translation services than Loco, possibility to edit translation directly in the app for non tech users, automatic download on production environment, …
PHP Translation provides most of these features with a framework agnostic approach. A bundle for Symfony and a Laravel package are available.
There are many supported translation services, and you can create your own adapter. Let’s focus on the Symfony bundle.
Section intitulée tools-and-workflowTools and workflow
We are still using Loco in addition to php-translation bundle. The following schema shows how we separate responsibilities between developers, product owner and translators.
Developers are responsible to push new translations keys in Loco. They must pay attention to write clear and precise keys because the next actor in the workflow is the translator which is not necessary a technician. To push new keys, our favorite solution is to use the Web Profiler Panel feature. The bundle changes the display of the Translation Panel in the Profiler to add these features. Here, we can find missing local keys, we can select and push all of them to Loco:
If the developers have translation messages for one or more languages or if they find a typo, they can edit messages and push them to Loco. In parallel, the updated messages will be written in the local xliff (or yml, xml, …) files:
Once new keys are pushed to Loco, translators can do their job in as many languages as necessary. Then, developers can pull new translated data to update their local application.
Oh wait, what happens if the product owner catches a mistake on the homepage on pre-production? No problem, if we have activated the edit-in-place feature, then the product owner can directly correct the mistake on the homepage and push it back to Loco.
Source: php-translation bundle edit-in-place documentation
Section intitulée what-about-testing-environmentWhat about testing environment?
When we test our application, we don’t want to test the Symfony translator. Hence, we usually just disable the translator in the config_test.yml
. Therefore, we’ll be able to test pure translation keys.
If there are i18n routes, we cannot do that as we want the keys from the “routes” domain to be translated. We can decorate the translator with a custom one that only does its job for these keys.
Here is an example:
services:
app.decorating_translator:
class: AppBundle\Translator\NoTranslator
decorates: translator
arguments:
- '@app.decorating_translator.inner'
public: false
Our NoTranslator.php
must translate only keys under domain routes
:
class NoTranslator implements TranslatorInterface, TranslatorBagInterface
{
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function trans($id, array $parameters = [], $domain = null, $locale = null)
{
if ($domain === 'routes'){
return $this->translator->trans($id, $parameters, $domain, $locale);
}
return $id;
}
public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null)
{
if ($domain === 'routes'){
return $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
}
return $id;
}
public function getCatalogue($locale = null)
{
if (!$this->translator instanceof TranslatorBagInterface){
throw new \RuntimeException("Translator doesn't implement TranslatorBagInterface.");
}
return $this->translator->getCatalogue($locale);
}
public function setLocale($locale)
{
return $this->translator->setLocale($locale);
}
public function getLocale()
{
return $this->translator->getLocale();
}
}
Now, we can write a test to assert the presence of a translation key in the Response content:
public function testUnicornsRunTheWorldTitlePresence()
{
$crawler = $this->client->request('GET', '/unicorns-run-the-world');
$this->assertContains('unicorns_page.title', $crawler->filter('body section.content h1')->text());
}
Section intitulée from-translators-to-productionFrom translators to production
It is important to keep this translation workflow “deployment independent”. We can deploy at any time of the translation process, because during the deploy the missing keys and messages will be pulled from Loco. After the deployment, a worker on the prod env (aka. a crontab job) will pull new messages every X minutes from Loco. The production environment is always up to date, almost in real time.
Recently, php-translation got a cache clearing feature for the translation download command. It computes a MD5 of the translation directory, fetches the translations from Loco, and computes the MD5 again. If the hashes are different, it clears the translation cache. That way, we don’t have to do anything to have updated translations on our deployments and our clients are 100% independent from technicians to change texts on the website.
If your translation service is down, what happens? The synchronization will be broken, but don’t be afraid: the next crontab will not erase all existing messages. It will be a real problem if the service is not available for days, because the only way to push new translated messages in production is to pull from the external service. Until now, we have not found a solution to solve this potential problem – which indeed never happened for real.
Section intitulée what-is-missingWhat is missing
As users of Loco, there is one bad thing which is the catalog management. Loco doesn’t mind of translation domains. For instance, if you want to translate all validator messages, you must create a new Loco project. It’s a bit tricky, because for each project you have to declare a new configuration in the bundle. There surely is a possible improvement for Loco Adapter to manage domains between fetch from Loco and the writing in files.
Obviously, in many projects, we also need to translate strings on the browser side, and most of time those strings are already translated in our Symfony app. We wondered “how to make our translations available on the frontend?”. Today, the best way to expose translations to the browser is to use the famous BazingaJsTranslationBundle.
Now, you have all the cards in your game to master app translations. Do not hesitate to contribute to the php-translation organization to add new adapters or improve the Symfony bundle.
We want to give a special thanks to Tobias for his amazing work on the php-translation packages ! 👏
Commentaires et discussions
Translation workflow with Symfony2
I have an issue with Symfony2 translations. No in fact, let’s say I have an issue with app translation in general. It’s painful to write, to sync, to contextualize and to maintain. I’ve built many web applications with Symfony (1 & 2) and have yet to find the perfect workflow…
Lire la suite de l’article Translation workflow with Symfony2
Nos articles sur le même sujet
Nos formations sur ce sujet
Notre expertise est aussi disponible sous forme de formations professionnelles !
Symfony
Formez-vous à Symfony, l’un des frameworks Web PHP les complet au monde
Symfony avancée
Découvrez les fonctionnalités et concepts avancés de Symfony
Ces clients ont profité de notre expertise
Dans le cadre d’une refonte complète de son architecture Web, le journal en ligne Mediapart a sollicité l’expertise de JoliCode afin d’accompagner ses équipes. Mediapart.fr est un des rares journaux 100% en ligne qui n’appartient qu’à ses lecteurs qui amène un fort traffic authentifiés et donc difficilement cachable. Pour effectuer cette migration, …
JoliCode accompagne l’équipe technique Dayuse dans l’optimisation des performances de sa plateforme. Nous sommes intervenus sur différents sujets : La fonctionnalité de recherche d’hôtels, en remplaçant MongoDB et Algolia par Redis et Elasticsearch. La mise en place d’un workflow de réservation, la migration d’un site en Twig vers une SPA à base de…
Nous avons travaillé en étroite collaboration avec Cerfrance pour améliorer la qualité de leurs projets PHP et Symfony, tout en renforçant les compétences de leur équipe. Notre intervention a consisté à mettre en place une intégration continue (CI), à coacher l’équipe pour l’ajout de fixtures et de tests, à dockeriser l’application, et à installer…