Express i18n Made Easy!
Tags: express, i18n, l10n, javascript
Published: 2019-05-11 17:13:00

---
title: Express i18n Made Easy!
published: true
description: A quick tutorial on how to i18n an express application.
tags: express, i18n, l10n, javascript
---
*Update: An example project demonstrating how this works can be found here- https://github.com/bmanley91/express-i18n-example*

Recently my team was charged with internationalizing our product. We needed to support both English and Spanish in our application with multiple user-facing clients. Since our only common point was our Express backend, we decided that it would serve up message strings to be consumed by our clients. This allowed us to reduce the code impact of the project and assured that the clients would have consistent user messaging going forward.

This article will cover the approach we took to internationalize our Express backend.

# Setting the stage

Let's say we already have a very simple express application. It has a `/greeting` endpoint which will respond to `GET` requests with a message.
```javascript
const express = require('express');
const app = express();

app.get('/greeting', (req, res) => {
    const response = 'hello!';
    res.status(200);
    res.send(response);
});

app.listen(8080, () => console.log('App listening on port 8080!'));
```
If you've worked with Express before, this example likely looks pretty familiar. If not, I recommend [this tutorial](https://medium.com/@adnanrahic/hello-world-app-with-node-js-and-express-c1eb7cfa8a30) which helped me get up and running when learning Node.

# Enter the message file

Typically you'll want to avoid setting strings that go to the end user right in your code. The idea behind a message file or a collection of message files is to centralize where message strings are held so that they can be easily located and updated. As we'll see later in this post, libraries that deal with i18n often require utilization of a message file.

We'll create a message file named `message.json` containing our greeting.

```json
{
    "greeting": "hello!"
}
```

To keep things organized, we'll also create a `resources` directory to hold our new file. After this, our directory structure looks something like this.
```
.
├── server.js
├── resources
|   └── message.json
├── package.json
├── package-lock.json
├── node_modules
```

And we'll modify our app to pull from this file like this.

```javascript
...

const messages = require('./resources/messages.json');

app.get('/greeting', (req, res) => {
    const response = messages.greeting;
    res.status(200);
    res.send(response);
});
...

```

So far not much has really changed. We've just centralized where our messages are located.

# Internationalize! 

Next we'll introduce the modules that'll do most of the lifting for us. We'll be using the following:
* **i18next** - our core i18n framework
* **i18next-express-middleware** - adds i18n functionality to our express routes
* **i18next-node-fs-backend** - lets us pull messages from our message file

After introducing these dependencies, there is one organizational modification we'll need to make. i18next can use our directory structure to decide what files to use for what languages. We'll rename our `messages.json` file to `translation.json` and move it to a new directory `resources/locales/en/`.

Our directory structure should now look like this.
```
.
├── server.js
├── resources
|   └── locales
|        └── en
|            └── translation.json
├── package.json
├── package-lock.json
├── node_modules
```

Now that we have all we need, let's run through how to get it up and running.

## Initialization

We initialize `i18next` like this.
```javascript
const i18next = require('i18next');
const Backend = require('i18next-node-fs-backend');
const i18nextMiddleware = require('i18next-express-middleware');

i18next
    .use(Backend)
    .use(i18nextMiddleware.LanguageDetector)
    .init({
        backend: {
            loadPath: __dirname + '/resources/locales/{{lng}}/{{ns}}.json'
        },
        fallbackLng: 'en',
        preload: ['en']
    });


const app = express();

app.use(i18nextMiddleware.handle(i18next));
```

There's a decent amount going on here, so let's walk through it.

First, with `use(Backend)` we're telling i18next to use **i18next-node-fs-backend** as its backing resource. This means we'll be getting our strings from the filesystem.

Second, we're setting up language detection with `use(i18nextMiddleware.LanguageDetector)`. This lets our application decide what language it will use based on the `Accept-Language` header sent from consumers.

Next, we actually init i18next. 

The `backend` object specifies the path that i18next will load our messages from. The curly bracketed parameters will come into play later. `{{lng}}` represents the language in the directory and `{{ns}}` represents the "namespace" of the strings in the file. The namespace is useful for larger applications that may have tons of strings they need to serve up. Since we're only going to serve up a few strings, we're just going to use one namespace here.

The `preload` parameter takes an array of languages that i18next will load at time of initialization. `fallback` defines your default language that will be used if there's no translated string for a certain message.

Lastly, with `app.use(i18nextMiddleware.handle(i18next));` we tell Express to use i18next's middleware.

Alright i18n is all set up! Let's actually use it now.

## t
One of the things that **i18next-express-middleware** gets us is the `t` function on our Express request. This function will look for a key in the messages that i18next has loaded and return it in the specified language.

Here's how we can use `t` in our example project.
```javascript
const express = require('express');
const i18next = require('i18next');
const Backend = require('i18next-node-fs-backend');
const i18nextMiddleware = require('i18next-express-middleware');

i18next
    .use(Backend)
    .use(i18nextMiddleware.LanguageDetector)
    .init({
        backend: {
            loadPath: __dirname + '/resources/locales/{{lng}}/{{ns}}.json'
        },
        fallbackLng: 'en',
        preload: ['en']
    });

const app = express();

app.use(i18nextMiddleware.handle(i18next));

app.get('/greeting', (req, res) => {
    const response = req.t('greeting');
    res.status(200);
    res.send(response);
});

app.listen(8080, () => console.log('Example app listening on port 8080!'));
```

Now, our app is sending back a string that it's getting from i18next! This isn't exactly exciting since we only have our `en` language file so far. So let's get another language set up.

(I am sadly monolingual. So we're going with "hello" and "hola" here. 😔)

Create a new language file in `resources/locales/es/translation.json` like this.
```json
{
    "greeting": "¡hola!"
}
```

Next, modify the i18next init call by adding the `es` locale to the `preload` array.

```javascript
...
.init({
        backend: {
            loadPath: __dirname + '/resources/locales/{{lng}}/{{ns}}.json'
        },
        fallbackLng: 'en',
        preload: ['en', 'es']
    });
...
```

## Test it out
We've set up our translation files and configured i18next to use them. Next, we need to test it out. Let's start up the express server with `node server.js`.

Our app will decide what language to use based on the `Accept-Language` header. As we've set it up here, it will return Spanish for `es` and English for anything else, including if no language header is sent.

We'll use `curl localhost:8080/greeting` to test our base case. With no header we should get this response.
```
hello!
```

Now let's actually test that our i18n works with `curl localhost:8080/greeting -H "Accept-Language: es"`. We should get this response.
```
¡hola!
```

We did it! We can now display strings for our users in multiple languages! Now begins the fun of translating every message in your app. 

Happy translating!

If you'd like to see a working example of everything outlined here and more, check out https://github.com/bmanley91/express-i18n-example.