@intl-party/react 1.0.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 +21 -0
- package/dist/index.js +1026 -0
- package/dist/index.mjs +958 -0
- package/package.json +63 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
// src/index.tsx
|
|
2
|
+
import React4 from "react";
|
|
3
|
+
|
|
4
|
+
// src/context/I18nContext.tsx
|
|
5
|
+
import {
|
|
6
|
+
createContext,
|
|
7
|
+
useContext,
|
|
8
|
+
useMemo,
|
|
9
|
+
useState,
|
|
10
|
+
useEffect
|
|
11
|
+
} from "react";
|
|
12
|
+
import {
|
|
13
|
+
createI18n
|
|
14
|
+
} from "@intl-party/core";
|
|
15
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
16
|
+
var I18nContext = createContext(null);
|
|
17
|
+
function I18nProvider({
|
|
18
|
+
children,
|
|
19
|
+
config,
|
|
20
|
+
i18n: externalI18n,
|
|
21
|
+
initialLocale,
|
|
22
|
+
initialNamespace,
|
|
23
|
+
loadingComponent,
|
|
24
|
+
fallbackComponent,
|
|
25
|
+
onLocaleChange,
|
|
26
|
+
onNamespaceChange,
|
|
27
|
+
onError
|
|
28
|
+
}) {
|
|
29
|
+
const i18nInstance = useMemo(() => {
|
|
30
|
+
if (externalI18n) {
|
|
31
|
+
return externalI18n;
|
|
32
|
+
}
|
|
33
|
+
if (!config) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"Either config or i18n instance must be provided to I18nProvider"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const instance = createI18n(config);
|
|
39
|
+
if (initialLocale && config.locales.includes(initialLocale)) {
|
|
40
|
+
instance.setLocale(initialLocale);
|
|
41
|
+
}
|
|
42
|
+
if (initialNamespace && config.namespaces.includes(initialNamespace)) {
|
|
43
|
+
instance.setNamespace(initialNamespace);
|
|
44
|
+
}
|
|
45
|
+
return instance;
|
|
46
|
+
}, [config, externalI18n, initialLocale, initialNamespace]);
|
|
47
|
+
const [locale, setLocaleState] = useState(i18nInstance.getLocale());
|
|
48
|
+
const [namespace, setNamespaceState] = useState(
|
|
49
|
+
i18nInstance.getNamespace()
|
|
50
|
+
);
|
|
51
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
52
|
+
const [error, setError] = useState(null);
|
|
53
|
+
const handleLocaleChange = (newLocale) => {
|
|
54
|
+
try {
|
|
55
|
+
setIsLoading(true);
|
|
56
|
+
i18nInstance.setLocale(newLocale);
|
|
57
|
+
setLocaleState(newLocale);
|
|
58
|
+
onLocaleChange?.(newLocale);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const error2 = err instanceof Error ? err : new Error("Failed to change locale");
|
|
61
|
+
setError(error2);
|
|
62
|
+
onError?.(error2);
|
|
63
|
+
} finally {
|
|
64
|
+
setIsLoading(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const handleNamespaceChange = (newNamespace) => {
|
|
68
|
+
try {
|
|
69
|
+
i18nInstance.setNamespace(newNamespace);
|
|
70
|
+
setNamespaceState(newNamespace);
|
|
71
|
+
onNamespaceChange?.(newNamespace);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
const error2 = err instanceof Error ? err : new Error("Failed to change namespace");
|
|
74
|
+
setError(error2);
|
|
75
|
+
onError?.(error2);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const handleLocaleChangeEvent = ({
|
|
80
|
+
locale: newLocale
|
|
81
|
+
}) => {
|
|
82
|
+
setLocaleState(newLocale);
|
|
83
|
+
onLocaleChange?.(newLocale);
|
|
84
|
+
};
|
|
85
|
+
const handleNamespaceChangeEvent = ({
|
|
86
|
+
namespace: newNamespace
|
|
87
|
+
}) => {
|
|
88
|
+
setNamespaceState(newNamespace);
|
|
89
|
+
onNamespaceChange?.(newNamespace);
|
|
90
|
+
};
|
|
91
|
+
const handleTranslationsPreloading = () => setIsLoading(true);
|
|
92
|
+
const handleTranslationsPreloaded = () => setIsLoading(false);
|
|
93
|
+
i18nInstance.on("localeChange", handleLocaleChangeEvent);
|
|
94
|
+
i18nInstance.on("namespaceChange", handleNamespaceChangeEvent);
|
|
95
|
+
i18nInstance.on("translationsPreloading", handleTranslationsPreloading);
|
|
96
|
+
i18nInstance.on("translationsPreloaded", handleTranslationsPreloaded);
|
|
97
|
+
return () => {
|
|
98
|
+
i18nInstance.off("localeChange", handleLocaleChangeEvent);
|
|
99
|
+
i18nInstance.off("namespaceChange", handleNamespaceChangeEvent);
|
|
100
|
+
i18nInstance.off("translationsPreloading", handleTranslationsPreloading);
|
|
101
|
+
i18nInstance.off("translationsPreloaded", handleTranslationsPreloaded);
|
|
102
|
+
};
|
|
103
|
+
}, [i18nInstance, onLocaleChange, onNamespaceChange]);
|
|
104
|
+
const contextValue = useMemo(
|
|
105
|
+
() => ({
|
|
106
|
+
i18n: i18nInstance,
|
|
107
|
+
locale,
|
|
108
|
+
namespace,
|
|
109
|
+
t: i18nInstance.t,
|
|
110
|
+
setLocale: handleLocaleChange,
|
|
111
|
+
setNamespace: handleNamespaceChange,
|
|
112
|
+
isLoading
|
|
113
|
+
}),
|
|
114
|
+
[i18nInstance, locale, namespace, isLoading]
|
|
115
|
+
);
|
|
116
|
+
if (error && fallbackComponent) {
|
|
117
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallbackComponent });
|
|
118
|
+
}
|
|
119
|
+
if (error) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
if (isLoading && loadingComponent) {
|
|
123
|
+
return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent });
|
|
124
|
+
}
|
|
125
|
+
return /* @__PURE__ */ jsx(I18nContext.Provider, { value: contextValue, children });
|
|
126
|
+
}
|
|
127
|
+
function useI18nContext() {
|
|
128
|
+
const context = useContext(I18nContext);
|
|
129
|
+
if (!context) {
|
|
130
|
+
throw new Error("useI18nContext must be used within an I18nProvider");
|
|
131
|
+
}
|
|
132
|
+
return context;
|
|
133
|
+
}
|
|
134
|
+
var TypedI18nContext = createContext(null);
|
|
135
|
+
function TypedI18nProvider({
|
|
136
|
+
children,
|
|
137
|
+
...props
|
|
138
|
+
}) {
|
|
139
|
+
return /* @__PURE__ */ jsx(I18nProvider, { ...props, children: /* @__PURE__ */ jsx(TypedI18nContextWrapper, { children }) });
|
|
140
|
+
}
|
|
141
|
+
function TypedI18nContextWrapper({
|
|
142
|
+
children
|
|
143
|
+
}) {
|
|
144
|
+
const { i18n, locale, namespace, setLocale, setNamespace, isLoading } = useI18nContext();
|
|
145
|
+
const typedContextValue = useMemo(
|
|
146
|
+
() => ({
|
|
147
|
+
i18n,
|
|
148
|
+
locale,
|
|
149
|
+
namespace,
|
|
150
|
+
t: i18n.t,
|
|
151
|
+
setLocale,
|
|
152
|
+
setNamespace,
|
|
153
|
+
isLoading
|
|
154
|
+
}),
|
|
155
|
+
[i18n, locale, namespace, setLocale, setNamespace, isLoading]
|
|
156
|
+
);
|
|
157
|
+
return /* @__PURE__ */ jsx(TypedI18nContext.Provider, { value: typedContextValue, children });
|
|
158
|
+
}
|
|
159
|
+
function useTypedI18nContext() {
|
|
160
|
+
const context = useContext(
|
|
161
|
+
TypedI18nContext
|
|
162
|
+
);
|
|
163
|
+
if (!context) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
"useTypedI18nContext must be used within a TypedI18nProvider"
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return context;
|
|
169
|
+
}
|
|
170
|
+
function withI18n(Component) {
|
|
171
|
+
const WrappedComponent = (props) => {
|
|
172
|
+
const i18nContext = useI18nContext();
|
|
173
|
+
return /* @__PURE__ */ jsx(Component, { ...props, i18n: i18nContext });
|
|
174
|
+
};
|
|
175
|
+
WrappedComponent.displayName = `withI18n(${Component.displayName || Component.name})`;
|
|
176
|
+
return WrappedComponent;
|
|
177
|
+
}
|
|
178
|
+
function ScopedI18nProvider({
|
|
179
|
+
children,
|
|
180
|
+
namespace,
|
|
181
|
+
locale
|
|
182
|
+
}) {
|
|
183
|
+
const parentContext = useI18nContext();
|
|
184
|
+
const scopedContextValue = useMemo(
|
|
185
|
+
() => ({
|
|
186
|
+
...parentContext,
|
|
187
|
+
namespace,
|
|
188
|
+
locale: locale || parentContext.locale,
|
|
189
|
+
t: parentContext.i18n.createScopedTranslator(namespace)
|
|
190
|
+
}),
|
|
191
|
+
[parentContext, namespace, locale]
|
|
192
|
+
);
|
|
193
|
+
return /* @__PURE__ */ jsx(I18nContext.Provider, { value: scopedContextValue, children });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/hooks/useTranslations.ts
|
|
197
|
+
import { useCallback, useMemo as useMemo2 } from "react";
|
|
198
|
+
function useTranslations(namespace) {
|
|
199
|
+
const { i18n, namespace: currentNamespace } = useI18nContext();
|
|
200
|
+
const targetNamespace = namespace || currentNamespace;
|
|
201
|
+
return useCallback(
|
|
202
|
+
(key, options) => {
|
|
203
|
+
return i18n.t(key, { ...options, namespace: targetNamespace });
|
|
204
|
+
},
|
|
205
|
+
[i18n, targetNamespace]
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
function useTypedTranslations(namespace) {
|
|
209
|
+
const { i18n, namespace: currentNamespace } = useTypedI18nContext();
|
|
210
|
+
const targetNamespace = namespace || currentNamespace;
|
|
211
|
+
return useCallback(
|
|
212
|
+
(key, options) => {
|
|
213
|
+
return i18n.t(key, {
|
|
214
|
+
...options,
|
|
215
|
+
namespace: targetNamespace
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
[i18n, targetNamespace]
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
var useT = useTranslations;
|
|
222
|
+
var useTypedT = useTypedTranslations;
|
|
223
|
+
function useScopedTranslations(namespace) {
|
|
224
|
+
const { i18n } = useI18nContext();
|
|
225
|
+
return useMemo2(() => {
|
|
226
|
+
return i18n.createScopedTranslator(namespace);
|
|
227
|
+
}, [i18n, namespace]);
|
|
228
|
+
}
|
|
229
|
+
function useMultipleTranslations(namespaces) {
|
|
230
|
+
const { i18n } = useI18nContext();
|
|
231
|
+
return useMemo2(() => {
|
|
232
|
+
const translators = {};
|
|
233
|
+
for (const namespace of namespaces) {
|
|
234
|
+
translators[namespace] = i18n.createScopedTranslator(namespace);
|
|
235
|
+
}
|
|
236
|
+
return translators;
|
|
237
|
+
}, [i18n, namespaces]);
|
|
238
|
+
}
|
|
239
|
+
function useOptionalTranslation(key, namespace, options) {
|
|
240
|
+
const { i18n, namespace: currentNamespace } = useI18nContext();
|
|
241
|
+
const targetNamespace = namespace || currentNamespace;
|
|
242
|
+
return useMemo2(() => {
|
|
243
|
+
if (i18n.hasTranslation(key, targetNamespace)) {
|
|
244
|
+
return i18n.t(key, { ...options, namespace: targetNamespace });
|
|
245
|
+
}
|
|
246
|
+
return void 0;
|
|
247
|
+
}, [i18n, key, targetNamespace, options]);
|
|
248
|
+
}
|
|
249
|
+
function useTranslationWithFallback(key, fallback, namespace, options) {
|
|
250
|
+
const { i18n, namespace: currentNamespace } = useI18nContext();
|
|
251
|
+
const targetNamespace = namespace || currentNamespace;
|
|
252
|
+
return useMemo2(() => {
|
|
253
|
+
return i18n.t(key, {
|
|
254
|
+
...options,
|
|
255
|
+
namespace: targetNamespace,
|
|
256
|
+
fallback
|
|
257
|
+
});
|
|
258
|
+
}, [i18n, key, fallback, targetNamespace, options]);
|
|
259
|
+
}
|
|
260
|
+
function useHasTranslation() {
|
|
261
|
+
const { i18n, namespace: currentNamespace } = useI18nContext();
|
|
262
|
+
return useCallback(
|
|
263
|
+
(key, namespace) => {
|
|
264
|
+
const targetNamespace = namespace || currentNamespace;
|
|
265
|
+
return i18n.hasTranslation(key, targetNamespace);
|
|
266
|
+
},
|
|
267
|
+
[i18n, currentNamespace]
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
function useTranslationValue() {
|
|
271
|
+
const { i18n, namespace: currentNamespace } = useI18nContext();
|
|
272
|
+
return useCallback(
|
|
273
|
+
(key, namespace) => {
|
|
274
|
+
const targetNamespace = namespace || currentNamespace;
|
|
275
|
+
return i18n.getTranslation(key, targetNamespace);
|
|
276
|
+
},
|
|
277
|
+
[i18n, currentNamespace]
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
function useInterpolatedTranslation(key, variables, namespace) {
|
|
281
|
+
const t = useTranslations(namespace);
|
|
282
|
+
return useMemo2(() => {
|
|
283
|
+
return t(key, { interpolation: variables });
|
|
284
|
+
}, [t, key, variables]);
|
|
285
|
+
}
|
|
286
|
+
function usePluralization(key, count, namespace, additionalOptions) {
|
|
287
|
+
const t = useTranslations(namespace);
|
|
288
|
+
return useMemo2(() => {
|
|
289
|
+
return t(key, { ...additionalOptions, count });
|
|
290
|
+
}, [t, key, count, additionalOptions]);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/hooks/useLocale.ts
|
|
294
|
+
import { useCallback as useCallback2, useMemo as useMemo3 } from "react";
|
|
295
|
+
function useLocale() {
|
|
296
|
+
const { locale, setLocale } = useI18nContext();
|
|
297
|
+
return [locale, setLocale];
|
|
298
|
+
}
|
|
299
|
+
function useLocaleInfo() {
|
|
300
|
+
const { locale, i18n } = useI18nContext();
|
|
301
|
+
return useMemo3(() => {
|
|
302
|
+
const availableLocales = i18n.getAvailableLocales();
|
|
303
|
+
const fallbackChain = i18n.getFallbackChain(locale);
|
|
304
|
+
return {
|
|
305
|
+
current: locale,
|
|
306
|
+
available: availableLocales,
|
|
307
|
+
fallbackChain,
|
|
308
|
+
isRTL: isRTLLocale(locale),
|
|
309
|
+
direction: isRTLLocale(locale) ? "rtl" : "ltr"
|
|
310
|
+
};
|
|
311
|
+
}, [locale, i18n]);
|
|
312
|
+
}
|
|
313
|
+
function useLocaleSwitch() {
|
|
314
|
+
const { i18n, setLocale } = useI18nContext();
|
|
315
|
+
const switchLocale = useCallback2(
|
|
316
|
+
(locale) => {
|
|
317
|
+
const availableLocales = i18n.getAvailableLocales();
|
|
318
|
+
if (!availableLocales.includes(locale)) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
`Locale "${locale}" is not available. Available locales: ${availableLocales.join(", ")}`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
setLocale(locale);
|
|
324
|
+
},
|
|
325
|
+
[i18n, setLocale]
|
|
326
|
+
);
|
|
327
|
+
const isLocaleAvailable = useCallback2(
|
|
328
|
+
(locale) => {
|
|
329
|
+
return i18n.getAvailableLocales().includes(locale);
|
|
330
|
+
},
|
|
331
|
+
[i18n]
|
|
332
|
+
);
|
|
333
|
+
return {
|
|
334
|
+
switchLocale,
|
|
335
|
+
isLocaleAvailable,
|
|
336
|
+
availableLocales: i18n.getAvailableLocales()
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function useBrowserLocale() {
|
|
340
|
+
const { i18n } = useI18nContext();
|
|
341
|
+
return useMemo3(() => {
|
|
342
|
+
if (typeof window === "undefined") return null;
|
|
343
|
+
const detected = i18n.detectLocale({
|
|
344
|
+
request: void 0,
|
|
345
|
+
url: window.location.href,
|
|
346
|
+
userAgent: navigator.userAgent
|
|
347
|
+
});
|
|
348
|
+
return {
|
|
349
|
+
detected,
|
|
350
|
+
browser: navigator.language,
|
|
351
|
+
supported: i18n.getAvailableLocales().includes(detected)
|
|
352
|
+
};
|
|
353
|
+
}, [i18n]);
|
|
354
|
+
}
|
|
355
|
+
function useLocalePreference() {
|
|
356
|
+
const { locale, setLocale, i18n } = useI18nContext();
|
|
357
|
+
const savePreference = useCallback2(
|
|
358
|
+
(newLocale) => {
|
|
359
|
+
if (typeof window !== "undefined") {
|
|
360
|
+
localStorage.setItem("intl-party-locale", newLocale);
|
|
361
|
+
}
|
|
362
|
+
setLocale(newLocale);
|
|
363
|
+
},
|
|
364
|
+
[setLocale]
|
|
365
|
+
);
|
|
366
|
+
const loadPreference = useCallback2(() => {
|
|
367
|
+
if (typeof window === "undefined") return null;
|
|
368
|
+
const saved = localStorage.getItem("intl-party-locale");
|
|
369
|
+
if (saved && i18n.getAvailableLocales().includes(saved)) {
|
|
370
|
+
return saved;
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}, [i18n]);
|
|
374
|
+
const clearPreference = useCallback2(() => {
|
|
375
|
+
if (typeof window !== "undefined") {
|
|
376
|
+
localStorage.removeItem("intl-party-locale");
|
|
377
|
+
}
|
|
378
|
+
}, []);
|
|
379
|
+
return {
|
|
380
|
+
current: locale,
|
|
381
|
+
save: savePreference,
|
|
382
|
+
load: loadPreference,
|
|
383
|
+
clear: clearPreference
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function isRTLLocale(locale) {
|
|
387
|
+
const rtlLocales = [
|
|
388
|
+
"ar",
|
|
389
|
+
"arc",
|
|
390
|
+
"ckb",
|
|
391
|
+
"dv",
|
|
392
|
+
"fa",
|
|
393
|
+
"ha",
|
|
394
|
+
"he",
|
|
395
|
+
"khw",
|
|
396
|
+
"ks",
|
|
397
|
+
"ku",
|
|
398
|
+
"ps",
|
|
399
|
+
"sd",
|
|
400
|
+
"ur",
|
|
401
|
+
"yi"
|
|
402
|
+
];
|
|
403
|
+
const baseLocale = locale.split("-")[0];
|
|
404
|
+
return rtlLocales.includes(baseLocale);
|
|
405
|
+
}
|
|
406
|
+
function useDirection() {
|
|
407
|
+
const { locale } = useI18nContext();
|
|
408
|
+
return useMemo3(() => {
|
|
409
|
+
return isRTLLocale(locale) ? "rtl" : "ltr";
|
|
410
|
+
}, [locale]);
|
|
411
|
+
}
|
|
412
|
+
function useFormatting() {
|
|
413
|
+
const { i18n } = useI18nContext();
|
|
414
|
+
return useMemo3(
|
|
415
|
+
() => ({
|
|
416
|
+
formatDate: (date, options) => i18n.formatDate(date, options),
|
|
417
|
+
formatNumber: (number, options) => i18n.formatNumber(number, options),
|
|
418
|
+
formatCurrency: (amount, currency, options) => i18n.formatCurrency(amount, currency, options),
|
|
419
|
+
formatRelativeTime: (value, unit, options) => i18n.formatRelativeTime(value, unit, options)
|
|
420
|
+
}),
|
|
421
|
+
[i18n]
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/hooks/useNamespace.ts
|
|
426
|
+
import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
|
|
427
|
+
function useNamespace() {
|
|
428
|
+
const { namespace, setNamespace } = useI18nContext();
|
|
429
|
+
return [namespace, setNamespace];
|
|
430
|
+
}
|
|
431
|
+
function useNamespaceInfo() {
|
|
432
|
+
const { namespace, i18n } = useI18nContext();
|
|
433
|
+
return useMemo4(() => {
|
|
434
|
+
const availableNamespaces = i18n.getAvailableNamespaces();
|
|
435
|
+
return {
|
|
436
|
+
current: namespace,
|
|
437
|
+
available: availableNamespaces,
|
|
438
|
+
isAvailable: availableNamespaces.includes(namespace)
|
|
439
|
+
};
|
|
440
|
+
}, [namespace, i18n]);
|
|
441
|
+
}
|
|
442
|
+
function useNamespaceSwitch() {
|
|
443
|
+
const { i18n, setNamespace } = useI18nContext();
|
|
444
|
+
const switchNamespace = useCallback3(
|
|
445
|
+
(namespace) => {
|
|
446
|
+
const availableNamespaces = i18n.getAvailableNamespaces();
|
|
447
|
+
if (!availableNamespaces.includes(namespace)) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`Namespace "${namespace}" is not available. Available namespaces: ${availableNamespaces.join(", ")}`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
setNamespace(namespace);
|
|
453
|
+
},
|
|
454
|
+
[i18n, setNamespace]
|
|
455
|
+
);
|
|
456
|
+
const isNamespaceAvailable = useCallback3(
|
|
457
|
+
(namespace) => {
|
|
458
|
+
return i18n.getAvailableNamespaces().includes(namespace);
|
|
459
|
+
},
|
|
460
|
+
[i18n]
|
|
461
|
+
);
|
|
462
|
+
return {
|
|
463
|
+
switchNamespace,
|
|
464
|
+
isNamespaceAvailable,
|
|
465
|
+
availableNamespaces: i18n.getAvailableNamespaces()
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function useMultipleNamespaces(namespaces) {
|
|
469
|
+
const { i18n } = useI18nContext();
|
|
470
|
+
const translators = useMemo4(() => {
|
|
471
|
+
return namespaces.reduce(
|
|
472
|
+
(acc, ns) => {
|
|
473
|
+
acc[ns] = i18n.createScopedTranslator(ns);
|
|
474
|
+
return acc;
|
|
475
|
+
},
|
|
476
|
+
{}
|
|
477
|
+
);
|
|
478
|
+
}, [i18n, namespaces]);
|
|
479
|
+
const isAllAvailable = useMemo4(() => {
|
|
480
|
+
const available = i18n.getAvailableNamespaces();
|
|
481
|
+
return namespaces.every((ns) => available.includes(ns));
|
|
482
|
+
}, [i18n, namespaces]);
|
|
483
|
+
const getMissingNamespaces = useCallback3(() => {
|
|
484
|
+
const available = i18n.getAvailableNamespaces();
|
|
485
|
+
return namespaces.filter((ns) => !available.includes(ns));
|
|
486
|
+
}, [i18n, namespaces]);
|
|
487
|
+
return {
|
|
488
|
+
translators,
|
|
489
|
+
isAllAvailable,
|
|
490
|
+
getMissingNamespaces
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function useNamespacePreloading() {
|
|
494
|
+
const { i18n, locale } = useI18nContext();
|
|
495
|
+
const preloadNamespace = useCallback3(
|
|
496
|
+
async (namespace) => {
|
|
497
|
+
await i18n.preloadTranslations(locale, namespace);
|
|
498
|
+
},
|
|
499
|
+
[i18n, locale]
|
|
500
|
+
);
|
|
501
|
+
const preloadNamespaces = useCallback3(
|
|
502
|
+
async (namespaces) => {
|
|
503
|
+
await i18n.preloadTranslations(locale, namespaces);
|
|
504
|
+
},
|
|
505
|
+
[i18n, locale]
|
|
506
|
+
);
|
|
507
|
+
return {
|
|
508
|
+
preloadNamespace,
|
|
509
|
+
preloadNamespaces
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/components/Trans.tsx
|
|
514
|
+
import React2, { Fragment as Fragment2, useMemo as useMemo5 } from "react";
|
|
515
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
516
|
+
function Trans({
|
|
517
|
+
i18nKey,
|
|
518
|
+
namespace,
|
|
519
|
+
values = {},
|
|
520
|
+
components = {},
|
|
521
|
+
count,
|
|
522
|
+
fallback,
|
|
523
|
+
children
|
|
524
|
+
}) {
|
|
525
|
+
const t = useTranslations(namespace);
|
|
526
|
+
const rendered = useMemo5(() => {
|
|
527
|
+
const options = {
|
|
528
|
+
interpolation: values,
|
|
529
|
+
count,
|
|
530
|
+
fallback
|
|
531
|
+
};
|
|
532
|
+
const translation = t(i18nKey, options);
|
|
533
|
+
if (Object.keys(components).length === 0) {
|
|
534
|
+
return translation;
|
|
535
|
+
}
|
|
536
|
+
return parseTranslationWithComponents(translation, components);
|
|
537
|
+
}, [t, i18nKey, values, components, count, fallback]);
|
|
538
|
+
if (typeof rendered === "string") {
|
|
539
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: rendered });
|
|
540
|
+
}
|
|
541
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: rendered });
|
|
542
|
+
}
|
|
543
|
+
function ConditionalTrans({
|
|
544
|
+
when = true,
|
|
545
|
+
fallbackComponent,
|
|
546
|
+
...transProps
|
|
547
|
+
}) {
|
|
548
|
+
if (!when) {
|
|
549
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: fallbackComponent });
|
|
550
|
+
}
|
|
551
|
+
return /* @__PURE__ */ jsx2(Trans, { ...transProps });
|
|
552
|
+
}
|
|
553
|
+
function PluralTrans({
|
|
554
|
+
count,
|
|
555
|
+
zero,
|
|
556
|
+
one,
|
|
557
|
+
other,
|
|
558
|
+
i18nKey,
|
|
559
|
+
...props
|
|
560
|
+
}) {
|
|
561
|
+
const selectedKey = useMemo5(() => {
|
|
562
|
+
if (count === 0 && zero) return zero;
|
|
563
|
+
if (count === 1 && one) return one;
|
|
564
|
+
if (other) return other;
|
|
565
|
+
return i18nKey;
|
|
566
|
+
}, [count, zero, one, other, i18nKey]);
|
|
567
|
+
return /* @__PURE__ */ jsx2(
|
|
568
|
+
Trans,
|
|
569
|
+
{
|
|
570
|
+
...props,
|
|
571
|
+
i18nKey: selectedKey,
|
|
572
|
+
count,
|
|
573
|
+
values: { count, ...props.values }
|
|
574
|
+
}
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
function RichTrans({
|
|
578
|
+
allowedTags = ["strong", "em", "br", "span"],
|
|
579
|
+
sanitize = true,
|
|
580
|
+
...transProps
|
|
581
|
+
}) {
|
|
582
|
+
const t = useTranslations(transProps.namespace);
|
|
583
|
+
const rendered = useMemo5(() => {
|
|
584
|
+
const translation = t(transProps.i18nKey, {
|
|
585
|
+
interpolation: transProps.values,
|
|
586
|
+
count: transProps.count,
|
|
587
|
+
fallback: transProps.fallback
|
|
588
|
+
});
|
|
589
|
+
if (sanitize) {
|
|
590
|
+
const sanitized = translation.replace(
|
|
591
|
+
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
|
|
592
|
+
""
|
|
593
|
+
);
|
|
594
|
+
return parseHTMLString(sanitized, allowedTags);
|
|
595
|
+
}
|
|
596
|
+
return parseHTMLString(translation, allowedTags);
|
|
597
|
+
}, [t, transProps, allowedTags, sanitize]);
|
|
598
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: rendered });
|
|
599
|
+
}
|
|
600
|
+
function parseTranslationWithComponents(text, components) {
|
|
601
|
+
const parts = [];
|
|
602
|
+
let currentIndex = 0;
|
|
603
|
+
const componentRegex = /<(\w+)>(.*?)<\/\1>/g;
|
|
604
|
+
let match;
|
|
605
|
+
while ((match = componentRegex.exec(text)) !== null) {
|
|
606
|
+
const [fullMatch, componentKey, content] = match;
|
|
607
|
+
const matchStart = match.index;
|
|
608
|
+
if (matchStart > currentIndex) {
|
|
609
|
+
parts.push(text.slice(currentIndex, matchStart));
|
|
610
|
+
}
|
|
611
|
+
if (components[componentKey]) {
|
|
612
|
+
if (React2.isValidElement(components[componentKey])) {
|
|
613
|
+
parts.push(
|
|
614
|
+
React2.cloneElement(
|
|
615
|
+
components[componentKey],
|
|
616
|
+
{ key: parts.length },
|
|
617
|
+
content
|
|
618
|
+
)
|
|
619
|
+
);
|
|
620
|
+
} else {
|
|
621
|
+
parts.push(components[componentKey]);
|
|
622
|
+
}
|
|
623
|
+
} else {
|
|
624
|
+
parts.push(content);
|
|
625
|
+
}
|
|
626
|
+
currentIndex = matchStart + fullMatch.length;
|
|
627
|
+
}
|
|
628
|
+
if (currentIndex < text.length) {
|
|
629
|
+
parts.push(text.slice(currentIndex));
|
|
630
|
+
}
|
|
631
|
+
return parts.length > 0 ? parts : [text];
|
|
632
|
+
}
|
|
633
|
+
function parseHTMLString(html, allowedTags) {
|
|
634
|
+
if (!allowedTags.length) {
|
|
635
|
+
return html;
|
|
636
|
+
}
|
|
637
|
+
const tagRegex = new RegExp(
|
|
638
|
+
`<(/?)(${allowedTags.join("|")})(?:\\s[^>]*)?>`,
|
|
639
|
+
"gi"
|
|
640
|
+
);
|
|
641
|
+
const parts = html.split(tagRegex).filter(Boolean);
|
|
642
|
+
const elements = [];
|
|
643
|
+
let i = 0;
|
|
644
|
+
while (i < parts.length) {
|
|
645
|
+
const part = parts[i];
|
|
646
|
+
if (allowedTags.some((tag) => part.toLowerCase() === tag)) {
|
|
647
|
+
const tag = part.toLowerCase();
|
|
648
|
+
let content = "";
|
|
649
|
+
let depth = 1;
|
|
650
|
+
i++;
|
|
651
|
+
while (i < parts.length && depth > 0) {
|
|
652
|
+
const nextPart = parts[i];
|
|
653
|
+
if (nextPart === "/") {
|
|
654
|
+
i++;
|
|
655
|
+
if (i < parts.length && parts[i].toLowerCase() === tag) {
|
|
656
|
+
depth--;
|
|
657
|
+
i++;
|
|
658
|
+
}
|
|
659
|
+
} else if (allowedTags.some((t) => nextPart.toLowerCase() === t)) {
|
|
660
|
+
depth++;
|
|
661
|
+
content += `<${nextPart}>`;
|
|
662
|
+
i++;
|
|
663
|
+
} else {
|
|
664
|
+
content += nextPart;
|
|
665
|
+
i++;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
elements.push(
|
|
669
|
+
React2.createElement(tag, { key: elements.length }, content)
|
|
670
|
+
);
|
|
671
|
+
} else {
|
|
672
|
+
elements.push(part);
|
|
673
|
+
i++;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return elements.length === 1 ? elements[0] : elements;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/components/LocaleSelector.tsx
|
|
680
|
+
import { useMemo as useMemo6 } from "react";
|
|
681
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
682
|
+
function LocaleSelector({
|
|
683
|
+
className,
|
|
684
|
+
style,
|
|
685
|
+
placeholder = "Select language",
|
|
686
|
+
disabled = false,
|
|
687
|
+
showNativeNames = true,
|
|
688
|
+
filterLocales,
|
|
689
|
+
formatLocale,
|
|
690
|
+
onLocaleChange,
|
|
691
|
+
variant = "select"
|
|
692
|
+
}) {
|
|
693
|
+
const [currentLocale, setLocale] = useLocale();
|
|
694
|
+
const { available } = useLocaleInfo();
|
|
695
|
+
const filteredLocales = useMemo6(() => {
|
|
696
|
+
return filterLocales ? available.filter(filterLocales) : available;
|
|
697
|
+
}, [available, filterLocales]);
|
|
698
|
+
const handleLocaleChange = (locale) => {
|
|
699
|
+
setLocale(locale);
|
|
700
|
+
onLocaleChange?.(locale);
|
|
701
|
+
};
|
|
702
|
+
const formatLocaleDisplay = (locale) => {
|
|
703
|
+
if (formatLocale) {
|
|
704
|
+
return formatLocale(locale);
|
|
705
|
+
}
|
|
706
|
+
if (showNativeNames) {
|
|
707
|
+
try {
|
|
708
|
+
const intlLocale = new Intl.Locale(locale);
|
|
709
|
+
const displayNames = new Intl.DisplayNames([locale], {
|
|
710
|
+
type: "language"
|
|
711
|
+
});
|
|
712
|
+
return displayNames.of(intlLocale.language) || locale;
|
|
713
|
+
} catch {
|
|
714
|
+
return locale;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return locale;
|
|
718
|
+
};
|
|
719
|
+
if (variant === "select") {
|
|
720
|
+
return /* @__PURE__ */ jsxs(
|
|
721
|
+
"select",
|
|
722
|
+
{
|
|
723
|
+
className,
|
|
724
|
+
style,
|
|
725
|
+
value: currentLocale,
|
|
726
|
+
disabled,
|
|
727
|
+
onChange: (e) => handleLocaleChange(e.target.value),
|
|
728
|
+
children: [
|
|
729
|
+
placeholder && /* @__PURE__ */ jsx3("option", { value: "", disabled: true, children: placeholder }),
|
|
730
|
+
filteredLocales.map((locale) => /* @__PURE__ */ jsx3("option", { value: locale, children: formatLocaleDisplay(locale) }, locale))
|
|
731
|
+
]
|
|
732
|
+
}
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
if (variant === "buttons") {
|
|
736
|
+
return /* @__PURE__ */ jsx3("div", { className, style, children: filteredLocales.map((locale) => /* @__PURE__ */ jsx3(
|
|
737
|
+
"button",
|
|
738
|
+
{
|
|
739
|
+
type: "button",
|
|
740
|
+
disabled,
|
|
741
|
+
onClick: () => handleLocaleChange(locale),
|
|
742
|
+
style: {
|
|
743
|
+
fontWeight: currentLocale === locale ? "bold" : "normal",
|
|
744
|
+
opacity: currentLocale === locale ? 1 : 0.7
|
|
745
|
+
},
|
|
746
|
+
children: formatLocaleDisplay(locale)
|
|
747
|
+
},
|
|
748
|
+
locale
|
|
749
|
+
)) });
|
|
750
|
+
}
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
function FlagLocaleSelector({
|
|
754
|
+
flagMap = {},
|
|
755
|
+
showFlags = true,
|
|
756
|
+
showLabels = true,
|
|
757
|
+
variant = "buttons",
|
|
758
|
+
...props
|
|
759
|
+
}) {
|
|
760
|
+
const [currentLocale, setLocale] = useLocale();
|
|
761
|
+
const { available } = useLocaleInfo();
|
|
762
|
+
const defaultFlagMap = {
|
|
763
|
+
en: "\u{1F1FA}\u{1F1F8}",
|
|
764
|
+
es: "\u{1F1EA}\u{1F1F8}",
|
|
765
|
+
fr: "\u{1F1EB}\u{1F1F7}",
|
|
766
|
+
de: "\u{1F1E9}\u{1F1EA}",
|
|
767
|
+
it: "\u{1F1EE}\u{1F1F9}",
|
|
768
|
+
pt: "\u{1F1F5}\u{1F1F9}",
|
|
769
|
+
ru: "\u{1F1F7}\u{1F1FA}",
|
|
770
|
+
ja: "\u{1F1EF}\u{1F1F5}",
|
|
771
|
+
ko: "\u{1F1F0}\u{1F1F7}",
|
|
772
|
+
zh: "\u{1F1E8}\u{1F1F3}"
|
|
773
|
+
};
|
|
774
|
+
const combinedFlagMap = { ...defaultFlagMap, ...flagMap };
|
|
775
|
+
const formatLocaleWithFlag = (locale) => {
|
|
776
|
+
const parts = [];
|
|
777
|
+
if (showFlags && combinedFlagMap[locale]) {
|
|
778
|
+
parts.push(combinedFlagMap[locale]);
|
|
779
|
+
}
|
|
780
|
+
if (showLabels) {
|
|
781
|
+
if (props.formatLocale) {
|
|
782
|
+
parts.push(props.formatLocale(locale));
|
|
783
|
+
} else {
|
|
784
|
+
try {
|
|
785
|
+
const displayNames = new Intl.DisplayNames([locale], {
|
|
786
|
+
type: "language"
|
|
787
|
+
});
|
|
788
|
+
const intlLocale = new Intl.Locale(locale);
|
|
789
|
+
parts.push(displayNames.of(intlLocale.language) || locale);
|
|
790
|
+
} catch {
|
|
791
|
+
parts.push(locale);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return parts.join(" ");
|
|
796
|
+
};
|
|
797
|
+
return /* @__PURE__ */ jsx3(
|
|
798
|
+
LocaleSelector,
|
|
799
|
+
{
|
|
800
|
+
...props,
|
|
801
|
+
variant,
|
|
802
|
+
formatLocale: formatLocaleWithFlag,
|
|
803
|
+
showNativeNames: false
|
|
804
|
+
}
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
function CompactLocaleSelector({
|
|
808
|
+
maxVisibleLocales = 3,
|
|
809
|
+
...props
|
|
810
|
+
}) {
|
|
811
|
+
const { available } = useLocaleInfo();
|
|
812
|
+
if (available.length <= maxVisibleLocales) {
|
|
813
|
+
return /* @__PURE__ */ jsx3(LocaleSelector, { ...props, variant: "buttons" });
|
|
814
|
+
}
|
|
815
|
+
return /* @__PURE__ */ jsx3(LocaleSelector, { ...props, variant: "select" });
|
|
816
|
+
}
|
|
817
|
+
function AccessibleLocaleSelector({
|
|
818
|
+
ariaLabel = "Select language",
|
|
819
|
+
ariaDescribedBy,
|
|
820
|
+
...props
|
|
821
|
+
}) {
|
|
822
|
+
const [currentLocale] = useLocale();
|
|
823
|
+
const enhancedProps = {
|
|
824
|
+
...props,
|
|
825
|
+
style: {
|
|
826
|
+
...props.style,
|
|
827
|
+
// Ensure minimum touch target size for accessibility
|
|
828
|
+
minHeight: "44px",
|
|
829
|
+
minWidth: "44px"
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
if (props.variant === "select") {
|
|
833
|
+
return /* @__PURE__ */ jsx3(
|
|
834
|
+
LocaleSelector,
|
|
835
|
+
{
|
|
836
|
+
...enhancedProps,
|
|
837
|
+
className: `${props.className || ""} accessible-locale-selector`
|
|
838
|
+
}
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
return /* @__PURE__ */ jsxs(
|
|
842
|
+
"div",
|
|
843
|
+
{
|
|
844
|
+
role: "group",
|
|
845
|
+
"aria-label": ariaLabel,
|
|
846
|
+
"aria-describedby": ariaDescribedBy,
|
|
847
|
+
className: props.className,
|
|
848
|
+
style: props.style,
|
|
849
|
+
children: [
|
|
850
|
+
/* @__PURE__ */ jsx3(LocaleSelector, { ...enhancedProps }),
|
|
851
|
+
/* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
|
|
852
|
+
"Current language: ",
|
|
853
|
+
currentLocale
|
|
854
|
+
] })
|
|
855
|
+
]
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// src/index.tsx
|
|
861
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
862
|
+
var VERSION = "0.1.0";
|
|
863
|
+
function createI18nHook() {
|
|
864
|
+
return {
|
|
865
|
+
useTranslations: useTypedTranslations,
|
|
866
|
+
useT: useTypedTranslations
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function createNamespaceHOC(namespace) {
|
|
870
|
+
return function withNamespace(Component) {
|
|
871
|
+
return function NamespacedComponent(props) {
|
|
872
|
+
return /* @__PURE__ */ jsx4(ScopedI18nProvider, { namespace, children: /* @__PURE__ */ jsx4(Component, { ...props }) });
|
|
873
|
+
};
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
var I18nErrorBoundary = class extends React4.Component {
|
|
877
|
+
constructor(props) {
|
|
878
|
+
super(props);
|
|
879
|
+
this.state = { hasError: false };
|
|
880
|
+
}
|
|
881
|
+
static getDerivedStateFromError(error) {
|
|
882
|
+
return { hasError: true, error };
|
|
883
|
+
}
|
|
884
|
+
componentDidCatch(error, errorInfo) {
|
|
885
|
+
this.props.onError?.(error, errorInfo);
|
|
886
|
+
}
|
|
887
|
+
render() {
|
|
888
|
+
if (this.state.hasError) {
|
|
889
|
+
const FallbackComponent = this.props.fallback;
|
|
890
|
+
if (FallbackComponent && this.state.error) {
|
|
891
|
+
return /* @__PURE__ */ jsx4(FallbackComponent, { error: this.state.error });
|
|
892
|
+
}
|
|
893
|
+
return /* @__PURE__ */ jsxs2(
|
|
894
|
+
"div",
|
|
895
|
+
{
|
|
896
|
+
style: {
|
|
897
|
+
padding: "1rem",
|
|
898
|
+
border: "1px solid red",
|
|
899
|
+
borderRadius: "4px"
|
|
900
|
+
},
|
|
901
|
+
children: [
|
|
902
|
+
/* @__PURE__ */ jsx4("h3", { children: "Translation Error" }),
|
|
903
|
+
/* @__PURE__ */ jsx4("p", { children: "Something went wrong with translations." }),
|
|
904
|
+
process.env.NODE_ENV === "development" && this.state.error && /* @__PURE__ */ jsxs2("details", { children: [
|
|
905
|
+
/* @__PURE__ */ jsx4("summary", { children: "Error details" }),
|
|
906
|
+
/* @__PURE__ */ jsx4("pre", { children: this.state.error.message })
|
|
907
|
+
] })
|
|
908
|
+
]
|
|
909
|
+
}
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
return this.props.children;
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
export {
|
|
916
|
+
AccessibleLocaleSelector,
|
|
917
|
+
CompactLocaleSelector,
|
|
918
|
+
ConditionalTrans,
|
|
919
|
+
FlagLocaleSelector,
|
|
920
|
+
I18nErrorBoundary,
|
|
921
|
+
I18nProvider,
|
|
922
|
+
LocaleSelector,
|
|
923
|
+
PluralTrans,
|
|
924
|
+
RichTrans,
|
|
925
|
+
ScopedI18nProvider,
|
|
926
|
+
Trans,
|
|
927
|
+
TypedI18nProvider,
|
|
928
|
+
VERSION,
|
|
929
|
+
createI18nHook,
|
|
930
|
+
createNamespaceHOC,
|
|
931
|
+
useBrowserLocale,
|
|
932
|
+
useDirection,
|
|
933
|
+
useFormatting,
|
|
934
|
+
useHasTranslation,
|
|
935
|
+
useI18nContext,
|
|
936
|
+
useInterpolatedTranslation,
|
|
937
|
+
useLocale,
|
|
938
|
+
useLocaleInfo,
|
|
939
|
+
useLocalePreference,
|
|
940
|
+
useLocaleSwitch,
|
|
941
|
+
useMultipleNamespaces,
|
|
942
|
+
useMultipleTranslations,
|
|
943
|
+
useNamespace,
|
|
944
|
+
useNamespaceInfo,
|
|
945
|
+
useNamespacePreloading,
|
|
946
|
+
useNamespaceSwitch,
|
|
947
|
+
useOptionalTranslation,
|
|
948
|
+
usePluralization,
|
|
949
|
+
useScopedTranslations,
|
|
950
|
+
useT,
|
|
951
|
+
useTranslationValue,
|
|
952
|
+
useTranslationWithFallback,
|
|
953
|
+
useTranslations,
|
|
954
|
+
useTypedI18nContext,
|
|
955
|
+
useTypedT,
|
|
956
|
+
useTypedTranslations,
|
|
957
|
+
withI18n
|
|
958
|
+
};
|