microapps / gatsby-plugin-react-i18next

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

Language fallback #72

Closed 4www closed 2 years ago

4www commented 3 years ago

Hello!

Thanks for the great plugin, it is currently my favorite setup for i18n in gatsby.

I am considering to to add support to it for a fallback language, as described in i18next https://www.i18next.com/overview/configuration-options#languages-namespaces-resources

Looking at your repository, I've found this line https://github.com/microapps/gatsby-plugin-react-i18next/blob/master/src/plugin/wrapPageElement.tsx#L111 which seems to setup i18next for using the default language as a fallback.

This solution is pretty neat, though we could enhance it to support the i18next "classic" options.

I am curious what is your opinion on this feature, and how you would see it implemented.

I am not exactly sure where to start, but would love to look a bit more at it with your advices/guidance.

Thanks in advance, cheers

PS: i've noted from closed PR that you mention that this feature is not supported yet, and that you'd welcome a PR (here, there) PS.2: it seems that the reason the "fallback" language is "not working" could be that the default language data is not loaded or passed to the wrapPageElement.tsx component? Maybe something to be done in createNode?

4www commented 3 years ago

I've been putting some console.logs in the plugin to see what could be done, but have had a hard time figuring out what could be "missing/blocking" this feature. But it seems the solution could be much simpler, as in nothing-to-change-solution; something I might have missed from not being an experience Gatsby user.

On a page graphql query, not specifying the $language variable as a filter of the locales: allLocales query, seems to return all locales (of all languages), and therefore allows the fallback on the correct default language by i18next to work.

The way this plugin is built, seems to load in i18next all languages resulting from the page query; so no filters loads them all. Maybe the solution described here can be improved by filtering on two languages, the current and the default (not to load them all, and slow down build time).

With a /gatsby-config.js, setup as in the plugin's readme, except the key options.i18nextOptions.fallbackLng

{
    resolve: `gatsby-plugin-react-i18next`,
    options: {
        localeJsonSourceName: `locale`,
        languages: [`en`, `de`, `fr`],
        defaultLanguage: `en`,
        siteUrl: `https://example.com/`,
        i18nextOptions: {
            fallbackLng: 'en', // here we provide the fallback language to i18next
            interpolation: {
                escapeValue: false
            },
            keySeparator: false,
            nsSeparator: false
        }
    }
}

A working page query, with fallback on the fallbackLng:

// /pages/index.js
export const query = graphql`
  query {
    locales: allLocale {
      edges {
        node {
          ns
          data
          language
        }
      }
    }
  }
`;

Still, the locale $language variable, can be used in an other query fragment; as in here with a blog in markdown format

// /pages/blog.js
export const query = graphql`
  query($language: String) {
    locales: allLocale {
      edges {
        node {
          ns
          data
          language
        }
      }
    }
    allMarkdownRemark(filter: {fields: {langKey: {eq: $language}}}) {
      edges {
        node {
          html
          frontmatter {
            title
          }
          fields {
            langKey
          }
        }
      }
    }
  }
`;

Where ./gatsby-node.js would look like (to be noted that here in the markdown example, there is no language fallback):

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

/* Add a `langKey` field on each node;
   based on which "root /content" folder, the nodes are in */
exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (node.internal.type === `MarkdownRemark`) {
    const fileNode = getNode(node.parent)
    const langKey = fileNode.relativePath.split('/')[0]
    createNodeField({
      node,
      name: `langKey`,
      value: langKey,
    })
  }
};

Do you think this is a legitimate solution to the language fallback?

It seems that in my setup, this solution gives the expected result.

I think this issue can therefore be closed, or I can also add a line or two to the readme if it needs.

saintasia commented 3 years ago

If the site is multilingual then you'd be loading up multiple languages with this approach which isn't ideal, the context has i18n data including defaultLanguage so it could probably be exposed at the filter level and passed as a second parameter to only load 2