Photo by Amador Loureiro on Unsplash
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
- 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 hook, when asking for the react i18next conext, always received and empty one. Not the instance that was set on the provider
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 configuration:
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.
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
import { getI18n, getDefaults, ReportNamespaces, I18nContext } from './context';
// ....
const { i18n: i18nFromContext, defaultNS: defaultNSFromContext } = useContext(I18nContext) || {};
This phenomenon is explained in more details here
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 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
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
module.exports = {
resolve: {
alias: {
'react-i18next': require.resolve('react-i18next'),
},
extensions: ['.ts', '.js'],
}
}
Top comments (0)