How to create a multi-language website in GatsbyJS?

Jakub Pomykała
Jakub Pomykała
Last updated: September 15, 20239 min read
How to create a multi-language website in GatsbyJS?

Background

In this article, I will show you how to create a multi-language landing page in GatsbyJS. I assume you already set up a Gatsby project, and we will start right from adding multi-language pages.

Check our template repository on GitHub.

Install dependencies

I will use FormatJS library as it's one of the most popular React library for internationalization, and it plays nice with a Gatsby plugin system.

# Npm users
npm install --save react-intl

# Yarn users
yarn add react react-intl

Create i18n configuration file

Create a i18n.js file in your src directory to keep all i18n configuration in one place.

const languages = ['en', 'pl', 'es'];
const defaultLanguage = 'en';

exports.defaultLanguage = defaultLanguage;
exports.languages = languages;

Add utility functions

Create a new file with utility functions. I named it linkUtils.js and I put this next to i18n.js file in the src directory.

import {defaultLanguage, languages} from "./i18n";

const getTranslatedPath = (pathname, to) => {
  const currentPageLanguage = getCurrentPageLanguage(pathname);

  let languagePath = '';
  const isDefaultLanguage = defaultLanguage === currentPageLanguage;
  if (!isDefaultLanguage) {
    languagePath = '/' + currentPageLanguage
  }


  let outputPath = `${languagePath}${to}`;

  const hasTrailingSlash = outputPath.endsWith("/");
  if (!hasTrailingSlash) {
    outputPath += "/";
  }

  return outputPath;
}

const getCurrentPageLanguage = (pathname) => {
  const pathElements = pathname.split("/");
  for (let element of pathElements) {
    for (let language of languages) {
      if (element === language) {
        return language;
      }
    }
  }
  return defaultLanguage;
};

export {getTranslatedPath, getCurrentPageLanguage};
  • getTranslatedPath Function returns a route to a page with the same language as the current page.
  • getCurrentPageLanguage Function returns language from current page path.

To give a user to navigate through our website and keep the page language, we need to create a custom <a/> element.

import React from "react";
import {Location} from '@reach/router';
import {getCurrentPageLanguage, getTranslatedPath} from "./linkUtils";

const LinkTranslated = ({children = [], className = "", href = "/"}) => {
  return (
    <Location>
      {locationProps => {
        const {pathname = ""} = locationProps.location;
        return <a className={className}
                  href={getTranslatedPath(pathname, href)}
                  hrefLang={getCurrentPageLanguage(pathname)}
        >
          {children}
        </a>;
      }}
    </Location>
  );
};

export default LinkTranslated;

Now we can use LinkTranslated same as a regular <a/> tag from HTML. We can pass a href property with base link to some page, and it will be converted into a translated link.

Learn more about hrefLang attribute.

I used Location component from @reach/router, to get access to the location object.

Add a wrapper for IntlProvider

import React from "react";
import {IntlProvider} from "react-intl";

function SimpleLocalize(props) {
  const language = props.pageContext.language;
  const messages = props.pageContext.messages;
  return (
    <IntlProvider
      locale={language}
      messages={messages}>
      {props.children}
    </IntlProvider>
  );
}

export default SimpleLocalize;

The main role of this component is to simplify a usage of <IntlProvider/> on every page.

Add translations

Create a i18n-translations.json file with translations for all languages.

{
  "en" : {
    "welcome-on-our-website" : "Welcome on our multi-language website",
    "hello-world" : "Hello World!",
    "about-us" : "About us",
    "learn-more-about-us" : "Learn more about us",
    "homepage" : "Homepage"
  },
  "pl" : {
    "welcome-on-our-website" : "Witamy na naszej wielojęzycznej stronie internetowej",
    "hello-world" : "Witaj Świecie!",
    "about-us" : "O nas",
    "learn-more-about-us" : "Dowiedz się więcej o nas",
    "homepage" : "Strona główna"
  },
  "es" : {
    "welcome-on-our-website" : "Bienvenido a nuestro sitio web multilingüe",
    "hello-world" : "Hola, mundo.",
    "about-us" : "Sobre nosotros",
    "learn-more-about-us" : "Más información sobre nosotros",
    "homepage" : "Página web"
  }
}

Managing a big JSON file like this might be hard and done only by a software team. This is the reason we created SimpleLocalize where you can manage JSON files like this with ease, automatically upload and download them and use OpenAI, Google Translate or DeepL to auto-translate the texts. Try it for free now

Configure multi-language page generation

Now, edit your gatsby-node.js configuration file and override onCreatePage function like below.

const messages = require("./i18n-translations.json")
const {languages, defaultLanguage} = require("./src/i18n");

exports.onCreatePage = async ({page, actions}) => {
  const {createPage, deletePage} = actions;
  return new Promise((resolve) => {
    let path = page.path;
    deletePage(page);

    for (let language of languages) {
      const isDefaultLanguage = language === defaultLanguage;
      if (!isDefaultLanguage) {
        path = '/' + language + page.path;
      }

      const pageForLanguage = Object.assign({}, page, {
        originalPath: page.path,
        path: path,
        context: {
          language,
          messages: messages[language]
        }
      });
      createPage(pageForLanguage)
    }
    resolve()
  })
};

Many things are happening here, right? Let start from the beginning.

  • First of all, import translations.json file from root directory
  • Use deletePage to remove default page created by Gatsby
  • Iterate over all languages from i18n.js
  • Add /{language} to path if the language is not the default one
  • Get translations for given language from messages object
  • Add language and messages to a page context
  • Use createPage to recreate a page with a new path and translations

Create a first multi-language page

Create index.js in /src/pages directory and paste code below:

import React from "react";
import {FormattedMessage} from "react-intl";
import LinkTranslated from "../LinkTranslated";
import SimpleLocalize from "../SimpleLocalize";

function IndexPage(props) {
  return (
    <SimpleLocalize {...props}>
      <h1>
        <FormattedMessage
          id="hello-world"
          defaultMessage="Hello World!"
        />
      </h1>

      <p>
        <FormattedMessage
          id="welcome-on-our-website"
          defaultMessage="Welcome on our multi-language website"
        />
      </p>

      <LinkTranslated
        className="btn btn-link"
        href="/about"
      >
        <FormattedMessage
          id="learn-more-about-us"
          defaultMessage="Learn more about us"
        />
      </LinkTranslated>
      <ul>
        <li><a href="/">English (Default)</a></li>
        <li><a href="/pl/">Polish</a></li>
        <li><a href="/es/">Spanish</a></li>
      </ul>

    </SimpleLocalize>
  );
}
export default IndexPage

SimpleLocalize Component will provide a <IntlProvider/> and intl object for <FormattedMessage/> components.

Create second multi-language page

Create file about.js in /src/pages directory and paste code like below:

import React from "react";
import SimpleLocalize from "../SimpleLocalize";
import {FormattedMessage} from "react-intl";
import LinkTranslated from "../LinkTranslated";

function AboutPage(props) {
  return (
    <SimpleLocalize {...props}>
      <h1>
        <FormattedMessage
          id="about-us"
          defaultMessage="About us"
        />
      </h1>

      <LinkTranslated
        className="btn btn-link"
        href="/"
      >
        <FormattedMessage
          id="homepage"
          defaultMessage="Homepage"
        />
      </LinkTranslated>
    </SimpleLocalize>
  );
}
export default AboutPage;

Result

Now, start your project with npm run develop and you should see

Multi-language Gatsby project example

Full code can be found on GitHub: github.com/simplelocalize/multi-language-gatsby-example

Message extraction

Uploading extracted translation keys to translation editor

In the repository, you will also find additional scripts in package.json.

I installed @formatjs/cli as a devDependency to extract all translations and IDs from <FormattedMessage/> components.

## npm
npm install --save-dev @formatjs/cli

## yarn
yarn add -D @formatjs/cli

I used simplelocalize-cli to upload found IDs and download ready-to-use translations.

Scripts explanation:

  • npm run extract script will extract translations to i18n-extracted.json
  • npm run sync script will upload found translation keys from i18n-extracted.json and download the latest translations from the server to i18n-translations.json

Once you change a translation in JSON file, you need to restart GatsbyJS to see the effect. Sometimes you might not see a changed translation, then you need to run npm run clean before npm run develop.

Editing translations in SimpleLocalize

Below you see a screenshot from a sample project which I pushed on GitHub. Now, I can manage all translations from one place, add more languages and use OpenAI ChatGPT, DeepL or Google Translate to automatically translate them and use in my Gatsby page.

Manage your translation strings in Translation Editor

Multi-language content with properly used hrefLang attributes will score much higher in SEO results.

Jakub Pomykała
Jakub Pomykała
Founder of SimpleLocalize