/
useTranslation.js
123 lines (103 loc) · 4.31 KB
/
useTranslation.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { useState, useEffect, useContext, useRef } from 'react';
import { getI18n, getDefaults, ReportNamespaces, I18nContext } from './context';
import { warnOnce, loadNamespaces, hasLoadedNamespace } from './utils';
const usePrevious = (value, ignore) => {
const ref = useRef();
useEffect(() => {
ref.current = ignore ? ref.current : value;
}, [value, ignore]);
return ref.current;
};
export function useTranslation(ns, props = {}) {
// assert we have the needed i18nInstance
const { i18n: i18nFromProps } = props;
const { i18n: i18nFromContext, defaultNS: defaultNSFromContext } = useContext(I18nContext) || {};
const i18n = i18nFromProps || i18nFromContext || getI18n();
if (i18n && !i18n.reportNamespaces) i18n.reportNamespaces = new ReportNamespaces();
if (!i18n) {
warnOnce('You will need to pass in an i18next instance by using initReactI18next');
const notReadyT = (k) => (Array.isArray(k) ? k[k.length - 1] : k);
const retNotReady = [notReadyT, {}, false];
retNotReady.t = notReadyT;
retNotReady.i18n = {};
retNotReady.ready = false;
return retNotReady;
}
if (i18n.options.react && i18n.options.react.wait !== undefined)
warnOnce(
'It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.',
);
const i18nOptions = { ...getDefaults(), ...i18n.options.react, ...props };
const { useSuspense, keyPrefix } = i18nOptions;
// prepare having a namespace
let namespaces = ns || defaultNSFromContext || (i18n.options && i18n.options.defaultNS);
namespaces = typeof namespaces === 'string' ? [namespaces] : namespaces || ['translation'];
// report namespaces as used
if (i18n.reportNamespaces.addUsedNamespaces) i18n.reportNamespaces.addUsedNamespaces(namespaces);
// are we ready? yes if all namespaces in first language are loaded already (either with data or empty object on failed load)
const ready =
(i18n.isInitialized || i18n.initializedStoreOnce) &&
namespaces.every((n) => hasLoadedNamespace(n, i18n, i18nOptions));
// binding t function to namespace (acts also as rerender trigger)
function getT() {
return i18n.getFixedT(
null,
i18nOptions.nsMode === 'fallback' ? namespaces : namespaces[0],
keyPrefix,
);
}
const [t, setT] = useState(getT);
const joinedNS = namespaces.join();
const previousJoinedNS = usePrevious(joinedNS);
const isMounted = useRef(true);
useEffect(() => {
const { bindI18n, bindI18nStore } = i18nOptions;
isMounted.current = true;
// if not ready and not using suspense load the namespaces
// in side effect and do not call resetT if unmounted
if (!ready && !useSuspense) {
loadNamespaces(i18n, namespaces, () => {
if (isMounted.current) setT(getT);
});
}
if (ready && previousJoinedNS && previousJoinedNS !== joinedNS && isMounted.current) {
setT(getT);
}
function boundReset() {
if (isMounted.current) setT(getT);
}
// bind events to trigger change, like languageChanged
if (bindI18n && i18n) i18n.on(bindI18n, boundReset);
if (bindI18nStore && i18n) i18n.store.on(bindI18nStore, boundReset);
// unbinding on unmount
return () => {
isMounted.current = false;
if (bindI18n && i18n) bindI18n.split(' ').forEach((e) => i18n.off(e, boundReset));
if (bindI18nStore && i18n)
bindI18nStore.split(' ').forEach((e) => i18n.store.off(e, boundReset));
};
}, [i18n, joinedNS]); // re-run effect whenever list of namespaces changes
// t is correctly initialized by useState hook. We only need to update it after i18n
// instance was replaced (for example in the provider).
const isInitial = useRef(true);
useEffect(() => {
if (isMounted.current && !isInitial.current) {
setT(getT);
}
isInitial.current = false;
}, [i18n, keyPrefix]); // re-run when i18n instance or keyPrefix were replaced
const ret = [t, i18n, ready];
ret.t = t;
ret.i18n = i18n;
ret.ready = ready;
// return hook stuff if ready
if (ready) return ret;
// not yet loaded namespaces -> load them -> and return if useSuspense option set false
if (!ready && !useSuspense) return ret;
// not yet loaded namespaces -> load them -> and trigger suspense
throw new Promise((resolve) => {
loadNamespaces(i18n, namespaces, () => {
resolve();
});
});
}