-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add an official guide for internationalizing websites with Gatsby #3853
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: 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....
|
FYI @angeloocana |
I'll write a short summary of how we handle it at the moment using 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 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 |
@szimek If i understand you correctly then you have 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 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 The main principles are the following:
LocalesTranslations have been mostly grouped by page, with one namespace (= JSON file) per page + one global file (for shared texts such as header / footer).
Sadly, there is no hot-reloading on locales modifications 👎 i18nexti18next 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 informationsBefore generating all the i18n versions of our pages, we need to know some informations that we grouped in a 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:
And where path are the pathnames of our future versions (languages) of our pages. Build pagesWith 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 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 Display the right languageWe'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 👎 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
This is the best solution I have seen so far. |
@deltaskelta Glad to see you like it!
Check your 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 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 @szimek how is |
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 |
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 |
I think I might see why now...are you passing 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
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. #3830 (comment) layouts are rendered before pages, so without creating multiple layouts there is no way to pass messages through context, from the 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 The generated pages are:
In the real app we're using a modified version of 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. |
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. |
Just made this starter to help you folks: https://github.com/smakosh/gatsby-starter-i18n |
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 #3853 (comment) (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
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. |
@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 |
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! |
@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
|
Hmm interesting, I think the former makes usually more sense though as the pathPrefix is kind of making your domain to be Now the question comes up, why are you using the 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 |
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.
The text was updated successfully, but these errors were encountered: