Skip to content

Commit 13c95ce

Browse files
authoredMar 8, 2021
Merge pull request #1273 from medihack/replaceable-i18n-instance
Fix allow to replace i18n in provider with useTranslation hook (#1272)
2 parents 254e43c + bd657b6 commit 13c95ce

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed
 

‎src/useTranslation.js

+18-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export function useTranslation(ns, props = {}) {
1010
if (i18n && !i18n.reportNamespaces) i18n.reportNamespaces = new ReportNamespaces();
1111
if (!i18n) {
1212
warnOnce('You will need to pass in an i18next instance by using initReactI18next');
13-
const notReadyT = k => (Array.isArray(k) ? k[k.length - 1] : k);
13+
const notReadyT = (k) => (Array.isArray(k) ? k[k.length - 1] : k);
1414
const retNotReady = [notReadyT, {}, false];
1515
retNotReady.t = notReadyT;
1616
retNotReady.i18n = {};
@@ -19,7 +19,9 @@ export function useTranslation(ns, props = {}) {
1919
}
2020

2121
if (i18n.options.react && i18n.options.react.wait !== undefined)
22-
warnOnce('It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.');
22+
warnOnce(
23+
'It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.',
24+
);
2325

2426
const i18nOptions = { ...getDefaults(), ...i18n.options.react, ...props };
2527
const { useSuspense } = i18nOptions;
@@ -34,7 +36,7 @@ export function useTranslation(ns, props = {}) {
3436
// are we ready? yes if all namespaces in first language are loaded already (either with data or empty object on failed load)
3537
const ready =
3638
(i18n.isInitialized || i18n.initializedStoreOnce) &&
37-
namespaces.every(n => hasLoadedNamespace(n, i18n, i18nOptions));
39+
namespaces.every((n) => hasLoadedNamespace(n, i18n, i18nOptions));
3840

3941
// binding t function to namespace (acts also as rerender trigger)
4042
function getT() {
@@ -68,12 +70,22 @@ export function useTranslation(ns, props = {}) {
6870
// unbinding on unmount
6971
return () => {
7072
isMounted.current = false;
71-
if (bindI18n && i18n) bindI18n.split(' ').forEach(e => i18n.off(e, boundReset));
73+
if (bindI18n && i18n) bindI18n.split(' ').forEach((e) => i18n.off(e, boundReset));
7274
if (bindI18nStore && i18n)
73-
bindI18nStore.split(' ').forEach(e => i18n.store.off(e, boundReset));
75+
bindI18nStore.split(' ').forEach((e) => i18n.store.off(e, boundReset));
7476
};
7577
}, [namespaces.join()]); // re-run effect whenever list of namespaces changes
7678

79+
// t is correctly initialized by useState hook. We only need to update it after i18n
80+
// instance was replaced (for example in the provider).
81+
const isInitial = useRef(true);
82+
useEffect(() => {
83+
if (isMounted.current && !isInitial.current) {
84+
setT(getT());
85+
}
86+
isInitial.current = false;
87+
}, [i18n]); // re-run when i18n instance was replaced
88+
7789
const ret = [t.t, i18n, ready];
7890
ret.t = t.t;
7991
ret.i18n = i18n;
@@ -86,7 +98,7 @@ export function useTranslation(ns, props = {}) {
8698
if (!ready && !useSuspense) return ret;
8799

88100
// not yet loaded namespaces -> load them -> and trigger suspense
89-
throw new Promise(resolve => {
101+
throw new Promise((resolve) => {
90102
loadNamespaces(i18n, namespaces, () => {
91103
resolve();
92104
});

‎test/useTranslation.spec.js

+25
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,29 @@ describe('useTranslation', () => {
113113
expect(i18nInstance.reportNamespaces.getUsedNamespaces()).toContain(namespace);
114114
});
115115
});
116+
117+
describe('replacing i18n instance in provider', () => {
118+
i18nInstance.addResource('fr', 'translation', 'key1', 'test2');
119+
const i18nInstanceClone = i18nInstance.cloneInstance({ lng: 'fr' });
120+
const wrapper = ({ children, i18n }) => (
121+
<I18nextProvider i18n={i18n}>{children}</I18nextProvider>
122+
);
123+
124+
it('should render correct content', () => {
125+
const { result, rerender } = renderHook(() => useTranslation(), {
126+
wrapper,
127+
initialProps: {
128+
i18n: i18nInstance,
129+
},
130+
});
131+
132+
const { t: t1 } = result.current;
133+
expect(t1('key1')).toBe('test');
134+
135+
rerender({ i18n: i18nInstanceClone });
136+
137+
const { t: t2 } = result.current;
138+
expect(t2('key1')).toBe('test2');
139+
});
140+
});
116141
});

0 commit comments

Comments
 (0)
Please sign in to comment.