@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.6 → 3.5.0-ultramodern.76

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 (221) hide show
  1. package/dist/cjs/cli/index.js +2 -1
  2. package/dist/cjs/cli/locales.js +15 -15
  3. package/dist/cjs/runtime/Link.js +8 -89
  4. package/dist/cjs/runtime/context.js +78 -91
  5. package/dist/cjs/runtime/contextHelpers.js +143 -0
  6. package/dist/cjs/runtime/core.js +32 -132
  7. package/dist/cjs/runtime/hooks.js +54 -21
  8. package/dist/cjs/runtime/i18n/backend/defaults.node.js +7 -7
  9. package/dist/cjs/runtime/i18n/detection/cache.js +150 -0
  10. package/dist/cjs/runtime/i18n/detection/detector.js +100 -0
  11. package/dist/cjs/runtime/i18n/detection/index.js +8 -263
  12. package/dist/cjs/runtime/i18n/detection/initOptions.js +69 -0
  13. package/dist/cjs/runtime/i18n/detection/language.js +62 -0
  14. package/dist/cjs/runtime/i18n/detection/path.js +49 -0
  15. package/dist/cjs/runtime/i18n/detection/priority.js +66 -0
  16. package/dist/cjs/runtime/i18n/detection/ssr.js +70 -0
  17. package/dist/cjs/runtime/i18n/detection/types.js +18 -0
  18. package/dist/cjs/runtime/i18n/instance.js +34 -10
  19. package/dist/cjs/runtime/linkHelpers.js +148 -0
  20. package/dist/cjs/runtime/pluginSetup.js +102 -0
  21. package/dist/cjs/runtime/providerComposition.js +115 -0
  22. package/dist/cjs/runtime/reactI18next.js +43 -0
  23. package/dist/cjs/runtime/routerAdapter.js +16 -8
  24. package/dist/cjs/runtime/utils.js +1 -9
  25. package/dist/cjs/server/apiPrefix.js +72 -0
  26. package/dist/cjs/server/detectorOptions.js +97 -0
  27. package/dist/cjs/server/index.js +17 -144
  28. package/dist/cjs/server/redirectPolicy.js +96 -0
  29. package/dist/cjs/shared/detection.js +2 -104
  30. package/dist/cjs/shared/localisedUrls/config.js +49 -0
  31. package/dist/cjs/shared/localisedUrls/index.js +91 -0
  32. package/dist/cjs/shared/localisedUrls/normalise.js +87 -0
  33. package/dist/cjs/shared/localisedUrls/pathname.js +68 -0
  34. package/dist/cjs/shared/localisedUrls/patterns.js +130 -0
  35. package/dist/cjs/shared/localisedUrls/redirect.js +103 -0
  36. package/dist/cjs/shared/localisedUrls/resolve.js +88 -0
  37. package/dist/cjs/shared/localisedUrls/routes.js +130 -0
  38. package/dist/cjs/shared/localisedUrls/types.js +18 -0
  39. package/dist/cjs/shared/localisedUrls.js +28 -294
  40. package/dist/cjs/shared/utils.js +2 -8
  41. package/dist/esm/cli/index.mjs +2 -1
  42. package/dist/esm/cli/locales.mjs +13 -13
  43. package/dist/esm/runtime/Link.mjs +1 -82
  44. package/dist/esm/runtime/context.mjs +76 -92
  45. package/dist/esm/runtime/contextHelpers.mjs +87 -0
  46. package/dist/esm/runtime/core.mjs +30 -133
  47. package/dist/esm/runtime/hooks.mjs +56 -23
  48. package/dist/esm/runtime/i18n/backend/defaults.node.mjs +5 -5
  49. package/dist/esm/runtime/i18n/detection/cache.mjs +103 -0
  50. package/dist/esm/runtime/i18n/detection/detector.mjs +62 -0
  51. package/dist/esm/runtime/i18n/detection/index.mjs +4 -260
  52. package/dist/esm/runtime/i18n/detection/initOptions.mjs +28 -0
  53. package/dist/esm/runtime/i18n/detection/language.mjs +21 -0
  54. package/dist/esm/runtime/i18n/detection/path.mjs +11 -0
  55. package/dist/esm/runtime/i18n/detection/priority.mjs +28 -0
  56. package/dist/esm/runtime/i18n/detection/ssr.mjs +26 -0
  57. package/dist/esm/runtime/i18n/detection/types.mjs +0 -0
  58. package/dist/esm/runtime/i18n/instance.mjs +35 -8
  59. package/dist/esm/runtime/linkHelpers.mjs +98 -0
  60. package/dist/esm/runtime/pluginSetup.mjs +64 -0
  61. package/dist/esm/runtime/providerComposition.mjs +77 -0
  62. package/dist/esm/runtime/reactI18next.mjs +5 -0
  63. package/dist/esm/runtime/routerAdapter.mjs +16 -8
  64. package/dist/esm/runtime/utils.mjs +2 -10
  65. package/dist/esm/server/apiPrefix.mjs +31 -0
  66. package/dist/esm/server/detectorOptions.mjs +59 -0
  67. package/dist/esm/server/index.mjs +8 -135
  68. package/dist/esm/server/redirectPolicy.mjs +46 -0
  69. package/dist/esm/shared/detection.mjs +1 -71
  70. package/dist/esm/shared/localisedUrls/config.mjs +11 -0
  71. package/dist/esm/shared/localisedUrls/index.mjs +7 -0
  72. package/dist/esm/shared/localisedUrls/normalise.mjs +37 -0
  73. package/dist/esm/shared/localisedUrls/pathname.mjs +24 -0
  74. package/dist/esm/shared/localisedUrls/patterns.mjs +86 -0
  75. package/dist/esm/shared/localisedUrls/redirect.mjs +56 -0
  76. package/dist/esm/shared/localisedUrls/resolve.mjs +47 -0
  77. package/dist/esm/shared/localisedUrls/routes.mjs +89 -0
  78. package/dist/esm/shared/localisedUrls/types.mjs +0 -0
  79. package/dist/esm/shared/localisedUrls.mjs +1 -283
  80. package/dist/esm/shared/utils.mjs +1 -1
  81. package/dist/esm-node/cli/index.mjs +2 -1
  82. package/dist/esm-node/cli/locales.mjs +13 -13
  83. package/dist/esm-node/runtime/Link.mjs +1 -82
  84. package/dist/esm-node/runtime/context.mjs +76 -92
  85. package/dist/esm-node/runtime/contextHelpers.mjs +88 -0
  86. package/dist/esm-node/runtime/core.mjs +30 -133
  87. package/dist/esm-node/runtime/hooks.mjs +56 -23
  88. package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +5 -5
  89. package/dist/esm-node/runtime/i18n/detection/cache.mjs +104 -0
  90. package/dist/esm-node/runtime/i18n/detection/detector.mjs +63 -0
  91. package/dist/esm-node/runtime/i18n/detection/index.mjs +4 -260
  92. package/dist/esm-node/runtime/i18n/detection/initOptions.mjs +29 -0
  93. package/dist/esm-node/runtime/i18n/detection/language.mjs +22 -0
  94. package/dist/esm-node/runtime/i18n/detection/path.mjs +12 -0
  95. package/dist/esm-node/runtime/i18n/detection/priority.mjs +29 -0
  96. package/dist/esm-node/runtime/i18n/detection/ssr.mjs +27 -0
  97. package/dist/esm-node/runtime/i18n/detection/types.mjs +1 -0
  98. package/dist/esm-node/runtime/i18n/instance.mjs +35 -8
  99. package/dist/esm-node/runtime/linkHelpers.mjs +99 -0
  100. package/dist/esm-node/runtime/pluginSetup.mjs +65 -0
  101. package/dist/esm-node/runtime/providerComposition.mjs +78 -0
  102. package/dist/esm-node/runtime/reactI18next.mjs +6 -0
  103. package/dist/esm-node/runtime/routerAdapter.mjs +16 -8
  104. package/dist/esm-node/runtime/utils.mjs +2 -10
  105. package/dist/esm-node/server/apiPrefix.mjs +32 -0
  106. package/dist/esm-node/server/detectorOptions.mjs +60 -0
  107. package/dist/esm-node/server/index.mjs +8 -135
  108. package/dist/esm-node/server/redirectPolicy.mjs +47 -0
  109. package/dist/esm-node/shared/detection.mjs +1 -71
  110. package/dist/esm-node/shared/localisedUrls/config.mjs +12 -0
  111. package/dist/esm-node/shared/localisedUrls/index.mjs +8 -0
  112. package/dist/esm-node/shared/localisedUrls/normalise.mjs +38 -0
  113. package/dist/esm-node/shared/localisedUrls/pathname.mjs +25 -0
  114. package/dist/esm-node/shared/localisedUrls/patterns.mjs +87 -0
  115. package/dist/esm-node/shared/localisedUrls/redirect.mjs +57 -0
  116. package/dist/esm-node/shared/localisedUrls/resolve.mjs +48 -0
  117. package/dist/esm-node/shared/localisedUrls/routes.mjs +90 -0
  118. package/dist/esm-node/shared/localisedUrls/types.mjs +1 -0
  119. package/dist/esm-node/shared/localisedUrls.mjs +1 -283
  120. package/dist/esm-node/shared/utils.mjs +1 -1
  121. package/dist/types/cli/locales.d.ts +1 -1
  122. package/dist/types/runtime/Link.d.ts +3 -7
  123. package/dist/types/runtime/context.d.ts +21 -5
  124. package/dist/types/runtime/contextHelpers.d.ts +29 -0
  125. package/dist/types/runtime/core.d.ts +4 -8
  126. package/dist/types/runtime/hooks.d.ts +3 -7
  127. package/dist/types/runtime/i18n/backend/middleware.common.d.ts +2 -1
  128. package/dist/types/runtime/i18n/detection/cache.d.ts +14 -0
  129. package/dist/types/runtime/i18n/detection/detector.d.ts +8 -0
  130. package/dist/types/runtime/i18n/detection/index.d.ts +5 -50
  131. package/dist/types/runtime/i18n/detection/initOptions.d.ts +10 -0
  132. package/dist/types/runtime/i18n/detection/language.d.ts +11 -0
  133. package/dist/types/runtime/i18n/detection/middleware.d.ts +4 -4
  134. package/dist/types/runtime/i18n/detection/middleware.node.d.ts +4 -4
  135. package/dist/types/runtime/i18n/detection/path.d.ts +5 -0
  136. package/dist/types/runtime/i18n/detection/priority.d.ts +10 -0
  137. package/dist/types/runtime/i18n/detection/ssr.d.ts +9 -0
  138. package/dist/types/runtime/i18n/detection/types.d.ts +32 -0
  139. package/dist/types/runtime/i18n/instance.d.ts +31 -27
  140. package/dist/types/runtime/i18n/react-i18next.d.ts +1 -6
  141. package/dist/types/runtime/i18n/utils.d.ts +14 -5
  142. package/dist/types/runtime/linkHelpers.d.ts +17 -0
  143. package/dist/types/runtime/pluginSetup.d.ts +32 -0
  144. package/dist/types/runtime/providerComposition.d.ts +16 -0
  145. package/dist/types/runtime/reactI18next.d.ts +7 -0
  146. package/dist/types/runtime/routerAdapter.d.ts +7 -6
  147. package/dist/types/runtime/utils.d.ts +7 -2
  148. package/dist/types/server/apiPrefix.d.ts +7 -0
  149. package/dist/types/server/detectorOptions.d.ts +21 -0
  150. package/dist/types/server/index.d.ts +1 -6
  151. package/dist/types/server/redirectPolicy.d.ts +22 -0
  152. package/dist/types/shared/detection.d.ts +1 -11
  153. package/dist/types/shared/localisedUrls/config.d.ts +9 -0
  154. package/dist/types/shared/localisedUrls/index.d.ts +9 -0
  155. package/dist/types/shared/localisedUrls/normalise.d.ts +10 -0
  156. package/dist/types/shared/localisedUrls/pathname.d.ts +4 -0
  157. package/dist/types/shared/localisedUrls/patterns.d.ts +5 -0
  158. package/dist/types/shared/localisedUrls/redirect.d.ts +11 -0
  159. package/dist/types/shared/localisedUrls/resolve.d.ts +7 -0
  160. package/dist/types/shared/localisedUrls/routes.d.ts +3 -0
  161. package/dist/types/shared/localisedUrls/types.d.ts +14 -0
  162. package/dist/types/shared/localisedUrls.d.ts +2 -36
  163. package/dist/types/shared/utils.d.ts +0 -2
  164. package/package.json +17 -20
  165. package/rstest.config.mts +8 -1
  166. package/src/cli/index.ts +5 -4
  167. package/src/cli/locales.ts +3 -3
  168. package/src/runtime/Link.tsx +11 -138
  169. package/src/runtime/context.tsx +170 -207
  170. package/src/runtime/contextHelpers.ts +242 -0
  171. package/src/runtime/core.tsx +48 -275
  172. package/src/runtime/hooks.ts +86 -24
  173. package/src/runtime/i18n/backend/defaults.node.ts +2 -2
  174. package/src/runtime/i18n/backend/middleware.common.ts +5 -4
  175. package/src/runtime/i18n/detection/cache.ts +189 -0
  176. package/src/runtime/i18n/detection/detector.ts +166 -0
  177. package/src/runtime/i18n/detection/index.ts +10 -641
  178. package/src/runtime/i18n/detection/initOptions.ts +74 -0
  179. package/src/runtime/i18n/detection/language.ts +64 -0
  180. package/src/runtime/i18n/detection/middleware.node.ts +26 -7
  181. package/src/runtime/i18n/detection/middleware.ts +15 -7
  182. package/src/runtime/i18n/detection/path.ts +41 -0
  183. package/src/runtime/i18n/detection/priority.ts +82 -0
  184. package/src/runtime/i18n/detection/ssr.ts +47 -0
  185. package/src/runtime/i18n/detection/types.ts +41 -0
  186. package/src/runtime/i18n/instance.ts +98 -34
  187. package/src/runtime/i18n/react-i18next.ts +1 -6
  188. package/src/runtime/i18n/utils.ts +18 -9
  189. package/src/runtime/linkHelpers.ts +174 -0
  190. package/src/runtime/pluginSetup.ts +189 -0
  191. package/src/runtime/providerComposition.tsx +148 -0
  192. package/src/runtime/reactI18next.ts +20 -0
  193. package/src/runtime/routerAdapter.tsx +36 -15
  194. package/src/runtime/utils.ts +13 -35
  195. package/src/server/apiPrefix.ts +67 -0
  196. package/src/server/detectorOptions.ts +105 -0
  197. package/src/server/index.ts +14 -319
  198. package/src/server/redirectPolicy.ts +127 -0
  199. package/src/shared/detection.ts +1 -1
  200. package/src/shared/localisedUrls/config.ts +18 -0
  201. package/src/shared/localisedUrls/index.ts +24 -0
  202. package/src/shared/localisedUrls/normalise.ts +67 -0
  203. package/src/shared/localisedUrls/pathname.ts +60 -0
  204. package/src/shared/localisedUrls/patterns.ts +160 -0
  205. package/src/shared/localisedUrls/redirect.ts +93 -0
  206. package/src/shared/localisedUrls/resolve.ts +135 -0
  207. package/src/shared/localisedUrls/routes.ts +234 -0
  208. package/src/shared/localisedUrls/types.ts +18 -0
  209. package/src/shared/localisedUrls.ts +26 -623
  210. package/src/shared/utils.ts +2 -2
  211. package/tests/federatedI18nBoundary.client.test.tsx +112 -0
  212. package/tests/federatedI18nBoundary.test.tsx +120 -0
  213. package/tests/fixtures/localised-routes.golden.json +156 -0
  214. package/tests/link.test.tsx +111 -0
  215. package/tests/localisedUrlRewriteMatrix.fork.test.ts +147 -0
  216. package/tests/localisedUrls.test.ts +148 -2
  217. package/tests/reactI18nextRuntimeBoundary.test.ts +6 -3
  218. package/tests/redirectPolicy.test.ts +45 -0
  219. package/tests/routerAdapter.test.tsx +603 -0
  220. package/tests/type-fixture/fixture-globals.d.ts +11 -0
  221. package/tests/type-fixture/tsconfig.json +1 -0
@@ -0,0 +1,189 @@
1
+ import type {
2
+ I18nInitOptions,
3
+ I18nInstance,
4
+ LanguageDetectorOptions,
5
+ } from '../instance';
6
+
7
+ interface DetectorCacheEntry {
8
+ instance: I18nInstance;
9
+ isTemporary: boolean;
10
+ configKey: string;
11
+ }
12
+
13
+ type DetectorCleanupInstance = I18nInstance & {
14
+ removeAllListeners?: () => void;
15
+ off?: (event: string) => void;
16
+ services?: I18nInstance['services'] & {
17
+ backendConnector?: {
18
+ backend?: {
19
+ stop?: () => void;
20
+ close?: () => void;
21
+ };
22
+ };
23
+ };
24
+ };
25
+
26
+ export const detectorInstanceCache = new WeakMap<
27
+ I18nInstance,
28
+ DetectorCacheEntry
29
+ >();
30
+
31
+ const DETECTOR_SAFE_OPTION_KEYS: string[] = [
32
+ 'lowerCaseLng',
33
+ 'nonExplicitSupportedLngs',
34
+ 'load',
35
+ 'partialBundledLanguages',
36
+ 'returnNull',
37
+ 'returnEmptyString',
38
+ 'returnObjects',
39
+ 'joinArrays',
40
+ 'keySeparator',
41
+ 'nsSeparator',
42
+ 'pluralSeparator',
43
+ 'contextSeparator',
44
+ 'fallbackNS',
45
+ 'ns',
46
+ 'defaultNS',
47
+ 'debug',
48
+ ];
49
+
50
+ /**
51
+ * Stable stringify that sorts object keys to ensure consistent output
52
+ * regardless of property order
53
+ */
54
+ const stableStringify = (value: unknown): string => {
55
+ if (value === null || value === undefined) {
56
+ return JSON.stringify(value);
57
+ }
58
+
59
+ if (typeof value !== 'object') {
60
+ return JSON.stringify(value);
61
+ }
62
+
63
+ if (Array.isArray(value)) {
64
+ // Arrays maintain their order
65
+ return `[${value.map(item => stableStringify(item)).join(',')}]`;
66
+ }
67
+
68
+ // For objects, sort keys and recursively stringify values
69
+ const record = value as Record<string, unknown>;
70
+ const sortedKeys = Object.keys(record).sort();
71
+ const sortedEntries = sortedKeys.map(key => {
72
+ const stringifiedValue = stableStringify(record[key]);
73
+ return `${JSON.stringify(key)}:${stringifiedValue}`;
74
+ });
75
+
76
+ return `{${sortedEntries.join(',')}}`;
77
+ };
78
+
79
+ export const buildDetectorConfigKey = (
80
+ languages: string[],
81
+ fallbackLanguage: string,
82
+ mergedDetection: LanguageDetectorOptions,
83
+ ): string => {
84
+ return stableStringify({
85
+ languages,
86
+ fallbackLanguage,
87
+ detection: mergedDetection,
88
+ });
89
+ };
90
+
91
+ export const pickSafeDetectionOptions = (
92
+ userInitOptions?: I18nInitOptions,
93
+ ): Partial<I18nInitOptions> & Record<string, unknown> => {
94
+ if (!userInitOptions) {
95
+ return {};
96
+ }
97
+ const safeOptions: Partial<I18nInitOptions> & Record<string, unknown> = {};
98
+ for (const key of DETECTOR_SAFE_OPTION_KEYS) {
99
+ const value = (userInitOptions as Record<string, unknown>)[key];
100
+ if (value !== undefined) {
101
+ safeOptions[key] = value;
102
+ }
103
+ }
104
+ if (userInitOptions.interpolation) {
105
+ safeOptions.interpolation = { ...userInitOptions.interpolation };
106
+ }
107
+ return safeOptions;
108
+ };
109
+
110
+ const cleanupDetectorCacheEntry = (entry?: DetectorCacheEntry) => {
111
+ if (!entry || !entry.isTemporary) {
112
+ return;
113
+ }
114
+ const instance = entry.instance as DetectorCleanupInstance;
115
+ try {
116
+ instance?.removeAllListeners?.();
117
+ } catch (error) {
118
+ void error;
119
+ }
120
+ try {
121
+ instance?.off?.('*');
122
+ } catch (error) {
123
+ void error;
124
+ }
125
+ try {
126
+ instance?.services?.backendConnector?.backend?.stop?.();
127
+ } catch (error) {
128
+ void error;
129
+ }
130
+ try {
131
+ instance?.services?.backendConnector?.backend?.close?.();
132
+ } catch (error) {
133
+ void error;
134
+ }
135
+ };
136
+
137
+ export const createDetectorInstance = (
138
+ baseInstance: I18nInstance,
139
+ configKey: string,
140
+ ): { instance: I18nInstance; isTemporary: boolean } => {
141
+ const cached = detectorInstanceCache.get(baseInstance);
142
+ if (cached && cached.configKey === configKey) {
143
+ return { instance: cached.instance, isTemporary: cached.isTemporary };
144
+ }
145
+
146
+ if (cached) {
147
+ cleanupDetectorCacheEntry(cached);
148
+ detectorInstanceCache.delete(baseInstance);
149
+ }
150
+
151
+ const createNewInstance = (): {
152
+ instance: I18nInstance;
153
+ isTemporary: boolean;
154
+ } => {
155
+ if (typeof baseInstance.createInstance === 'function') {
156
+ try {
157
+ const created = baseInstance.createInstance();
158
+ if (created) {
159
+ return { instance: created, isTemporary: true };
160
+ }
161
+ } catch (error) {
162
+ void error;
163
+ }
164
+ }
165
+
166
+ if (typeof baseInstance.cloneInstance === 'function') {
167
+ try {
168
+ const cloned = baseInstance.cloneInstance();
169
+ if (cloned) {
170
+ return { instance: cloned, isTemporary: true };
171
+ }
172
+ } catch (error) {
173
+ void error;
174
+ }
175
+ }
176
+
177
+ return { instance: baseInstance, isTemporary: false };
178
+ };
179
+
180
+ const created = createNewInstance();
181
+ if (created.isTemporary) {
182
+ detectorInstanceCache.set(baseInstance, {
183
+ instance: created.instance,
184
+ isTemporary: true,
185
+ configKey,
186
+ });
187
+ }
188
+ return created;
189
+ };
@@ -0,0 +1,166 @@
1
+ import { isBrowser } from '@modern-js/runtime';
2
+ import type { I18nInitOptions, I18nInstance } from '../instance';
3
+
4
+ import {
5
+ buildDetectorConfigKey,
6
+ createDetectorInstance,
7
+ detectorInstanceCache,
8
+ pickSafeDetectionOptions,
9
+ } from './cache';
10
+
11
+ import { mergeDetectionOptions } from './initOptions';
12
+
13
+ import { getSupportedLanguage, isLanguageSupported } from './language';
14
+
15
+ import { detectLanguage, useI18nextLanguageDetector } from './middleware';
16
+
17
+ import type {
18
+ BaseLanguageDetectionOptions,
19
+ LanguageDetectionSsrContext,
20
+ } from './types';
21
+
22
+ /**
23
+ * Initialize i18n instance for detector if needed
24
+ */
25
+ interface DetectorInitResult {
26
+ detectorInstance: I18nInstance;
27
+ isTemporary: boolean;
28
+ }
29
+
30
+ type MutableDetectorInstanceState = Omit<I18nInstance, 'language'> & {
31
+ language?: string;
32
+ };
33
+
34
+ const initializeI18nForDetector = async (
35
+ i18nInstance: I18nInstance,
36
+ options: BaseLanguageDetectionOptions,
37
+ ): Promise<DetectorInitResult> => {
38
+ const mergedDetection = mergeDetectionOptions(
39
+ options.i18nextDetector,
40
+ options.detection,
41
+ options.localePathRedirect,
42
+ options.userInitOptions,
43
+ );
44
+
45
+ const configKey = buildDetectorConfigKey(
46
+ options.languages,
47
+ options.fallbackLanguage,
48
+ mergedDetection,
49
+ );
50
+
51
+ const { instance, isTemporary } = createDetectorInstance(
52
+ i18nInstance,
53
+ configKey,
54
+ );
55
+
56
+ const safeUserOptions = pickSafeDetectionOptions(options.userInitOptions);
57
+
58
+ // Only initialize detection capability, don't load resources to avoid conflicts with subsequent backend initialization
59
+ const initOptions: I18nInitOptions = {
60
+ ...safeUserOptions,
61
+ fallbackLng: options.fallbackLanguage,
62
+ supportedLngs: options.languages,
63
+ detection: mergedDetection,
64
+ initImmediate: true,
65
+ interpolation: {
66
+ ...(safeUserOptions?.interpolation || {}),
67
+ escapeValue: safeUserOptions?.interpolation?.escapeValue ?? false,
68
+ },
69
+ react: {
70
+ useSuspense: false,
71
+ },
72
+ };
73
+
74
+ // Ensure the detector instance has the language detection plugin loaded
75
+ useI18nextLanguageDetector(instance);
76
+
77
+ if (!instance.isInitialized) {
78
+ await instance.init(initOptions);
79
+ } else if (isTemporary) {
80
+ await instance.init(initOptions);
81
+ }
82
+
83
+ return { detectorInstance: instance, isTemporary };
84
+ };
85
+
86
+ /**
87
+ * Priority 3: Detect language using i18next detector
88
+ */
89
+ export const detectLanguageFromI18nextDetector = async (
90
+ i18nInstance: I18nInstance,
91
+ options: BaseLanguageDetectionOptions & {
92
+ ssrContext?: LanguageDetectionSsrContext;
93
+ },
94
+ ): Promise<string | undefined> => {
95
+ if (!options.i18nextDetector) {
96
+ return undefined;
97
+ }
98
+
99
+ // Merge detection options to pass to detector
100
+ const mergedDetection = mergeDetectionOptions(
101
+ options.i18nextDetector,
102
+ options.detection,
103
+ options.localePathRedirect,
104
+ options.userInitOptions,
105
+ );
106
+
107
+ const { detectorInstance, isTemporary } = await initializeI18nForDetector(
108
+ i18nInstance,
109
+ options,
110
+ );
111
+
112
+ try {
113
+ const request = options.ssrContext?.request;
114
+ if (!isBrowser() && !request) {
115
+ return undefined;
116
+ }
117
+
118
+ const detectorLang = detectLanguage(
119
+ detectorInstance,
120
+ request,
121
+ mergedDetection,
122
+ );
123
+
124
+ // Use getSupportedLanguage to get the matching supported language
125
+ // This handles both exact match and base language code match (e.g., 'zh-CN' -> 'zh')
126
+ if (detectorLang) {
127
+ const supportedLang = getSupportedLanguage(
128
+ detectorLang,
129
+ options.languages,
130
+ );
131
+ if (supportedLang) {
132
+ return supportedLang;
133
+ }
134
+ }
135
+
136
+ // Fallback to instance's current language if detector didn't detect
137
+ if (detectorInstance.isInitialized && detectorInstance.language) {
138
+ const currentLang = detectorInstance.language;
139
+ if (isLanguageSupported(currentLang, options.languages)) {
140
+ return currentLang;
141
+ }
142
+ }
143
+ } catch (error) {
144
+ // Silently ignore errors
145
+ } finally {
146
+ // Clean up temporary instance to avoid affecting subsequent formal initialization
147
+ if (isTemporary && detectorInstance !== i18nInstance) {
148
+ // Temporary instance is saved in cache for reuse
149
+ detectorInstanceCache.set(i18nInstance, {
150
+ instance: detectorInstance,
151
+ isTemporary: true,
152
+ configKey: buildDetectorConfigKey(
153
+ options.languages,
154
+ options.fallbackLanguage,
155
+ mergedDetection,
156
+ ),
157
+ });
158
+ } else if (detectorInstance === i18nInstance) {
159
+ // As a fallback, prevent i18nInstance from being polluted by detector init
160
+ i18nInstance.isInitialized = false;
161
+ delete (i18nInstance as unknown as MutableDetectorInstanceState).language;
162
+ }
163
+ }
164
+
165
+ return undefined;
166
+ };