Gatsby: load i18n content dynamically from contentful - graphql

We have a static site using Gatsby and contentful. Now we want to support multi-languages, with localized content from contentful. I am able to populate a graghql query:
query frontpageTeaser($lang: String) {
contentfulFrontpage(node_locale: { eq: "zh-CN" }) {
myArticalContent
...
}
}
This query is able to load the Chinese content from contentful, and English if changed to node_locale: { eq: "en-US" }.
Now the issue is: we want to support a language switch, so that when switching language, the graphql loads corresponding localized content.
We are using gatsby-plugin-react-i18next, which has this great feature:
Support multi-language url routes in a single page component. You don’t have to create separate pages such as pages/en/index.js or pages/es/index.js.
Pages like http://localhost:8000/zh-CN/ does load Chinese from local /locales/zh-CN/translation.json, but how to load localized content when switching language?
Graphql seems providing page query, so i added gatsby-node.js:
exports.createPages = async function ({ actions, graphql }) {
actions.createPage({
path: '/zh-CN/',
component: require.resolve(`./src/pages/index.js`),
context: { lang: 'zh-CN' },
})
}
And use this on page:
export const query = graphql`
query frontpageTeaser($lang: String) {
contentfulFrontpage(node_locale: { eq: $lang }) {
myArticalContent
}
}
`
But it always returns en. Please kindly help :). Thanks.

This can be a complex switch. There is an example project that has smoothly done it with another CMS + Gatsby, here.
Specific places to point out in the codebase:
Configuration of which locales you use, here
A dynamic link depending on the active locale, here
The context for your whole app to know what the active locale is, here
Actually implementing the locale context provider in the higher order component Layout, here
There is also some magic inside of the gatsby-node.js which updates what you've already been working on! You can find that, here.
Hope that helps :)

Before getting a better solution, this one works:
// #todo gatsby plugin https://www.gatsbyjs.org/packages/gatsby-plugin-react-i18next/
// this plugin provides current language `context.i18n.language`, which not know how to pass it to graphql page query.
// This snippet moves it one-level up to `context.locale`.
// #todo need to explore a better solution.
exports.onCreatePage = ({ page, actions }) => {
const { createPage, deletePage } = actions
if (!page.context.locale) {
const language = page.context.i18n.language
const locale = language === 'en' ? 'en-US' : language
deletePage(page)
createPage({
...page,
context: {
...page.context,
locale,
}
})
}
}

Related

Gatsby: How to handle undefined fields in siteMetadata?

I want to build a site header based on having defined a logo field or not (if it is not defined it will be used the site title instead).
gatsby-config.js:
module.exports = {
siteMetadata: {
title: 'Hello Web',
logo: '/images/logo.png'
}
}
query:
const query = graphql`
{
site {
siteMetadata {
title
logo
}
}
}
The above works fine, but if I remove the logo field the build breaks.
I read the Schema Customization section on Gatsby documentation, but I didn't find anything that apply (am I missing something?).
My current solution is to set logo to some "dummy content". Although it works, it's clearly not an elegant solution and have some drawbacks as the project grows.
Screenshot:
Check out create types in this blog post. and fixing fields in the the docs.
createTypes can be used to define, fix, or extend a Gatsby GraphQL type that represents some of your app’s data. Think of it like an escape hatch to politely inform Gatsby of your data’s shape.
Gatsby infers the schema based on the data it has available at build time. If you try to query for a field that doesn't exist, you will get a build error.
With createTypes you can tell gatsby that the logo field is a nullable String on the SiteSiteMetadata type. Now if the field is not found in your data source, you will get a null value for it, but gatsby can successfully build.
// gatsby-node.js
exports.sourceNodes = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type SiteSiteMetadata implements Node {
logo: String
}
`
createTypes(typeDefs)
}
Note that the rest of the fields (title, description, author) don’t have to be provided, they will still be handled by Gatsby’s type inference.

How to pass variable to GraphQL in a non-template based page (part of src/pages)

While I'm able to pass variable via pageContext to GraphQL on all template based pages ( which use createPage function), I'm not able to pass/modify the context for pages which are part of src/page.
Consider this home page : src/pages/index.js
//want to pass dynamic value to $today
const IndexPage = ( {data, pageContext} ) => {
console.log(pageContext); // prints "isCreatedByStatefulCreatePages: true"
return (
<Layout />
)};
export default IndexPage;
export const Query = graphql`
offers( where : { day: $today})
{
..fields
}
`;
In order to modify automatically created pages you will need to use the onCreatePage node API. You basically recreate (delete/create) pages while modifying the page object. Just follow instructions in the docs Creating and Modifying Pages

Access JSON chunk exported from Gatsby Static Query

I have a React Component in a Gatsby app that is using the useStaticQuery hook to pull in data from the GraphQL layer. This component gets used in my application, but it also gets used as part of a JavaScript embed/widget that is created in a separate Webpack configuration.
I don't want the widget to depend on Gatsby, so I've shimmed the relevant bits of Gatsby, but I still need to pass in data to the shim I've created for useStaticQuery. I found that my Gatsby app is generating a file at public/static/d/2250905522.json that contains a perfect representation of the query data, and I'd like to use it like so:
// This file gets substituted when importing from `gatsby`
import queryResult from "../public/static/d/2250905522.json"
export const useStaticQuery = () => queryResult.data
export const graphql = () => {}
This works, but I haven't figured out where this is coming from or how to determine the file name in a way that is deterministic/stable. How is Gatsby determining this file name, and what internals might I use to do the same?
Edit: I found this routine in the Gatsby codebase that appears to be using staticQueryComponent.hash to determine the number. staticQueryComponent is being destructured from store.getState() where store is associated with Redux, but I'm still not sure where the hash is being determined yet.
Edit 2: Found another mention of this in the documentation here. It sounds like hash is a hash of the query itself, so this will change over time if the query changes (which is likely), so I'm still looking for the routine used to compute the hash.
Due to changes in the babel-plugin-remove-graphql-queries, coreyward's (awesome) answer should be updated to:
const { stripIgnoredCharacters } = require('graphql/utilities/stripIgnoredCharacters');
const murmurModule = require('babel-plugin-remove-graphql-queries/murmur');
const murmurhash = typeof murmurModule === 'function' ? murmurModule : murmurModule.murmurhash;
const GATSBY_HASH_SEED = 'abc';
function hashQuery(query) {
const result = murmurhash(stripIgnoredCharacters(query), GATSBY_HASH_SEED).toString();
return result;
}
module.exports = hashQuery;
The changes are:
fix the way murmurhash is imported. Credit to github user veloce, see: https://github.com/birkir/gatsby-source-graphql-universal/pull/16/files
Change to using stripIgnoredCharacters in order to match the updated way that gatsby internally hashes queries by first stripping whitespace and comment lines for efficiency.
Gatsby is using murmurhash with a seed of "abc" to calculate the hash of the full text of the query (including whitespace). This occurs in babel-plugin-remove-graphql-queries.
Since the reused components are isolated from Gatsby, the graphql tagged template literal can be shimmed in order to get the original query for hashing:
// webpack.config.js
module.exports = {
resolve: {
alias: {
gatsby: path.resolve(__dirname, "gatsby-shim.js"),
},
},
}
// gatsby-shim.js
import { murmurhash } from "babel-plugin-remove-graphql-queries/murmur"
import {
stripIgnoredCharacters,
} from "graphql/utilities/stripIgnoredCharacters"
const GATSBY_HASH_SEED = "abc"
const hashQuery = (query) =>
murmurhash(
stripIgnoredCharacters(query),
GATSBY_HASH_SEED
).toString()
export const graphql = query => hashQuery(query.raw[0])
This results in the query hash being passed into useStaticQuery, which can be shimmed similarly to retrieve the cached query from disk.
Also worth noting, newer versions of Gatsby store the StaticQuery result data in public/page-data/sq/d/[query hash].json.
If you're looking to do something similar, I've written up a much longer blog post about the details of this process and the solution I arrived at here.

Complex query variables in GraphQL (via Gatsby)

I am building a localized static website using Gatsby, with the help of gatsby-plugin-intl. This plugin adds a context variable named intl to pages (including template-based pages), which is an object: https://github.com/wiziple/gatsby-plugin-intl/blob/master/src/gatsby-node.js#L27-L34
I would like to access the intl.language variable from the context within a page query. This is my (failing) code at this stage:
query($slug: String!, $intl: String) {
contentfulPerson(slug: {eq: $slug}, node_locale: {eq: $intl.language}) {
name
}
}
Contentful is the headless CMS I use and from which I would like to fetch data in the correct locale.
Obviously this code has two problems: $intl is not a string, and $intl.language is not syntactically correct. But I don't know how to fix either problem.
I guess I could either fork the plugin or do something in my own gatsby-node.js to make the language available as a top-level variable in the context, but I'm interested to know if there is a way to do it directly.
The Gatsby docs say that query variables can be complex (https://www.gatsbyjs.org/docs/graphql-reference/#query-variables) but in the example they provide, they don't show how the types are defined or how to access a property within these variables.
EDIT : I tried moving the language to a top-level context variable in my gatsby-node.js using this code:
exports.onCreatePage = ({page, actions}) => {
const { createPage, deletePage } = actions
deletePage(page)
createPage({
...page,
context: {
...page.context,
language: page.context.intl.language
}
})
}
but the program runs out of memory (even when increasing max_old_space_size)
You can move the language field to a top-level context by doing this:
exports.onCreatePage = ({ page, actions }) => {
const { createPage, deletePage } = actions
const oldPage = Object.assign({}, page)
page.context.language = page.context.intl.language;
if (page.context.language !== oldPage.context.language) {
// Replace new page with old page
deletePage(oldPage)
createPage(page)
}
}
Checking if the field has changed avoids the infinity loop.

Repeating Dataset from API response in Wix Code

How can I repeat the display of response from API in Wix?
I have the following code in my backend module that queries the API for data like so:
export function getTopCoins() {
const url = 'https://api.coinmarketcap.com/v1/ticker/?limit=10';
return fetch(url, {method: 'get'})
.then(response => response.json());
}
and then in the frontend, I want to render it in the browser like so:
export function page1_viewportEnter(event, $w) {
getTopCoins().then(response => {
response.forEach(($w, itemData, index) => {
$w('#coinList').text = itemData.name;
});
});
}
So basically I have a Paragraph element with the ID of name coinList. So as you can see I want to display a list of names of the top 10 coins from coin market cap. How can I achieve this?
A working version of this was one that displayed the name of the first item on the list, this is the code:
export function page1_viewportEnter(event, $w) {
getTopCoins().then(response => {
$w('#coinList').text = "Name: " + response[0].name + "\n";
});
}
You can use the (pretty new) feature of Wix called Repeaters. Its in a Beta phase at the moment. You may find it under Add Panel --> Lists & Grids...
Then, in Wix Code IDE, you may write something like that (assuming you connected all the proper data binding with the connect panel configuration between you dataset and the repeater component:
export function page1_viewportEnter(event, $w) {
getTopCoins().then(response => {
$w('#repeater1').data = response;
});
}
You may refer to the Repeater API here.
One more thing, if I'm allowed to suggest: if you don't have any backend logic when calling getTopCoins from your backend code, you may use Wix Fetch API in you frontend code directly and save a network hop (faster and better performance)
Enjoy!

Resources