Skip to content

Commit d1fddaa

Browse files
committed
Merge branch 'master' into pr_gen_default_lang_page
2 parents 9147e61 + 3785097 commit d1fddaa

File tree

8 files changed

+228
-50
lines changed

8 files changed

+228
-50
lines changed

README.md

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ Easily translate your Gatsby website into multiple languages.
1515

1616
When you build multilingual sites, Google recommends using different URLs for each language version of a page rather than using cookies or browser settings to adjust the content language on the page. [(read more)](https://support.google.com/webmasters/answer/182192?hl=en&ref_topic=2370587)
1717

18-
## How is it different from other gatsby i18next plugins?
18+
## :boom: Breaking change since v0.0.27
1919

20-
This plugin does not require fetching translations with graphql query on each page, everything is done automatically. Just use `react-i18next` to translate your pages.
20+
As of v0.0.28, language JSON resources should be loaded by `gatsby-source-filesystem` plugin and than fetched by GraphQL query. It enables incremental build and hot-reload as language JSON files change.
21+
22+
Users who have loaded language JSON files using `path` option will be affected. Please check configuration example on below.
2123

2224
## Demo
2325

@@ -50,9 +52,17 @@ npm install --save gatsby-plugin-react-i18next i18next react-i18next
5052
// In your gatsby-config.js
5153
plugins: [
5254
{
53-
resolve: `gatsby-plugin-react-i18next`,
55+
resolve: `gatsby-source-filesystem`,
5456
options: {
5557
path: `${__dirname}/locales`,
58+
name: `locale`,
59+
ignore: [`**/\.*`, `**/*~`]
60+
}
61+
},
62+
{
63+
resolve: `gatsby-plugin-react-i18next`,
64+
options: {
65+
localeJsonSourceName: `locale`, // name given to `gatsby-source-filesystem` plugin.
5666
languages: [`en`, `es`, `de`],
5767
defaultLanguage: `en`,
5868
// if you are using Helmet, you must include siteUrl, and make sure you add http:https
@@ -149,6 +159,20 @@ const IndexPage = () => {
149159
};
150160

151161
export default IndexPage;
162+
163+
export const query = graphql`
164+
query($language: String!) {
165+
locales: allLocale(filter: {language: {eq: $language}}) {
166+
edges {
167+
node {
168+
ns
169+
data
170+
language
171+
}
172+
}
173+
}
174+
}
175+
`;
152176
```
153177

154178
and in `locales/en/translations.json` you will have
@@ -245,7 +269,8 @@ const Header = ({siteTitle}) => {
245269

246270
| Option | Type | Description |
247271
| --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
248-
| path | string | path to the folder with JSON translations |
272+
| localeJsonSourceName | string | name of JSON translation file nodes that are loaded by `gatsby-source-filesystem` (set by `option.name`). Default is `locale` |
273+
| localeJsonNodeName | string | name of GraphQL node that holds locale data. Default is `locales` |
249274
| languages | string[] | supported language keys |
250275
| defaultLanguage | string | default language when visiting `/page` instead of `/es/page` |
251276
| generateDefaultLanguagePage | string | generate dedicated page for default language. e.g) `/en/page`. It is useful when you need page urls for all languages. For example, server-side [redirect](https://www.gatsbyjs.com/docs/reference/config-files/actions/#createRedirect) using `Accept-Language` header. Default is `false`. |
@@ -372,6 +397,26 @@ export const query = graphql`
372397
`;
373398
```
374399

400+
## How to fetch translations of specific namespaces only
401+
402+
You can use `ns` and `language` field in gatsby page queries to fetch specific namespaces that are being used in the page. This will be useful when you have several big pages with lots of translations.
403+
404+
```javascript
405+
export const query = graphql`
406+
query($language: String!) {
407+
locales: allLocale(filter: {ns: {regex: "/common|about/"}, language: {eq: $language}}) {
408+
edges {
409+
node {
410+
ns
411+
data
412+
language
413+
}
414+
}
415+
}
416+
}
417+
`;
418+
```
419+
375420
## How to add `sitemap.xml` for all language specific pages
376421

377422
You can use [gatsby-plugin-sitemap](https://www.gatsbyjs.org/packages/gatsby-plugin-sitemap/) to automatically generate a sitemap during build time. You need to customize `query` to fetch only original pages and then `serialize` data to build a sitemap. Here is an example:

gatsby-node.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
const {onCreatePage} = require('./dist/plugin/onCreatePage');
2+
const {onCreateNode} = require('./dist/plugin/onCreateNode');
3+
const {onPreBootstrap} = require('./dist/plugin/onPreBootstrap');
4+
25
exports.onCreatePage = onCreatePage;
6+
exports.onCreateNode = onCreateNode;
7+
exports.onPreBootstrap = onPreBootstrap;

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@
6565
"dependencies": {
6666
"bluebird": "^3.7.2",
6767
"browser-lang": "^0.1.0",
68-
"glob": "^7.1.6",
6968
"path-to-regexp": "^6.1.0"
7069
},
7170
"peerDependencies": {

src/plugin/onCreateNode.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {CreateNodeArgs, Node} from 'gatsby';
2+
import {FileSystemNode, PluginOptions, LocaleNodeInput} from '../types';
3+
4+
export function unstable_shouldOnCreateNode({node}: {node: Node}) {
5+
// We only care about JSON content.
6+
return node.internal.mediaType === `application/json`;
7+
}
8+
9+
export const onCreateNode = async (
10+
{
11+
node,
12+
actions,
13+
loadNodeContent,
14+
createNodeId,
15+
createContentDigest,
16+
reporter
17+
}: CreateNodeArgs<FileSystemNode>,
18+
{localeJsonSourceName = 'locale'}: PluginOptions
19+
) => {
20+
if (!unstable_shouldOnCreateNode({node})) {
21+
return;
22+
}
23+
24+
const {
25+
absolutePath,
26+
internal: {mediaType, type},
27+
sourceInstanceName,
28+
relativeDirectory,
29+
name,
30+
id
31+
} = node;
32+
33+
// Currently only support file resources
34+
if (type !== 'File') {
35+
return;
36+
}
37+
38+
// User is not using this feature
39+
if (localeJsonSourceName == null) {
40+
return;
41+
}
42+
43+
if (sourceInstanceName !== localeJsonSourceName) {
44+
return;
45+
}
46+
47+
const activity = reporter.activityTimer(
48+
`gatsby-plugin-react-i18next: create node: ${relativeDirectory}/${name}`
49+
);
50+
activity.start();
51+
52+
// relativeDirectory name is language name.
53+
const language = relativeDirectory;
54+
const content = await loadNodeContent(node);
55+
56+
// verify & canonicalize indent. (do not care about key order)
57+
let data: string;
58+
try {
59+
data = JSON.stringify(JSON.parse(content), undefined, '');
60+
} catch {
61+
const hint = node.absolutePath ? `file ${node.absolutePath}` : `in node ${node.id}`;
62+
throw new Error(`Unable to parse JSON: ${hint}`);
63+
}
64+
65+
const {createNode, createParentChildLink} = actions;
66+
67+
const localeNode: LocaleNodeInput = {
68+
id: createNodeId(`${id} >>> Locale`),
69+
children: [],
70+
parent: id,
71+
internal: {
72+
content: data,
73+
contentDigest: createContentDigest(data),
74+
type: `Locale`
75+
},
76+
language: language,
77+
ns: name,
78+
data,
79+
fileAbsolutePath: absolutePath
80+
};
81+
82+
createNode(localeNode);
83+
84+
// @ts-ignore
85+
// staled issue: https://github.com/gatsbyjs/gatsby/issues/19993
86+
createParentChildLink({parent: node, child: localeNode});
87+
88+
activity.end();
89+
};

src/plugin/onCreatePage.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,7 @@
1-
import _glob from 'glob';
21
import {CreatePageArgs, Page} from 'gatsby';
32
import BP from 'bluebird';
4-
import fs from 'fs';
5-
import util from 'util';
63
import {match} from 'path-to-regexp';
7-
import {PageContext, PageOptions, PluginOptions, Resources} from '../types';
8-
9-
const readFile = util.promisify(fs.readFile);
10-
const glob = util.promisify(_glob);
11-
12-
const getResources = async (path: string, language: string) => {
13-
const files = await glob(`${path}/${language}/*.json`);
14-
return BP.reduce<string, Resources>(
15-
files,
16-
async (result, file) => {
17-
const [, ns] = /[\/(\w+|\-)]+\/([\w|\-]+)\.json/.exec(file)!;
18-
const content = await readFile(file, 'utf8');
19-
result[language][ns] = JSON.parse(content);
20-
return result;
21-
},
22-
{[language]: {}}
23-
);
24-
};
4+
import {PageContext, PageOptions, PluginOptions} from '../types';
255

266
export const onCreatePage = async (
277
{page, actions}: CreatePageArgs<PageContext>,
@@ -54,7 +34,6 @@ export const onCreatePage = async (
5434
routed = false,
5535
pageOptions
5636
}: GeneratePageParams): Promise<Page<PageContext>> => {
57-
const resources = await getResources(pluginOptions.path, language);
5837
return {
5938
...page,
6039
path,
@@ -67,7 +46,6 @@ export const onCreatePage = async (
6746
defaultLanguage,
6847
generateDefaultLanguagePage,
6948
routed,
70-
resources,
7149
originalPath,
7250
path
7351
}

src/plugin/onPreBootstrap.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {ParentSpanPluginArgs} from 'gatsby';
2+
import {PluginOptions} from '../types';
3+
import report from 'gatsby-cli/lib/reporter';
4+
5+
export const onPreBootstrap = (_args: ParentSpanPluginArgs, pluginOptions: PluginOptions) => {
6+
// Check for deprecated option.
7+
if (pluginOptions.hasOwnProperty('path')) {
8+
report.error(
9+
`gatsby-plugin-react-i18next: 💥💥💥 "path" option is deprecated and won't be working as it was before. Please update setting on your gastby-config.js.\n\nSee detail: https://github.com/microapps/gatsby-plugin-react-i18next\n\n`
10+
);
11+
}
12+
};

src/plugin/wrapPageElement.tsx

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import {withPrefix, WrapPageElementBrowserArgs} from 'gatsby';
33
// @ts-ignore
44
import browserLang from 'browser-lang';
5-
import {I18NextContext, LANGUAGE_KEY, PageContext, PluginOptions} from '../types';
5+
import {I18NextContext, LANGUAGE_KEY, PageContext, PluginOptions, LocaleNode} from '../types';
66
import i18next, {i18n as I18n} from 'i18next';
77
import {I18nextProvider} from 'react-i18next';
88
import {I18nextContext} from '../i18nextContext';
@@ -23,20 +23,13 @@ export const wrapPageElement = (
2323
i18nextOptions = {},
2424
redirect = true,
2525
generateDefaultLanguagePage = false,
26-
siteUrl
26+
siteUrl,
27+
localeJsonNodeName = 'locales'
2728
}: PluginOptions
2829
) => {
2930
if (!props) return;
30-
const {pageContext, location} = props;
31-
const {
32-
routed,
33-
language,
34-
languages,
35-
originalPath,
36-
defaultLanguage,
37-
resources,
38-
path
39-
} = pageContext.i18n;
31+
const {data, pageContext, location} = props;
32+
const {routed, language, languages, originalPath, defaultLanguage, path} = pageContext.i18n;
4033
const isRedirect = redirect && !routed;
4134

4235
if (isRedirect) {
@@ -71,18 +64,18 @@ export const wrapPageElement = (
7164
...i18nextOptions,
7265
lng: language,
7366
fallbackLng: defaultLanguage,
74-
resources,
7567
react: {
7668
useSuspense: false
7769
}
7870
});
7971
}
8072

81-
Object.keys(resources[language]).map((ns) => {
82-
if (!i18n.hasResourceBundle(language, ns)) {
83-
i18n.addResourceBundle(language, ns, resources[language][ns]);
84-
}
85-
});
73+
if (data && data[localeJsonNodeName]) {
74+
data[localeJsonNodeName].edges.forEach(({node}: {node: LocaleNode}) => {
75+
const parsedData = JSON.parse(node.data);
76+
i18n.addResourceBundle(node.language, node.ns, parsedData);
77+
});
78+
}
8679

8780
if (i18n.language !== language) {
8881
i18n.changeLanguage(language);

0 commit comments

Comments
 (0)