Integrating Twig.js and BazingaJsTranslationBundle

Integrating Twig.js with BazingaJsTranslationBundle for consistent translations

Recently I had the need to run a twig template that uses the trans filter on my frontend using twig.js, a pure JavaScript port of twig written by the good Johannes Schmitt. The JavaScript version does not handle all the functionalities offered by the original PHP version (even if it goes pretty close) and in particular it does not natively handle the trans filter.

So, at first, I got a JavaScript runtime exception on my page when trying to use the template. Luckily enough the JavaScript version of twig is extensible like the PHP one and it is very easy to add new filters and functions.

In my specific case I had a Symfony application where I was already using BazingaJsTranslationBundle to manage dynamic translations on the frontend. As I discovered the twig.js extensibility, it was very easy to start building a twig.js extension by using the Translator JavaScript object offered by the Bazinga bundle.

Note: I will not go into the details about how to install twig.js and BazingaJsTranslationBundle in a Symfony application. You can find all the needed informations on their websites/github pages.

In my first attempt I wrote something like this:

Twig.setFilter('trans', function(id, params, domain, locale) {
  return Translator.trans(id, params, domain, locale)
})

That seemed to work pretty good until I started to use translation strings with parameters. Parameters were not replaced with their respective values! The problem laid in a subtle differece on how the BazingaJsTranslationBundle and the standard twig handle parameters. Let’s see a simple example.

Suppose we have the string hello %name%. With twig we expect to do something like:

{{ 'hello %name%'|trans({ '%name%' : 'Alice' }) }}

Note the % delimiters around the parameter name.

The Translator.trans method expects an hash map without parameter delimiters in it. So we would have to do something like this:

Translator.trans("hello %name%", { 'name' ; 'Alice' });

Note that there’s no % delimiter this time. The Translator.trans method manages the detection of parameters by itself and you can also decide to customize the delimiters by setting the values: Translator.placeHolderPrefix and Translator.placeHolderSuffix. Obviously I suggest you to be consistent and use the same placeholders you use with PHP (especially if you need to share templates and translations from the backend to the frontend).

So my final solution was the following:

Twig.setFilter('trans', function(id, params, domain, locale) {
  params = params || {}

  // normalizes params (removes placeholder prefixes and suffixes)
  for (var key in params) {
    if (
      params.hasOwnProperty(key) &&
      key[0] == Translator.placeHolderPrefix &&
      key[key.length - 1] == Translator.placeHolderSuffix
    ) {
      params[key.substr(1, key.length - 2)] = params[key]
      delete params[key]
    }
  }

  return Translator.trans(id, params, domain, locale)
})

This way it automatically normalizes parameters for the Translator object (by removing any delimiter from parameter keys) and I have a consistent behavior between twig and twig.js. My normalization approach is very rough and you can surely find a better approach (maybe using a regex). Let me know if you do it ;)

Obviously you can also avoid the normalization and keep the responsibility to pass the parameters hash map in the way the Translator object expects it (without delimiters). In this case you can stick to my first implementation.

That’s all. See ya ;)

Sharing is caring!

If you got value from this article, please consider sharing it with your friends and colleagues.

Found a typo or something that can be improved?

In the spirit of Open Source, you can contribute to this article by submitting a PR on GitHub.

You might also like

Cover picture for a blog post titled 6 Rules of thumb to build blazing fast web server applications

6 Rules of thumb to build blazing fast web server applications

This post highlights 6 important rules to keep in mind when developing performant web applications: avoid premature optimization, do the minimum required work, defer non-critical tasks, leverage caching, avoid N+1 queries, and design for horizontal scaling. Following these guidelines will help you write efficient code from the start and build apps ready to handle growth.

Calendar Icon

Cover picture for a blog post titled Symfony, edit the Response globally using the Kernel Response event

Symfony, edit the Response globally using the Kernel Response event

The Symfony HttpKernel Component allows interacting with the response generation through events. The Kernel Response event permits modifying the response before sending it out. Two examples show how to use it to add custom headers and cookies without touching controller logic.

Calendar Icon

Cover picture for a blog post titled Transparent pixel response with Symfony, how to track email opening

Transparent pixel response with Symfony, how to track email opening

This blog post explains how to implement email open tracking in Symfony using a transparent tracking pixel. It provides code examples for generating a tracking image response and handling the tracking logic in a controller.

Calendar Icon

Cover picture for a blog post titled Write a console application using Symfony and Pimple

Write a console application using Symfony and Pimple

This article shows how to build a simple command line application using the Symfony Console component and Pimple dependency injection container. It provides a step-by-step guide on structuring the code, defining services, configuring parameters and wiring everything together to create a executable console app.

Calendar Icon

Cover picture for a blog post titled Symfony security: authentication made simple (well, maybe!)

Symfony security: authentication made simple (well, maybe!)

This post collects resources and provides a graph to understand how Symfony authentication works behind the scenes, from the initial request to the final authenticated token. It clarifies the relationships between key classes like firewall, authentication provider and authentication listener.

Calendar Icon

Cover picture for a blog post titled Migrating from Gatsby to Astro

Migrating from Gatsby to Astro

This article discuss the reason why I wanted to migrate this blog from Gatsby to Astro and the process I followed to do it. Plus a bunch of interesting and quirky bugs that I had to troubleshoot and fix along the way.

Calendar Icon