Supporting multiple languages in a PWA — Part One — Clientside routing with React & TypeScript

Building the next big thing

So you finally have the idea for that web app that you want to make. It’s going to be the next Skype, Facebook, Tinder or Instagram. You could create it in your native language first and focus on obtaining market share in your own country. Testing if it’s a success before attempting to aim at the rest of the world. But then you take the risk that somebody copies your concept and goes international before you do.

Options for supporting multiple languages

If you look at other sites, there are different approaches for supporting multiple languages.

1 — Use one international brand name (“Google” or “Dell”) and domain name ( or

To serve the desired language from one domain, you can either:

  • Load the language based on the browser settings. This has the disadvantage that only the English page appears in the Google Search results as the Google crawler bot is apparently English.
  • Load the language based on a part of the url, for example or a subdomain like wikipedia does: This might be some configuration effort on the server side.

2 — Use multiple domains, like points to the Dutch version of Ebay and points to the English version.

My approach

I decided to go for multiple domains as it is very easy for me to configure multiple domains in Google App Engine. This blog post will be a tutorial to build a multilingual React app using create-react-app with TypeScript. The app will use client side routing to render the title, meta tags and text in the correct language based on the domain. My example will only support English and Dutch, but it’s easy to add more languages.

If you don’t want to go through the entire tutorial, you can find the result on Github.

Generate a project with create-react-app and TypeScript

For a full guide on how to generate a project with create-react-app, check the github page. You’ll need Node.js and the “npx” command. If you don’t have npx installed, run: npm i -g npx Then run npx create-react-app pwa-seo --template typescript to generate a project.

Run the following commands to start the server:

Adding the routing

Let’s create three pages: “Home”, “News” and “About”. I would use a structure like this:

Fill the About.tsx, Home.tsx and News.tsx with content such as:

Install the react-router-dom packages:

npm install react-router-dom @types/react-router-dom

Change the App.tsx to:

Run npm start and verify if you can browse through the three pages by clicking the links.

Testing the routing

First I want a simple test that verifies the page header. It took me a while to get going with the react testing library. I’m used to simply get elements by their id’s and then verify the element in my unit tests.

The author of the React Testing framework wants users to search elements by their label, placeholder, text contents, alt text, title, display value, role, test ID. See his full explanation in his blog post about it. Other people have tried to get a findById method in the React Testing library, but it seems like it won’t happen. Maybe I will consider using that approach in the future, but for now I’ll stick to id’s.

My first attempt looked like this:

TypeScript has optional chaining since 3.7.0 (and it’s also supported in react-scripts 3.3.0 and later) and when I apply that it makes my test look like:

Let’s make a test that navigates to the news and about pages and verify that the page header changes to the correct header. For this we need to add the fireEvent and waitForElement functions to our existing import in the App.test.tsx file.

I have grouped all tests in a describe block. Here is the block with my two new tests that navigate to the news and about pages.

Again, run npm test to verify. You’ll notice that oddly enough the third test fails.

What? All these tests run fine individually. At first I thought that this must have been a bug in React Testing Library. But it actually makes sense that our tests actually navigate to another page and thus actually modify the window.location object.

It took me a long time to figure out a way to reset this properly before each test. The best way I’ve came up with for now is:

This way we reset the window.location object before every test runs.

Continue to part two

In the next part I’ll show how to add different translations with the i18n-next library.

Java/TypeScript Developer. Interested in web/mobile/backend/database/cloud. Freelancing, only interested in job offers from employers directly. No middle men.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store