Skip to content
This repository has been archived by the owner on Dec 27, 2022. It is now read-only.

Fix i18n support #310

Merged
merged 6 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 69 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ Source plugin for pulling documents into Gatsby from a Strapi API.
<summary><strong>Table of contents</strong></summary>

- [gatsby-source-strapi](#gatsby-source-strapi)
- [Install](#installing-the-plugin)
- [Installing the plugin](#installing-the-plugin)
- [Using yarn](#using-yarn)
- [Or using NPM](#or-using-npm)
- [Setting up the plugin](#setting-up-the-plugin)
- [Basic usage](#basic-usage)
- [Advanced usage](#advanced-usage)
- [Deep queries populate](#deep-queries-populate)
Expand All @@ -17,7 +20,14 @@ Source plugin for pulling documents into Gatsby from a Strapi API.
- [Rich text field](#rich-text-field)
- [Components](#components)
- [Dynamic zones](#dynamic-zones)
- [Internationalization](#internationalization)
- [Gatsby cloud and preview environment setup](#gatsby-cloud-and-preview-environment-setup)
- [Setup](#setup)
- [Enabling Content Sync](#enabling-content-sync)
- [Installing the @strapi/plugin-gatsby-preview](#installing-the-strapiplugin-gatsby-preview)
- [Using yarn](#using-yarn-1)
- [Using npm](#using-npm)
- [Configurations](#configurations)
- [Restrictions and limitations](#restrictions-and-limitations)

</details>
Expand All @@ -42,7 +52,7 @@ You can enable and configure this plugin in your `gatsby-config.js` file.

### Basic usage

First, you need to configure the `STRAPI_API_URL` and the `STRAPI_TOKEN` environment variables. We recommend using [`dotenv`][https://github.com/motdotla/dotenv] to expose these variables.
First, you need to configure the `STRAPI_API_URL` and the `STRAPI_TOKEN` environment variables. We recommend using [`dotenv`](https://github.com/motdotla/dotenv) to expose these variables.

Make sure to create a full-access [API token](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/api-tokens.html) in Strapi.

Expand Down Expand Up @@ -248,7 +258,7 @@ To query a specific component use the following query:

#### Dynamic zones

To query dynamic zones, , write a query using [inline GraphQL fragments](https://graphql.org/learn/queries/#inline-fragments).
To query dynamic zones, write a query using [inline GraphQL fragments](https://graphql.org/learn/queries/#inline-fragments).

You can use the following query:

Expand Down Expand Up @@ -280,6 +290,62 @@ You can use the following query:
}
```

#### Internationalization

Content types in Strapi can be localized with the [i18n plugin](https://docs.strapi.io/developer-docs/latest/plugins/i18n.html). But by default, gatsby-source-strapi will only fetch data in the default locale of your Strapi app. To specify which locale should be fetched, an `i18n` object can be provided in the content type's `pluginOptions`. You can also set the locale to `all` to get all available localizations of a content type:

```javascript
const strapiConfig = {
// ...
collectionTypes: [
{
singularName: 'article',
pluginOptions: {
i18n: {
locale: 'fr', // Only fetch a specific locale
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also provide an array of locales right? Don't know if it's worth mentioning

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's possible, it's either one locale or "all" to fetch everything

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested with an array ["fr", "en"] and seemed to work but I'm not sure if it is defaulting to 'all' or fetching what is specified in the array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just checked and it seems to work indeed. But I see no mention of this in the Strapi i18n docs, so maybe we shouldn't document it either

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed 👍

},
},
},
],
singleTypes: [
{
singularName: 'global',
pluginOptions: {
i18n: {
locale: 'all', // Fetch all localizations
},
},
},
],
// ...
};
```

Then use the one of the following queries to fetch a localized content type:

```graphql
{
# Get content in all available localizations
allStrapiGlobal {
nodes {
locale
}
}

# Get a single type in a specific locale
strapiGlobal(locale: {eq: "fr"}) {
locale
}

# Get a collection type in a specific locale
allStrapiArticle(filter: {locale: {eq: "fr"}}) {
nodes {
locale
}
}
}
```

## Gatsby cloud and preview environment setup

### Setup
Expand Down
68 changes: 61 additions & 7 deletions src/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import createInstance from './axiosInstance';
import qs from 'qs';
import { cleanData } from './clean-data';

const fetchStrapiContentTypes = async (pluginOptions) => {
const axiosInstance = createInstance(pluginOptions);
const fetchStrapiContentTypes = async (strapiConfig) => {
const axiosInstance = createInstance(strapiConfig);
const [
{
data: { data: contentTypes },
Expand All @@ -24,10 +24,13 @@ const fetchStrapiContentTypes = async (pluginOptions) => {
};
};

const fetchEntity = async ({ endpoint, queryParams, uid }, ctx) => {
const fetchEntity = async ({ endpoint, queryParams, uid, pluginOptions }, ctx) => {
const { strapiConfig, reporter } = ctx;
const axiosInstance = createInstance(strapiConfig);

// Ignore queryParams locale in favor of pluginOptions
delete queryParams.locale;
remidej marked this conversation as resolved.
Show resolved Hide resolved

const opts = {
method: 'GET',
url: endpoint,
Expand All @@ -38,9 +41,52 @@ const fetchEntity = async ({ endpoint, queryParams, uid }, ctx) => {
try {
reporter.info(`Starting to fetch data from Strapi - ${opts.url} with ${JSON.stringify(opts)}`);

// Handle internationalization
const locale = pluginOptions?.i18n?.locale;
const otherLocales = [];
if (locale) {
if (locale === 'all') {
// Get all available locales
const { data: response } = await axiosInstance({
...opts,
params: {
populate: {
localizations: {
fields: ['locale'],
},
},
},
});
response.data.attributes.localizations.data.forEach((localization) =>
otherLocales.push(localization.attributes.locale)
);
} else {
// Only one locale
opts.params.locale = locale;
}
}

// Fetch default entity based on request options
const { data } = await axiosInstance(opts);

return castArray(data.data).map((entry) => cleanData(entry, { ...ctx, contentTypeUid: uid }));
// Fetch other localizations of this entry if there are any
const otherLocalizationsPromises = otherLocales.map(async (locale) => {
const { data: localizationResponse } = await axiosInstance({
...opts,
params: {
...opts.params,
locale,
},
});
return localizationResponse.data;
});

// Run queries in parallel
const otherLocalizationsData = await Promise.all(otherLocalizationsPromises);

return castArray([data.data, ...otherLocalizationsData]).map((entry) =>
cleanData(entry, { ...ctx, contentTypeUid: uid })
);
} catch (error) {
// reporter.panic(
// `Failed to fetch data from Strapi ${opts.url} with ${JSON.stringify(opts)}`,
Expand All @@ -50,17 +96,25 @@ const fetchEntity = async ({ endpoint, queryParams, uid }, ctx) => {
}
};

const fetchEntities = async ({ endpoint, queryParams, uid }, ctx) => {
const fetchEntities = async ({ endpoint, queryParams, uid, pluginOptions }, ctx) => {
const { strapiConfig, reporter } = ctx;
const axiosInstance = createInstance(strapiConfig);

// Ignore queryParams locale in favor of pluginOptions
delete queryParams.locale;

const opts = {
method: 'GET',
url: endpoint,
params: queryParams,
paramsSerializer: (params) => qs.stringify(params, { encodeValuesOnly: true }),
};

// Use locale from pluginOptions if it's defined
if (pluginOptions?.i18n?.locale) {
opts.params.locale = pluginOptions.i18n.locale;
}

try {
reporter.info(
`Starting to fetch data from Strapi - ${opts.url} with ${JSON.stringify(opts.params)}`
Expand All @@ -78,7 +132,7 @@ const fetchEntities = async ({ endpoint, queryParams, uid }, ctx) => {
length: pageCount - page,
}).map((_, i) => i + page + 1);

const arrayOfPromises = pagesToGet.map((page) => {
const fetchPagesPromises = pagesToGet.map((page) => {
return (async () => {
const options = {
...opts,
Expand All @@ -104,7 +158,7 @@ const fetchEntities = async ({ endpoint, queryParams, uid }, ctx) => {
})();
});

const results = await Promise.all(arrayOfPromises);
const results = await Promise.all(fetchPagesPromises);

const cleanedData = [...data, ...flattenDeep(results)].map((entry) =>
cleanData(entry, { ...ctx, contentTypeUid: uid })
Expand Down
16 changes: 8 additions & 8 deletions src/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ exports.sourceNodes = async (
getNodes,
getNode,
},
pluginOptions
strapiConfig
) => {
// Cast singleTypes and collectionTypes to empty arrays if they're not defined
if (!Array.isArray(pluginOptions.singleTypes)) {
pluginOptions.singleTypes = [];
if (!Array.isArray(strapiConfig.singleTypes)) {
strapiConfig.singleTypes = [];
}
if (!Array.isArray(pluginOptions.collectionTypes)) {
pluginOptions.collectionTypes = [];
if (!Array.isArray(strapiConfig.collectionTypes)) {
strapiConfig.collectionTypes = [];
}

const { schemas } = await fetchStrapiContentTypes(pluginOptions);
const { schemas } = await fetchStrapiContentTypes(strapiConfig);

const { deleteNode, touchNode } = actions;

const ctx = {
strapiConfig: pluginOptions,
strapiConfig,
actions,
schemas,
createContentDigest,
Expand All @@ -58,7 +58,7 @@ exports.sourceNodes = async (

existingNodes.forEach((n) => touchNode(n));

const endpoints = getEndpoints(pluginOptions, schemas);
const endpoints = getEndpoints(strapiConfig, schemas);

const lastFetched = await cache.get(LAST_FETCHED_KEY);

Expand Down
4 changes: 3 additions & 1 deletion src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const getEndpoints = ({ collectionTypes, singleTypes }, schemas) => {
)
.map(({ schema: { kind, singularName, pluralName }, uid }) => {
const options = types.find((config) => config.singularName === singularName);
const { queryParams, queryLimit } = options;
const { queryParams, queryLimit, pluginOptions } = options;

if (kind === 'singleType') {
return {
Expand All @@ -110,6 +110,7 @@ const getEndpoints = ({ collectionTypes, singleTypes }, schemas) => {
queryParams: queryParams || {
populate: '*',
},
pluginOptions,
};
}

Expand All @@ -127,6 +128,7 @@ const getEndpoints = ({ collectionTypes, singleTypes }, schemas) => {
},
populate: queryParams?.populate || '*',
},
pluginOptions,
};
});

Expand Down