@octanejs/i18next 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +151 -0
- package/package.json +73 -0
- package/src/I18nextProvider.js +9 -0
- package/src/IcuTrans.js +104 -0
- package/src/IcuTransUtils/TranslationParserError.js +24 -0
- package/src/IcuTransUtils/htmlEntityDecoder.js +264 -0
- package/src/IcuTransUtils/index.js +4 -0
- package/src/IcuTransUtils/renderTranslation.js +216 -0
- package/src/IcuTransUtils/tokenizer.js +78 -0
- package/src/IcuTransWithoutContext.js +147 -0
- package/src/Trans.js +46 -0
- package/src/TransWithoutContext.d.ts +160 -0
- package/src/TransWithoutContext.js +687 -0
- package/src/Translation.js +15 -0
- package/src/context.js +61 -0
- package/src/defaults.js +21 -0
- package/src/helpers.d.ts +3 -0
- package/src/i18nInstance.js +7 -0
- package/src/index.d.ts +315 -0
- package/src/index.js +26 -0
- package/src/initReactI18next.d.ts +3 -0
- package/src/initReactI18next.js +11 -0
- package/src/internal.js +42 -0
- package/src/unescape.js +31 -0
- package/src/useSSR.js +55 -0
- package/src/useTranslation.js +285 -0
- package/src/utils.js +93 -0
- package/src/withSSR.js +22 -0
- package/src/withTranslation.js +38 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// Ported from react-i18next@17.0.9 (8b4a9ea). The subscription contract maps
|
|
2
|
+
// directly to octane; Suspense unwraps through use(thenable) instead of a raw
|
|
3
|
+
// Promise throw, and composed base hooks receive deterministic sub-slots.
|
|
4
|
+
import {
|
|
5
|
+
use,
|
|
6
|
+
useContext,
|
|
7
|
+
useCallback,
|
|
8
|
+
useMemo,
|
|
9
|
+
useEffect,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
useSyncExternalStore,
|
|
13
|
+
} from 'octane';
|
|
14
|
+
import { getI18n, getDefaults, ReportNamespaces, I18nContext } from './context.js';
|
|
15
|
+
import {
|
|
16
|
+
warnOnce,
|
|
17
|
+
loadNamespaces,
|
|
18
|
+
loadLanguages,
|
|
19
|
+
hasLoadedNamespace,
|
|
20
|
+
isString,
|
|
21
|
+
isObject,
|
|
22
|
+
} from './utils.js';
|
|
23
|
+
import { splitSlot, subSlot } from './internal.js';
|
|
24
|
+
|
|
25
|
+
const notReadyT = (k, optsOrDefaultValue) => {
|
|
26
|
+
if (isString(optsOrDefaultValue)) return optsOrDefaultValue;
|
|
27
|
+
if (isObject(optsOrDefaultValue) && isString(optsOrDefaultValue.defaultValue))
|
|
28
|
+
return optsOrDefaultValue.defaultValue;
|
|
29
|
+
// Selector functions and arrays of selector functions cannot be meaningfully resolved
|
|
30
|
+
// before i18n is ready — return empty string rather than leaking a function reference.
|
|
31
|
+
if (typeof k === 'function') return '';
|
|
32
|
+
if (Array.isArray(k)) {
|
|
33
|
+
const last = k[k.length - 1];
|
|
34
|
+
return typeof last === 'function' ? '' : last;
|
|
35
|
+
}
|
|
36
|
+
return k;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const notReadySnapshot = { t: notReadyT, ready: false };
|
|
40
|
+
const dummySubscribe = () => () => {};
|
|
41
|
+
|
|
42
|
+
// An initial client render that suspends may discard hook memo state. Cache the
|
|
43
|
+
// backend load outside the render scope so replay always sees the same thenable;
|
|
44
|
+
// it is evicted after settlement, by which point i18next reports the namespace
|
|
45
|
+
// ready. Sharing by instance/language/namespaces also coalesces sibling consumers.
|
|
46
|
+
const suspenseLoadCache = new WeakMap();
|
|
47
|
+
|
|
48
|
+
const getSuspenseLoadPromise = (i18n, lng, namespaces) => {
|
|
49
|
+
let byRequest = suspenseLoadCache.get(i18n);
|
|
50
|
+
if (!byRequest) {
|
|
51
|
+
byRequest = new Map();
|
|
52
|
+
suspenseLoadCache.set(i18n, byRequest);
|
|
53
|
+
}
|
|
54
|
+
const key = `${lng || ''}\u0000${namespaces.join('\u0001')}`;
|
|
55
|
+
let promise = byRequest.get(key);
|
|
56
|
+
if (!promise) {
|
|
57
|
+
promise = new Promise((resolve) => {
|
|
58
|
+
if (lng) loadLanguages(i18n, lng, namespaces, resolve);
|
|
59
|
+
else loadNamespaces(i18n, namespaces, resolve);
|
|
60
|
+
});
|
|
61
|
+
byRequest.set(key, promise);
|
|
62
|
+
promise.finally(() => {
|
|
63
|
+
if (byRequest.get(key) === promise) byRequest.delete(key);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return promise;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const useTranslation = (...args) => {
|
|
70
|
+
const [user, slot] = splitSlot(args);
|
|
71
|
+
const ns = user[0];
|
|
72
|
+
const props = user[1] || {};
|
|
73
|
+
const { i18n: i18nFromProps } = props;
|
|
74
|
+
const { i18n: i18nFromContext, defaultNS: defaultNSFromContext } = useContext(I18nContext) || {};
|
|
75
|
+
const i18n = i18nFromProps || i18nFromContext || getI18n();
|
|
76
|
+
|
|
77
|
+
if (i18n && !i18n.reportNamespaces) i18n.reportNamespaces = new ReportNamespaces();
|
|
78
|
+
|
|
79
|
+
if (!i18n) {
|
|
80
|
+
warnOnce(
|
|
81
|
+
i18n,
|
|
82
|
+
'NO_I18NEXT_INSTANCE',
|
|
83
|
+
'useTranslation: You will need to pass in an i18next instance by using initReactI18next',
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const i18nOptions = useMemo(
|
|
88
|
+
() => ({ ...getDefaults(), ...i18n?.options?.react, ...props }),
|
|
89
|
+
[i18n, props],
|
|
90
|
+
subSlot(slot, 'ut:options'),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const { useSuspense, keyPrefix } = i18nOptions;
|
|
94
|
+
|
|
95
|
+
const nsOrContext = ns || defaultNSFromContext || i18n?.options?.defaultNS;
|
|
96
|
+
const unstableNamespaces = isString(nsOrContext) ? [nsOrContext] : nsOrContext || ['translation'];
|
|
97
|
+
const namespaces = useMemo(
|
|
98
|
+
() => unstableNamespaces,
|
|
99
|
+
unstableNamespaces,
|
|
100
|
+
subSlot(slot, 'ut:namespaces'),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
i18n?.reportNamespaces?.addUsedNamespaces?.(namespaces);
|
|
104
|
+
|
|
105
|
+
const revisionRef = useRef(0, subSlot(slot, 'ut:revision'));
|
|
106
|
+
const subscribe = useCallback(
|
|
107
|
+
(callback) => {
|
|
108
|
+
if (!i18n) return dummySubscribe;
|
|
109
|
+
const { bindI18n, bindI18nStore } = i18nOptions;
|
|
110
|
+
|
|
111
|
+
const wrappedCallback = () => {
|
|
112
|
+
revisionRef.current += 1;
|
|
113
|
+
callback();
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (bindI18n) i18n.on(bindI18n, wrappedCallback);
|
|
117
|
+
if (bindI18nStore) i18n.store.on(bindI18nStore, wrappedCallback);
|
|
118
|
+
return () => {
|
|
119
|
+
if (bindI18n) bindI18n.split(' ').forEach((e) => i18n.off(e, wrappedCallback));
|
|
120
|
+
if (bindI18nStore)
|
|
121
|
+
bindI18nStore.split(' ').forEach((e) => i18n.store.off(e, wrappedCallback));
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
[i18n, i18nOptions],
|
|
125
|
+
subSlot(slot, 'ut:subscribe'),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const snapshotRef = useRef(undefined, subSlot(slot, 'ut:snapshotRef'));
|
|
129
|
+
const getSnapshot = useCallback(
|
|
130
|
+
() => {
|
|
131
|
+
if (!i18n) {
|
|
132
|
+
return notReadySnapshot;
|
|
133
|
+
}
|
|
134
|
+
const calculatedReady =
|
|
135
|
+
!!(i18n.isInitialized || i18n.initializedStoreOnce) &&
|
|
136
|
+
namespaces.every((n) => hasLoadedNamespace(n, i18n, i18nOptions));
|
|
137
|
+
const currentLng = props.lng || i18n.language;
|
|
138
|
+
const currentRevision = revisionRef.current;
|
|
139
|
+
|
|
140
|
+
const lastSnapshot = snapshotRef.current;
|
|
141
|
+
if (
|
|
142
|
+
lastSnapshot &&
|
|
143
|
+
lastSnapshot.ready === calculatedReady &&
|
|
144
|
+
lastSnapshot.lng === currentLng &&
|
|
145
|
+
lastSnapshot.keyPrefix === keyPrefix &&
|
|
146
|
+
lastSnapshot.revision === currentRevision // Check revision
|
|
147
|
+
) {
|
|
148
|
+
return lastSnapshot;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// `scopeNs` (4th opts arg, i18next ≥ 26.0.10) gives the selector API access
|
|
152
|
+
// to the full hook namespace list while `ns` (resolution-scope) stays at
|
|
153
|
+
// the primary string. Without it, `t($ => $.secondaryNs.foo)` would silently
|
|
154
|
+
// miss under default `nsMode` because `o.ns` is a single string and i18next's
|
|
155
|
+
// selector rewrite only fires on multi-ns input.
|
|
156
|
+
const calculatedT = i18n.getFixedT(
|
|
157
|
+
currentLng,
|
|
158
|
+
i18nOptions.nsMode === 'fallback' ? namespaces : namespaces[0],
|
|
159
|
+
keyPrefix,
|
|
160
|
+
{ scopeNs: namespaces },
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const newSnapshot = {
|
|
164
|
+
t: calculatedT,
|
|
165
|
+
ready: calculatedReady,
|
|
166
|
+
lng: currentLng,
|
|
167
|
+
keyPrefix,
|
|
168
|
+
revision: currentRevision, // Store revision
|
|
169
|
+
};
|
|
170
|
+
snapshotRef.current = newSnapshot;
|
|
171
|
+
return newSnapshot;
|
|
172
|
+
},
|
|
173
|
+
[i18n, namespaces, keyPrefix, i18nOptions, props.lng],
|
|
174
|
+
subSlot(slot, 'ut:snapshot'),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// We still need a state to manually trigger a re-render on load when the store doesn't emit an event.
|
|
178
|
+
const [loadCount, setLoadCount] = useState(0, subSlot(slot, 'ut:loadCount'));
|
|
179
|
+
const { t, ready } = useSyncExternalStore(
|
|
180
|
+
subscribe,
|
|
181
|
+
getSnapshot,
|
|
182
|
+
getSnapshot,
|
|
183
|
+
subSlot(slot, 'ut:externalStore'),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
useEffect(
|
|
187
|
+
() => {
|
|
188
|
+
if (i18n && !ready && !useSuspense) {
|
|
189
|
+
const onLoaded = () => setLoadCount((c) => c + 1);
|
|
190
|
+
if (props.lng) {
|
|
191
|
+
loadLanguages(i18n, props.lng, namespaces, onLoaded);
|
|
192
|
+
} else {
|
|
193
|
+
loadNamespaces(i18n, namespaces, onLoaded);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
[i18n, props.lng, namespaces, ready, useSuspense, loadCount],
|
|
198
|
+
subSlot(slot, 'ut:load'),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const finalI18n = i18n || {};
|
|
202
|
+
|
|
203
|
+
// cache one wrapper per hook caller and only recreate it when language changes
|
|
204
|
+
const wrapperRef = useRef(null, subSlot(slot, 'ut:wrapper'));
|
|
205
|
+
const wrapperLangRef = useRef(undefined, subSlot(slot, 'ut:wrapperLang'));
|
|
206
|
+
|
|
207
|
+
// helper to create a wrapper instance (avoid duplicating descriptor logic)
|
|
208
|
+
const createI18nWrapper = (original) => {
|
|
209
|
+
const descriptors = Object.getOwnPropertyDescriptors(original);
|
|
210
|
+
if (descriptors.__original) delete descriptors.__original;
|
|
211
|
+
const wrapper = Object.create(Object.getPrototypeOf(original), descriptors);
|
|
212
|
+
|
|
213
|
+
if (!Object.prototype.hasOwnProperty.call(wrapper, '__original')) {
|
|
214
|
+
try {
|
|
215
|
+
Object.defineProperty(wrapper, '__original', {
|
|
216
|
+
value: original,
|
|
217
|
+
writable: false,
|
|
218
|
+
enumerable: false,
|
|
219
|
+
configurable: false,
|
|
220
|
+
});
|
|
221
|
+
} catch (_) {
|
|
222
|
+
/* ignore */
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return wrapper;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const ret = useMemo(
|
|
230
|
+
() => {
|
|
231
|
+
const original = finalI18n;
|
|
232
|
+
const lang = original?.language;
|
|
233
|
+
|
|
234
|
+
let i18nWrapper = original;
|
|
235
|
+
|
|
236
|
+
if (original) {
|
|
237
|
+
// if we already created a wrapper for this original instance
|
|
238
|
+
if (wrapperRef.current && wrapperRef.current.__original === original) {
|
|
239
|
+
// language changed -> create fresh wrapper so identity changes
|
|
240
|
+
if (wrapperLangRef.current !== lang) {
|
|
241
|
+
i18nWrapper = createI18nWrapper(original);
|
|
242
|
+
|
|
243
|
+
wrapperRef.current = i18nWrapper;
|
|
244
|
+
wrapperLangRef.current = lang;
|
|
245
|
+
} else {
|
|
246
|
+
// reuse existing wrapper when language didn't change
|
|
247
|
+
i18nWrapper = wrapperRef.current;
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
// first time for this original instance -> create wrapper
|
|
251
|
+
i18nWrapper = createI18nWrapper(original);
|
|
252
|
+
|
|
253
|
+
wrapperRef.current = i18nWrapper;
|
|
254
|
+
wrapperLangRef.current = lang;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const effectiveT =
|
|
259
|
+
!ready && !useSuspense
|
|
260
|
+
? (...args) => {
|
|
261
|
+
warnOnce(
|
|
262
|
+
i18n,
|
|
263
|
+
'USE_T_BEFORE_READY',
|
|
264
|
+
'useTranslation: t was called before ready. When using useSuspense: false, make sure to check the ready flag before using t.',
|
|
265
|
+
);
|
|
266
|
+
return t(...args);
|
|
267
|
+
}
|
|
268
|
+
: t;
|
|
269
|
+
|
|
270
|
+
const arr = [effectiveT, i18nWrapper, ready];
|
|
271
|
+
arr.t = effectiveT;
|
|
272
|
+
arr.i18n = i18nWrapper;
|
|
273
|
+
arr.ready = ready;
|
|
274
|
+
return arr;
|
|
275
|
+
},
|
|
276
|
+
[t, finalI18n, ready, finalI18n.resolvedLanguage, finalI18n.language, finalI18n.languages],
|
|
277
|
+
subSlot(slot, 'ut:result'),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
if (i18n && useSuspense && !ready) {
|
|
281
|
+
use(getSuspenseLoadPromise(i18n, props.lng, namespaces));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return ret;
|
|
285
|
+
};
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** @type {(i18n:any,code:import('../TransWithoutContext').ErrorCode,msg?:string, rest?:{[key:string]: any})=>void} */
|
|
2
|
+
export const warn = (i18n, code, msg, rest) => {
|
|
3
|
+
const args = [msg, { code, ...(rest || {}) }];
|
|
4
|
+
if (i18n?.services?.logger?.forward) {
|
|
5
|
+
return i18n.services.logger.forward(args, 'warn', 'react-i18next::', true);
|
|
6
|
+
}
|
|
7
|
+
if (isString(args[0])) args[0] = `react-i18next:: ${args[0]}`;
|
|
8
|
+
if (i18n?.services?.logger?.warn) {
|
|
9
|
+
i18n.services.logger.warn(...args);
|
|
10
|
+
} else if (console?.warn) {
|
|
11
|
+
console.warn(...args);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const alreadyWarned = {};
|
|
15
|
+
/** @type {typeof warn} */
|
|
16
|
+
export const warnOnce = (i18n, code, msg, rest) => {
|
|
17
|
+
if (isString(msg) && alreadyWarned[msg]) return;
|
|
18
|
+
if (isString(msg)) alreadyWarned[msg] = new Date();
|
|
19
|
+
warn(i18n, code, msg, rest);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// not needed right now
|
|
23
|
+
//
|
|
24
|
+
// export const deprecated = (i18n, ...args) => {
|
|
25
|
+
// if (process && process.env && (!process.env.NODE_ENV || process.env.NODE_ENV === 'development')) {
|
|
26
|
+
// if (isString(args[0])) args[0] = `deprecation warning -> ${args[0]}`;
|
|
27
|
+
// warnOnce(i18n, ...args);
|
|
28
|
+
// }
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
const loadedClb = (i18n, cb) => () => {
|
|
32
|
+
// delay ready if not yet initialized i18n instance
|
|
33
|
+
if (i18n.isInitialized) {
|
|
34
|
+
cb();
|
|
35
|
+
} else {
|
|
36
|
+
const initialized = () => {
|
|
37
|
+
// due to emitter removing issue in i18next we need to delay remove
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
i18n.off('initialized', initialized);
|
|
40
|
+
}, 0);
|
|
41
|
+
cb();
|
|
42
|
+
};
|
|
43
|
+
i18n.on('initialized', initialized);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const loadNamespaces = (i18n, ns, cb) => {
|
|
48
|
+
i18n.loadNamespaces(ns, loadedClb(i18n, cb));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// should work with I18NEXT >= v22.5.0
|
|
52
|
+
export const loadLanguages = (i18n, lng, ns, cb) => {
|
|
53
|
+
// eslint-disable-next-line no-param-reassign
|
|
54
|
+
if (isString(ns)) ns = [ns];
|
|
55
|
+
if (i18n.options.preload && i18n.options.preload.indexOf(lng) > -1)
|
|
56
|
+
return loadNamespaces(i18n, ns, cb);
|
|
57
|
+
ns.forEach((n) => {
|
|
58
|
+
if (i18n.options.ns.indexOf(n) < 0) i18n.options.ns.push(n);
|
|
59
|
+
});
|
|
60
|
+
i18n.loadLanguages(lng, loadedClb(i18n, cb));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const hasLoadedNamespace = (ns, i18n, options = {}) => {
|
|
64
|
+
if (!i18n.languages || !i18n.languages.length) {
|
|
65
|
+
warnOnce(i18n, 'NO_LANGUAGES', 'i18n.languages were undefined or empty', {
|
|
66
|
+
languages: i18n.languages,
|
|
67
|
+
});
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return i18n.hasLoadedNamespace(ns, {
|
|
72
|
+
lng: options.lng,
|
|
73
|
+
precheck: (i18nInstance, loadNotPending) => {
|
|
74
|
+
if (
|
|
75
|
+
options.bindI18n &&
|
|
76
|
+
options.bindI18n.indexOf('languageChanging') > -1 &&
|
|
77
|
+
i18nInstance.services.backendConnector.backend &&
|
|
78
|
+
i18nInstance.isLanguageChangingTo &&
|
|
79
|
+
!loadNotPending(i18nInstance.isLanguageChangingTo, ns)
|
|
80
|
+
)
|
|
81
|
+
return false;
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const getDisplayName = (Component) =>
|
|
87
|
+
Component.displayName ||
|
|
88
|
+
Component.name ||
|
|
89
|
+
(isString(Component) && Component.length > 0 ? Component : 'Unknown');
|
|
90
|
+
|
|
91
|
+
export const isString = (obj) => typeof obj === 'string';
|
|
92
|
+
|
|
93
|
+
export const isObject = (obj) => typeof obj === 'object' && obj !== null;
|
package/src/withSSR.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Ported from react-i18next@17.0.9 (8b4a9ea): descriptor creation -> octane.
|
|
2
|
+
import { createElement } from 'octane';
|
|
3
|
+
import { useSSR } from './useSSR.js';
|
|
4
|
+
import { composeInitialProps } from './context.js';
|
|
5
|
+
import { getDisplayName } from './utils.js';
|
|
6
|
+
|
|
7
|
+
export const withSSR = () =>
|
|
8
|
+
function Extend(WrappedComponent) {
|
|
9
|
+
function I18nextWithSSR({ initialI18nStore, initialLanguage, ...rest }) {
|
|
10
|
+
useSSR(initialI18nStore, initialLanguage);
|
|
11
|
+
|
|
12
|
+
return createElement(WrappedComponent, {
|
|
13
|
+
...rest,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
I18nextWithSSR.getInitialProps = composeInitialProps(WrappedComponent);
|
|
18
|
+
I18nextWithSSR.displayName = `withI18nextSSR(${getDisplayName(WrappedComponent)})`;
|
|
19
|
+
I18nextWithSSR.WrappedComponent = WrappedComponent;
|
|
20
|
+
|
|
21
|
+
return I18nextWithSSR;
|
|
22
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Ported from react-i18next@17.0.9 (8b4a9ea): octane uses ref-as-prop.
|
|
2
|
+
import { createElement } from 'octane';
|
|
3
|
+
import { useTranslation } from './useTranslation.js';
|
|
4
|
+
import { getDisplayName } from './utils.js';
|
|
5
|
+
import { S } from './internal.js';
|
|
6
|
+
|
|
7
|
+
export const withTranslation = (ns, options = {}) =>
|
|
8
|
+
function Extend(WrappedComponent) {
|
|
9
|
+
function I18nextWithTranslation({ forwardedRef, ref, ...rest }) {
|
|
10
|
+
const [t, i18n, ready] = useTranslation(
|
|
11
|
+
ns,
|
|
12
|
+
{ ...rest, keyPrefix: options.keyPrefix },
|
|
13
|
+
S('withTranslation:useTranslation'),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const passDownProps = {
|
|
17
|
+
...rest,
|
|
18
|
+
t,
|
|
19
|
+
i18n,
|
|
20
|
+
tReady: ready,
|
|
21
|
+
};
|
|
22
|
+
const resolvedRef = ref || forwardedRef;
|
|
23
|
+
if (options.withRef && resolvedRef) {
|
|
24
|
+
passDownProps.ref = resolvedRef;
|
|
25
|
+
} else if (!options.withRef && forwardedRef) {
|
|
26
|
+
passDownProps.forwardedRef = forwardedRef;
|
|
27
|
+
}
|
|
28
|
+
return createElement(WrappedComponent, passDownProps);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
I18nextWithTranslation.displayName = `withI18nextTranslation(${getDisplayName(
|
|
32
|
+
WrappedComponent,
|
|
33
|
+
)})`;
|
|
34
|
+
|
|
35
|
+
I18nextWithTranslation.WrappedComponent = WrappedComponent;
|
|
36
|
+
|
|
37
|
+
return I18nextWithTranslation;
|
|
38
|
+
};
|