Please wait while you are redirected...or Click Here if you do not want to wait.

Gatsby i18n: A Hands-on Guide

Gatsby i18n | Phrase

Hit the ground running quickly with this Gatsby i18n tutorial.

  1. Phrase Blog
  2. Developers
  3. Gatsby i18n: A Hands-on Guide

Let’s face it, the modern React workflow can be a pain in the neck: We have routing, server-side rendering (SSR), static generation, and more. So we find ourselves tending towards a React-based framework that gives us all these functions out of the box. One of the first and most popular of these frameworks is Gatsby. While its architectural approach does have a learning curve, the framework’s maturity and large plugin ecosystem make it an attractive option among its ilk.

When it comes to internationalization (i18n), Gatsby’s robust official offerings can take some fiddling to get working (perhaps like all things Gatsby?). This guide aims to help with that: We’ll build a small demo app and localize it with the official Gatsby i18n theme and i18next, walking through the i18n step by step. Shall we?


Packages and versions used

The following NPM packages were used in the building of our demo app.

When we run gatsby new in a few minutes, the default Gatsby starter will install the following packages among others.

  • gatsby@4.12.1 ➞ all hail the great Gatsby
  • react@17.0.1 ➞ the ubiquitous UI library Gatsby is built on top of

We’ll add the following packages ourselves as we work through the article:

  • gatsby-plugin-mdx@3.12.1 ➞ integrates MDX Markdown with Gatsby
  • gatsby-theme-i18n@3.0.0 ➞ provides foundational i18n functionality for Gatsby apps
  • i18next@21.6.16 ➞ the popular i18n library provides UI string localization functions
  • react-i18next@11.16.8 ➞ provides components and hooks to easily integrate i18next with React apps
  • i18next-phrase-in-context-editor-post-processor@1.3.0 ➞ adds support for the Phrase In-Context Editor (optional, covered below)

🔗 Resource » See the package.json file in the accompanying GitHub repo for all package dependencies.

To begin (the starter app)

Our small demo app, the fictional fslivre, is a blog that celebrates the writing of F. Scott Fitzgerald.

Our demo app before localization

Let’s quickly run through how to build this puppy.

🔗 Resource » You can get the code for the app before localization from the start branch of our accompanying GitHub repo.

With the Gatsby CLI installed, we can run the following in our command line to spin up our app.$ gatsby new fslivre

This should yield a fresh Gatsby app with the official default starter packages in place.

Building the blog

A simple file-based MDX structure will do the trick for our little blog. To work with MDX files we need to install the official Gatsby MDX plugin by running the following from the command line.$ npm install gatsby-plugin-mdx

As usual, we need to add the MDX plugin to gatsby-config.js to complete its setup.gatsby-config.jsmodule.exports = { // … plugins: [ `gatsby-plugin-react-helmet`, `gatsby-plugin-image`, `gatsby-transformer-sharp`, `gatsby-plugin-sharp`, `gatsby-plugin-postcss`, `gatsby-plugin-mdx`, // … ],}

We can now add our blog content as MDX files, and pull it into our pages with GraphQL. Let’s organize our files:.├── blog/│ ├── courage-after-greatness/│ │ ├── cover.jpg│ │ └── index.mdx│ ├── the-other-side-of-paradise/│ │ ├── cover.jpg│ │ └── index.mdx│ └── …└── src/ └── pages/ ├── blog/ │ └── {mdx.frontmatter__slug}.js └── index.js

Our MDX files are standard-fare Markdown with front matter. Note the slug field in the front matter, as we’ll use it for our dynamic routing in a minute.blog/courage-after-greatness/index.mdx—title: Courage After Greatnessslug: courage-after-greatnesspublished_at: “2022-04-11″hero_image: image: “./cover.jpg” alt: “Cover of book: Tender is the Night”—Fitzgerald began the novel in 1925 after the publication of his third novel _The Great Gatsby_…

Our home page, at the root route /, can query all the MDX files and present them as post teasers.src/pages/index.jsimport * as React from “react”import { graphql } from “gatsby”import { getImage } from “gatsby-plugin-image”import Seo from “../components/seo”import Teaser from “../components/teaser”import Layout from “../components/layout”exportconst query = graphql` query BlogPosts { allMdx { nodes { frontmatter { hero_image { image { childImageSharp { gatsbyImageData( width: 150 placeholder: BLURRED formats: [AUTO, WEBP, AVIF] ) } } alt } slug title published_at } id excerpt(pruneLength: 150) } } }`const IndexPage = ({ data }) => ( <Layout> <Seo title=”Home” /> <h2>Recent writing</h2> {data.allMdx.nodes.map(post => { const image = getImage(post.frontmatter.hero_image.image) return <Teaser key={post.id} post={post} image={image} /> })} </Layout>)exportdefault IndexPage

Our Teaser component is largely presentational, so we’ll skip much of it here for brevity. Importantly, however, the header of each post in a Teaser links to a page with the full body of the post.src/components/teaser.js// …export default function Teaser({ post, image }) { return ( <article> // Image rendering code omitted for brevity <div> <h3> <Link to={`blog/${post.frontmatter.slug}`}> {post.frontmatter.title} </Link> </h3> <p> Published {post.frontmatter.published_at} </p> <p>{post.excerpt}</p> </div> </article> )}

🤿 Go deeper » You can get the full code of the Teaser component from GitHub.

The dynamic <Link> is handled by Gatsby’s routing system so that the route /blog/my-frontmatter-slug automatically hits our page, src/pages/blog/{mdx.frontmatter__slug}.js. This page attempts to find a blog by the given slug and displays its contents.

🤿 Go deeper » We’re using Gatsby’s file system route API to map our front matter slugs to our route.

src/pages/blog/{mdx.frontmatter__slug}.jsimport * as React from “react”import { graphql } from “gatsby”import { MDXRenderer } from “gatsby-plugin-mdx”import Seo from “../../components/seo”import Layout from “../../components/layout”exportconst query = graphql` query PostBySlug($frontmatter__slug: String) { mdx(frontmatter: { slug: { eq: $frontmatter__slug } }) { frontmatter { title published_at # … } body } }`const BlogPost = ({ data }) => { const post = data.mdx return ( <Layout> <Seo title={post.frontmatter.title} /> <h1>{post.frontmatter.title}</h1> <p> Published at {post.frontmatter.published_at} </p> // Image rendering code omitted for brevity <article> <MDXRenderer>{post.body}</MDXRenderer> </article> </Layout> )}exportdefault BlogPost

So when a visitor hits the route /blog/courage-after-greatness, the post with the slug courage-after-greatness is loaded, for example.

The standalone blog post page

We have a basic blog structure working, but alas, it’s all English. What if we want our site presented in other languages and locales? Well, we internationalize and localize, of course. Let’s get cooking.