microapps / gatsby-plugin-react-i18next

Easily translate your Gatsby website into multiple languages
MIT License
121 stars 72 forks source link

How to translate slugs? #49

Open wottpal opened 3 years ago

wottpal commented 3 years ago

Hey there,

I have German and English markdown files like:

which should have different slugs/urls on the site like (respectively):

I'm already matching those urls with matchPath in the config and set getLanguageFromPath to true but still, this is only working out if the url after the lang-part is exactly the same. How can I "link" those files together so that my language-switcher (using navigate from usei18next) still works and re-routes between those slugs?

Thank you in advance :)

wottpal commented 3 years ago

Update: I just implemented a way myself via redirects. For others having the same problem I post my code underneath. @microapps Do you think that could be a nice PR in any way?

Helper file i18n-redirects.js

/**
 * Returns relative file path of given node under given `basePath` (optionally omitting file-extension)
 */
function getNodeRelativePath(
  { fileAbsolutePath },
  omitFileExt,
  basePath = `/src/markdown`
) {
  const relativePath = (fileAbsolutePath || ``).split(basePath)?.[1]
  if (!relativePath || !omitFileExt) return relativePath
  return relativePath.split(`.`)?.[0]
}

/**
 * Returns language code from file path of given node. As a fallback `defaultLang` is returned.
 */
function getNodeLangCode({ fileAbsolutePath }, defaultLang = `en`) {
  const langCodeRegex = /(?:[^/]*\.)(.*)(?:\.md)/
  return (fileAbsolutePath || ``).match(langCodeRegex)?.[1] || defaultLang
}

/**
 * Creates and returns the theoretically translated url with the original slug.
 * If `omitDefaultLang` si true, the given `defaultLang` will not be included in paths for this language.
 */
function getTranslatedUrlPath(
  slug,
  sourceLang,
  destLang,
  omitDefaultLang,
  defaultLang = `en`
) {
  const baseUrlPathRegex = new RegExp(`(?:/${sourceLang})?(/.*)`)
  const baseUrlPath = slug.match(baseUrlPathRegex)?.[1] || `/`
  return omitDefaultLang && destLang === defaultLang
    ? `${baseUrlPath}`
    : `/${destLang}${baseUrlPath}`
}

/**
 * Determines equally named markdown-files with a different language,
 * and creates redirects from the theoretically translated url to the actual slug.
 */
module.exports = function createMultilingualRedirects(
  { createRedirect },
  allNodes,
  node
) {
  const { slug } = node.frontmatter
  const relativePath = getNodeRelativePath(node, true)
  const langCode = getNodeLangCode(node)
  allNodes
    .map(({ node: translatedNode }) => ({
      translatedNode,
      translatedNodeLangCode: getNodeLangCode(translatedNode),
      translatedNodeRelativePath: getNodeRelativePath(translatedNode, true),
    }))
    .filter(
      ({ translatedNodeLangCode, translatedNodeRelativePath }) =>
        langCode !== translatedNodeLangCode &&
        relativePath === translatedNodeRelativePath
    )
    .forEach(({ translatedNode, translatedNodeLangCode }) => {
      const newRedirect = {
        fromPath: getTranslatedUrlPath(
          slug,
          langCode,
          translatedNodeLangCode,
          true
        ),
        toPath: translatedNode.frontmatter.slug,
        isPermanent: true,
        force: true,
        redirectInBrowser: true,
      }

      console.log(`Adding Redirect: `, newRedirect)
      createRedirect(newRedirect)
    })
}

gatsby-node.js

const path = require(`path`)
const createMultilingualRedirects = require(`./i18n-redirects`)

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  const result = await graphql(`
    {
      allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }) {
        edges {
          node {
            id
            fileAbsolutePath
            frontmatter {
              slug
            }
          }
        }
      }
    }
  `)
  if (result.errors) return Promise.reject(result.errors)

  // Create pages for each markdown-file
  const pageTemplate = path.resolve(`src/templates/article.js`)
  const articleNodes = result.data.allMarkdownRemark.edges
  articleNodes.forEach(({ node }) => {
    // Create Redirects for multilingual pages with different slugs
    createMultilingualRedirects(actions, articleNodes, node)

    // Create page for each article
    createPage({
      path: node.frontmatter.slug,
      component: pageTemplate,
      context: {
        // additional data can be passed via context
        slug: node.frontmatter.slug,
      },
    })
  })
}