Development · July 2018
i18n with Gatsby
Taking advantage of Gatsby nodes to add internationalisation to a Gatsby project.


Nowadays, the world is our audience. When building a global product for multiple markets, Internationalisation is essential and must be catered from the get-go. English might be a safe bet for most, but there’s undeniable power in serving people content in their native language.
Coming from a Middleman background we honestly were a bit puzzled on how scattered i18n support with Gatsby was: There are some issues at Github, a plugin that requires us to duplicate code and a semi-official blog post with an approach that doesn’t statically render the strings into the html, which IMHO defeats the purpose of using a Static Site Generator. When you build, you’ll just get empty strings that get populated when the JS kicks in. Overall, adding i18n just wasn’t as smooth as it should be.
This quick article gives an alternate approach to i18n with Gatsby for those of us who need all the content to be statically rendered. It also provides two examples on how to use it: one with no external dependencies (except for gatsby source and transformer plugins) and one with React-Intl.
Creating pages for each locale
There’s one fundamental step to achieve static rendering: duplicating each page for each locale.
In this example, we’ll assume that the default language doesn’t require language in the path (i.e. If we have English (default) and Portuguese, we’ll need /about
and /pt/about
).
It’s always a good idea to centralize the locales definition, so create a locales file somewhere:
1// src/constants/locales.js23module.exports = {4 en: {5 path: 'en',6 locale: 'English',7 default: true8 },9 pt: {10 path: 'pt',11 locale: 'Português'12 }13}
Then, in gatsby-node.js
let’s create a page for each locale:
1const locales = require('./src/constants/locales')23exports.onCreatePage = ({ page, actions }) => {4 const { createPage, deletePage } = actions56 return new Promise(resolve => {7 deletePage(page)89 Object.keys(locales).map(lang => {10 const localizedPath = locales[lang].default11 ? page.path12 : locales[lang].path + page.path1314 return createPage({15 ...page,16 path: localizedPath,17 context: {18 locale: lang19 }20 })21 })2223 resolve()24 })25}
For each page, we’re basically just deleting it (so we can take over) and creating it again for each language passing the locale to the page context.
Now, if you go to http://localhost:8000/___graphql
and query the site’s pages you’ll see a duplicate for each locale:
Honestly, that’s pretty much it. Now you can use thepageContext‘slocalein any way you like. 🎉
Example A — with React-Intl
React-Intl is a very powerful i18n package with cool helpers for date relatives, currencies and whatnot. It’s my go-to package for internationalisation so this example will make use of it.
Each page will receive the pageContext object. The main idea is to get the locale from that object and pass it to a Layout component that takes care of creating and feeding the Provider.
First, create some string files. In my case they’ll be src/i18n/en.json
1{2 "hello": "Hello world"3}
and src/i18n/pt.json
:
1{2 "hello": "Olá mundo"3}
Finally, the Layout
Component:
1// src/components/Layout.js23import React from 'react'4import { IntlProvider, addLocaleData } from 'react-intl'56// Locale data7import enData from 'react-intl/locale-data/en'8import ptData from 'react-intl/locale-data/pt'910// Messages11import en from '../i18n/en.json'12import pt from '../i18n/pt.json'1314const messages = { en, pt }1516addLocaleData([...enData, ...ptData])1718const Layout = ({ locale, children }) => (19 <IntlProvider locale={locale} messages={messages[locale]}>20 {children}21 </IntlProvider>22)2324export default Layout
All that’s left is to simply use it in the pages:
1import React from 'react'2import { FormattedMessage } from 'react-intl'34import Layout from '../components/Layout'56const IndexPage = ({ pathContext: { locale } }) => (7 <Layout locale={locale}>8 <FormattedMessage id="hello" />9 </Layout>10)1112export default IndexPage
What about links?
Sure, if we use Gatsby’s Link
as-is, we’ll have some trouble navigating between pages. As the URL is our source-of-truth for the locale, if we simply link some page to /about
we’ll always be directed to the English about.
We need to make a wrapper around Link
, so we can still use <Link to="/about">
as usual, without worrying about the current locale.
Luckily, React-Intl has a very convenient injectIntl
HOC that provides us with the intl
object (that, unsurprisingly, contains a locale
key).
1import React from 'react'2import { Link } from 'gatsby'3import { injectIntl, intlShape } from 'react-intl'45import locales from '../constants/locales'67const LocalizedLink = ({ to, intl: { locale }, ...props }) => {8 const path = locales[locale].default ? to : `/${locale}${to}`910 return <Link {...props} to={path} />11}1213export default injectIntl(LocalizedLink)
Example B— with no external dependencies
Sometimes you don’t need React-Intl’s power (or don’t want to add yet another dependency bloating the project).
The bare and simplest way to load some strings would be something like
1import React from 'react'2import enMessages from '../data/index/en.json'3import ptMessages from '../data/index/pt.json'4const messages = {5 en: enMessages,6 pt: ptMessages7}8const IndexPage = ({ pathContext: { locale } }) => (9 <div>10 <h1>{messages[locale].hello}</h1>11 </div>12)13export default IndexPage
But in this example we’ll use Gatsby’s data layer to query the content with graphql.
Unfortunately, and even though I’m using Gatsby V2 in these examples, it appears that the new StaticQuery component doesn’t support query variables, so there’s a big drawback with this approach: You’ll have to query everything in the pages and pass it down to components.
Let’s assume we have a folder at src/data
. There, we’ll organize the content per folder with JSON files inside (e.g. src/data/index/en.json
).
First thing we need to do is to add two gatsby dependencies:
- gatsby-source-filesystem to query files
- gatsby-transformer-json to get the JSON’s content
1yarn add gatsby-source-filesystem gatsby-transformer-json
and add them to your plugin configuration in gatsby-config.js
1plugins: [2 // your plugins3 'gatsby-transformer-json',4 {5 resolve: `gatsby-source-filesystem`,6 options: {7 name: `data`,8 path: `${__dirname}/src/data/`9 }10 }11]
Page’s context data is also available as query variables, so just use that to filter graphql’s results on the page query:
1import React from 'react'2import { graphql } from 'gatsby'34const IndexPage = ({ data }) => (5 <div>6 <h1>7 {data.file.childIndexJson.hello}8 </h1>9 </div>10)1112export const query = graphql`13 query Home($locale: String) {14 file(name: { eq: $locale }, relativeDirectory: { eq: "index" }) {15 childIndexJson {16 hello17 }18 }19 }20`2122export default IndexPage
Now that we’re using Gatsby’s data layer, we can even take advantage of its powerful transformation features. We can, for instance, load different images per-language and still be able to use gatsby-image:
1import React from 'react'2import { graphql } from 'gatsby'3import Img from 'gatsby-image'45const AboutPage = ({ data }) => {6 const { image } = data.file.childAboutJson7 return (8 <div>9 <Img resolutions={image.source.childImageSharp.resolutions} />10 </div>11 )12}1314export const query = graphql`15 query About($locale: String) {16 file(name: { eq: $locale }, relativeDirectory: { eq: "about" }) {17 childAboutJson {18 image {19 childImageSharp {20 resolutions(width: 125, height: 125) {21 ...GatsbyImageSharpResolutions22 }23 }24 }25 }26 }27 }28`2930export default AboutPage
You can also create a Layout component that accepts the locale (same as above) but then creates a React Context and exports its Consumer. Implementing a LocalizedLink
with that would be trivial. Take a look at the repo in the end of the article for an example.
Conclusion
Even though Gatsby doesn’t support i18n out-of-the-box, its powerful lifecycle APIs make it really easy to implement it (in less than 5 minutes).
All the examples here were quickly built for the purpose of the article and should be adapted to your project’s reality. Understanding Gatsby’s nodes and the process of duplicating pages for each locale is, after all, the fundamental of this approach.
Take a look at the repo with examples built using both approaches:
https://github.com/pbrandone/gatsby-i18n