Gatsby et i18next sans Suspense

Gatsby est un framework JS très pratique pour créer des sites statiques rapidement avec React. C'est d'ailleurs le framework utilisé par la version actuelle de ce site. D'autre part, le package i18next facilite l'internationalisation de ses sites et applications, et propose des adaptateurs pour les frameworks les plus courants, tels que React, NextJS, Vue ou Angular.

Les problèmes arrivent lorsqu'on tente de combiner les deux... En effet, Gatsby ne fonctionne pas avec React.Suspense, du fait que cette fonctionnalité n'est, pour le moment, pas implémentée pour le rendu côté serveur. Ainsi, lorsqu'on tente un gatsby build sur un tel projet, on obtient l'erreur suivante :

$ gatsby build
[...]
failed Building static HTML for pages - 1.307s

 ERROR #95313

Building static HTML failed for path "/"

See our docs page for more info on this error: https://gatsby.dev/debug-html

  Error: Minified React error #294; visit https://reactjs.org/docs/error-decoder.html?invariant=294 for the fu  ll message or use the non-minified dev environment for full errors and additional helpful warnings.
[...]

Pas très parlant...

Désactiver l'usage de Suspense par i18next

Heureusement, il est très simple de configurer i18next pour ne pas utiliser React.Suspense. Dans le fichier src/components/i18n.js où se fait la configuration :

import i18n from "i18next"
import Backend from "i18next-http-backend"
import LanguageDetector from "i18next-browser-languagedetector"
import { initReactI18next } from "react-i18next"

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: "en",
    supportedLngs: ["en", "fr"],
    interpolation: {
      escapeValue: false,
    },
    react: {
      useSuspense: false, // <- et voilà !
    },
  })

export default i18n

Avec cette configuration, le build s'exécute sans erreur. Mais un nouveau problème se pose : le chargement des fichiers de traduction n'est pas immédiat. On se retrouve donc avec un texte qui saute.

Recréer un faux Suspense

Pour répondre à ce nouveau désagrément, il est possible d'utiliser un callback après l'initialisation de l'instance i18n.

Ainsi, dans le fichier layout.js :

import React, { useState } from "react"
import Footer from "./footer"
import Menu from "./menu"
import Spinner from "./spinner"
import { setCallback } from "./i18n"

const Layout = props => {
  const { children, className } = props
  const [loading, setLoading] = useState(true)

  if (loading) {
    setCallback(() => setLoading(false))
    return <Spinner />
  }
  return (
    <>
      <Menu />
      <main className={className}>{children}</main>
      <Footer />
    </>
  )
}

Et dans le fichier i18n.js :

import i18n from "i18next"
import Backend from "i18next-http-backend"
import LanguageDetector from "i18next-browser-languagedetector"
import { initReactI18next } from "react-i18next"

let ready
let readyCallback

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: "en",
    supportedLngs: ["en", "fr"],
    interpolation: {
      escapeValue: false,
    },
    react: {
      useSuspense: false,
    },
  })
  .then(() => {
    ready = true
    if (readyCallback) {
      readyCallback()
    }
  })

export const setCallback = callback => {
  readyCallback = callback
  if (ready) {
    readyCallback()
  }
}

export default i18n

De cette manière, un spinner est affiché tant que les traductions ne sont pas chargées :

À lire ensuite