Drupal 8, React, i18n and Typescript

Elendev
Swissquote Tech Blog
4 min readMay 24, 2018

Lately I’ve had the occasion to start a React project on top of Drupal 8 at my workplace.

Since I work at Swissquote, I took this opportunity to use Crafty, an open-source tool able to easily manipulate building tools like Webpack or Gulp. I’ve also decided to give TypeScript a try, since there is a strong community of TypeScript users at Swissquote.

The project had to be incorporated into an existing Drupal 8 website, but the specificity is that the website is multilingual and, since we are using Smartling, the translations are stored directly in Drupal.

The use of TypeScript might seem like a detail, but my newbism in TypeScript made me make a mistake that allowed me to discover how exactly the translations in Drupal 8 works.

The first question is…

How can I use the JavaScript translation mechanism of Drupal?

Drupal provides a Drupal utility class in JavaScript, and this class provides a Drupal.t() function that… translate texts, exactly the same as Drupal’s t() function in PHP.

There are some questions at this point: how can the front-end know the required translations? Are all the translations loaded into the web page? We’ll see as soon as we have something working.

For now, let’s start by including core/drupal in the library file.

my_lib:
version:
1.0
css:
component:
path-to-my-lib.min.css:
{}
js:
path-to-my-lib.min.js:
{}
dependencies:
- core/drupal

Since I’m using TypeScript, I would love to have everything typed, so let’s declare a Drupal.ts file that provides the typed Drupal class and the Drupal object:

class DrupalClass {
// ... all function declarations
}

declare var Drupal: DrupalClass;
export default Drupal;

As I’ve discovered, this is not the correct way to handle global variables in TypeScript, but I’ll address this later in this article.

Now, let’s import it and use Drupal.t() to translate the user interface of my React application:

import Drupal from "./Drupal.ts";
[...]
return <div>{Drupal.t("My button")}</div>

It’s not working. So, what’s wrong? To discover that, I have to answer the second question:

How does it work?

Let start by looking at the Drupal.t() function:

The Drupal.t() function uses the drupalTranslations global variable. This variable is not defined in any of the JavaScript files (even those from the core), nor is it directly in the page. But after some research, it looks like the locale module provides the translations:

So, let’s add locale/translations as a dependency to our library.

my_lib:
...

dependencies:
- core/drupal
- locale/translations

Unfortunately it does not work better, but since there is some magic here, let’s find out what is (not) happening. In the locale.module file there is an implementation of hook_js_alter() looking for the non-existent javascript library:

This library is only here to tell Drupal to use a locale_js_translate() method on the linked javascript files. The locale_js_translate() function then calls the _locale_parse_js_file() function and that’s where the magic happens: the _locale_parse_js_file() function uses a regex to find every occurrence of Drupal.t() inside every included javascript files.

All the translation keys that are used in the JavaScript file are now known by Drupal, and allow it to add them in a global drupalTranslations variable inside a custom translation file.

In my case, the french translation file is located in the files directory: sites/default/files/languages/fr_oL0e[...].js

window.drupalTranslations = {
"strings": {
"": {
"My button": "Mon bouton"
}
}
};

Now we know that not all the translation keys are sent to the client, but only the required ones.

It also means that it’s not possible to use Drupal.t() with dynamic keys.

It’s nevertheless possible to force Drupal to provide a set of translations by adding them inside one of the TypeScript files, for them to be present in the compiled file:

// put this in any of the JavaScript / TypeScript included files
Drupal.t('Appearance');
Drupal.t('Content');

There is a question remaining:

So, what’s wrong with the use of Drupal.t() inside my React / Typescript application ?

Spoiler alert: this is where my newbism in TypeScript becomes… important.

I’ve declared the global Drupal variable in a module and I load it through an import. The compiled file is minified and transformed to be ES5 compatible.

An import like this:

import Drupal from “../Drupal.ts”let title = Drupal.t(‘my title’);

will be minified like this:

var a=require('Drupal');var title=a.t(‘my title’);

In this case, Drupal is not able to recognize the Drupal.t() call.

The solution is to use a declaration file (.d.ts) and to declare Drupal directly in it. The Drupal global variable will then be available on every file without any compilation issues (and with amazing auto-completion in a good IDE).

And don’t forget: if the translations files are not present (or not updated), it’s maybe because the files are generated through the “watch” mechanism of Webpack and are never fully dumped on the disk, making Drupal unable to parse them.

What did I learn?

  • We need to require both core/drupal and locale/translations libraries
  • Drupal parses the JavaScript files to find the translation keys, so they need to be present in the compiled and minified file
  • Only the required translations are sent to the front-end
  • It’s not possible to have translation of dynamic values
  • The Drupalvariable cannot be imported as a module

In conclusion, the only required action is to add a Drupal.d.ts definition file somewhere in the project for TypeScript to find it, and compile the project from time to time to allow Drupal to update the translation files.

Useful links

Since there is no official Drupal.d.ts definition file, here’s mine: https://gist.github.com/Elendev/d4ea83412ebb1f4a0d569daa596ecea0

--

--