@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.30 → 3.5.0-ultramodern.31

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.
Files changed (161) hide show
  1. package/dist/cjs/cli/index.js +2 -1
  2. package/dist/cjs/runtime/Link.js +8 -89
  3. package/dist/cjs/runtime/context.js +18 -82
  4. package/dist/cjs/runtime/contextHelpers.js +137 -0
  5. package/dist/cjs/runtime/core.js +29 -132
  6. package/dist/cjs/runtime/i18n/detection/cache.js +149 -0
  7. package/dist/cjs/runtime/i18n/detection/detector.js +100 -0
  8. package/dist/cjs/runtime/i18n/detection/index.js +8 -263
  9. package/dist/cjs/runtime/i18n/detection/initOptions.js +69 -0
  10. package/dist/cjs/runtime/i18n/detection/language.js +62 -0
  11. package/dist/cjs/runtime/i18n/detection/path.js +49 -0
  12. package/dist/cjs/runtime/i18n/detection/priority.js +66 -0
  13. package/dist/cjs/runtime/i18n/detection/ssr.js +70 -0
  14. package/dist/cjs/runtime/i18n/detection/types.js +18 -0
  15. package/dist/cjs/runtime/linkHelpers.js +148 -0
  16. package/dist/cjs/runtime/pluginSetup.js +102 -0
  17. package/dist/cjs/runtime/providerComposition.js +113 -0
  18. package/dist/cjs/runtime/reactI18next.js +43 -0
  19. package/dist/cjs/server/apiPrefix.js +72 -0
  20. package/dist/cjs/server/detectorOptions.js +97 -0
  21. package/dist/cjs/server/index.js +17 -152
  22. package/dist/cjs/server/redirectPolicy.js +102 -0
  23. package/dist/cjs/shared/localisedUrls/config.js +49 -0
  24. package/dist/cjs/shared/localisedUrls/index.js +75 -0
  25. package/dist/cjs/shared/localisedUrls/normalise.js +87 -0
  26. package/dist/cjs/shared/localisedUrls/pathname.js +65 -0
  27. package/dist/cjs/shared/localisedUrls/patterns.js +130 -0
  28. package/dist/cjs/shared/localisedUrls/resolve.js +91 -0
  29. package/dist/cjs/shared/localisedUrls/routes.js +129 -0
  30. package/dist/cjs/shared/localisedUrls/types.js +18 -0
  31. package/dist/cjs/shared/localisedUrls.js +13 -294
  32. package/dist/esm/cli/index.mjs +2 -1
  33. package/dist/esm/runtime/Link.mjs +1 -82
  34. package/dist/esm/runtime/context.mjs +18 -82
  35. package/dist/esm/runtime/contextHelpers.mjs +84 -0
  36. package/dist/esm/runtime/core.mjs +30 -133
  37. package/dist/esm/runtime/i18n/detection/cache.mjs +102 -0
  38. package/dist/esm/runtime/i18n/detection/detector.mjs +62 -0
  39. package/dist/esm/runtime/i18n/detection/index.mjs +4 -260
  40. package/dist/esm/runtime/i18n/detection/initOptions.mjs +28 -0
  41. package/dist/esm/runtime/i18n/detection/language.mjs +21 -0
  42. package/dist/esm/runtime/i18n/detection/path.mjs +11 -0
  43. package/dist/esm/runtime/i18n/detection/priority.mjs +28 -0
  44. package/dist/esm/runtime/i18n/detection/ssr.mjs +26 -0
  45. package/dist/esm/runtime/i18n/detection/types.mjs +0 -0
  46. package/dist/esm/runtime/linkHelpers.mjs +98 -0
  47. package/dist/esm/runtime/pluginSetup.mjs +64 -0
  48. package/dist/esm/runtime/providerComposition.mjs +75 -0
  49. package/dist/esm/runtime/reactI18next.mjs +5 -0
  50. package/dist/esm/server/apiPrefix.mjs +31 -0
  51. package/dist/esm/server/detectorOptions.mjs +59 -0
  52. package/dist/esm/server/index.mjs +4 -139
  53. package/dist/esm/server/redirectPolicy.mjs +52 -0
  54. package/dist/esm/shared/localisedUrls/config.mjs +11 -0
  55. package/dist/esm/shared/localisedUrls/index.mjs +6 -0
  56. package/dist/esm/shared/localisedUrls/normalise.mjs +37 -0
  57. package/dist/esm/shared/localisedUrls/pathname.mjs +24 -0
  58. package/dist/esm/shared/localisedUrls/patterns.mjs +86 -0
  59. package/dist/esm/shared/localisedUrls/resolve.mjs +50 -0
  60. package/dist/esm/shared/localisedUrls/routes.mjs +88 -0
  61. package/dist/esm/shared/localisedUrls/types.mjs +0 -0
  62. package/dist/esm/shared/localisedUrls.mjs +1 -283
  63. package/dist/esm-node/cli/index.mjs +2 -1
  64. package/dist/esm-node/runtime/Link.mjs +1 -82
  65. package/dist/esm-node/runtime/context.mjs +18 -82
  66. package/dist/esm-node/runtime/contextHelpers.mjs +85 -0
  67. package/dist/esm-node/runtime/core.mjs +30 -133
  68. package/dist/esm-node/runtime/i18n/detection/cache.mjs +103 -0
  69. package/dist/esm-node/runtime/i18n/detection/detector.mjs +63 -0
  70. package/dist/esm-node/runtime/i18n/detection/index.mjs +4 -260
  71. package/dist/esm-node/runtime/i18n/detection/initOptions.mjs +29 -0
  72. package/dist/esm-node/runtime/i18n/detection/language.mjs +22 -0
  73. package/dist/esm-node/runtime/i18n/detection/path.mjs +12 -0
  74. package/dist/esm-node/runtime/i18n/detection/priority.mjs +29 -0
  75. package/dist/esm-node/runtime/i18n/detection/ssr.mjs +27 -0
  76. package/dist/esm-node/runtime/i18n/detection/types.mjs +1 -0
  77. package/dist/esm-node/runtime/linkHelpers.mjs +99 -0
  78. package/dist/esm-node/runtime/pluginSetup.mjs +65 -0
  79. package/dist/esm-node/runtime/providerComposition.mjs +76 -0
  80. package/dist/esm-node/runtime/reactI18next.mjs +6 -0
  81. package/dist/esm-node/server/apiPrefix.mjs +32 -0
  82. package/dist/esm-node/server/detectorOptions.mjs +60 -0
  83. package/dist/esm-node/server/index.mjs +4 -139
  84. package/dist/esm-node/server/redirectPolicy.mjs +53 -0
  85. package/dist/esm-node/shared/localisedUrls/config.mjs +12 -0
  86. package/dist/esm-node/shared/localisedUrls/index.mjs +7 -0
  87. package/dist/esm-node/shared/localisedUrls/normalise.mjs +38 -0
  88. package/dist/esm-node/shared/localisedUrls/pathname.mjs +25 -0
  89. package/dist/esm-node/shared/localisedUrls/patterns.mjs +87 -0
  90. package/dist/esm-node/shared/localisedUrls/resolve.mjs +51 -0
  91. package/dist/esm-node/shared/localisedUrls/routes.mjs +89 -0
  92. package/dist/esm-node/shared/localisedUrls/types.mjs +1 -0
  93. package/dist/esm-node/shared/localisedUrls.mjs +1 -283
  94. package/dist/types/runtime/Link.d.ts +3 -7
  95. package/dist/types/runtime/context.d.ts +2 -2
  96. package/dist/types/runtime/contextHelpers.d.ts +28 -0
  97. package/dist/types/runtime/core.d.ts +3 -7
  98. package/dist/types/runtime/i18n/detection/cache.d.ts +14 -0
  99. package/dist/types/runtime/i18n/detection/detector.d.ts +8 -0
  100. package/dist/types/runtime/i18n/detection/index.d.ts +5 -50
  101. package/dist/types/runtime/i18n/detection/initOptions.d.ts +10 -0
  102. package/dist/types/runtime/i18n/detection/language.d.ts +11 -0
  103. package/dist/types/runtime/i18n/detection/path.d.ts +5 -0
  104. package/dist/types/runtime/i18n/detection/priority.d.ts +10 -0
  105. package/dist/types/runtime/i18n/detection/ssr.d.ts +9 -0
  106. package/dist/types/runtime/i18n/detection/types.d.ts +29 -0
  107. package/dist/types/runtime/i18n/react-i18next.d.ts +1 -6
  108. package/dist/types/runtime/linkHelpers.d.ts +17 -0
  109. package/dist/types/runtime/pluginSetup.d.ts +32 -0
  110. package/dist/types/runtime/providerComposition.d.ts +15 -0
  111. package/dist/types/runtime/reactI18next.d.ts +7 -0
  112. package/dist/types/server/apiPrefix.d.ts +7 -0
  113. package/dist/types/server/detectorOptions.d.ts +21 -0
  114. package/dist/types/server/index.d.ts +1 -6
  115. package/dist/types/server/redirectPolicy.d.ts +16 -0
  116. package/dist/types/shared/localisedUrls/config.d.ts +9 -0
  117. package/dist/types/shared/localisedUrls/index.d.ts +7 -0
  118. package/dist/types/shared/localisedUrls/normalise.d.ts +10 -0
  119. package/dist/types/shared/localisedUrls/pathname.d.ts +3 -0
  120. package/dist/types/shared/localisedUrls/patterns.d.ts +5 -0
  121. package/dist/types/shared/localisedUrls/resolve.d.ts +8 -0
  122. package/dist/types/shared/localisedUrls/routes.d.ts +3 -0
  123. package/dist/types/shared/localisedUrls/types.d.ts +14 -0
  124. package/dist/types/shared/localisedUrls.d.ts +2 -42
  125. package/package.json +10 -10
  126. package/src/cli/index.ts +3 -2
  127. package/src/runtime/Link.tsx +11 -138
  128. package/src/runtime/context.tsx +42 -196
  129. package/src/runtime/contextHelpers.ts +232 -0
  130. package/src/runtime/core.tsx +42 -273
  131. package/src/runtime/i18n/detection/cache.ts +175 -0
  132. package/src/runtime/i18n/detection/detector.ts +157 -0
  133. package/src/runtime/i18n/detection/index.ts +10 -641
  134. package/src/runtime/i18n/detection/initOptions.ts +74 -0
  135. package/src/runtime/i18n/detection/language.ts +64 -0
  136. package/src/runtime/i18n/detection/path.ts +41 -0
  137. package/src/runtime/i18n/detection/priority.ts +82 -0
  138. package/src/runtime/i18n/detection/ssr.ts +47 -0
  139. package/src/runtime/i18n/detection/types.ts +33 -0
  140. package/src/runtime/i18n/react-i18next.ts +1 -6
  141. package/src/runtime/linkHelpers.ts +174 -0
  142. package/src/runtime/pluginSetup.ts +189 -0
  143. package/src/runtime/providerComposition.tsx +142 -0
  144. package/src/runtime/reactI18next.ts +20 -0
  145. package/src/server/apiPrefix.ts +67 -0
  146. package/src/server/detectorOptions.ts +105 -0
  147. package/src/server/index.ts +12 -327
  148. package/src/server/redirectPolicy.ts +152 -0
  149. package/src/shared/localisedUrls/config.ts +18 -0
  150. package/src/shared/localisedUrls/index.ts +13 -0
  151. package/src/shared/localisedUrls/normalise.ts +67 -0
  152. package/src/shared/localisedUrls/pathname.ts +57 -0
  153. package/src/shared/localisedUrls/patterns.ts +160 -0
  154. package/src/shared/localisedUrls/resolve.ts +118 -0
  155. package/src/shared/localisedUrls/routes.ts +226 -0
  156. package/src/shared/localisedUrls/types.ts +18 -0
  157. package/src/shared/localisedUrls.ts +20 -629
  158. package/tests/link.test.tsx +111 -0
  159. package/tests/localisedUrls.test.ts +50 -0
  160. package/tests/reactI18nextRuntimeBoundary.test.ts +5 -2
  161. package/tests/routerAdapter.test.tsx +56 -0
@@ -1,31 +1,7 @@
1
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { RuntimeContext, isBrowser } from "@modern-js/runtime";
3
- import { Helmet } from "@modern-js/runtime/head";
4
- import { merge } from "@modern-js/runtime-utils/merge";
5
- import { useContext, useEffect, useMemo, useRef, useState } from "react";
6
- import { ModernI18nProvider, useModernI18n } from "./context.mjs";
7
- import { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader } from "./hooks.mjs";
8
- import { getI18nInstance } from "./i18n/index.mjs";
9
- import { mergeBackendOptions } from "./i18n/backend/index.mjs";
10
- import { detectLanguageWithPriority, exportServerLngToWindow, mergeDetectionOptions } from "./i18n/detection/index.mjs";
11
- import { useI18nextLanguageDetector } from "./i18n/detection/middleware.mjs";
12
- import { getI18nextInstanceForProvider } from "./i18n/instance.mjs";
13
- import { buildLocalizedUrl, getPathname, splitUrlTarget } from "./utils.mjs";
1
+ import { setupI18nBeforeRender } from "./pluginSetup.mjs";
2
+ import { createI18nRootWrapper } from "./providerComposition.mjs";
3
+ import { resolveReactI18nextIntegration } from "./reactI18next.mjs";
14
4
  import "./types.mjs";
15
- let i18nLifecycleHelpersPromise;
16
- function loadI18nLifecycleHelpers() {
17
- i18nLifecycleHelpersPromise ??= Promise.all([
18
- import("./i18n/backend/middleware.mjs"),
19
- import("./i18n/utils.mjs")
20
- ]).then(([backendMiddleware, utils])=>({
21
- useI18nextBackend: backendMiddleware.useI18nextBackend,
22
- changeI18nLanguage: utils.changeI18nLanguage,
23
- ensureLanguageMatch: utils.ensureLanguageMatch,
24
- initializeI18nInstance: utils.initializeI18nInstance,
25
- setupClonedInstance: utils.setupClonedInstance
26
- }));
27
- return i18nLifecycleHelpersPromise;
28
- }
29
5
  const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
30
6
  name: '@modern-js/plugin-i18n',
31
7
  setup: (api)=>{
@@ -34,119 +10,40 @@ const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
34
10
  const { enabled: backendEnabled = false } = backend || {};
35
11
  let latestI18nInstance;
36
12
  let I18nextProvider;
37
- const resolveReactI18nextIntegration = async ()=>{
38
- if (!reactI18next) return null;
39
- return loadReactI18nextIntegration?.() ?? null;
40
- };
41
13
  api.onBeforeRender(async (context)=>{
42
- const { useI18nextBackend, changeI18nLanguage, ensureLanguageMatch, initializeI18nInstance, setupClonedInstance } = await loadI18nLifecycleHelpers();
43
- let i18nInstance = await getI18nInstance(userI18nInstance);
44
- const { i18n: otherConfig } = api.getRuntimeConfig();
45
- const { initOptions: otherInitOptions } = otherConfig || {};
46
- const userInitOptions = merge(otherInitOptions || {}, initOptions || {});
47
- const reactI18nextIntegration = await resolveReactI18nextIntegration();
48
- I18nextProvider = reactI18nextIntegration?.I18nextProvider ?? null;
49
- if (reactI18nextIntegration?.initReactI18next) i18nInstance.use(reactI18nextIntegration.initReactI18next);
50
- const pathname = getPathname(context);
51
- if (i18nextDetector) useI18nextLanguageDetector(i18nInstance);
52
- const mergedDetection = mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions);
53
- const mergedBackend = mergeBackendOptions(backend, userInitOptions);
54
- const hasSdkConfig = 'function' == typeof userInitOptions?.backend?.sdk || mergedBackend?.sdk && 'function' == typeof mergedBackend.sdk;
55
- if (mergedBackend && (backendEnabled || hasSdkConfig)) useI18nextBackend(i18nInstance, mergedBackend);
56
- const { finalLanguage } = await detectLanguageWithPriority(i18nInstance, {
57
- languages,
58
- fallbackLanguage,
59
- localePathRedirect,
14
+ latestI18nInstance = await setupI18nBeforeRender(context, {
15
+ api,
16
+ userI18nInstance,
17
+ initOptions,
18
+ backend,
19
+ backendEnabled,
60
20
  i18nextDetector,
61
21
  detection,
62
- userInitOptions,
63
- mergedBackend,
64
- pathname,
65
- ssrContext: context.ssrContext
66
- });
67
- await initializeI18nInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, mergedDetection, mergedBackend, userInitOptions);
68
- if (!isBrowser() && i18nInstance.cloneInstance) {
69
- i18nInstance = i18nInstance.cloneInstance();
70
- await setupClonedInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, backendEnabled, backend, i18nextDetector, detection, localePathRedirect, userInitOptions);
71
- }
72
- if (localePathRedirect) await ensureLanguageMatch(i18nInstance, finalLanguage);
73
- if (!isBrowser()) exportServerLngToWindow(context, finalLanguage);
74
- context.i18nInstance = i18nInstance;
75
- latestI18nInstance = i18nInstance;
76
- context.changeLanguage = async (newLang)=>{
77
- await changeI18nLanguage(i18nInstance, newLang, {
78
- detectionOptions: mergedDetection
79
- });
80
- };
81
- });
82
- api.wrapRoot((App)=>(props)=>{
83
- const runtimeContext = useContext(RuntimeContext);
84
- const i18nInstance = runtimeContext.i18nInstance || latestI18nInstance;
85
- const initialLang = useMemo(()=>i18nInstance?.language || (localeDetection?.fallbackLanguage ?? 'en'), [
86
- i18nInstance?.language,
87
- localeDetection?.fallbackLanguage
88
- ]);
89
- const [lang, setLang] = useState(initialLang);
90
- const [forceUpdate, setForceUpdate] = useState(0);
91
- const prevLangRef = useRef(lang);
92
- const runtimeContextRef = useRef(runtimeContext);
93
- runtimeContextRef.current = runtimeContext;
94
- useEffect(()=>{
95
- if (i18nInstance?.language) {
96
- const translator = i18nInstance.translator;
97
- if (translator) translator.language = i18nInstance.language;
98
- }
99
- }, [
100
- i18nInstance?.language
101
- ]);
102
- useEffect(()=>{
103
- prevLangRef.current = lang;
104
- }, [
105
- lang
106
- ]);
107
- useSdkResourcesLoader(i18nInstance, setForceUpdate);
108
- useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang);
109
- useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls);
110
- const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang), [
111
- lang,
112
- i18nInstance,
113
- entryName,
114
- languages,
115
- localePathRedirect,
116
- ignoreRedirectRoutes,
117
- localisedUrls,
118
- forceUpdate
119
- ]);
120
- const children = props.children;
121
- const appContent = /*#__PURE__*/ jsxs(Fragment, {
122
- children: [
123
- Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
124
- htmlAttributes: {
125
- lang
126
- }
127
- }),
128
- /*#__PURE__*/ jsx(ModernI18nProvider, {
129
- value: contextValue,
130
- children: App ? /*#__PURE__*/ jsx(App, {
131
- ...props,
132
- children: children
133
- }) : children
134
- })
135
- ]
136
- });
137
- if (!i18nInstance) return appContent;
138
- if (I18nextProvider) {
139
- const i18nextInstanceForProvider = getI18nextInstanceForProvider(i18nInstance);
140
- return /*#__PURE__*/ jsx(I18nextProvider, {
141
- i18n: i18nextInstanceForProvider,
142
- children: appContent
143
- });
22
+ localePathRedirect,
23
+ languages,
24
+ fallbackLanguage,
25
+ resolveReactI18nextIntegration: ()=>resolveReactI18nextIntegration(reactI18next, loadReactI18nextIntegration),
26
+ setI18nextProvider: (provider)=>{
27
+ I18nextProvider = provider;
144
28
  }
145
- return appContent;
146
29
  });
30
+ });
31
+ api.wrapRoot(createI18nRootWrapper({
32
+ entryName,
33
+ htmlLangAttr,
34
+ localePathRedirect,
35
+ languages,
36
+ fallbackLanguage,
37
+ ignoreRedirectRoutes,
38
+ localisedUrls,
39
+ getLatestI18nInstance: ()=>latestI18nInstance,
40
+ getI18nextProvider: ()=>I18nextProvider
41
+ }));
147
42
  }
148
43
  });
44
+ export { useModernI18n } from "./context.mjs";
149
45
  export { I18nLink } from "./I18nLink.mjs";
150
46
  export { Link } from "./Link.mjs";
151
47
  export { canonicalPath, localizePath, useLocalizedLocation, useLocalizedPaths } from "./localizedPaths.mjs";
152
- export { buildLocalizedUrl, createI18nPlugin, splitUrlTarget, useModernI18n };
48
+ export { buildLocalizedUrl, splitUrlTarget } from "./utils.mjs";
49
+ export { createI18nPlugin };
@@ -0,0 +1,102 @@
1
+ const detectorInstanceCache = new WeakMap();
2
+ const DETECTOR_SAFE_OPTION_KEYS = [
3
+ 'lowerCaseLng',
4
+ 'nonExplicitSupportedLngs',
5
+ 'load',
6
+ 'partialBundledLanguages',
7
+ 'returnNull',
8
+ 'returnEmptyString',
9
+ 'returnObjects',
10
+ 'joinArrays',
11
+ 'keySeparator',
12
+ 'nsSeparator',
13
+ 'pluralSeparator',
14
+ 'contextSeparator',
15
+ 'fallbackNS',
16
+ 'ns',
17
+ 'defaultNS',
18
+ 'debug'
19
+ ];
20
+ const stableStringify = (value)=>{
21
+ if (null == value) return JSON.stringify(value);
22
+ if ('object' != typeof value) return JSON.stringify(value);
23
+ if (Array.isArray(value)) return `[${value.map((item)=>stableStringify(item)).join(',')}]`;
24
+ const sortedKeys = Object.keys(value).sort();
25
+ const sortedEntries = sortedKeys.map((key)=>{
26
+ const stringifiedValue = stableStringify(value[key]);
27
+ return `${JSON.stringify(key)}:${stringifiedValue}`;
28
+ });
29
+ return `{${sortedEntries.join(',')}}`;
30
+ };
31
+ const buildDetectorConfigKey = (languages, fallbackLanguage, mergedDetection)=>stableStringify({
32
+ languages,
33
+ fallbackLanguage,
34
+ detection: mergedDetection
35
+ });
36
+ const pickSafeDetectionOptions = (userInitOptions)=>{
37
+ if (!userInitOptions) return {};
38
+ const safeOptions = {};
39
+ for (const key of DETECTOR_SAFE_OPTION_KEYS){
40
+ const value = userInitOptions[key];
41
+ if (void 0 !== value) safeOptions[key] = value;
42
+ }
43
+ if (userInitOptions.interpolation) safeOptions.interpolation = {
44
+ ...userInitOptions.interpolation
45
+ };
46
+ return safeOptions;
47
+ };
48
+ const cleanupDetectorCacheEntry = (entry)=>{
49
+ if (!entry || !entry.isTemporary) return;
50
+ const instance = entry.instance;
51
+ try {
52
+ instance?.removeAllListeners?.();
53
+ } catch (error) {}
54
+ try {
55
+ instance?.off?.('*');
56
+ } catch (error) {}
57
+ try {
58
+ instance?.services?.backendConnector?.backend?.stop?.();
59
+ } catch (error) {}
60
+ try {
61
+ instance?.services?.backendConnector?.backend?.close?.();
62
+ } catch (error) {}
63
+ };
64
+ const createDetectorInstance = (baseInstance, configKey)=>{
65
+ const cached = detectorInstanceCache.get(baseInstance);
66
+ if (cached && cached.configKey === configKey) return {
67
+ instance: cached.instance,
68
+ isTemporary: cached.isTemporary
69
+ };
70
+ if (cached) {
71
+ cleanupDetectorCacheEntry(cached);
72
+ detectorInstanceCache.delete(baseInstance);
73
+ }
74
+ const createNewInstance = ()=>{
75
+ if ('function' == typeof baseInstance.createInstance) try {
76
+ const created = baseInstance.createInstance();
77
+ if (created) return {
78
+ instance: created,
79
+ isTemporary: true
80
+ };
81
+ } catch (error) {}
82
+ if ('function' == typeof baseInstance.cloneInstance) try {
83
+ const cloned = baseInstance.cloneInstance();
84
+ if (cloned) return {
85
+ instance: cloned,
86
+ isTemporary: true
87
+ };
88
+ } catch (error) {}
89
+ return {
90
+ instance: baseInstance,
91
+ isTemporary: false
92
+ };
93
+ };
94
+ const created = createNewInstance();
95
+ if (created.isTemporary) detectorInstanceCache.set(baseInstance, {
96
+ instance: created.instance,
97
+ isTemporary: true,
98
+ configKey
99
+ });
100
+ return created;
101
+ };
102
+ export { buildDetectorConfigKey, createDetectorInstance, detectorInstanceCache, pickSafeDetectionOptions };
@@ -0,0 +1,62 @@
1
+ import { isBrowser } from "@modern-js/runtime";
2
+ import { buildDetectorConfigKey, createDetectorInstance, detectorInstanceCache, pickSafeDetectionOptions } from "./cache.mjs";
3
+ import { mergeDetectionOptions } from "./initOptions.mjs";
4
+ import { getSupportedLanguage, isLanguageSupported } from "./language.mjs";
5
+ import { detectLanguage, useI18nextLanguageDetector } from "./middleware.mjs";
6
+ const initializeI18nForDetector = async (i18nInstance, options)=>{
7
+ const mergedDetection = mergeDetectionOptions(options.i18nextDetector, options.detection, options.localePathRedirect, options.userInitOptions);
8
+ const configKey = buildDetectorConfigKey(options.languages, options.fallbackLanguage, mergedDetection);
9
+ const { instance, isTemporary } = createDetectorInstance(i18nInstance, configKey);
10
+ const safeUserOptions = pickSafeDetectionOptions(options.userInitOptions);
11
+ const initOptions = {
12
+ ...safeUserOptions,
13
+ fallbackLng: options.fallbackLanguage,
14
+ supportedLngs: options.languages,
15
+ detection: mergedDetection,
16
+ initImmediate: true,
17
+ interpolation: {
18
+ ...safeUserOptions?.interpolation || {},
19
+ escapeValue: safeUserOptions?.interpolation?.escapeValue ?? false
20
+ },
21
+ react: {
22
+ useSuspense: false
23
+ }
24
+ };
25
+ useI18nextLanguageDetector(instance);
26
+ if (instance.isInitialized) {
27
+ if (isTemporary) await instance.init(initOptions);
28
+ } else await instance.init(initOptions);
29
+ return {
30
+ detectorInstance: instance,
31
+ isTemporary
32
+ };
33
+ };
34
+ const detectLanguageFromI18nextDetector = async (i18nInstance, options)=>{
35
+ if (!options.i18nextDetector) return;
36
+ const mergedDetection = mergeDetectionOptions(options.i18nextDetector, options.detection, options.localePathRedirect, options.userInitOptions);
37
+ const { detectorInstance, isTemporary } = await initializeI18nForDetector(i18nInstance, options);
38
+ try {
39
+ const request = options.ssrContext?.request;
40
+ if (!isBrowser() && !request) return;
41
+ const detectorLang = detectLanguage(detectorInstance, request, mergedDetection);
42
+ if (detectorLang) {
43
+ const supportedLang = getSupportedLanguage(detectorLang, options.languages);
44
+ if (supportedLang) return supportedLang;
45
+ }
46
+ if (detectorInstance.isInitialized && detectorInstance.language) {
47
+ const currentLang = detectorInstance.language;
48
+ if (isLanguageSupported(currentLang, options.languages)) return currentLang;
49
+ }
50
+ } catch (error) {} finally{
51
+ if (isTemporary && detectorInstance !== i18nInstance) detectorInstanceCache.set(i18nInstance, {
52
+ instance: detectorInstance,
53
+ isTemporary: true,
54
+ configKey: buildDetectorConfigKey(options.languages, options.fallbackLanguage, mergedDetection)
55
+ });
56
+ else if (detectorInstance === i18nInstance) {
57
+ i18nInstance.isInitialized = false;
58
+ delete i18nInstance.language;
59
+ }
60
+ }
61
+ };
62
+ export { detectLanguageFromI18nextDetector };
@@ -1,260 +1,4 @@
1
- import { isBrowser } from "@modern-js/runtime";
2
- import { detectLanguageFromPath } from "../../utils.mjs";
3
- import { isI18nWrapperInstance } from "../instance.mjs";
4
- import { mergeDetectionOptions } from "./config.mjs";
5
- import { cacheUserLanguage, detectLanguage, readLanguageFromStorage, useI18nextLanguageDetector } from "./middleware.mjs";
6
- const detectorInstanceCache = new WeakMap();
7
- const DETECTOR_SAFE_OPTION_KEYS = [
8
- 'lowerCaseLng',
9
- 'nonExplicitSupportedLngs',
10
- 'load',
11
- 'partialBundledLanguages',
12
- 'returnNull',
13
- 'returnEmptyString',
14
- 'returnObjects',
15
- 'joinArrays',
16
- 'keySeparator',
17
- 'nsSeparator',
18
- 'pluralSeparator',
19
- 'contextSeparator',
20
- 'fallbackNS',
21
- 'ns',
22
- 'defaultNS',
23
- 'debug'
24
- ];
25
- const stableStringify = (value)=>{
26
- if (null == value) return JSON.stringify(value);
27
- if ('object' != typeof value) return JSON.stringify(value);
28
- if (Array.isArray(value)) return `[${value.map((item)=>stableStringify(item)).join(',')}]`;
29
- const sortedKeys = Object.keys(value).sort();
30
- const sortedEntries = sortedKeys.map((key)=>{
31
- const stringifiedValue = stableStringify(value[key]);
32
- return `${JSON.stringify(key)}:${stringifiedValue}`;
33
- });
34
- return `{${sortedEntries.join(',')}}`;
35
- };
36
- const buildDetectorConfigKey = (languages, fallbackLanguage, mergedDetection)=>stableStringify({
37
- languages,
38
- fallbackLanguage,
39
- detection: mergedDetection
40
- });
41
- const pickSafeDetectionOptions = (userInitOptions)=>{
42
- if (!userInitOptions) return {};
43
- const safeOptions = {};
44
- for (const key of DETECTOR_SAFE_OPTION_KEYS){
45
- const value = userInitOptions[key];
46
- if (void 0 !== value) safeOptions[key] = value;
47
- }
48
- if (userInitOptions.interpolation) safeOptions.interpolation = {
49
- ...userInitOptions.interpolation
50
- };
51
- return safeOptions;
52
- };
53
- const cleanupDetectorCacheEntry = (entry)=>{
54
- if (!entry || !entry.isTemporary) return;
55
- const instance = entry.instance;
56
- try {
57
- instance?.removeAllListeners?.();
58
- } catch (error) {}
59
- try {
60
- instance?.off?.('*');
61
- } catch (error) {}
62
- try {
63
- instance?.services?.backendConnector?.backend?.stop?.();
64
- } catch (error) {}
65
- try {
66
- instance?.services?.backendConnector?.backend?.close?.();
67
- } catch (error) {}
68
- };
69
- function exportServerLngToWindow(context, lng) {
70
- context.__i18nData__ = {
71
- lng
72
- };
73
- }
74
- const getLanguageFromSSRData = (window1)=>{
75
- try {
76
- const ssrData = window1._SSR_DATA;
77
- if (!ssrData || !ssrData.data || !ssrData.data.i18nData) return;
78
- const lng = ssrData.data.i18nData.lng;
79
- return 'string' == typeof lng && '' !== lng.trim() ? lng : void 0;
80
- } catch (error) {
81
- return;
82
- }
83
- };
84
- const normalizeLanguageCode = (language)=>{
85
- if (!language) return language;
86
- const baseLang = language.split('-')[0];
87
- return baseLang;
88
- };
89
- const isLanguageSupported = (language, supportedLanguages)=>{
90
- if (!language) return false;
91
- if (0 === supportedLanguages.length) return true;
92
- if (supportedLanguages.includes(language)) return true;
93
- const baseLang = normalizeLanguageCode(language);
94
- if (baseLang !== language && supportedLanguages.includes(baseLang)) return true;
95
- return false;
96
- };
97
- const getSupportedLanguage = (language, supportedLanguages)=>{
98
- if (!language) return;
99
- if (0 === supportedLanguages.length) return language;
100
- if (supportedLanguages.includes(language)) return language;
101
- const baseLang = normalizeLanguageCode(language);
102
- if (baseLang !== language && supportedLanguages.includes(baseLang)) return baseLang;
103
- };
104
- const detectLanguageFromSSR = (languages)=>{
105
- if (!isBrowser()) return;
106
- try {
107
- const ssrLanguage = getLanguageFromSSRData(window);
108
- if (ssrLanguage && isLanguageSupported(ssrLanguage, languages)) return ssrLanguage;
109
- } catch (error) {}
110
- };
111
- const detectLanguageFromPathPriority = (pathname, languages, localePathRedirect)=>{
112
- if (!localePathRedirect) return;
113
- if (!languages || 0 === languages.length) return;
114
- if (!pathname || '' === pathname.trim()) return;
115
- try {
116
- const pathDetection = detectLanguageFromPath(pathname, languages, localePathRedirect);
117
- if (true === pathDetection.detected && pathDetection.language) return pathDetection.language;
118
- } catch (error) {}
119
- };
120
- const createDetectorInstance = (baseInstance, configKey)=>{
121
- const cached = detectorInstanceCache.get(baseInstance);
122
- if (cached && cached.configKey === configKey) return {
123
- instance: cached.instance,
124
- isTemporary: cached.isTemporary
125
- };
126
- if (cached) {
127
- cleanupDetectorCacheEntry(cached);
128
- detectorInstanceCache.delete(baseInstance);
129
- }
130
- const createNewInstance = ()=>{
131
- if ('function' == typeof baseInstance.createInstance) try {
132
- const created = baseInstance.createInstance();
133
- if (created) return {
134
- instance: created,
135
- isTemporary: true
136
- };
137
- } catch (error) {}
138
- if ('function' == typeof baseInstance.cloneInstance) try {
139
- const cloned = baseInstance.cloneInstance();
140
- if (cloned) return {
141
- instance: cloned,
142
- isTemporary: true
143
- };
144
- } catch (error) {}
145
- return {
146
- instance: baseInstance,
147
- isTemporary: false
148
- };
149
- };
150
- const created = createNewInstance();
151
- if (created.isTemporary) detectorInstanceCache.set(baseInstance, {
152
- instance: created.instance,
153
- isTemporary: true,
154
- configKey
155
- });
156
- return created;
157
- };
158
- const initializeI18nForDetector = async (i18nInstance, options)=>{
159
- const mergedDetection = detection_mergeDetectionOptions(options.i18nextDetector, options.detection, options.localePathRedirect, options.userInitOptions);
160
- const configKey = buildDetectorConfigKey(options.languages, options.fallbackLanguage, mergedDetection);
161
- const { instance, isTemporary } = createDetectorInstance(i18nInstance, configKey);
162
- const safeUserOptions = pickSafeDetectionOptions(options.userInitOptions);
163
- const initOptions = {
164
- ...safeUserOptions,
165
- fallbackLng: options.fallbackLanguage,
166
- supportedLngs: options.languages,
167
- detection: mergedDetection,
168
- initImmediate: true,
169
- interpolation: {
170
- ...safeUserOptions?.interpolation || {},
171
- escapeValue: safeUserOptions?.interpolation?.escapeValue ?? false
172
- },
173
- react: {
174
- useSuspense: false
175
- }
176
- };
177
- useI18nextLanguageDetector(instance);
178
- if (instance.isInitialized) {
179
- if (isTemporary) await instance.init(initOptions);
180
- } else await instance.init(initOptions);
181
- return {
182
- detectorInstance: instance,
183
- isTemporary
184
- };
185
- };
186
- const detectLanguageFromI18nextDetector = async (i18nInstance, options)=>{
187
- if (!options.i18nextDetector) return;
188
- const mergedDetection = detection_mergeDetectionOptions(options.i18nextDetector, options.detection, options.localePathRedirect, options.userInitOptions);
189
- const { detectorInstance, isTemporary } = await initializeI18nForDetector(i18nInstance, options);
190
- try {
191
- const request = options.ssrContext?.request;
192
- if (!isBrowser() && !request) return;
193
- const detectorLang = detectLanguage(detectorInstance, request, mergedDetection);
194
- if (detectorLang) {
195
- const supportedLang = getSupportedLanguage(detectorLang, options.languages);
196
- if (supportedLang) return supportedLang;
197
- }
198
- if (detectorInstance.isInitialized && detectorInstance.language) {
199
- const currentLang = detectorInstance.language;
200
- if (isLanguageSupported(currentLang, options.languages)) return currentLang;
201
- }
202
- } catch (error) {} finally{
203
- if (isTemporary && detectorInstance !== i18nInstance) detectorInstanceCache.set(i18nInstance, {
204
- instance: detectorInstance,
205
- isTemporary: true,
206
- configKey: buildDetectorConfigKey(options.languages, options.fallbackLanguage, mergedDetection)
207
- });
208
- else if (detectorInstance === i18nInstance) {
209
- i18nInstance.isInitialized = false;
210
- delete i18nInstance.language;
211
- }
212
- }
213
- };
214
- const detectLanguageWithPriority = async (i18nInstance, options)=>{
215
- const { languages, fallbackLanguage, localePathRedirect, i18nextDetector, detection, userInitOptions, pathname, ssrContext } = options;
216
- let detectedLanguage;
217
- detectedLanguage = detectLanguageFromSSR(languages);
218
- if (!detectedLanguage) detectedLanguage = detectLanguageFromPathPriority(pathname, languages, localePathRedirect);
219
- if (!detectedLanguage && i18nextDetector) detectedLanguage = isI18nWrapperInstance(i18nInstance) ? readLanguageFromStorage(detection_mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions)) : await detectLanguageFromI18nextDetector(i18nInstance, {
220
- languages,
221
- fallbackLanguage,
222
- localePathRedirect,
223
- i18nextDetector,
224
- detection,
225
- userInitOptions,
226
- mergedBackend: options.mergedBackend,
227
- ssrContext
228
- });
229
- const finalLanguage = detectedLanguage || userInitOptions?.lng || fallbackLanguage;
230
- return {
231
- detectedLanguage,
232
- finalLanguage
233
- };
234
- };
235
- const buildInitOptions = (params)=>{
236
- const { finalLanguage, fallbackLanguage, languages, userInitOptions, mergedDetection, mergeBackend } = params;
237
- return {
238
- ...userInitOptions || {},
239
- lng: finalLanguage,
240
- fallbackLng: fallbackLanguage,
241
- supportedLngs: languages,
242
- detection: mergedDetection,
243
- backend: mergeBackend,
244
- interpolation: {
245
- ...userInitOptions?.interpolation || {},
246
- escapeValue: userInitOptions?.interpolation?.escapeValue ?? false
247
- },
248
- react: {
249
- useSuspense: isBrowser()
250
- }
251
- };
252
- };
253
- const detection_mergeDetectionOptions = (i18nextDetector, detection, localePathRedirect, userInitOptions)=>{
254
- let mergedDetection;
255
- mergedDetection = i18nextDetector ? mergeDetectionOptions(detection, userInitOptions?.detection) : userInitOptions?.detection || {};
256
- if (!mergedDetection || 'object' != typeof mergedDetection) mergedDetection = {};
257
- if (localePathRedirect && mergedDetection.order) mergedDetection.order = mergedDetection.order.filter((item)=>'path' !== item);
258
- return mergedDetection;
259
- };
260
- export { buildInitOptions, cacheUserLanguage, detectLanguageWithPriority, detection_mergeDetectionOptions as mergeDetectionOptions, exportServerLngToWindow, getLanguageFromSSRData };
1
+ export { buildInitOptions, mergeDetectionOptions } from "./initOptions.mjs";
2
+ export { cacheUserLanguage } from "./middleware.mjs";
3
+ export { detectLanguageWithPriority } from "./priority.mjs";
4
+ export { exportServerLngToWindow, getLanguageFromSSRData } from "./ssr.mjs";
@@ -0,0 +1,28 @@
1
+ import { isBrowser } from "@modern-js/runtime";
2
+ import { mergeDetectionOptions } from "./config.mjs";
3
+ const buildInitOptions = (params)=>{
4
+ const { finalLanguage, fallbackLanguage, languages, userInitOptions, mergedDetection, mergeBackend } = params;
5
+ return {
6
+ ...userInitOptions || {},
7
+ lng: finalLanguage,
8
+ fallbackLng: fallbackLanguage,
9
+ supportedLngs: languages,
10
+ detection: mergedDetection,
11
+ backend: mergeBackend,
12
+ interpolation: {
13
+ ...userInitOptions?.interpolation || {},
14
+ escapeValue: userInitOptions?.interpolation?.escapeValue ?? false
15
+ },
16
+ react: {
17
+ useSuspense: isBrowser()
18
+ }
19
+ };
20
+ };
21
+ const initOptions_mergeDetectionOptions = (i18nextDetector, detection, localePathRedirect, userInitOptions)=>{
22
+ let mergedDetection;
23
+ mergedDetection = i18nextDetector ? mergeDetectionOptions(detection, userInitOptions?.detection) : userInitOptions?.detection || {};
24
+ if (!mergedDetection || 'object' != typeof mergedDetection) mergedDetection = {};
25
+ if (localePathRedirect && mergedDetection.order) mergedDetection.order = mergedDetection.order.filter((item)=>'path' !== item);
26
+ return mergedDetection;
27
+ };
28
+ export { buildInitOptions, initOptions_mergeDetectionOptions as mergeDetectionOptions };
@@ -0,0 +1,21 @@
1
+ const normalizeLanguageCode = (language)=>{
2
+ if (!language) return language;
3
+ const baseLang = language.split('-')[0];
4
+ return baseLang;
5
+ };
6
+ const isLanguageSupported = (language, supportedLanguages)=>{
7
+ if (!language) return false;
8
+ if (0 === supportedLanguages.length) return true;
9
+ if (supportedLanguages.includes(language)) return true;
10
+ const baseLang = normalizeLanguageCode(language);
11
+ if (baseLang !== language && supportedLanguages.includes(baseLang)) return true;
12
+ return false;
13
+ };
14
+ const getSupportedLanguage = (language, supportedLanguages)=>{
15
+ if (!language) return;
16
+ if (0 === supportedLanguages.length) return language;
17
+ if (supportedLanguages.includes(language)) return language;
18
+ const baseLang = normalizeLanguageCode(language);
19
+ if (baseLang !== language && supportedLanguages.includes(baseLang)) return baseLang;
20
+ };
21
+ export { getSupportedLanguage, isLanguageSupported };
@@ -0,0 +1,11 @@
1
+ import { detectLanguageFromPath } from "../../utils.mjs";
2
+ const detectLanguageFromPathPriority = (pathname, languages, localePathRedirect)=>{
3
+ if (!localePathRedirect) return;
4
+ if (!languages || 0 === languages.length) return;
5
+ if (!pathname || '' === pathname.trim()) return;
6
+ try {
7
+ const pathDetection = detectLanguageFromPath(pathname, languages, localePathRedirect);
8
+ if (true === pathDetection.detected && pathDetection.language) return pathDetection.language;
9
+ } catch (error) {}
10
+ };
11
+ export { detectLanguageFromPathPriority };