Gatsby Integration

Learn how to integrate i18next with Gatsby websites for internationalization.

Integrate i18next with your Gatsby website to support multiple languages using Lengrise-managed translations, with both server-side rendering and client-side capabilities.

Prerequisites

  • Gatsby project (v4.0.0+)
  • Node.js (v16.0.0+)
  • Package manager (npm, yarn, or pnpm)
  • Translations downloaded from Lengrise

Pull Translation Files

First, you must pull your translation files from Lengrise API. These files will contain all the translated text for your application.

  1. Go to the Installation Guide and follow the steps to download your translations
  2. Place the downloaded JSON files in your project as shown in the project structure below
  3. Ensure that each language has its own JSON file named with the language code (e.g., en.json, es.json)

Integration Setup

After downloading your translation files, install the necessary i18next packages:

npm install i18next react-i18next i18next-browser-languagedetector gatsby-plugin-react-i18next --save 

Project Structure

package.jsonadd
gatsby-config.jsadd
gatsby-node.jsadd
gatsby-browser.jsadd
gatsby-ssr.jsadd
srcadd
pagesadd
index.jsadd
{language}/index.jsadd
componentsadd
layout.jsadd
LanguageSwitcher.jsadd
seo.jsadd
templatesadd
blog-post.jsadd
localesadd
en.jsonadd
es.jsonadd

Configuration Steps

1. Configure gatsby-config.js

Update your gatsby-config.js to include the i18next plugin:

module.exports = {
  siteMetadata: {
    title: `My Multilingual Gatsby Site`,
    description: `A site built with Gatsby and i18next`,
    author: `@yourname`,
    siteUrl: `https://www.yourdomain.com`,
  },
  plugins: [
    // Other plugins...
    {
      resolve: `gatsby-plugin-react-i18next`,
      options: {
        localeJsonSourceName: `locale`, // name given to locales files
        languages: [`en`, `es`],
        defaultLanguage: `en`,
        // Path to locales directory relative to the root
        path: `${__dirname}/locales`,
        // You can create a custom redirect behavior if needed
        redirect: true,
        // Add a language name for displaying in a language switcher
        i18nextOptions: {
          interpolation: {
            escapeValue: false, // not needed for react
          },
          keySeparator: false,
          nsSeparator: false,
        },
        pages: [
          {
            matchPath: "/:lang?/blog/:uid",
            getLanguageFromPath: true,
          },
          {
            matchPath: "/preview",
            languages: ["en"],
          },
        ],
      },
    },
  ],
};

2. Set up gatsby-node.js for path generation

Update your gatsby-node.js to add support for language-specific paths:

const path = require("path");

// Customize the pages for each language
exports.onCreatePage = ({ page, actions }) => {
  const { createPage, deletePage } = actions;

  // Only create language versions for pages not already handled by the plugin
  // Skip if it's already a language path
  if (page.path.match(/^\/[a-z]{2}\//) || page.context.language) {
    return;
  }

  // Delete the original page (we'll create language-specific versions)
  deletePage(page);

  // Create one page for each language
  ["en", "es"].forEach((language) => {
    const localizedPath =
      language === "en" ? page.path : `/${language}${page.path}`;

    createPage({
      ...page,
      path: localizedPath,
      context: {
        ...page.context,
        language,
      },
    });
  });
};

// If you're creating blog posts or other content programmatically:
exports.createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions;
  const result = await graphql(`
    query {
      allMarkdownRemark {
        nodes {
          id
          frontmatter {
            slug
          }
        }
      }
    }
  `);

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

  // Create blog posts pages
  const posts = result.data.allMarkdownRemark.nodes;
  const blogPostTemplate = path.resolve(`src/templates/blog-post.js`);

  // Create one page per post per language
  posts.forEach((post) => {
    const slug = post.frontmatter.slug;

    ["en", "es"].forEach((language) => {
      const localizedSlug =
        language === "en" ? `/blog/${slug}` : `/${language}/blog/${slug}`;

      createPage({
        path: localizedSlug,
        component: blogPostTemplate,
        context: {
          id: post.id,
          language,
        },
      });
    });
  });
};

3. Create a Language Switcher Component

Create a file at src/components/LanguageSwitcher.js:

import React from "react";
import { Link, useI18next } from "gatsby-plugin-react-i18next";

const LanguageSwitcher = () => {
  const { languages, language, originalPath } = useI18next();

  return (
    <div className="language-switcher">
      <ul>
        {languages.map((lng) => (
          <li key={lng}>
            <Link
              to={originalPath}
              language={lng}
              className={lng === language ? "active" : ""}
            >
              {lng === "en" ? "English" : "Español"}
            </Link>
          </li>
        ))}
      </ul>

      <style jsx>{`
        .language-switcher {
          margin: 1rem 0;
        }
        ul {
          display: flex;
          list-style-type: none;
          padding: 0;
        }
        li {
          margin-right: 1rem;
        }
        a {
          text-decoration: none;
          color: #333;
        }
        .active {
          font-weight: bold;
          color: #0066cc;
        }
      `}</style>
    </div>
  );
};

export default LanguageSwitcher;

4. Update your main Layout component

Update your layout to include the language switcher and handle language:

import React from "react";
import { useTranslation } from "gatsby-plugin-react-i18next";
import LanguageSwitcher from "./LanguageSwitcher";

const Layout = ({ children }) => {
  const { t } = useTranslation();

  return (
    <div className="layout">
      <header>
        <h1>{t("site.title")}</h1>
        <nav>
          <ul>
            <li>
              <Link to="/">{t("nav.home")}</Link>
            </li>
            <li>
              <Link to="/blog">{t("nav.blog")}</Link>
            </li>
            <li>
              <Link to="/about">{t("nav.about")}</Link>
            </li>
          </ul>
        </nav>
        <LanguageSwitcher />
      </header>
      <main>{children}</main>
      <footer>
        <p>{t("footer.copyright", { year: new Date().getFullYear() })}</p>
      </footer>
    </div>
  );
};

export default Layout;

5. Create language-specific page versions

For each page you want translated, create a file structure like:

// src/pages/{language}/index.js
import React from "react";
import { graphql } from "gatsby";
import { useTranslation } from "gatsby-plugin-react-i18next";
import Layout from "../../components/layout";
import Seo from "../../components/seo";

const IndexPage = () => {
  const { t } = useTranslation();

  return (
    <Layout>
      <Seo title={t("home.title")} />
      <h1>{t("home.welcome")}</h1>
      <p>{t("home.description")}</p>
    </Layout>
  );
};

export default IndexPage;

// This query is necessary for the i18next plugin to work
export const query = graphql`
  query ($language: String!) {
    locales: allLocale(filter: { language: { eq: $language } }) {
      edges {
        node {
          ns
          data
          language
        }
      }
    }
  }
`;

Using Translations

In Components

Use the useTranslation hook from gatsby-plugin-react-i18next in your components:

import React from "react";
import { useTranslation } from "gatsby-plugin-react-i18next";

const Welcome = () => {
  const { t } = useTranslation();

  return (
    <div>
      <h1>{t("welcome.title")}</h1>
      <p>{t("welcome.description")}</p>
    </div>
  );
};

export default Welcome;

Translations with Parameters

Pass parameters to your translations:

import React from "react";
import { useTranslation } from "gatsby-plugin-react-i18next";

const Greeting = ({ username }) => {
  const { t } = useTranslation();

  return (
    <div>
      <p>{t("greeting", { name: username })}</p>
      <p>{t("messages.count", { count: 5 })}</p>
    </div>
  );
};

export default Greeting;

In GraphQL Queries

To get translated data from GraphQL:

import React from "react";
import { graphql } from "gatsby";
import { useTranslation } from "gatsby-plugin-react-i18next";
import Layout from "../components/layout";

const BlogPage = ({ data }) => {
  const { t } = useTranslation();

  return (
    <Layout>
      <h1>{t("blog.title")}</h1>
      <ul>
        {data.allMarkdownRemark.nodes.map((post) => (
          <li key={post.id}>
            <Link to={post.frontmatter.slug}>{post.frontmatter.title}</Link>
          </li>
        ))}
      </ul>
    </Layout>
  );
};

export default BlogPage;

export const query = graphql`
  query ($language: String!) {
    locales: allLocale(filter: { language: { eq: $language } }) {
      edges {
        node {
          ns
          data
          language
        }
      }
    }
    allMarkdownRemark(
      filter: { frontmatter: { language: { eq: $language } } }
      sort: { frontmatter: { date: DESC } }
    ) {
      nodes {
        id
        frontmatter {
          title
          slug
          date(formatString: "MMMM DD, YYYY")
        }
        excerpt
      }
    }
  }
`;

Translation Examples

Your locales/en.json file should contain:

{
  "site": {
    "title": "My Multilingual Gatsby Site",
    "description": "A website built with Gatsby and i18next"
  },
  "nav": {
    "home": "Home",
    "blog": "Blog",
    "about": "About"
  },
  "home": {
    "title": "Welcome to My Site",
    "welcome": "Hello, World!",
    "description": "This is a multilingual Gatsby site."
  },
  "blog": {
    "title": "Blog Posts"
  },
  "greeting": "Hello, {{name}}!",
  "messages": {
    "count": "You have {{count}} message",
    "count_plural": "You have {{count}} messages"
  },
  "footer": {
    "copyright": "© {{year}} My Website. All rights reserved."
  }
}

And your locales/es.json file:

{
  "site": {
    "title": "Mi Sitio Gatsby Multilingüe",
    "description": "Un sitio web construido con Gatsby e i18next"
  },
  "nav": {
    "home": "Inicio",
    "blog": "Blog",
    "about": "Acerca de"
  },
  "home": {
    "title": "Bienvenido a mi sitio",
    "welcome": "¡Hola, Mundo!",
    "description": "Este es un sitio Gatsby multilingüe."
  },
  "blog": {
    "title": "Artículos del Blog"
  },
  "greeting": "¡Hola, {{name}}!",
  "messages": {
    "count": "Tienes {{count}} mensaje",
    "count_plural": "Tienes {{count}} mensajes"
  },
  "footer": {
    "copyright": "© {{year}} Mi Sitio Web. Todos los derechos reservados."
  }
}

SEO Considerations

Update your SEO component for proper language support:

import React from "react";
import { useTranslation } from "gatsby-plugin-react-i18next";
import { Helmet } from "react-helmet";
import { useI18next } from "gatsby-plugin-react-i18next";

function SEO({ description, title, children }) {
  const { t } = useTranslation();
  const { language } = useI18next();

  const metaDescription = description || t("site.description");
  const defaultTitle = t("site.title");

  return (
    <Helmet
      htmlAttributes={{
        lang: language,
      }}
      title={title}
      titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : null}
      meta={[
        {
          name: `description`,
          content: metaDescription,
        },
        {
          property: `og:title`,
          content: title,
        },
        {
          property: `og:description`,
          content: metaDescription,
        },
        {
          property: `og:type`,
          content: `website`,
        },
        {
          name: `twitter:card`,
          content: `summary`,
        },
        {
          name: `twitter:title`,
          content: title,
        },
        {
          name: `twitter:description`,
          content: metaDescription,
        },
      ]}
    >
      {children}
    </Helmet>
  );
}

export default SEO;

Resources

For more detailed information, check out these resources: