intor 2.2.4 → 2.2.6

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.
@@ -0,0 +1,622 @@
1
+ import * as React6 from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { logry } from 'logry';
4
+ import Keyv from 'keyv';
5
+ import merge from 'lodash.merge';
6
+ import { Translator } from 'intor-translator';
7
+
8
+ // src/client/react/contexts/intor-provider/intor-provider.tsx
9
+ var ConfigContext = React6.createContext(void 0);
10
+ function ConfigProvider({
11
+ value: { config, pathname },
12
+ children
13
+ }) {
14
+ const value = React6.useMemo(() => ({ config, pathname }), [config, pathname]);
15
+ return /* @__PURE__ */ jsx(ConfigContext.Provider, { value, children });
16
+ }
17
+ function useConfig() {
18
+ const context = React6.useContext(ConfigContext);
19
+ if (!context) throw new Error("useConfig must be used within ConfigProvider");
20
+ return context;
21
+ }
22
+
23
+ // src/config/constants/cache.constants.ts
24
+ var DEFAULT_CACHE_OPTIONS = {
25
+ enabled: process.env.NODE_ENV === "production",
26
+ ttl: 60 * 60 * 1e3
27
+ // 1 hour
28
+ };
29
+
30
+ // src/server/shared/logger/global-logger-pool.ts
31
+ function getGlobalLoggerPool() {
32
+ if (!globalThis.__INTOR_LOGGER_POOL__) {
33
+ globalThis.__INTOR_LOGGER_POOL__ = /* @__PURE__ */ new Map();
34
+ }
35
+ return globalThis.__INTOR_LOGGER_POOL__;
36
+ }
37
+
38
+ // src/server/shared/logger/get-logger.ts
39
+ var DEFAULT_FORMATTER_CONFIG = {
40
+ node: { meta: { compact: true }, lineBreaksAfter: 1 }
41
+ };
42
+ function getLogger({
43
+ id = "default",
44
+ formatterConfig,
45
+ preset,
46
+ ...options
47
+ }) {
48
+ const pool = getGlobalLoggerPool();
49
+ let logger = pool.get(id);
50
+ const useDefault = !formatterConfig && !preset;
51
+ if (!logger) {
52
+ logger = logry({
53
+ id,
54
+ formatterConfig: useDefault ? DEFAULT_FORMATTER_CONFIG : formatterConfig,
55
+ preset,
56
+ ...options
57
+ });
58
+ pool.set(id, logger);
59
+ if (pool.size > 1e3) {
60
+ const keys = [...pool.keys()];
61
+ for (const key of keys.slice(0, 200)) pool.delete(key);
62
+ }
63
+ }
64
+ return logger;
65
+ }
66
+
67
+ // src/server/messages/shared/utils/is-valid-messages.ts
68
+ function isPlainObject(value) {
69
+ return typeof value === "object" && value !== null && !Array.isArray(value);
70
+ }
71
+ function isValidMessages(value) {
72
+ if (!isPlainObject(value)) return false;
73
+ const stack = [value];
74
+ while (stack.length > 0) {
75
+ const current = stack.pop();
76
+ for (const v of Object.values(current)) {
77
+ if (typeof v === "string") continue;
78
+ if (isPlainObject(v)) {
79
+ stack.push(v);
80
+ } else {
81
+ return false;
82
+ }
83
+ }
84
+ }
85
+ return true;
86
+ }
87
+ function getGlobalMessagesPool() {
88
+ if (!globalThis.__INTOR_MESSAGES_POOL__) {
89
+ globalThis.__INTOR_MESSAGES_POOL__ = new Keyv();
90
+ }
91
+ return globalThis.__INTOR_MESSAGES_POOL__;
92
+ }
93
+ var mergeMessages = (staticMessages = {}, loadedMessages = {}) => {
94
+ if (!loadedMessages) return { ...staticMessages };
95
+ return merge({}, staticMessages, loadedMessages);
96
+ };
97
+
98
+ // src/shared/utils/normalize-cache-key.ts
99
+ var CACHE_KEY_DELIMITER = "|";
100
+ var sanitize = (k) => k.replaceAll(/[\u200B-\u200D\uFEFF]/g, "").replaceAll(/[\r\n]/g, "").trim();
101
+ var normalizeCacheKey = (key, delimiter = CACHE_KEY_DELIMITER) => {
102
+ if (key === null || key === void 0) return null;
103
+ if (Array.isArray(key)) {
104
+ if (key.length === 0) return null;
105
+ const normalized = key.map((k) => {
106
+ if (k === null) return "__null";
107
+ if (k === void 0) return "__undefined";
108
+ if (typeof k === "boolean") return k ? "__true" : "__false";
109
+ return sanitize(String(k));
110
+ });
111
+ return normalized.join(delimiter);
112
+ }
113
+ if (typeof key === "boolean") return key ? "__true" : "__false";
114
+ return String(key);
115
+ };
116
+
117
+ // src/shared/constants/prefix-placeholder.ts
118
+ var PREFIX_PLACEHOLDER = "{locale}";
119
+
120
+ // src/shared/utils/resolve-namespaces.ts
121
+ var resolveNamespaces = ({
122
+ config,
123
+ pathname
124
+ }) => {
125
+ const { loader } = config;
126
+ const { routeNamespaces = {}, namespaces } = loader || {};
127
+ if (Object.keys(routeNamespaces).length === 0 && !namespaces)
128
+ return void 0;
129
+ const standardizedPathname = standardizePathname({ config, pathname });
130
+ const placeholderRemovedPathname = standardizedPathname.replace(
131
+ `/${PREFIX_PLACEHOLDER}`,
132
+ ""
133
+ );
134
+ const collected = [
135
+ ...routeNamespaces.default || [],
136
+ // default
137
+ ...namespaces || [],
138
+ // default
139
+ ...routeNamespaces[standardizedPathname] || [],
140
+ // exact match
141
+ ...routeNamespaces[placeholderRemovedPathname] || []
142
+ // exact match
143
+ ];
144
+ const prefixPatterns = Object.keys(routeNamespaces).filter(
145
+ (pattern) => pattern.endsWith("/*")
146
+ );
147
+ for (const pattern of prefixPatterns) {
148
+ const basePath = pattern.replace(/\/\*$/, "");
149
+ if (standardizedPathname.startsWith(basePath) || placeholderRemovedPathname.startsWith(basePath)) {
150
+ collected.push(...routeNamespaces[pattern] || []);
151
+ }
152
+ }
153
+ return [...new Set(collected)];
154
+ };
155
+
156
+ // src/shared/utils/pathname/normalize-pathname.ts
157
+ var normalizePathname = (rawPathname, options = {}) => {
158
+ const length = rawPathname.length;
159
+ let start = 0;
160
+ let end = length - 1;
161
+ while (start <= end && (rawPathname.codePointAt(start) ?? 0) <= 32) start++;
162
+ while (end >= start && (rawPathname.codePointAt(end) ?? 0) <= 32) end--;
163
+ if (start > end) return "/";
164
+ let result = "";
165
+ let hasSlash = false;
166
+ for (let i = start; i <= end; i++) {
167
+ const char = rawPathname[i];
168
+ if (char === "/") {
169
+ if (!hasSlash) {
170
+ hasSlash = true;
171
+ }
172
+ } else {
173
+ result += hasSlash || result === "" ? "/" + char : char;
174
+ hasSlash = false;
175
+ }
176
+ }
177
+ if (options.removeLeadingSlash && result.startsWith("/")) {
178
+ result = result.slice(1);
179
+ }
180
+ return result || "/";
181
+ };
182
+
183
+ // src/shared/utils/pathname/standardize-pathname.ts
184
+ var standardizePathname = ({
185
+ config,
186
+ pathname
187
+ }) => {
188
+ const { routing } = config;
189
+ const { basePath } = routing;
190
+ const parts = [
191
+ normalizePathname(basePath),
192
+ PREFIX_PLACEHOLDER,
193
+ normalizePathname(pathname)
194
+ ];
195
+ const standardizedPathname = parts.join("/").replaceAll(/\/{2,}/g, "/");
196
+ return normalizePathname(standardizedPathname);
197
+ };
198
+
199
+ // src/server/messages/load-remote-messages/fetch-locale-messages/fetch-locale-messages.ts
200
+ var fetchLocaleMessages = async ({
201
+ remoteUrl,
202
+ remoteHeaders,
203
+ searchParams,
204
+ locale,
205
+ extraOptions: { loggerOptions } = {}
206
+ }) => {
207
+ const baseLogger = getLogger({ ...loggerOptions });
208
+ const logger = baseLogger.child({ scope: "fetch-locale-messages" });
209
+ try {
210
+ const params = new URLSearchParams(searchParams);
211
+ params.append("locale", locale);
212
+ const url = `${remoteUrl}?${params.toString()}`;
213
+ const headers = {
214
+ "Content-Type": "application/json",
215
+ ...remoteHeaders
216
+ };
217
+ const response = await fetch(url, {
218
+ method: "GET",
219
+ headers,
220
+ cache: "no-store"
221
+ });
222
+ if (!response.ok) {
223
+ throw new Error(`HTTP error ${response.status} ${response.statusText}`);
224
+ }
225
+ const data = await response.json();
226
+ if (!isValidMessages(data[locale])) {
227
+ throw new Error("JSON file does not match NamespaceMessages structure");
228
+ }
229
+ return data;
230
+ } catch (error) {
231
+ logger.warn("Fetching locale messages failed.", {
232
+ locale,
233
+ remoteUrl,
234
+ searchParams: decodeURIComponent(searchParams.toString()),
235
+ error
236
+ });
237
+ return;
238
+ }
239
+ };
240
+
241
+ // src/server/messages/load-remote-messages/fetch-locale-messages/utils/build-search-params.ts
242
+ var buildSearchParams = (params) => {
243
+ const searchParams = new URLSearchParams();
244
+ const appendParam = (key, value) => {
245
+ if (value === void 0 || value === null) return;
246
+ if (Array.isArray(value) && value.length === 0) return;
247
+ if (Array.isArray(value)) {
248
+ value.forEach((v) => v && searchParams.append(key, v));
249
+ } else {
250
+ searchParams.append(key, value);
251
+ }
252
+ };
253
+ Object.entries(params).forEach(([key, value]) => {
254
+ appendParam(key, value);
255
+ });
256
+ return searchParams;
257
+ };
258
+
259
+ // src/server/messages/load-remote-messages/load-remote-messages.ts
260
+ var loadRemoteMessages = async ({
261
+ pool = getGlobalMessagesPool(),
262
+ rootDir,
263
+ remoteUrl,
264
+ remoteHeaders,
265
+ locale,
266
+ fallbackLocales = [],
267
+ namespaces = [],
268
+ extraOptions: {
269
+ cacheOptions = DEFAULT_CACHE_OPTIONS,
270
+ loggerOptions = { id: "default" }
271
+ } = {},
272
+ allowCacheWrite
273
+ }) => {
274
+ const baseLogger = getLogger({ ...loggerOptions });
275
+ const logger = baseLogger.child({ scope: "load-remote-messages" });
276
+ const start = performance.now();
277
+ logger.debug("Loading remote messages from api.", { remoteUrl });
278
+ const key = normalizeCacheKey([
279
+ loggerOptions.id,
280
+ "loaderType:remote",
281
+ rootDir,
282
+ locale,
283
+ (fallbackLocales ?? []).toSorted().join(","),
284
+ (namespaces ?? []).toSorted().join(",")
285
+ ]);
286
+ if (cacheOptions.enabled && key) {
287
+ const cached = await pool?.get(key);
288
+ if (cached) {
289
+ logger.debug("Messages cache hit.", { key });
290
+ return cached;
291
+ }
292
+ }
293
+ const searchParams = buildSearchParams({ rootDir, namespaces });
294
+ const candidateLocales = [locale, ...fallbackLocales || []];
295
+ let messages;
296
+ for (const candidateLocale of candidateLocales) {
297
+ try {
298
+ const result = await fetchLocaleMessages({
299
+ remoteUrl,
300
+ remoteHeaders,
301
+ searchParams,
302
+ locale: candidateLocale,
303
+ extraOptions: { loggerOptions }
304
+ });
305
+ if (result && Object.values(result[candidateLocale] || {}).length > 0) {
306
+ messages = result;
307
+ break;
308
+ }
309
+ } catch (error) {
310
+ logger.error("Failed to fetch locale messages.", {
311
+ locale: candidateLocale,
312
+ error
313
+ });
314
+ }
315
+ }
316
+ if (allowCacheWrite && cacheOptions.enabled && key && messages) {
317
+ await pool?.set(key, messages, cacheOptions.ttl);
318
+ }
319
+ const end = performance.now();
320
+ const duration = Math.round(end - start);
321
+ logger.trace("Finished loading remote messages.", {
322
+ loadedLocale: messages ? Object.keys(messages)[0] : void 0,
323
+ duration: `${duration} ms`
324
+ });
325
+ return messages;
326
+ };
327
+
328
+ // src/client/react/contexts/messages/utils/use-refetch-messages.ts
329
+ var useRefetchMessages = ({
330
+ config,
331
+ pathname,
332
+ setLoadedMessages,
333
+ setIsLoadingMessages
334
+ }) => {
335
+ const { messages: staticMessages } = config;
336
+ const namespaces = React6.useMemo(() => {
337
+ if (!config.loader) return [];
338
+ return resolveNamespaces({ config, pathname });
339
+ }, [config, pathname]);
340
+ const refetchMessages = React6.useCallback(
341
+ async (newLocale) => {
342
+ if (config.loader?.type === "remote") {
343
+ setIsLoadingMessages(true);
344
+ const loadedMessages = await loadRemoteMessages({
345
+ rootDir: config.loader.rootDir,
346
+ remoteUrl: config.loader.remoteUrl,
347
+ remoteHeaders: config.loader.remoteHeaders,
348
+ locale: newLocale,
349
+ fallbackLocales: config.fallbackLocales[newLocale] || [],
350
+ namespaces,
351
+ extraOptions: {
352
+ cacheOptions: config.cache,
353
+ loggerOptions: { id: config.id }
354
+ }
355
+ });
356
+ const messages = mergeMessages(staticMessages, loadedMessages);
357
+ setLoadedMessages(messages);
358
+ setIsLoadingMessages(false);
359
+ }
360
+ },
361
+ [
362
+ config.loader,
363
+ config.fallbackLocales,
364
+ config.id,
365
+ setIsLoadingMessages,
366
+ namespaces,
367
+ staticMessages,
368
+ setLoadedMessages
369
+ ]
370
+ );
371
+ return { refetchMessages };
372
+ };
373
+ var MessagesContext = React6.createContext(void 0);
374
+ function MessagesProvider({
375
+ value: { messages = {} },
376
+ children
377
+ }) {
378
+ const { config, pathname } = useConfig();
379
+ const [loadedMessages, setLoadedMessages] = React6.useState(null);
380
+ const [isLoadingMessages, setIsLoadingMessages] = React6.useState(false);
381
+ const { refetchMessages } = useRefetchMessages({
382
+ config,
383
+ pathname,
384
+ setLoadedMessages,
385
+ setIsLoadingMessages
386
+ });
387
+ const value = React6.useMemo(
388
+ () => ({
389
+ messages: loadedMessages || messages,
390
+ isLoading: isLoadingMessages,
391
+ setLoadedMessages,
392
+ setIsLoadingMessages,
393
+ refetchMessages
394
+ }),
395
+ [loadedMessages, messages, isLoadingMessages, refetchMessages]
396
+ );
397
+ return /* @__PURE__ */ jsx(MessagesContext.Provider, { value, children });
398
+ }
399
+ function useMessages() {
400
+ const context = React6.useContext(MessagesContext);
401
+ if (!context)
402
+ throw new Error("useMessages must be used within a MessagesProvider");
403
+ return context;
404
+ }
405
+
406
+ // src/client/react/contexts/locale/utils/use-init-lazy-load.ts
407
+ var useInitLazyLoad = ({
408
+ loaderOptions,
409
+ currentLocale
410
+ }) => {
411
+ const { refetchMessages } = useMessages();
412
+ const lazyLoad = !!loaderOptions?.lazyLoad;
413
+ const isFirstLoadedRef = React6.useRef(false);
414
+ React6.useEffect(() => {
415
+ if (lazyLoad && !isFirstLoadedRef.current) {
416
+ void refetchMessages(currentLocale);
417
+ isFirstLoadedRef.current = true;
418
+ }
419
+ }, [lazyLoad, currentLocale, refetchMessages, isFirstLoadedRef]);
420
+ };
421
+
422
+ // src/shared/utils/client/build-cookie-string.ts
423
+ var buildCookieString = (cookie, locale) => {
424
+ const parts = [`${cookie.name}=${encodeURIComponent(locale)}`];
425
+ if (cookie.maxAge) {
426
+ const expires = new Date(Date.now() + cookie.maxAge * 1e3).toUTCString();
427
+ parts.push(`expires=${expires}`, `max-age=${cookie.maxAge}`);
428
+ }
429
+ parts.push(`path=${cookie.path ?? "/"}`);
430
+ if (cookie.domain) {
431
+ parts.push(`domain=${cookie.domain}`);
432
+ }
433
+ if (cookie.sameSite) {
434
+ parts.push(
435
+ `SameSite=${cookie.sameSite[0].toUpperCase()}${cookie.sameSite.slice(1).toLowerCase()}`
436
+ );
437
+ }
438
+ if (cookie.secure !== false) {
439
+ parts.push(`Secure`);
440
+ }
441
+ return parts.join("; ");
442
+ };
443
+
444
+ // src/shared/utils/client/set-locale-cookie-browser.ts
445
+ var setLocaleCookieBrowser = ({
446
+ cookie,
447
+ locale
448
+ }) => {
449
+ if (globalThis.window === void 0) return;
450
+ if (cookie.disabled || !cookie.autoSetCookie) return;
451
+ const cookieString = buildCookieString(cookie, locale);
452
+ document.cookie = cookieString;
453
+ };
454
+
455
+ // src/client/react/contexts/locale/utils/use-init-locale-cookie.ts
456
+ var useInitLocaleCookie = ({
457
+ config,
458
+ locale
459
+ }) => {
460
+ React6.useEffect(() => {
461
+ if (typeof document === "undefined") return;
462
+ const { cookie, routing } = config;
463
+ const { firstVisit } = routing;
464
+ const cookies = document.cookie.split(";").map((c) => c.trim());
465
+ const isCookieExists = cookies.some((c) => c.startsWith(`${cookie.name}=`));
466
+ if (isCookieExists) return;
467
+ if (!firstVisit.redirect) return;
468
+ if (cookie.disabled || !cookie.autoSetCookie) return;
469
+ setLocaleCookieBrowser({ cookie, locale });
470
+ }, []);
471
+ };
472
+ var LocaleContext = React6.createContext(void 0);
473
+
474
+ // src/client/react/contexts/locale/utils/change-locale.ts
475
+ var changeLocale = ({
476
+ currentLocale,
477
+ newLocale,
478
+ loaderOptions,
479
+ cookie,
480
+ setLocale,
481
+ refetchMessages
482
+ }) => {
483
+ if (typeof document === "undefined") return;
484
+ const loaderType = loaderOptions?.type;
485
+ if (newLocale === currentLocale) return;
486
+ if (loaderType === "local") {
487
+ console.warn(
488
+ `[Intor] You are using dynamic local to switch languages. Please make sure to use the wrapped <Link> component to trigger a page reload, ensuring that the translation data is dynamically updated.`
489
+ );
490
+ }
491
+ setLocale(newLocale);
492
+ setLocaleCookieBrowser({ cookie, locale: newLocale });
493
+ document.documentElement.lang = newLocale;
494
+ if (loaderType === "remote" && refetchMessages) {
495
+ void refetchMessages(newLocale);
496
+ }
497
+ };
498
+ function LocaleProvider({
499
+ value: { initialLocale },
500
+ children
501
+ }) {
502
+ const { config } = useConfig();
503
+ const { refetchMessages } = useMessages();
504
+ const { loader: loaderOptions, cookie } = config;
505
+ const [currentLocale, setCurrentLocale] = React6.useState(initialLocale);
506
+ useInitLazyLoad({ loaderOptions, currentLocale });
507
+ useInitLocaleCookie({ config, locale: initialLocale });
508
+ const setLocale = React6.useCallback(
509
+ (newLocale) => {
510
+ changeLocale({
511
+ currentLocale,
512
+ newLocale,
513
+ loaderOptions,
514
+ cookie,
515
+ setLocale: setCurrentLocale,
516
+ refetchMessages
517
+ });
518
+ },
519
+ [currentLocale, loaderOptions, cookie, refetchMessages]
520
+ );
521
+ const value = React6.useMemo(
522
+ () => ({
523
+ locale: currentLocale,
524
+ setLocale
525
+ }),
526
+ [currentLocale, setLocale]
527
+ );
528
+ return /* @__PURE__ */ jsx(LocaleContext.Provider, { value, children });
529
+ }
530
+ function useLocale() {
531
+ const context = React6.useContext(LocaleContext);
532
+ if (!context)
533
+ throw new Error("useLocale must be used within a LocaleProvider");
534
+ return context;
535
+ }
536
+ var TranslateHandlersContext = React6.createContext(void 0);
537
+ var TranslateHandlersProvider = ({
538
+ children,
539
+ handlers
540
+ }) => {
541
+ const value = handlers;
542
+ return /* @__PURE__ */ jsx(TranslateHandlersContext.Provider, { value, children });
543
+ };
544
+ function useTranslateHandlers() {
545
+ const context = React6.useContext(TranslateHandlersContext);
546
+ return context;
547
+ }
548
+ var useInitLoadingState = (config) => {
549
+ const lazyLoad = !!config.loader?.lazyLoad;
550
+ const [isCsr, setIsCsr] = React6.useState(false);
551
+ React6.useEffect(() => {
552
+ setIsCsr(true);
553
+ }, []);
554
+ const isBeforeCSRLoading = lazyLoad && !isCsr;
555
+ return isBeforeCSRLoading;
556
+ };
557
+ var TranslatorContext = React6.createContext(void 0);
558
+ var EMPTY_OBJECT = Object.freeze({});
559
+ function TranslatorProvider({ children }) {
560
+ const { config } = useConfig();
561
+ const { messages, isLoading } = useMessages();
562
+ const { locale } = useLocale();
563
+ const translatorHandlers = useTranslateHandlers();
564
+ const { fallbackLocales, translator: translatorOptions } = config;
565
+ const isBeforeCSRLoading = useInitLoadingState(config);
566
+ const value = React6.useMemo(() => {
567
+ const translator = new Translator({
568
+ messages: messages || EMPTY_OBJECT,
569
+ locale,
570
+ fallbackLocales,
571
+ loadingMessage: translatorOptions?.loadingMessage,
572
+ placeholder: translatorOptions?.placeholder,
573
+ handlers: translatorHandlers
574
+ });
575
+ translator.setLoading(isBeforeCSRLoading || isLoading);
576
+ return { translator };
577
+ }, [
578
+ fallbackLocales,
579
+ isBeforeCSRLoading,
580
+ isLoading,
581
+ locale,
582
+ messages,
583
+ translatorHandlers,
584
+ translatorOptions?.loadingMessage,
585
+ translatorOptions?.placeholder
586
+ ]);
587
+ return /* @__PURE__ */ jsx(TranslatorContext.Provider, { value, children });
588
+ }
589
+ function useTranslator() {
590
+ const context = React6.useContext(TranslatorContext);
591
+ if (!context)
592
+ throw new Error(
593
+ "useTranslator must be used within IntorTranslatorProvider"
594
+ );
595
+ return context;
596
+ }
597
+ var IntorProvider = ({
598
+ value: { config, pathname = "", initialLocale, messages = config.messages },
599
+ children
600
+ }) => {
601
+ return /* @__PURE__ */ jsx(ConfigProvider, { value: { config, pathname }, children: /* @__PURE__ */ jsx(MessagesProvider, { value: { messages }, children: /* @__PURE__ */ jsx(LocaleProvider, { value: { initialLocale }, children: /* @__PURE__ */ jsx(TranslatorProvider, { children }) }) }) });
602
+ };
603
+
604
+ // src/client/react/hooks/use-translator.ts
605
+ function useTranslator2(preKey) {
606
+ const { translator } = useTranslator();
607
+ const { setLocale } = useLocale();
608
+ const props = {
609
+ messages: translator.messages,
610
+ locale: translator.locale,
611
+ isLoading: translator.isLoading,
612
+ setLocale
613
+ };
614
+ const scoped = translator.scoped(preKey);
615
+ return {
616
+ ...props,
617
+ hasKey: preKey ? scoped.hasKey : translator.hasKey,
618
+ t: preKey ? scoped.t : translator.t
619
+ };
620
+ }
621
+
622
+ export { IntorProvider, TranslateHandlersProvider, useTranslator2 as useTranslator };