DEV Community

Cover image for Making a multilingual site with Gatsby
Elves Sousa
Elves Sousa

Posted on • Edited on

Making a multilingual site with Gatsby

After converting a site from Jekyll to Gatsby, one thing was missing: how do I make it bilingual? With Jekyll I already knew how to do it, but not at Gatsby. I looked on several sites for any tips on how to do this, but most of them were tutorials for an integration with some CMS or external services. My need was just a basic one, a simple website with content made in Markdown files.

I didn't find any tutorial that got exactly what I needed, so I had to force myself to find a solution. Fortunately, it worked and this site is proof of that. Below I describe the process I used to achieve this goal.

Plugin installation

To add support for other languages on the site, I installed the gatsby-plugin-intl plugin. There are other extensions to achieve the same goal, but this was the one that best served me.

To install with Yarn just use this command in the terminal:

yarn add gatsby-plugin-intl
Enter fullscreen mode Exit fullscreen mode

If you are using NPM, use this other one.

npm install gatsby-plugin-intl
Enter fullscreen mode Exit fullscreen mode

Done. The installation is complete.

Configuration

In Gatsby, after installing a plugin, a configuration is made to include it in the build process. Just include the name of the plugin, along with your options within the list of plugins, in the gatsby-config.js file. Mine was configured as follows:

module.exports = {
  plugins: [
    /* PLUGIN CONFIGURATION */
    {
      resolve: `gatsby-plugin-intl`,
      options: {
        // Directory with the strings JSON
        path: `${__dirname}/src/intl`,
        // Supported languages
        languages: [`pt`, `en`],
        // Default site language
        defaultLanguage: `pt`,
        // Redirects to `/pt` in the route `/`
        redirect: false,
      },
    },
    /* END OF CONFIGURATION */
  ],
}
Enter fullscreen mode Exit fullscreen mode

A brief explanation of the above options:

  • resolve: name of the Gatsby plugin
  • options: list with options for configuration
  • path: path to the directory where the JSON files with the all translation strings are located. The keyword __dirname replaces the need to enter the absolute address of the folder.
  • languages: list with the ISO abbreviations of the desired language for the website. Example: pl for Polish andde for German. In my case, I used only Portuguese and English.
  • defaultLanguage: default language of the website. Portuguese in my case.
  • redirect: add /pt to the URL of the website with default language. I left it as false for my website, so as not to affect the existing addresses.

Terms for translation

In addition to the configuration, you must have a file with the terms to be translated on the website. Link names, static page titles and tooltips are good applications.

{
  "about": "Sobre",
  "comments": "Comentários",
  "home": "Início"
}
Enter fullscreen mode Exit fullscreen mode

In the example above, I used a list, with a term and its equivalent translation. The structure must be the same for all the languages you want to add to your site, just changing the translation, of course.

The file name must follow the [language-iso].json pattern, within the directory mentioned in the configuration.

Example: src/intl/en.json, src/intl/pt.json, etc.

Applying translations to files

After this is done, there comes the part of translating the pages and components. To do this, just follow the steps:

Import the useIntl hook from the installed plugin:

import React from "react"
// Import hook
import { useIntl } from "gatsby-plugin-intl"

export default function Index() {
  // Making useIntl available in the code
  const intl = useIntl()
  // Use language iso for the routes
  const locale = intl.locale !== "pt" ? `/${intl.locale}` : ""
Enter fullscreen mode Exit fullscreen mode

For the translation itself, the word to be translated is replaced by the formatMessage method.

  /* Before */
  <Link activeClassName="active" to="/">
    Início
  </Link>
  /* After */
  <Link activeClassName="active" to={`${locale}/`}>
    {intl.formatMessage({ id: "home" })}
  </Link>
Enter fullscreen mode Exit fullscreen mode

For dates, the component <FormattedDate /> is used.

<FormattedDate value={new Date(postDate)} month="long" day="numeric" />
Enter fullscreen mode Exit fullscreen mode

Documentation for the options available for the component can be found here.

Listing of articles at markdown

A bilingual website does not live only on word translations, but mainly on content. In the example mentioned in this article, it comes from Markdown files in the /posts directory. Nothing much different from normal was done in the gatsby-node.js file.

const path = require("path")

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions
  const blogPostTemplate = path.resolve("src/templates/blog-post.js")
  const search = await graphql(`
    query {
      allMarkdownRemark(
        sort: { order: DESC, fields: frontmatter___date }
        limit: 1000
      ) {
        edges {
          node {
            frontmatter {
              slug
              lang
            }
          }
        }
      }
    }
  `)

  if (search.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }

  // Context and page template for the content
  search.data.allMarkdownRemark.edges.forEach(({ node }) => {
    const language = node.frontmatter.lang
    const locale = language !== "pt" ? `/${language}` : ""
    createPage({
      path: `/post${node.frontmatter.slug}`,
      component: blogPostTemplate,
      context: {
        slug: node.frontmatter.slug,
        lang: language,
      },
    })
  })

  // Pagination for articles
  const posts = search.data.allMarkdownRemark.edges
  const postsPerPage = 20
  const numPages = Math.ceil(posts.length / postsPerPage)
  Array.from({ length: numPages }).forEach((_, i) => {
    createPage({
      path: i === 0 ? `/articles` : `/articles/${i + 1}`,
      component: path.resolve("./src/templates/articles.js"),
      context: {
        limit: postsPerPage,
        skip: i * postsPerPage,
        numPages,
        currentPage: i + 1,
      },
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

This file is responsible for reading the *.md files and turning them into HTML pages.

First, a query is made in GraphQL to find the data in the markdown files. Then, the template file for the page for the article is associated with its context. The context is what tells Gatsby which file to show when accessing a link.

Finally, the pagination of the list of articles, with 10 items per page. The number twenty appears there because there are ten posts for each language, as the site has 2, I left the postsPerPage as 20. I know it is not the most elegant way out, but it is the one that worked for me. If I find a better one, I update this article and the repository with it.

Markdown content for languages

The front matter, a kind of header for content files, has the structure below:

---
lang: pt
title: "Lorem ipsum"
slug: "/lorem-ipsum"
date: 2020-07-11
categories: lorem
thumbnail: https://lorempixel.com/1500/900
---

## Lorem

Lorem ipsum dolor sit amet consectetuer adispiscing elit.
Enter fullscreen mode Exit fullscreen mode

Nothing special, except language identification, for later filtering. Just place them in the folder informed to receive the files in gatsby-node.js. I was careful to separate them into subdirectories for each language.

Listing the content

To list the articles, I first made a query in GraphQL to bring all the articles, according to the specifications given in the gatsby-node.js file in thecreatePages page creation function.

export const articlesQuery = graphql`
  query articlesQuery($skip: Int!, $limit: Int!) {
    allMarkdownRemark(
      sort: { fields: frontmatter___date, order: DESC }
      limit: $limit
      skip: $skip
    ) {
      edges {
        node {
          id
          excerpt
          frontmatter {
            date
            slug
            title
            lang
          }
        }
      }
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

After that, the query result is used on the page.

import React from "react"
import { graphql, Link } from "gatsby"
import { useIntl } from "gatsby-plugin-intl"

export default function Articles(props) {
  // Internationalization
  const intl = useIntl()
  const locale = intl.locale !== "pt" ? `/${intl.locale}` : ""

  // Raw query data
  const posts = props.data.allMarkdownRemark.edges

  // Filtering posts by locale
  const filteredPosts = posts.filter((edge) =>
    edge.node.frontmatter.lang.includes(intl.locale)
  )

Enter fullscreen mode Exit fullscreen mode

For more details on this file, just visit the repository I made as an example on Github. The link is at the end of this article.

Switching between languages

Nothing special here either:

import React from "react"
import { Link } from "gatsby"

export default function LanguageSelector({ label, className }) {
  const labelText = label || "Languages"
  const selectorClass = className || "language-selector"

  return (
    <div className={selectorClass} data-label={labelText}>
      <ul>
        <li>
          <Link to="/en">En</Link>
        </li>
        <li>
          <Link to="/">Pt</Link>
        </li>
      </ul>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

As the internationalization plugin works based on routes, it was enough to make a link to the route of the desired language. I did this to avoid 404 errors when changing the language on the article's single page, since the URLs of the English and Portuguese versions are different.

Conclusion

It may not be the best strategy for creating multilingual sites, but this was the one that worked for me. As I said at the beginning of this article, it was more difficult than I thought to find any help on this topic. Perhaps because it is already so common for some, they forget that are people starting who still have no idea how to do it.

I left a link to the project repository on Github down below. Feel free to add any suggestions or comments!

Links


If this article helped you in some way, consider donating. This will help me to create more content like this!

Top comments (5)

Collapse
 
ranbena profile image
Ran Byron

Great post 👍🏻

I believe you can hand over all route handling to the plugin's <Link /> component which does it automatically.

Collapse
 
cdsaenz profile image
Charly S.

Great article. I found now that the function changeLocale can be used to switch languages too.

Collapse
 
kevincittadini profile image
Kevin Cittadini

I was just looking for this topic. Thanks a lot, very interesting.

Collapse
 
lautenschlagerdev profile image
Andreas Lautenschlager

Are they any options to auto translate the page via Google Translate API?

Collapse
 
elvessousa profile image
Elves Sousa

Nope. I just made a simple base where you can do the translation yourself.