Search code examples
graphqlinternationalizationgatsbynetlify

Using GraphQL to query only markdown pages of language selected


Gatsby/GraphQL newbie building a bilingual site. My site (navbar, footer, body content etc) has all bilingual content in en and zh json files respectively, and calls the t function from react-18next. However, there are several pages in my website that have not been 'internationalised' yet - my markdown pages. As of current, the file structure looks like this:

src
├── posts
│   └── positions
│       ├── en
│       │   ├── accounting-en.md
│       │   ├── socialmedia-en.md
│       │   └── swe-en.md
│       └── zh
│           ├── accounting-zh.md
│           ├── socialmedia-zh.md
│           └── swe-zh.md

where the .md files are rendered onto the page with MDXRenderer, and i'd like only the contents in either the en/zh folders to load when either language is selected. I'd like to have these markdown files editable on Netlify CMS, so this solution and react-markdown aren't things i'm considering.

so i was wondering: how can data from i18n.language be passed into a graphql query? is this the best way to internationalise markdown pages?

edits:

gatsby-node.js

const { createFilePath } = require(`gatsby-source-filesystem`);

exports.onCreateNode = ({ node, actions, getNode }) => {
    const { createNodeField } = actions

    if (node.internal.type === `Mdx`) {
        const value = createFilePath({ node, getNode })

        createNodeField({
            name: `slug`,
            node,
            value: `/posts${value}`,
            language,
        })
    }
}

const path = require("path")

exports.createPages = async ({ graphql, actions, reporter }) => { // Create blog pages dynamically
    const { createPage } = actions
    const result = await graphql(`
        query {
            allMdx {
                edges {
                    node {
                        id
                        fields {
                            slug
                            language
                        }
                    }
                }
            }
        }
    `)

    if (result.errors) {
        reporter.panicOnBuild('🚨  ERROR: Loading "createPages" query')
    }

    // Create blog post pages.
    const posts = result.data.allMdx.edges

    posts.forEach(({ node }, index) => {
        createPage({
            path: node.fields.slug,
            component: path.resolve(`./src/components/post-page-template.js`),
            context: { id: node.id },
        })
    })
}

posts.js (where all markdown files are rendered, but i'd like only markdown files ending in either -en or -zh to show up depending on the language selected. i'm open to changing this file structure in favour of nicer code)

import React from "react";
import styled from "styled-components";
import { graphql, Link } from "gatsby";

import Navbar from "../components/Navbar.js";
import FooterContainer from "../containers/footer";

import { useTranslation } from "react-i18next";
import i18next from "i18next";

const Button = styled.button`
  background-color: #ec1b2f;
  border: none;
  color: white;
  padding: 10px 40px;
  text-align: center;
  text-decoration: none;
  font-size: 16px;
  border-radius: 10px;
`;

const Body = styled.body`
  margin-left: 6%;
`;

const Divider = styled.hr`
  margin-top: 20px;
  line-height: 0;
  border-top: 1px;
  margin-left: 0px;

  &:last-child {
    margin-bottom: 30px;
  }
`;

const Title = styled.h1`
  font-family: "Arial";
  color: #ec1b2f;
  font-size: 25px;
  font-weight: 400;
`;

// console.log('----')
// console.log(typeof i18next.language)// type string 
// console.log(i18next.language) // output: i18next: languageChanged zh-Hant

const selectedLanguage = i18next.language // this doesn't work despite i18next.language being type string??
const selectedLangRegex = selectedLanguage.split(/languageChanged (.*)/gm)


export const query = (selectedLangRegex) => graphql`
    query SITE_INDEX_QUERY {
        site {
            siteMetadata {
               title
               description
               language
            }
        }
        allMdx(
            sort: {fields: [frontmatter___date], order: DESC},
            filter: {frontmatter: {published: {eq: true}, language: {eq: selectedLangRegex}}} //trying to regex selectedLangRegex
        ){
            nodes {
                id
                excerpt(pruneLength: 250)
                frontmatter {
                    title
                    date(formatString: "DD MMM YYYY")
                }
                fields {
                    slug
                }
            }
        }
    }
`;

const Careers = ({ props, data }) => {
  const { t } = useTranslation();
  return (
    <div>
      <Navbar />
      {data.allMdx.nodes.map(({ excerpt, frontmatter, fields }) => (
        <Body>
          <Title>{frontmatter.title}</Title>
          <p>{frontmatter.date}</p>
          <p>{excerpt}</p>
          <Link to={fields.slug}>
            <Button>{t("careers.apply_button")}</Button>
          </Link>
          <Divider />
        </Body>
      ))}
      <FooterContainer />
    </div>
  );
};

export default Careers;


Solution

  • so i was wondering: how can data from i18n.language be passed into a graphql query? is this the best way to internationalise markdown pages?

    Yes, of course. To me, passing a GraphQL variable to the template it's the cleanest and best way. In that way, you can add a fallback language (or leave it empty) to pick the non-internationalized file if it's not translated yet.

    Something like:

    const path = require("path")
    exports.createPages = async ({ graphql, actions }) => {
      const { createPage } = actions
      const queryResults = await graphql(`
        query getAllPosts {
          allMarkdownRemark {
            nodes {
              slug
              language #use relative/absolute path if needed
            }
          }
        }
      `)
      const postTemplate = path.resolve(`src/templates/posts.js`)
    
      queryResults.data.allProducts.nodes.forEach(node => {
        createPage({
          path: `/blog/${node.slug}`,
          component: postTemplate,
          context: {
            slug: node.slug,
            language: node.language || 'en' // the fallback
          },
        })
      })
    }
    

    Note: without knowing how your gatsby-node.js looks like you may need to tweak it a little bit the query as well as the createPage API.

    The idea is to query for a language field, which I'm assuming is a field of each post. If it's not, you can query for the file path (relative or absolute), which will contain the en or zh variable in its name. Adding the fallback language will force you to change all file names to send a valid variable.

    Then, in your template (posts.js) you can use the language field as you are using the slug to filter each entity.