Multilingual Drupal - Part 3: Decoupled Translations with Gatsby

AL Multilingual Drupal - Part 3: Decoupled Translations with Gatsby

So far in the series, we’ve had an in-depth look into what can be achieved when using Drupal as a translation management system, with the use of core functionality and some great contributed modules, and what the translation workflow can look like. We’re now going to shift the focus to look at translations in a decoupled Drupal architecture. 

First, we’ll look at a solution in which we integrated a Gatsby app with a pre-existing custom Drupal 7 translation repository, and then how we would approach decoupled translations with either Drupal 8 or Drupal 9 using the existing translation management system. We’ll be focussing on interface translation strings in this blog post. 

The Challenge

The goal of the project was to build a frontend that integrated with the client's third-party and in-house APIs and applications. Part of this was integrating with an existing Drupal 7 instance that housed the client’s content and an interface translation repository built with custom entities. The application needed to be provided in 8 different languages and due to the size and complexity of the application, introduced nearly 1000 interface translation strings. We needed to make sure that the translation team could continue using this translation repository as normal, and we’d need to be able to re-use some existing translations in the new Gatsby app. 

The Solution 

Initial setup

We started by creating a couple of custom endpoints in the client’s Drupal 7 instance that would allow us to read and write translations. We created an endpoint for listing out all of the translations in JSON format, keyed by langcode, and another endpoint for the creation of new translation entities.

Once the API endpoints were in place, we started to work on the translation framework for the Gatsby application. We chose to use the tried-and-tested i18next package, specifically react-i18next, for use with our React components. We had a large distributed team on the project, so to get us up and running quickly, we made sure there was a language switcher in place, and that the “t” function that i18next provides was available for use before completing the entire synchronisation feature with Drupal 7. This ensured the other developers could identify strings that needed translating and that we wouldn’t have to retrospectively update any text to make it translatable. Documentation for developers explaining the usage in the project was created, demonstrating example usage and the translation system architecture. The usage documentation within the Gatsby app looked similar to the example below:

import { useTranslation } from 'react-i18next';

function MyComponent () {
  const { t } = useTranslation();
  const siteName = ‘Amazee Translations’;
  return <h1>{t('page.section.element-name’,’Welcome to {{siteName}}’, {siteName} )}</h1>

Translation diagram

  • Translation key: a unique identifier for the translatable string
  • Default value: the English version of the translatable string. In our context, these matched our screen designs
  • Replacement variables and other options: some translatable strings will include dynamic variables

Synchronising translations

With the foundations set, we implemented the functionality to synchronise translations between the Gatsby app and Drupal 7.

Synchronising translations
This translation synchronisation happens when the Gatsby application is built, and also periodically using a scheduled cron job.


1) Scan the Gatsby files for translatable strings 

We included and configured the i18next-scanner package to parse all our Typescript files and extract any usages of the “t” function. The extracted translations were all written to a file called dev.json which is used to populate the custom translation entities in Drupal 7. As well as all the parameters that were passed to the “t” function, a link to a custom preview app that lists out all Storybook stories for the given translation key, which gives the translation team context around the string and how it’s used in the app.

// Translation key parameter from t function
“form.address.state-label": {
// Default value parameter from t function (on build this will be prepended with 
 "administrative_title": "State/Province/Territory",
// Default value parameter from t function 
  "en_translation": "State/Province/Territory",
// Link to a custom app that displays all components that use the given translation key
  "context": "”z

Example output from running the translation string scan
Example output from running the translation string scan.

2) Fetch existing translations

To get existing translations, we make a GET request to the custom endpoint we created in Drupal 7, and for each language, we create a JSON file that can be used when initialising i18next.

Example output from running the translation string fetching from Drupal 7
Example output from running the translation string fetching from Drupal 7.

3) Add any missing translations

Now that we have what should be available in Drupal 7 (all translatable strings from our Typescript files) and what actually exists in Drupal 7, we can do a quick diff between the dev.json file and the retrieved en.json (as we always create an English translation in Drupal 7 when syncing). 
This process means that when a new translation key is added to the Gatsby app, it will be pushed to Drupal on the next build, ready for translation into other languages. And vice versa, if a new translation is added to Drupal, it will be pulled into the app on the next build. 

In Drupal, each key's title is automatically prefixed with the app's version e.g. [1.1.0] Search (search.title). The version number is retrieved from the package.json file, which is updated when there's a new release. This helps identify translation batches for the translation team to work on.

Out in the wild

After the translation synchronisation feature was delivered, the translation process worked in this way:

  • Developers would ensure all strings used within the Gatsby app were wrapped in the t function and that a suitable key was provided. There’s error logging built into the synchronisation process to alert of any missing mandatory data, and using Typescript helps a lot for this too.
  • When features made it through the QA process, a deployment would be made to a pre-production environment where the translation sync would run and upsert the translations into Drupal 7. The translations would be tagged with the appropriate version number, and this would be communicated to the translation team at feature milestones.
  • Using the relevant version number, the translation team would then get to work on all the translations tagged with that version number, and subsequent builds would pull those into the Gatsby app. They would be able to use the preview links to confirm the context of the translation string and add the translation in Drupal 7.

Greenfield approach with Drupal 8 or 9

As the needs of our clients differ, we can’t always take a greenfield approach to solutions, and the solution above worked really well in the context of that project. In this example, we were only concerned with interface translations, so if we were to start afresh, here’s how we’d approach handling content and interface translation with Gatsby and Drupal 8 or 9.

Gatsby setup

We wouldn’t change a lot here in terms of localisation, we’d still use react-i18next in combination with i18next-scanner. It worked really well and one of the maintainers was super responsive and helpful when we ran into any issues (yay for the open-source community!). 

To synchronise interface translations with Drupal, we’d create a Drupal i18next backend plugin. To retrieve multilingual content from Drupal, we’d opt for using gatsby-source-graphql over gatsby-source-drupal due to gatsby-source-drupal not being multilingual-ready, and we are generally bigger fans of GraphQL and the flexibility it gives. Our very own John Albin presented the foundations for this kind of approach in his Decoupled Days 2019 talk 

Drupal setup

We’d set up GraphQL and use this as the endpoint for the Gatsby app to source its content from. We’d love to try out Gatsby Live Preview and Incremental Builds with this approach - something we’re very excited about after using this functionality successfully with a Gatsby + Contentful project. Content translations would work in the normal way, being retrieved with GraphQL, but for interface translations, i.e. the strings in the Gatsby app that Drupal isn’t aware of, we’d create a contributed module that would be able to communicate with the i18next backend plugin and then the interface strings from the Gatsby app could be managed in Drupal, as normal, as if they were retrieved from the usage of the t function in a Twig template.


With the progression of Drupal’s API-First initiative, from 7 to 8 and beyond, content and interface translations are now able to be retrieved from a Drupal instance and used in an entirely new frontend. This means there are lots of opportunities to level-up the UX of traditional Drupal 8 websites, without having to change the content and translation management process. Decoupled Drupal is a thriving topic and there are still many challenges to overcome; the important thing is to leave Drupal to what it’s best at, and the translation system is a fantastic and fundamental example of this.

Do you have a project that would benefit from a comprehensive translation strategy? We can help. Check out our services or get in touch today! 

Stay in the Loop

We will use the personal data you are sharing with us solely for the purpose of sending you our newsletter. See more here: Data Privacy.


Let us know how we can help you.