Gatsby: Best way to integrate multilanguage support into a gatsby v2 app with wordpress and wpml

Created on 18 Dec 2018  Â·  9Comments  Â·  Source: gatsbyjs/gatsby

Summary

I have a gatsby app which queries pages, blog entries, menus and custom post types from wordpress and the default language is german. now i want to add english as a second language with the wpml plugin. has anybody some experience in implementing this?

  1. what would be the best approach to structure you gatsby-node
  2. how to set up the pages in my pages directory (example page listed below) do i have to double them and have to pay the price of redundancy or is there a smarter way. right now i query the page by slug in the file
  3. how to translate things like button texts
  4. how to embed the locale specific menu from wp

example custom page in the pages directory

import React from "react";
import { graphql } from "gatsby";
import config from "../../data/SiteConfig";
import MainLayout from "../components/Layout/layout";
import TopNavigation from "../components/Layout/Navigation/Navigation";
import Header from "../components/Layout/Header/Header";
import PageContent from "../components/Layout/PageContent/PageContent";

export default class ImprintPage extends React.Component {
  render() {
    const { data, location } = this.props;

    const { page } = data;

    return (
      <MainLayout location={location}>
        <TopNavigation />
        <Header
          siteBrand={config.siteBrand}
          siteSubtitle="GmbH"
        />
        <PageContent page={page} />
      </MainLayout>
    );
  }
}

export const query = graphql`
  query ImprintPageQuery {
    page: wordpressPage(slug: { eq: "impressum" }) {
      title
      slug
      acf {
        page_title
        page_text_content
      }
    }
  }
`;

this is my current gatsby-node

const _ = require(`lodash`);
const Promise = require(`bluebird`);
const path = require(`path`);
const slash = require(`slash`);
const webpackLodashPlugin = require("lodash-webpack-plugin");

const DEPLOY_ENV = process.env.DEPLOY_ENV || "lbn_published_production";
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"



/**
 * Generate node edges
 *
 * @param {any} { node, actions, getNode }
 */
exports.onCreateNode = ({ node, actions }) => {
  const { createNodeField } = actions;

  if (!Object.prototype.hasOwnProperty.call(node, "meta")) {
    return;
  }

  let deploy;

  if (node.meta[DEPLOY_ENV]) {
    deploy = true;
  } else {
    deploy = false;
  }

  createNodeField({ node, name: "deploy", value: deploy });
};

// Will create pages for Wordpress posts (route : /{slug})

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions;
  return new Promise((resolve, reject) => {
    // Query all the pages on your WordPress
    graphql(
      `
        {
          allWordpressPage {
            edges {
              node {
                id
                slug
                status
                template
              }
            }
          }
        }
      `
    )
      .then(result => {
        if (result.errors) {
          console.log(result.errors);
          reject(result.errors);
        }
        // Create those pages with the wp_page.jsx template.

        _.each(result.data.allWordpressPage.edges, edge => {
          createPage({
            path: `/${edge.node.slug}/`,
            component: (() => {
              if (edge.node.wordpress_id === 20) {
                return slash(path.resolve(`./src/pages/about.jsx`));
              }
              if (edge.node.wordpress_id === 67) {
                return slash(path.resolve(`./src/pages/projekte.jsx`));
              }
              if (edge.node.wordpress_id === 23) {
                return slash(path.resolve(`./src/pages/jobs.jsx`));
              }
              return slash(path.resolve(`./src/templates/wp_page.jsx`));
            })(),
            context: {
              id: edge.node.id
            }
          });
        });
      })
      // Now, querying all Journal Posts (DEFAULT WP POST)
      .then(() => {
        graphql(
          `
            {
              allWordpressPost {
                edges {
                  node {
                    id
                    slug
                    modified
                    categories {
                      name
                    }
                  }
                }
              }
            }
          `
        ).then(result => {
          if (result.errors) {
            console.log(result.errors);
            reject(result.errors);
          }

          const postTemplate = path.resolve(`./src/templates/post/post.jsx`);
          // We want to create a detailed page for each
          // post node. We'll just use the Wordpress Slug for the slug.
          // The Post ID is prefixed with 'POST_'

          const { edges } = result.data.allWordpressPost;

          _.each(edges, (edge, index) => {
            // Create the page for Jounal Posts and add next / prev navigation

            createPage({
              path: `journal/${edge.node.slug}`,
              component: slash(postTemplate),
              context: {
                id: edge.node.id,
                prev: index === 0 ? null : edges[index - 1].node,
                next: index === edges.length - 1 ? null : edges[index + 1].node
              }
            });
          });
          // ==== END POSTS ====

          // Query and create all Job Pages (CUSTOM POST TYPE)
          graphql(`
            {
              allWordpressWpJobs {
                edges {
                  node {
                    id
                    slug
                  }
                }
              }
            }
          `).then(result => {
            if (result.errors) {
              console.log(result.errors);
              reject(result.errors);
            }
            // Create Page pages.
            const jobTemplate = path.resolve("./src/templates/job/job.jsx");
            _.each(result.data.allWordpressWpJobs.edges, edge => {
              createPage({
                path: `/jobs/${edge.node.slug}`,
                component: slash(jobTemplate),
                context: {
                  id: edge.node.id,
                  name: `/${edge.node.slug}/i`
                }
              });
            });
          });
          // ==== END JOB POSTS ====

          // Query and create all Project Pages (CUSTOM POST TYPE)
          graphql(`
            {
              allWordpressWpProjects {
                edges {
                  node {
                    id
                    slug
                  }
                }
              }
            }
          `).then(result => {
            if (result.errors) {
              console.log(result.errors);
              reject(result.errors);
            }

            // Create Project Pages.
            const projects = result.data.allWordpressWpProjects.edges;
            const projectTemplate = path.resolve(
              "./src/templates/project/project.jsx"
            );

            _.each(projects, (edge, index) => {
              createPage({
                path: `/projekte/${edge.node.slug}`,
                component: slash(projectTemplate),
                context: {
                  id: edge.node.id,
                  name: `/${edge.node.slug}/i`,
                  prev: index === 0 ? null : projects[index - 1].node,
                  next:
                    index === projects.length - 1
                      ? null
                      : projects[index + 1].node
                }
              });
            });
          });
        });
      });
    // === END TAGS ===
    resolve();
  });
};

exports.onCreateWebpackConfig = ({ stage, actions }) => {
  if (stage === "build-javascript") {
    actions.setWebpackConfig({
      plugins: [webpackLodashPlugin]
    });
  }
};

Relevant information

Environment (if relevant)

  System:
    OS: macOS High Sierra 10.13.2
    CPU: (4) x64 Intel(R) Core(TM) i5-7267U CPU @ 3.10GHz
    Shell: 5.3 - /bin/zsh
  Binaries:
    Node: 8.12.0 - /usr/local/bin/node
    Yarn: 1.12.3 - /usr/local/bin/yarn
    npm: 6.4.1 - /usr/local/bin/npm
  Browsers:
    Chrome: 71.0.3578.98
    Firefox: 63.0
    Safari: 11.0.2
  npmPackages:
    gatsby: ^2.0.63 => 2.0.63
    gatsby-image: ^2.0.22 => 2.0.22
    gatsby-link: ^2.0.7 => 2.0.7
    gatsby-plugin-catch-links: ^2.0.9 => 2.0.9
    gatsby-plugin-feed: ^2.0.11 => 2.0.11
    gatsby-plugin-google-analytics: ^2.0.8 => 2.0.8
    gatsby-plugin-manifest: ^2.0.11 => 2.0.11
    gatsby-plugin-nprogress: ^2.0.7 => 2.0.7
    gatsby-plugin-offline: ^2.0.18 => 2.0.18
    gatsby-plugin-page-transitions: ^1.0.7 => 1.0.7
    gatsby-plugin-react-helmet: ^3.0.4 => 3.0.4
    gatsby-plugin-sass: ^2.0.7 => 2.0.7
    gatsby-plugin-sharp: ^2.0.14 => 2.0.14
    gatsby-plugin-sitemap: ^2.0.3 => 2.0.3
    gatsby-plugin-styled-components: ^3.0.4 => 3.0.4
    gatsby-plugin-twitter: ^2.0.8 => 2.0.8
    gatsby-remark-autolink-headers: ^2.0.12 => 2.0.12
    gatsby-remark-copy-linked-files: ^2.0.7 => 2.0.7
    gatsby-remark-images: ^3.0.1 => 3.0.1
    gatsby-remark-prismjs: ^3.1.4 => 3.1.4
    gatsby-remark-responsive-iframe: ^2.0.7 => 2.0.7
    gatsby-source-filesystem: ^2.0.11 => 2.0.11
    gatsby-source-wordpress: ^3.0.19 => 3.0.19
    gatsby-transformer-remark: ^2.1.15 => 2.1.15
    gatsby-transformer-sharp: ^2.1.9 => 2.1.9
  npmGlobalPackages:
    gatsby-cli: 2.4.5
stale? question or discussion

Most helpful comment

A lot of questions you have there, I'll try to answer them short at first and then we could dig deeper into the specifics. You can have a look at my portfolio for reference while reading my answers: https://github.com/LekoArts/portfolio

1) That's personal preference IMO, there's no right or wrong. As you can see with my gatsby-node I placed some repeating parts into functions or the graphQL query in its own file. You can also use async/await to minimize some boilerplate you currently have.

2) https://github.com/LekoArts/portfolio/blob/master/gatsby-node.js#L52-L85
Delete your initial pages from src/pages and re-create them with the right language. That's the same idea as: https://www.gatsbyjs.org/docs/creating-and-modifying-pages/#pass-context-to-pages

3) You can either have every translation in your Wordpress and then query that or have them in files and use some i18n stuff like https://www.i18next.com/

4) You have to have a unique identifier for filtering your query to get the correct data for the corresponding locale. WP might give you the current locale back on which you could query. Or you create your own unique identifiers, e.g. here I gave every page a unique name.

All 9 comments

A lot of questions you have there, I'll try to answer them short at first and then we could dig deeper into the specifics. You can have a look at my portfolio for reference while reading my answers: https://github.com/LekoArts/portfolio

1) That's personal preference IMO, there's no right or wrong. As you can see with my gatsby-node I placed some repeating parts into functions or the graphQL query in its own file. You can also use async/await to minimize some boilerplate you currently have.

2) https://github.com/LekoArts/portfolio/blob/master/gatsby-node.js#L52-L85
Delete your initial pages from src/pages and re-create them with the right language. That's the same idea as: https://www.gatsbyjs.org/docs/creating-and-modifying-pages/#pass-context-to-pages

3) You can either have every translation in your Wordpress and then query that or have them in files and use some i18n stuff like https://www.i18next.com/

4) You have to have a unique identifier for filtering your query to get the correct data for the corresponding locale. WP might give you the current locale back on which you could query. Or you create your own unique identifiers, e.g. here I gave every page a unique name.

@LekoArts first thank you so much for taking your time to answer my question. i think your explanation gives me a good starting point to solve my own specific usecase.

Just stepping by to mention PR #10942 that should help creating translated pages and solves your issue #10915.

As I will work on the same WPML-front in the future, we could work out some best practices and enhance the gatsby-source-wordpress docs.

Hi, what approach did you use at the end?
Do you think in a custom post type is bad practice adding a custom field "Language", query it in gatsby-node and make the node based on the custom field content?
Thanks!

Hey @frivolta,

I think it is not quite maintainable to add custom fields for all fields + languages you might want to translate. A translation plugin like WPML or Polylang is definitely a better choice in my opinion.

The PR I filed is still waiting on review. If this does not get reviewed/merged soon I will fork the plugin temporarily – since I need that for a current project. This would allow you to fetch and query translations made with WPML.

I think it is not quite maintainable to add custom fields for all fields + languages you might want to translate.

No I mean, In a custom post type let's say "portoflio", I have just one custom field in a metabox with a dropdown where I can chose the custom post language. Obviously with wpml it would be better but how does it integrate with external plugin, let's say acf?

You can translate ACF field groups like any other post type. The drawback is, that you need to change every translated field group if you want to enhance this. The advantage is that you have full control over which language has which fields.

Hiya!

This issue has gone quiet. Spooky quiet. 👻

We get a lot of issues, so we currently close issues after 30 days of inactivity. It’s been at least 20 days since the last update here.

If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open!

Thanks for being a part of the Gatsby community! 💪💜

Hey again!

It’s been 30 days since anything happened on this issue, so our friendly neighborhood robot (that’s me!) is going to close it.

Please keep in mind that I’m only a robot, so if I’ve closed this issue in error, I’m HUMAN_EMOTION_SORRY. Please feel free to reopen this issue or create a new one if you need anything else.

Thanks again for being part of the Gatsby community!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kalinchernev picture kalinchernev  Â·  3Comments

benstr picture benstr  Â·  3Comments

jimfilippou picture jimfilippou  Â·  3Comments

magicly picture magicly  Â·  3Comments

ferMartz picture ferMartz  Â·  3Comments