react-i18next useTranslation Does not Get Same Provider
Tags: react, i18next, worknote, webpack
Published: 2022-02-09 07:11:12

<sub><sup>Photo by <a href="https://unsplash.com/@amadorloureiro?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Amador Loureiro</a> on <a href="https://unsplash.com/s/photos/language?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></sup></sup>

## Scenario

Using `react-i18next` with `I18nextProvider` instead of one global provider.

In our case, in a hybrid app with `Angular` and `React`, the `i18next` instance was generated  in the Angular app. The React sub app needed that instance instead of its own.

* js monorepo using yarn workspaces and [yarn pnp](https://yarnpkg.com/features/pnp)
* angular app with webpack bundler - bundle all including libraries and external third parties 
* react sub apps with rollup bundler - leaves out external to be bundle by the end app

## Case Problem 

At first, it seems that the provider was receiving the instance correctly. 

But the [useTranslation](https://react.i18next.com/latest/usetranslation-hook) hook, when asking for the react [i18next conext](https://github.com/i18next/react-i18next/blob/7012eed224fbfbb23eda179f3275ec4440313523/src/useTranslation.js#L8), always received and empty one. Not the instance that was set on the provider

```js
  const { i18n: i18nFromContext, defaultNS: defaultNSFromContext } = useContext(I18nContext) || {};
```

## Solution - TLDR

Make the js bundler provider the same `react-i18next` library instance for both apps/libraries.

For webpack and yarn pnp, we use the [reslove alias](https://webpack.js.org/configuration/resolve/#resolvealias) configuration:

```js
   resolve: {
      alias: {
        'react-i18next': require.resolve('react-i18next'),
      },
      extensions: ['.ts', '.js'],
    },
```

## Solution Walkthrough

To debug the situation, the context can be marked by adding a property guid to the object.

```js
import * as rx from '@proftit/rxjs';
import { I18nextProvider, I18nContext } from 'react-i18next';
import { TranslationsContext } from './TranslationsContext';
import { useObservable, useObservableState } from 'observable-hooks';

export function TranslationsProvider({ value, children }) {
  const i18n = useMemo(() => {
    value.__guid = 'translation-provider-top';
    return value;
  }, [value]);

  return (
    <TranslationsContext.Provider value={value}>
      {i18n && <I18nextProvider i18n={i18n}>{children}</I18nextProvider>}
    </TranslationsContext.Provider>
  );
}
```

Then, in the `useTranslation` that does not work at the further down component, we can step into the `useTranslation` code and see if the `useContext` gives the same instance with  the `__guid` property on it.

Since it does not, this indicate that the `I18nContext` given as a token for the `useContext` to search the correct context in its storage, is not the same.

To prove it, again, we can import the `I18nContext` in the `TranslationProvider` and tag it also. Then in the `useTranslation` we see it is not the same instance 

```js
import { getI18n, getDefaults, ReportNamespaces, I18nContext } from './context';

// ....
  const { i18n: i18nFromContext, defaultNS: defaultNSFromContext } = useContext(I18nContext) || {};
```

This phenomenon is explained in more details [here](https://github.com/yarnpkg/yarn/issues/6534#issuecomment-429293229)

In some js bundling scenarios, one library gets a different instance of the js module of third party library like react-i18next then the other library.

Since React [useContext](https://reactjs.org/docs/hooks-reference.html#usecontext) works on reference equality for the tokens for its dependency injection like mechanism, it does not find the correct context.

This can happen more generally for other libraries that stumble on the same use case situation. The solution for webpack is the same

```js
    resolve: {
      alias: {
        'react-i18next': require.resolve('react-i18next'),
        '@emotion/react/jsx-runtime': emotionJsxPath,
        '@emotion/react': require.resolve('@emotion/react'),
      },
   }
```
## Storybook Solution

This also happens in a monorepo with storybooks packages as self packages.

The solution is the same. You need to configure storybook `main.js` webpack settings to resolve the the import to the same package instance.

`main.js`

```js
module.exports = {
  resolve: {
      alias: {
        'react-i18next': require.resolve('react-i18next'),
      },
      extensions: ['.ts', '.js'],
    }
}
```
## References 

* [react-i18next](https://react.i18next.com/)
* [react-i18next Provider](https://react.i18next.com/latest/i18nextprovider)
* [package duplication discussion](https://github.com/yarnpkg/yarn/issues/6534)