Gatsby: Add an official guide for internationalizing websites with Gatsby

Created on 4 Feb 2018  ·  74Comments  ·  Source: gatsbyjs/gatsby

Seeing the reactions to my comment on an other issue, I decided to open this issue.

I think that i18n is much harder than it should be. I could not find any official documentation or plugin for internationalizing content on Gatsby-made websites. I came across jsLingui, which seems to solve most of the issues, but there are still no guides about maintaining e.g. markdown files/pages in different languages.

documentation

Most helpful comment

Hey guys, it's been almost a year 😅

I recently released new gatsby plugin gatsby-plugin-intl that easily makes your gatsby website as an internationalization framework out of the box.

DEMO: https://gatsby-starter-default-intl.netlify.com

  • Out of box internationalization-framework powered by react-intl

  • Support automatic redirection based on the user's preferred language in browser

  • Support multi-language url routes in a single page component. This means you don't have to create separate pages such as pages/en/index.js or pages/ko/index.js.

  • As some of you guys suggested above, now it bundles only current language during build time.

Also, I want to mention that many of i18n examples / starters are actually rendered on client side. The best way to check if the app is rendered as SSR is viewing the source code and checking whether the localized texts exist. Please double check this matter when you internationalize your gatsby website for SEO.

All 74 comments

There is this article about using i18next with GatsbyJS, which is the most advanced method so far for me.

But I don't feel like i18next is the "static way".

About the blog post, I had these questions/reservations:
https://twitter.com/semdubois/status/930389055388508160

There is https://github.com/angeloocana/gatsby-plugin-i18n but it has several limitations and it is not receiving much activity/attention. It might help to move it within the Gatsby repo. I too would love a proper consolidated solution.

I stumbled upon js lingui as well and it seems promising, especially with v2 just out.

Im also trying to figure this out. Using the i18next method on the post is the most convenient and feels intuitive, but I am left with two questions....

  1. how can I incorporate different markdown files for languages like in the gatsby-plugin-i18n solution?

  2. Does this completely forego static rendering of the content?

FYI @angeloocana

I'll write a short summary of how we handle it at the moment using react-intl. This app is not in production yet, so we might still find some issues with this setup, however it seems to work fine so far.

We keep almost all of our content (which was migrated from our Wordpress blog) in Contentful. We don't use its translation features, but instead we got one space (kind of a project or folder) per language. We're using gatsby-source-contentful plugin to fetch this data, however, earlier we were fetching and converting this data to JSON files ourselves and used gatsby-source-filesystem plugin (we used folder structure like /en/blog/..., /de/blog/...), so it doesn't really matter if one is using Contentful or not, as long as each node knows its locale.

We also have some text like button labels, some links or static content that doesn't come from Contentful, but is translated in Transifex instead and synced to JSON files that are stored in the repo. For this part we needed to use some i18n library and decided to use react-intl, just because I already know it and I know it handles date and number formatting as well. Here's how we set it up: https://github.com/gatsbyjs/gatsby/issues/3830#issuecomment-362710469. We're then using e.g. intl.formatMessage when generating meta tags and <FormattedMessage />, <FormattedDate /> etc. components in templates.

@szimek If i understand you correctly then you have react-intl handle the component text translation while the posts are in hardcoded routes under the pages directory?

That would mean that the hardcoded routes are the only ones that are statically rendered? And the i18n inter component translations are dynamically rendered?

@deltaskelta I'm not sure I understand your question.

We don't have any custom client-side routing (as described e.g. here) at the moment, only statically generated pages. When generating a post page (we also generate locale-specific and paginated index and category pages using slightly modified version of gatsby-pagination plugin), we're using the following code:

posts.edges.map(({ node }) => {
  const id = node.contentfulid;
  const locale = node.node_locale;

  return createPage({
    path: `/${locale}/blog/posts/${id}`,
    layout: locale,
    component: path.resolve('./src/templates/post-page.jsx'),
    context: {
      id,
      locale,
    },
  });
});

Even if you have JS disabled, all content, including meta tags, is rendered in the correct language.

On our side, we're using i18next with some tweaks

The main principles are the following:

  • Locales are available as .json files and simply moved into the /dist/ directory during build.
  • We're working on one single page that will produce as many static versions as we have languages, using onCreatePage (in gatsby-node.js).
  • Pages informations (e.g. pathname) are centralized in a single file.

Locales

Translations have been mostly grouped by page, with one namespace (= JSON file) per page + one global file (for shared texts such as header / footer).

|- src/
  |- locales/
    |- en/
      |- foo.json
      |- bar.json
    |- fr/
      |- foo.json
      |- bar.json

Sadly, there is no hot-reloading on locales modifications 👎

i18next

i18next is initialized with the following configuration:

import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import { reactI18nextModule } from "react-i18next";
import config from "config";  // Our custom configurations env-specifics

const NAMESPACES = [
  "foo",
  "bar",
  ...
];

export default function createI18n() {
  const options = {
    fallbackLng   : false,
    whitelist     : ["en", "fr"],
    ns            : NAMESPACES,
    debug         : config.debug,
    interpolation : {
      escapeValue : false
    },
    react         : {
      wait : true
    },
    backend       : {
      loadPath : `${config.pathPrefix}locales/{{lng}}/{{ns}}.json`
    },
    parseMissingKeyHandler : () => "",  // Display an empty string when missing/loading key
  };

  return i18n
    .use(Backend)
    .use(reactI18nextModule)
    .init(options);
}

Pages informations

Before generating all the i18n versions of our pages, we need to know some informations that we grouped in a pagesInfos.js file:

module.exports = {
  index : {
    id          : "index",
    namespace   : "home",
    path        : {
      fr : "/",
      en : "/en/"
    }
  },
  projects : {
    id          : "projects",
    namespace   : "projects",
    path        : {
      fr : "/nos-clients/",
      en : "/en/our-clients/"
    }
  },
  // etc...

Where the keys are the pages filenames, and the namespaces are the locales filenames. They can be differents 🚨

In this case:

|- src/
  |- pages/
    |- index.js
    |- projects.js
  |- locales/
    |- en/
      |- home.json
      |- projects.json
    |- fr/
      |- home.json
      |- projects.json

And where path are the pathnames of our future versions (languages) of our pages.

Build pages

With the same example as above, our goal here is to build a FR + EN version of the home and project pages.

In order to make it happen, we created a dedicated function:

/**
 * Generate a custom page informations
 * @param  {Object} defaultInfos  Default informations generated by Gatsby
 * @return {Object}               Customized page object
 */
function generatePagesInfos(defaultInfos) {
  const pageId = defaultInfos.jsonName.slice(0, -5);  // NOTE: Get pageId from "pageName.json"
  const pageInfos = pagesInfos[pageId];

  const pageFR = {
    ...defaultInfos,
    context : {
      pageId      : pageInfos.id,
      namespace   : pageInfos.namespace,
      language    : "fr"
    },
    path : pageInfos.path.fr
  };

  const pageEN = {
    ...defaultInfos,
    context : {
      pageId      : pageInfos.id,
      namespace   : pageInfos.namespace,
      language    : "en"
    },
    path : pageInfos.path.en
  };

  return [pageFR, pageEN];
}

This helper will then be used during the onCreatePage hook, selecting each page via a regex:

exports.onCreatePage = async ({ page, boundActionCreators }) => {
  const { createPage, deletePage } = boundActionCreators;

  return new Promise((resolve, reject) => {

    if (page.path.match(page.path.match(/^\/$/))) {
      const i18nPages = generatePagesInfos(page);
      deletePage(page);                         // Remove old default page
      i18nPages.map(page => createPage(page));  // Create custom i18n pages
    }

    if (page.path.match(/^\/projects\/?$/)) {
      const i18nPages = generatePagesInfos(page);
      deletePage(page);
      i18nPages.map(page => createPage(page));
    }

    // etc...

    resolve();
  });
}

We now have two versions of each page, with a custom pathname (from our pages informations file). You may have notice that we are passing a language information to each page via pathContext. This value will be used on each page to display the right language.

Display the right language

We're using React class for pages, with the following decorator automatically know the language of the current page and update i18n if needed:

import React from "react";
import PropTypes from "prop-types";
import { i18n } from "context";    // Our custom context

/**
 * @returns {React.PureComponent} Component with locales as proptypes
 */
export default function setLanguageFromPage() {

  return WrappedComponent => (
    class extends React.PureComponent {

      static propTypes = {
        pathContext : PropTypes.shape({
          language : PropTypes.string.isRequired
        })
      }

      componentDidMount() {
        const currentLanguage = i18n.language;
        const pageLanguage = this.props.pathContext.language;

        // First request
        if (!currentLanguage) {
          i18n.language = pageLanguage;
        }

        // Only update on language change
        if (currentLanguage !== pageLanguage) {
          i18n.changeLanguage(pageLanguage);
        }
      }

      render() {
        return <WrappedComponent {...this.props} />;
      }

    }
  );

}

Then call it on the pages:

@setLanguageFromPage()
export default class ProjectsPage extends React.PureComponent {
// ...

Pfew, all you have to do now is using the translate function of i18next.

Conclusion

👍 A single source file that generate as many versions as needed durig the build
👍 Easy pages output management
👍 Some efforts at first, but then everything is simple

👎 No hot-reload for locales

I feel like it's not really the "static way of life" ... But that's the best we managed to get for now.

I would love to see what you think about this, and how you guys manage this.

PS. Poke @szimek, that's what I wanted to show you this some days ago :)

I’ve been using @angeloocana’s https://github.com/angeloocana/gatsby-plugin-i18n + React-intl and it’s not as complicated as the methods above, but there are some limitations (see repo issues) and I’m not so happy about React-intl. would love to give a try to @tricoder42’s https://github.com/lingui/js-lingui, just haven’t had time.

@monsieurnebo what is the goal of running deletePage right before createPage? I like your solution and I have tried to implement it, but I have some errors.

  • I either get incorrect pathContext.language without using deletePage

  • or I get a build error when I include deletePage like your example. (it says TypeError: Cannot read property 'id' of undefined when the build gets to the run graphql queries stage)

This is the best solution I have seen so far.

@deltaskelta Glad to see you like it!

deletePage is used to cancel the creation of the default page by Gatsby. If you don't add this, you will get your custom pages, and the default one.

Check your public directory with and without this line, you will spot the difference ;)

About your errors, it's difficult to guess without code. Could you make a repository with your code? I would take a look a it then.

EDIT: Would you be interested by an example ?

Oh it was unrelated to the deletePage call. It was an issue of not copying the pages object since I had modified your example. I always stumble with object copying after being away from js for a while ;)

@monsieurnebo I have been playing around with your solution and it seems that you still need javascript to load the locales from the json files is that correct? On top of that it seems that all the components that are wrapped with the react-i18next HOC need javascript to render anything in the component...

Can you confirm if this is how it is working on your end?

EDIT: btw I missed your mention of an example, if you have the time I would love to see a full example.

@deltaskelta I will make an example when I have some free time :)

@monsieurnebo can you confirm that your method does not statically render the strings into the html and requires javascript?

Yup, that's why it's not totally the "static way of life" 😐

I would love a plugin that generate static text instead.

hmm I see. A static render would be the way I need to go...

I was thinking, since the contexts are already passed with the language name for most of the pages in your gatsby-node then it wouldn't be tooo much harder to just ask for they messages keys in the graphql queries for each page and pass them in that way, but I would rather use an i18n tool all the way through if possible...

@szimek how is react-intl rendering the translations on the build step and not using any js?

As mentioned, as far as I can see Gatsby-plugin-i18n generates static text. Did you check its example?

@deltaskelta Well, all translations are available during build time (that's why I'm using multiple layouts), so it "just works" ™️ ;) I just hope it keeps working in v2... I can prepare a sample app if you want. I haven't really looked into gatsby-plugin-i18n yet, because I'm working with Contentful, not files.

I haven’t read the details but there’s a discussion (or rather a monologue) about the gatsby-plugin-i18n + contentful combo here : https://github.com/angeloocana/gatsby-plugin-i18n/issues/31

@szimek I would be very interested in an example. @sedubois I know gatsby-plugin-i18n also generates static text but I don't see where the magic happens that is missing in i18next in order to generate totally static files....

I think I might see why now...are you passing react-intl the messages through pathContext in gatsby-node during the render?

EDIT: if this is the case (which makes sense), then the i18n library used is "somewhat" irrelevant because one is passing messages through context and rendering statically which is a pure gatsby setup and the i18n lib is only handling special cases such as dates and plurals with js.

Upon further testing of both i18next and react-intl it seems that the translate HOC of i18next requires javascript in order to load, so even if messages are passed via context and rendered statically, any use of the translate HOC will render the whole component needing javascript in order to run.

react-intl's FormattedMessage component, on the other hand, renders a default message (which can be based off of the context passed to it) and renders statically into the html on build.

By design I would think that the more natural integration of i18n would be with react-intl if you want to achieve statically rendered translations in HTML. If I have misunderstood the flow you guys are using please correct me

@mattferderer had the right idea here, but I think it needs a little tweaking. https://github.com/gatsbyjs/gatsby/issues/3830#issuecomment-362715706

layouts are rendered before pages, so without creating multiple layouts there is no way to pass messages through context, from the createPages function (correct me if I'm wrong here). So then, in order to make i18n easiest I would think that the layout should just call children() and then the different layouts per language effect would be accomplished by having gatsby-node create different path-per-language index pages from pages/index which can be passed messages via context

EDIT: sorry for the ramble but it all just became clear and I had to record it down somewhere

EDIT2: I am definitely wrong above, headers and footers need to go in the layout, I just don't know how to get the messages to them without creating multiple layouts. The only other way I can think would be to get into splitting urls and regexing for locale...but that feels like a hacky thing to do

@KyleAMathews with v2 having only one layout, how can we pass data such as i18n messages to the root layout component through context based on the path or an array of language keys?

If this could be done, then it would be easy to implement i18n, but I can't see how to do it without making multiple layouts

@deltaskelta Here's an example of what we're doing: https://github.com/szimek/gatsby-react-intl-example. It shows how to render individual posts, how to generate index page for each locale, how to use react-intl components, how to use react-intl injectIntl HOC to set a title (or any other meta tags) for index pages using intl.formatMessage helper etc.

The generated pages are:

  • /en
  • /en/hello-world
  • /pl
  • /pl/witaj-swiecie

In the real app we're using a modified version of gatsby-pagination, because the original version doesn't not support layout option. We also set post_id field for each post that allows us to find translations of the same post, e.g. in the case of this demo app, both posts would have the same post_id.

BTW. I just realized that we'll most likely need to generate separate sitemaps for each language, so that Swiftype (search engine we use) knows what pages we got.

@deltaskelta briefly you'd have page components for each language and then have a layout component per language. Here's one way you could do this.

// French
import React from 'react'
import FrenchLayout from '../components/layouts/french'
import ImportantPage from '../components/pages/important-page'

export default ({ data }) => (
  <FrenchLayout>
    <ImportantPage {...data} />
  </FrenchLayout>
)

// French query here
// English
import React from 'react'
import EnglishLayout from '../components/layouts/english'
import ImportantPage from '../components/pages/important-page'

export default ({ data }) => (
  <EnglishLayout>
    <ImportantPage {...data} />
  </EnglishLayout>
)

// English query here

@KyleAMathews These files are templates, right? Does it mean that if I have 3 page types and 7 languages, I'd need 21 templates? :)

The above is the most optimized way of doing it. If each layout component isn't that different, you could combine them into one layout component and then switch layout depending on which language is active.

I haven’t read the details but there’s a discussion (or rather a monologue) about the gatsby-plugin-i18n + contentful combo here : angeloocana/gatsby-plugin-i18n#31

@sedubois, haha, yea. Summary: got it working and included gatsby-starter-contentful-i18n starter repo in Gatsby docs via this PR: https://github.com/gatsbyjs/gatsby/pull/4138

Interested in the other solution above, especially how it compares to the community plugin re: SEO, etc.

@mccrodp Your solution looks pretty similar to mine, the main difference is that gatsby-plugin-i18n doesn't require you to explicitly pass the layout option to createPage, but does it for you behind the scenes. However, it's still using multiple layouts ;)

I've gone with @KyleAMathews suggestion and made a components/Layout that uses an i18n lib and deleted my gatsby layout folder entirely. This way, in gatsby-node I can make pages for my locales and they have access to everything a page has access to, including paths.

I can then pass locale directly to the layout component which passes it to i18n.

It is minorly inconvenient to have to wrap each page level component with the layout, but it has cleared up so much confusion and simplified my code a lot.

Hey @deltaskelta, do you have an example of your solution? Would love to see if anything can be learned from it to push upstream to the community i18n plugin. Thanks.

my whole project is in a messy state right now but I think I can lay out the basics...

  1. No layout - because (if I remember correctly), the layout comes into play before paths or something else that was very important, which prevented me from giving it the proper messages objects...

  2. Feed the proper messages and locale in the gatsby-node through context...

exports.onCreatePage = ({ page, boundActionCreators }) => {
  const { createPage, deletePage } = boundActionCreators;

  if (page.path.includes('404')) {
    return; // no need for localized 404 pages
  }

  return new Promise(resolve => {
    // if it is not the app page then I need localized static pages
    const pages = localizedPages(page);
    deletePage(page);
    pages.map(page => createPage(page));

    resolve();
  });
};

// to be passed to the localized pages so it can calculate the matchPath
const getMatchPath = lang => {
  return `${locales[lang]['path']}/app/:path`;
};

// this is a helper function that makes pages in each language.
const localizedPages = (page, matchPathFunc) => {
  var pages = [];
  Object.keys(locales).map(lang => {
    const path = locales[lang]['path'] + page.path;

    pages.push({
      ...page,
      path: path,
      matchPath: matchPathFunc ? matchPathFunc(lang) : undefined,
      context: {
        locale: lang,
        messages: locales[lang],
        pathRegex: `/.pages${page.path}./` // so pages can match markdown in their dir
      }
    });
  });

  return pages;
};
  1. Now make a global layout component that needs to be called on every page level component...
// this is the main entrypoint for the layout to the site
const GlobalLayout = ({ locale, children, path }) => {
  const theme = getTheme();
  return (
    <MuiThemeProvider theme={theme}>
      <CssBaseline>
        <IntlProvider locale={locale} messages={locales[locale]}>
          <div>
            <Header locale={locale} messages={locales[locale]} path={path} />
            {children}
          </div>
        </IntlProvider>
      </CssBaseline>
    </MuiThemeProvider>
  );
};
  1. Call the global layout component with your page level components which have the proper locale and messages passed from context
const BlogPost = ({ data, pathContext, location }) => {
  const { locale } = pathContext;
  return (
    <GlobalLayout locale={locale} path={location.pathname}>
      <FullWidth>
        <h1>{data.markdownRemark.frontmatter.title}</h1>
        <h3>{data.markdownRemark.frontmatter.date}</h3>
        <div dangerouslySetInnerHTML={{ __html: data.markdownRemark.html }} />
      </FullWidth>
    </GlobalLayout>
  );
};

I have to clean up and reorganize this code because I kind of just did it quick to prove that it could work and I am going to revisit it later...I hope this gives you the gist of it

I created a gatsby starter that makes use of js-lingui to translate messages:
https://github.com/dcroitoru/gatsby-starter-i18n-lingui

In the real app we're using a modified version of gatsby-pagination, because the original version doesn't not support layout option. We also set post_id field for each post that allows us to find translations of the same post, e.g. in the case of this demo app, both posts would have the same post_id.

@szimek is there any chance you could share your modified gatsby-pagination? I'd be very keen to see it as I'm having a similar issue myself.

@martynhoyer My patch's been merged, so I switched back to the original version of gatsby-pagination. What issue are you having?

Hi @sgoudie
I´m using this tutorial: https://www.gatsbyjs.org/blog/2017-10-17-building-i18n-with-gatsby/ but I can´t find any of my locales/{lang}/*.json. Does anyone have a clue?
2018-04-25 12_04_13-o intermedium agora e banco inter

My configuration:
gasbty-node.js
```javascriptexports.onPostBuild = () => {
console.log('Copying locales')
fs.copySync(
path.join(__dirname, '/src/locales'),
path.join(__dirname, '/public/locales')
)
}

```

Add

exports.onPostBootstrap = () => {
    console.log("Copying locales");
    fs.copySync(
        path.join(__dirname, "/src/locales"),
        path.join(__dirname, "/public/locales")
    );
};

to gatsby-node.js

@ThiagoMiranda I was facing the same issue then I realised that gatsby develop doesn't call onPostBuild, only gatsby build calls it. onPostBootstrap is called each time.

Anyone knows how to create https://moz.com/learn/seo/hreflang-tag in the layouts ?

@RobinHerzog We're creating them in templates using Helmet. They are specific to page type, so at least in our case it didn't make sense to create them in a layout.

@szimek thanks for reply. I understand but in my case would be interesting to have it in the layouts.

<link rel="alternate" href={Route['en-us'][this.props.data.prismicDocument.data.group]} hreflang="en-us" /> <link rel="alternate" href={Route['fr-fr'][this.props.data.prismicDocument.data.group]} hreflang="fr-fr" />

For the moment, is like you said, copy those lines in every templates.

I'm only starting developing with React/JavaScript but all of what I've seen to support i18n was too complex. Here is my own work for most common usage : wise-starter

Live reload, SEO friendly and Default languages doesn't use the key in url.
All .js pages are generated for all languages.
All layouts and .md must be created for all languages to prevent errors.
LangSelect and Link component are i18n smart.

If you can help me and explain to me how I could improve my code and style I would be grateful.

@Tom-Pichaud, I believe that since you are not passing messages through to the page components, they will not be statically rendered there.

The markdown i18n setup in gatsby-node looks to be similar to what people have been doing here, but I am curious if you are getting static rendering with javascript disabled on your page components?

Yes I do get static rendering, that was my aim anyway, i18n-react do the trick !

@TomPichaud would it be possible for you to share how the i18n-react you mentioned is better than js-lingui?

One thing I'm not quite getting is the actual need for external packages to load the translated messages (other than pluralisation and relatives, probably).

For a simple site with static content I'm just duplicating the pages for each language onCreatePage and pass the locale down to context:

// some file with the locales
const locales = {
  en: {
    path: 'en',
    default: true,
  },
  pt: {
    path: 'pt',
  },
}
// gatsby-node.js
exports.onCreatePage = ({ page, boundActionCreators }) => {
  const { createPage, deletePage } = boundActionCreators

  return new Promise(resolve => {
    deletePage(page)

    Object.keys(locales).map(lang => {
      const localizedPath = locales[lang].default
        ? page.path
        : locales[lang].path + page.path

      return createPage({
        ...page,
        path: localizedPath,
        context: {
          locale: lang,
        },
      })
    })

    resolve()
  })
}

Then, on the actual page, I use the locale in the context to query the content using graphql's filter.

Let's say I have the home content in /data/home/en.js and /data/home/pt.js:

import React from 'react'

const IndexPage = ({ pathContext: { locale }, ...props }) => {
  const { childHomeJson: data } = props.data.allFile.edges[0].node

  return <div>{data.hello}</div>
}

export const query = graphql`
  query HomeContent($locale: String) {
    allFile(filter: { name: { eq: $locale } }) {
      edges {
        node {
          childHomeJson {
            hello
          }
        }
      }
    }
  }
`

export default IndexPage

works fine with netlifyCMS (albeit a bit verbose until they support i18n) and images in the JSON files (tho we need to create a new NodeField with the relative path so Gatsby's filesystem gets it)

Isn't this enough for most cases?

I'm still testing and haven't used any of this in production, but I'm thinking of using react's context API for the locale to solve some pending issues like localised links

@pbrandone This seems to be a great approach to me. I think that something similar should be documented officially.

Thanks for all the inputs, the amount of ideas discussed here clearly marks the demand for well-documented i18n support.

@pbrandone Does it mean you have to explicitly specify all keys used by any child components in IndexPage in this query and pass translations to all components via props?

Also, I use pluralisation rules and relative dates, so I have to load additional locale specific data anyway :/

However, I agree that it would be great to have an official documentation how to do i18n without any library for simple cases and then how to do it with the most popular libraries.

@szimek well, in this case yes.

It's very easy to add react-intl (or any other i18n library) tho:

// in src/components/layout/index.js

import React from 'react'
import { IntlProvider, addLocaleData } from 'react-intl'

// Locale data
import enData from 'react-intl/locale-data/en'
import ptData from 'react-intl/locale-data/pt'

// Messages
import en from '../../data/en.json'
import pt from '../../data/pt.json'

const messages = { en, pt }

addLocaleData([...enData, ...ptData])

const Layout = ({ locale, children }) => (
  <IntlProvider locale={locale} messages={messages[locale]}>
    {children}
  </IntlProvider>
)

export default Layout

and then on the pages:

import React from 'react'
import { FormattedMessage } from 'react-intl'

import Layout from '../components/layouts'

const IndexPage = ({ pathContext: { locale } }) => (
  <Layout locale={locale}>
    <FormattedMessage id="hello" />
  </Layout>
)

export default IndexPage

But then you wouldn't be able to use multiple JSON files (e.g. per page), nor having all the CMS data in those files to query and transform with the power of graphql.
With the graphql approach we could, for instance, have some keys with paths for images in those JSON files to load different images per locale and still be able to use gatsby-image on them.
And then add netlify CMS to edit those JSON files 😃

Haven't properly looked at gatsby v2 but apparently there's a StaticQuery component that allows us to query in child components (someone correct me if I'm wrong!)

If that's the case, we could create a React Context to make the locale available anywhere and then query the necessary keys in each component with the locale filtering

@pbrandone you are right, it does statically render that way. I remember testing it in the past and failing, but that was before I had a decent grasp of how gatsby works, I might have had my react-intl setup in gatsby browser which seems like it might not render without javascript. My solution now looks just like yours

@KyleAMathews I'm trying to update our page to Gatsby to v2 and have an issue with our react-intl setup and graphql queries.

I previously explained how I use language-specific layouts to load language data in Gatsby v1 - https://github.com/szimek/gatsby-react-intl-example. In Gatsby v2 I had an idea to replace these layouts with language-specific page components. I'd have a language-agnostic src/templates/Post.js component and then language-specific components like src/templates/Post.en.js, src/templates/Post.de.js that would only load language data and render the language-agnostic component.

In your previous comment (https://github.com/gatsbyjs/gatsby/issues/3853#issuecomment-367115380) you showed an example where each page component has language specific query.

The problem with it is that when calling createPage, I'm passing names of these language-specific components (e.g. src/templates/Post.en.js) as component option, but the graphql query is in the language-agnostic component, because it's __exactly the same for all languages__ (it depends on locale, but I'm passing it in context). I'd like to avoid repeating exactly the same query in all these language-specific components.

Any ideas how to solve it? Can I extract this query to a variable? When I tried it, Gatsby complains about query and fragment names being the same...

I recently added a default Gatsby starter with features of multi-language url routes and browser language detection. (demo)

gatsby-starter-default-intl

Features:

  • Localization (Multilanguage) provided by react-intl.

  • Automatic redirection based on user's preferred language in browser provided by browser-lang.

  • Support multi-language url routes within a single page component. That means you don't have to create separate pages such as pages/en/index.js or pages/ko/index.js.

  • Based on gatsby-starter-default with least modification.

@wiziple Thanks! It looks really interesting. I had no idea that you can do something like this: https://github.com/wiziple/gatsby-starter-default-intl/blob/master/src/i18n/withIntl.js#L38 ;) Hopefully, it still works in Webpack 4...

Is it possible to load locale data in the same way here https://github.com/wiziple/gatsby-starter-default-intl/blob/master/src/i18n/withIntl.js#L6? We support 6 (soon 7 languages), so it would be great if I could load only the one that I'm building the page for. it's no big deal if it's not possible - fortunately, these locale data files are relatively small.

I'll also have to look into how you generate these pages, because in my case not every page is translated into all languages (there's no single "source" language), so the solution with onCreatePage will probably not work in my case.

Hopefully, this will solve my problem with the same graphql query in every language-specific page component.

@szimek
The website I manage has 14 languages and each language file is 12-15 KB. I'm pretty sure we need to provide the right language for each language router at the build time in order to generate SEO data. So I'm not sure how I can handle this without providing all languages.

I understand that sometimes it is hard to provide every page translated into all languages. You might be able to solve this by providing some exception on onCreatePage in gatsby-node.js. In my case, I just simply solved by not offering translated language regardless of the language router. 😆 You can find showcase website on production from the starter README.md and check its performance.

@wiziple Thank you so much!

I used your withIntl component with dynamic require trick for translations (I have no idea if there are any disadvantages to using it) and it seems to work great. It solved the problem I was struggling with - how to handle the same graphql query in multiple language-specific page components - by having a single page component for all languages.

@wiziple thanks for the repo share. Got me on the right path 😄 🎉

lingui seems to be a better alternative. I don't think @dcroitoru got proper recognition for a great example. Just needs a little love to push it to Gatsby 2.0

I agree that Lingui is indeed really nice, although still needs a complete starter, with the latest version of Gatsby but also Lingui. The mentioned starter is unofficial and was missing some features last time I checked (e.g using a loader to run lingui compile on the fly). Lingui's author @tricoder42 said that he would provide documentation when Lingui v3 is out (which appears to be soon).

NB: I noticed that my need for an i18n library decreased after integrating a CMS (DatoCMS), but I still need Lingui for some strings that don't find their place in the CMS and also for pluralization and possibly other things later so I definitely want to keep it in my codebase.

Anyhow in my case the existence of gatsby-plugin-i18n made things quite confusing as it is non-maintained, has a confusing name, and draws the attention away from these other really nice solutions like js-lingui and CMSes which I then took a while to figure out and assemble together.

I have made two internationalising examples with react-intl integration

First example is focused on bundling only current translations in js chunks (something I couldn't find in other plugins that I checked).

Second example focus on using dynamic queries to provide only requested translations for given page/language combination.

Hopefully, this examples would be useful to someone.

Also made a quick medium post (and forgot to post it here) with pretty much what's in https://github.com/gatsbyjs/gatsby/issues/3853#issuecomment-395432693 (albeit a bit more in depth).

https://blog.significa.pt/i18n-with-gatsby-528607b4da81 for anyone who's interested

Old issues will be closed after 30 days of inactivity. This issue has been quiet for 20 days and is being marked as stale. Reply here or add the label "not stale" to keep this issue open!

Hey guys, it's been almost a year 😅

I recently released new gatsby plugin gatsby-plugin-intl that easily makes your gatsby website as an internationalization framework out of the box.

DEMO: https://gatsby-starter-default-intl.netlify.com

  • Out of box internationalization-framework powered by react-intl

  • Support automatic redirection based on the user's preferred language in browser

  • Support multi-language url routes in a single page component. This means you don't have to create separate pages such as pages/en/index.js or pages/ko/index.js.

  • As some of you guys suggested above, now it bundles only current language during build time.

Also, I want to mention that many of i18n examples / starters are actually rendered on client side. The best way to check if the app is rendered as SSR is viewing the source code and checking whether the localized texts exist. Please double check this matter when you internationalize your gatsby website for SEO.

Hey guys, it's been almost a year 😅

I recently released new gatsby plugin gatsby-plugin-intl that easily makes your gatsby website as an internationalization framework out of the box.

DEMO: https://gatsby-starter-default-intl.netlify.com

  • Out of box internationalization-framework powered by react-intl
  • Support automatic redirection based on the user's preferred language in browser
  • Support multi-language url routes in a single page component. This means you don't have to create separate pages such as pages/en/index.js or pages/ko/index.js.
  • As some of you guys suggested above, now it bundles only current language during build time.

Also, I want to mention that many of i18n examples / starters are actually rendered on client side. The best way to check if the app is rendered as SSR is viewing the source code and checking whether the localized texts exist. Please double check this matter when you internationalize your gatsby website for SEO.

Hey @wiziple thanks so much for it, I was going crazy finding a solution for localize Gatsby.
Maybe I didn't get the point but, do you have ALL the strings of a language in just one file?
Would it be possible to split the JSON of each language in more files, maybe using the same structure of components?

@cant89 I see your point, but It's currently not possible without changing the plugin code. Or you can make a script that parses src directory and grab all component language files. Merge into one JSON then hook into gatsby develop or gatsby build.
Once you get all JSON files and merge as a nested object, you can also convert it as a flatten object.
https://github.com/yahoo/react-intl/wiki/Upgrade-Guide#flatten-messages-object

We have a good example setup for i18n. https://github.com/gatsbyjs/gatsby/tree/master/examples/using-i18n. We don't really have an opinion on i18n frameworks. Just pick one to your liking.

We have a good example setup for i18n. https://github.com/gatsbyjs/gatsby/tree/master/examples/using-i18n. We don't really have an opinion on i18n frameworks. Just pick one to your liking.

Cool, thanks, Im gonna try!
Anyway the link on the readme is broken, probably you mean https://using-i18n.netlify.com/ ?

@wardpeet Does your example generate static translated strings (on build time) ? Or does it generate the text during runtime?

@monsieurnebo looks like build time

@cant89 we're still updating the dns so it's up soon for now using-i18n.netlify.com/ is the correct link.

@monsieurnebo it's at build time. It creates a copy of your website for each language so everything can be statically build. This means your website stays fast as everything is just a .html.

not sure where else to ask this but a bit relevant. do any of these plugins support gatsby's pathPrefix?

i.e. 
// gatsby-config.js

modules.exports = {
    pathPrefix: 'bar'
}

https://foo.com => https://foo.com/bar

but now my language locales will now be https://foo.com/bar/de-DE/
when I think I would prefer it be https://foo.com/de-DE/bar if that makes sense.

Hmm interesting, I think the former makes usually more sense though as the pathPrefix is kind of making your domain to be domain.com/prefix so changing the root when you have installed Gatsby in a subdirectory, if you don't install it to a subdirectory you don't need it, if you use a subdirectory changing the prefix to be after the language would break it..

Now the question comes up, why are you using the pathPrefix in the first place?

Ref: pathPrefix docs

Hi,

Most of the discussions here are about how to i18n a gatsby site. But there's a difference between having a POC working, and having an optimized production ready system.

If you are interested to read more about code splitting and i18n files, and why most solutions in this thread are not optimized, you'll find this issue useful

Was this page helpful?
0 / 5 - 0 ratings

Related issues

KyleAMathews picture KyleAMathews  ·  97Comments

TuckerWhitehouse picture TuckerWhitehouse  ·  69Comments

jp887 picture jp887  ·  98Comments

antoinerousseau picture antoinerousseau  ·  139Comments

cusspvz picture cusspvz  ·  128Comments