@modern-js/plugin-i18n 2.69.7 → 3.0.0-alpha.1

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 (151) hide show
  1. package/README.md +6 -0
  2. package/dist/cjs/cli/index.cjs +154 -0
  3. package/dist/cjs/runtime/I18nLink.cjs +68 -0
  4. package/dist/cjs/runtime/context.cjs +138 -0
  5. package/dist/cjs/runtime/hooks.cjs +189 -0
  6. package/dist/cjs/runtime/i18n/backend/config.cjs +39 -0
  7. package/dist/cjs/runtime/i18n/backend/defaults.cjs +56 -0
  8. package/dist/cjs/runtime/i18n/backend/defaults.node.cjs +56 -0
  9. package/dist/cjs/runtime/i18n/backend/index.cjs +108 -0
  10. package/dist/cjs/runtime/i18n/backend/middleware.cjs +54 -0
  11. package/dist/cjs/runtime/i18n/backend/middleware.common.cjs +105 -0
  12. package/dist/cjs/runtime/i18n/backend/middleware.node.cjs +58 -0
  13. package/dist/cjs/runtime/i18n/backend/sdk-backend.cjs +171 -0
  14. package/dist/cjs/runtime/i18n/detection/config.cjs +63 -0
  15. package/dist/cjs/runtime/i18n/detection/index.cjs +309 -0
  16. package/dist/cjs/runtime/i18n/detection/middleware.cjs +185 -0
  17. package/dist/cjs/runtime/i18n/detection/middleware.node.cjs +74 -0
  18. package/dist/cjs/runtime/i18n/index.cjs +43 -0
  19. package/dist/cjs/runtime/i18n/instance.cjs +132 -0
  20. package/dist/cjs/runtime/i18n/utils.cjs +185 -0
  21. package/dist/cjs/runtime/index.cjs +172 -0
  22. package/dist/cjs/runtime/types.cjs +18 -0
  23. package/dist/cjs/runtime/utils.cjs +134 -0
  24. package/dist/cjs/server/index.cjs +178 -0
  25. package/dist/cjs/shared/deepMerge.cjs +54 -0
  26. package/dist/cjs/shared/detection.cjs +105 -0
  27. package/dist/cjs/shared/type.cjs +18 -0
  28. package/dist/cjs/shared/utils.cjs +78 -0
  29. package/dist/esm/cli/index.js +106 -0
  30. package/dist/esm/runtime/I18nLink.js +31 -0
  31. package/dist/esm/runtime/context.js +101 -0
  32. package/dist/esm/runtime/hooks.js +146 -0
  33. package/dist/esm/runtime/i18n/backend/config.js +5 -0
  34. package/dist/esm/runtime/i18n/backend/defaults.js +19 -0
  35. package/dist/esm/runtime/i18n/backend/defaults.node.js +19 -0
  36. package/dist/esm/runtime/i18n/backend/index.js +74 -0
  37. package/dist/esm/runtime/i18n/backend/middleware.common.js +61 -0
  38. package/dist/esm/runtime/i18n/backend/middleware.js +7 -0
  39. package/dist/esm/runtime/i18n/backend/middleware.node.js +8 -0
  40. package/dist/esm/runtime/i18n/backend/sdk-backend.js +137 -0
  41. package/dist/esm/runtime/i18n/detection/config.js +26 -0
  42. package/dist/esm/runtime/i18n/detection/index.js +260 -0
  43. package/dist/esm/runtime/i18n/detection/middleware.js +132 -0
  44. package/dist/esm/runtime/i18n/detection/middleware.node.js +31 -0
  45. package/dist/esm/runtime/i18n/index.js +3 -0
  46. package/dist/esm/runtime/i18n/instance.js +77 -0
  47. package/dist/esm/runtime/i18n/utils.js +136 -0
  48. package/dist/esm/runtime/index.js +129 -0
  49. package/dist/esm/runtime/types.js +0 -0
  50. package/dist/esm/runtime/utils.js +82 -0
  51. package/dist/esm/server/index.js +168 -0
  52. package/dist/esm/shared/deepMerge.js +20 -0
  53. package/dist/esm/shared/detection.js +71 -0
  54. package/dist/esm/shared/type.js +0 -0
  55. package/dist/esm/shared/utils.js +35 -0
  56. package/dist/esm-node/cli/index.js +106 -0
  57. package/dist/esm-node/runtime/I18nLink.js +31 -0
  58. package/dist/esm-node/runtime/context.js +101 -0
  59. package/dist/esm-node/runtime/hooks.js +146 -0
  60. package/dist/esm-node/runtime/i18n/backend/config.js +5 -0
  61. package/dist/esm-node/runtime/i18n/backend/defaults.js +19 -0
  62. package/dist/esm-node/runtime/i18n/backend/defaults.node.js +19 -0
  63. package/dist/esm-node/runtime/i18n/backend/index.js +74 -0
  64. package/dist/esm-node/runtime/i18n/backend/middleware.common.js +61 -0
  65. package/dist/esm-node/runtime/i18n/backend/middleware.js +7 -0
  66. package/dist/esm-node/runtime/i18n/backend/middleware.node.js +8 -0
  67. package/dist/esm-node/runtime/i18n/backend/sdk-backend.js +137 -0
  68. package/dist/esm-node/runtime/i18n/detection/config.js +26 -0
  69. package/dist/esm-node/runtime/i18n/detection/index.js +260 -0
  70. package/dist/esm-node/runtime/i18n/detection/middleware.js +132 -0
  71. package/dist/esm-node/runtime/i18n/detection/middleware.node.js +31 -0
  72. package/dist/esm-node/runtime/i18n/index.js +3 -0
  73. package/dist/esm-node/runtime/i18n/instance.js +77 -0
  74. package/dist/esm-node/runtime/i18n/utils.js +136 -0
  75. package/dist/esm-node/runtime/index.js +129 -0
  76. package/dist/esm-node/runtime/types.js +0 -0
  77. package/dist/esm-node/runtime/utils.js +82 -0
  78. package/dist/esm-node/server/index.js +168 -0
  79. package/dist/esm-node/shared/deepMerge.js +20 -0
  80. package/dist/esm-node/shared/detection.js +71 -0
  81. package/dist/esm-node/shared/type.js +0 -0
  82. package/dist/esm-node/shared/utils.js +35 -0
  83. package/dist/types/cli/index.d.ts +21 -0
  84. package/dist/types/runtime/I18nLink.d.ts +8 -0
  85. package/dist/types/runtime/context.d.ts +38 -0
  86. package/dist/types/runtime/hooks.d.ts +28 -0
  87. package/dist/types/runtime/i18n/backend/config.d.ts +2 -0
  88. package/dist/types/runtime/i18n/backend/defaults.d.ts +13 -0
  89. package/dist/types/runtime/i18n/backend/defaults.node.d.ts +8 -0
  90. package/dist/types/runtime/i18n/backend/index.d.ts +3 -0
  91. package/dist/types/runtime/i18n/backend/middleware.common.d.ts +14 -0
  92. package/dist/types/runtime/i18n/backend/middleware.d.ts +12 -0
  93. package/dist/types/runtime/i18n/backend/middleware.node.d.ts +13 -0
  94. package/dist/types/runtime/i18n/backend/sdk-backend.d.ts +52 -0
  95. package/dist/types/runtime/i18n/detection/config.d.ts +11 -0
  96. package/dist/types/runtime/i18n/detection/index.d.ts +50 -0
  97. package/dist/types/runtime/i18n/detection/middleware.d.ts +24 -0
  98. package/dist/types/runtime/i18n/detection/middleware.node.d.ts +17 -0
  99. package/dist/types/runtime/i18n/index.d.ts +3 -0
  100. package/dist/types/runtime/i18n/instance.d.ts +93 -0
  101. package/dist/types/runtime/i18n/utils.d.ts +29 -0
  102. package/dist/types/runtime/index.d.ts +20 -0
  103. package/dist/types/runtime/types.d.ts +15 -0
  104. package/dist/types/runtime/utils.d.ts +33 -0
  105. package/dist/types/server/index.d.ts +8 -0
  106. package/dist/types/shared/deepMerge.d.ts +1 -0
  107. package/dist/types/shared/detection.d.ts +11 -0
  108. package/dist/types/shared/type.d.ts +156 -0
  109. package/dist/types/shared/utils.d.ts +5 -0
  110. package/package.json +100 -34
  111. package/rslib.config.mts +4 -0
  112. package/src/cli/index.ts +245 -0
  113. package/src/runtime/I18nLink.tsx +76 -0
  114. package/src/runtime/context.tsx +256 -0
  115. package/src/runtime/hooks.ts +274 -0
  116. package/src/runtime/i18n/backend/config.ts +10 -0
  117. package/src/runtime/i18n/backend/defaults.node.ts +31 -0
  118. package/src/runtime/i18n/backend/defaults.ts +37 -0
  119. package/src/runtime/i18n/backend/index.ts +181 -0
  120. package/src/runtime/i18n/backend/middleware.common.ts +116 -0
  121. package/src/runtime/i18n/backend/middleware.node.ts +32 -0
  122. package/src/runtime/i18n/backend/middleware.ts +28 -0
  123. package/src/runtime/i18n/backend/sdk-backend.ts +292 -0
  124. package/src/runtime/i18n/detection/config.ts +32 -0
  125. package/src/runtime/i18n/detection/index.ts +641 -0
  126. package/src/runtime/i18n/detection/middleware.node.ts +84 -0
  127. package/src/runtime/i18n/detection/middleware.ts +251 -0
  128. package/src/runtime/i18n/index.ts +8 -0
  129. package/src/runtime/i18n/instance.ts +227 -0
  130. package/src/runtime/i18n/utils.ts +333 -0
  131. package/src/runtime/index.tsx +281 -0
  132. package/src/runtime/types.ts +17 -0
  133. package/src/runtime/utils.ts +151 -0
  134. package/src/server/index.ts +336 -0
  135. package/src/shared/deepMerge.ts +38 -0
  136. package/src/shared/detection.ts +131 -0
  137. package/src/shared/type.ts +170 -0
  138. package/src/shared/utils.ts +82 -0
  139. package/tsconfig.json +12 -0
  140. package/dist/cjs/index.js +0 -73
  141. package/dist/cjs/languageDetector.js +0 -51
  142. package/dist/cjs/utils/index.js +0 -39
  143. package/dist/esm/index.js +0 -61
  144. package/dist/esm/languageDetector.js +0 -33
  145. package/dist/esm/utils/index.js +0 -16
  146. package/dist/esm-node/index.js +0 -49
  147. package/dist/esm-node/languageDetector.js +0 -26
  148. package/dist/esm-node/utils/index.js +0 -15
  149. package/dist/types/index.d.ts +0 -34
  150. package/dist/types/languageDetector.d.ts +0 -6
  151. package/dist/types/utils/index.d.ts +0 -5
@@ -0,0 +1,146 @@
1
+ import { isBrowser } from "@modern-js/runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { cacheUserLanguage } from "./i18n/detection";
4
+ import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, getPathname, shouldIgnoreRedirect, useRouterHooks } from "./utils";
5
+ function createMinimalI18nInstance(language) {
6
+ const minimalInstance = {
7
+ language,
8
+ isInitialized: false,
9
+ init: ()=>Promise.resolve(void 0),
10
+ use: ()=>{},
11
+ createInstance: ()=>minimalInstance,
12
+ services: {}
13
+ };
14
+ return minimalInstance;
15
+ }
16
+ function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, setLang) {
17
+ const instance = i18nInstance || createMinimalI18nInstance(lang);
18
+ return {
19
+ language: lang,
20
+ i18nInstance: instance,
21
+ entryName,
22
+ languages,
23
+ localePathRedirect,
24
+ ignoreRedirectRoutes,
25
+ updateLanguage: setLang
26
+ };
27
+ }
28
+ function useSdkResourcesLoader(i18nInstance, setForceUpdate) {
29
+ useEffect(()=>{
30
+ if (!i18nInstance || !isBrowser()) return;
31
+ const handleSdkResourcesLoaded = (event)=>{
32
+ const customEvent = event;
33
+ const { language, namespace } = customEvent.detail;
34
+ const triggerUpdate = (retryCount = 0)=>{
35
+ const store = i18nInstance.store;
36
+ const hasResource = store?.data?.[language]?.[namespace];
37
+ if (hasResource || retryCount >= 10) {
38
+ if (store?.data?.[language]?.[namespace]) {
39
+ if ('function' == typeof store.emit) store.emit('added', language, namespace);
40
+ }
41
+ if ('function' == typeof i18nInstance.emit) {
42
+ i18nInstance.emit('loaded', {
43
+ language,
44
+ namespace
45
+ });
46
+ i18nInstance.emit('loaded', language, namespace);
47
+ }
48
+ if ('function' == typeof i18nInstance.reloadResources) i18nInstance.reloadResources(language, namespace).then(()=>{
49
+ if ('function' == typeof i18nInstance.emit) i18nInstance.emit('loaded', {
50
+ language,
51
+ namespace
52
+ });
53
+ setForceUpdate((prev)=>prev + 1);
54
+ }).catch(()=>{});
55
+ if ('function' == typeof i18nInstance.emit) i18nInstance.emit('languageChanged', language);
56
+ setForceUpdate((prev)=>prev + 1);
57
+ } else setTimeout(()=>triggerUpdate(retryCount + 1), 10);
58
+ };
59
+ triggerUpdate();
60
+ };
61
+ window.addEventListener('i18n-sdk-resources-loaded', handleSdkResourcesLoaded);
62
+ return ()=>{
63
+ window.removeEventListener('i18n-sdk-resources-loaded', handleSdkResourcesLoaded);
64
+ };
65
+ }, [
66
+ i18nInstance,
67
+ setForceUpdate
68
+ ]);
69
+ }
70
+ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes) {
71
+ const hasRedirectedRef = useRef(false);
72
+ const { navigate, location, hasRouter } = useRouterHooks();
73
+ useEffect(()=>{
74
+ if ('browser' !== process.env.MODERN_TARGET) return;
75
+ if (!localePathRedirect || !i18nInstance) return;
76
+ try {
77
+ const ssrData = window._SSR_DATA;
78
+ if (ssrData) return;
79
+ } catch {}
80
+ if (hasRedirectedRef.current) return;
81
+ if (!i18nInstance.isInitialized) return;
82
+ const currentPathname = hasRouter && location ? location.pathname : window.location.pathname;
83
+ const currentSearch = hasRouter && location ? location.search : window.location.search;
84
+ const currentHash = hasRouter && location ? location.hash : window.location.hash;
85
+ const entryPath = getEntryPath();
86
+ const relativePath = currentPathname.replace(entryPath, '');
87
+ if (shouldIgnoreRedirect(relativePath, languages, ignoreRedirectRoutes)) return;
88
+ const pathDetection = detectLanguageFromPath(currentPathname, languages, localePathRedirect);
89
+ if (pathDetection.detected) return;
90
+ const targetLanguage = i18nInstance.language || fallbackLanguage || languages[0] || 'en';
91
+ const newPath = buildLocalizedUrl(relativePath, targetLanguage, languages);
92
+ const newUrl = entryPath + newPath + currentSearch + currentHash;
93
+ if (newUrl !== currentPathname + currentSearch + currentHash) {
94
+ hasRedirectedRef.current = true;
95
+ if (hasRouter && navigate && location) navigate(newUrl, {
96
+ replace: true
97
+ });
98
+ else window.location.replace(newUrl);
99
+ }
100
+ }, [
101
+ navigate,
102
+ location,
103
+ hasRouter,
104
+ localePathRedirect,
105
+ i18nInstance,
106
+ languages,
107
+ fallbackLanguage,
108
+ ignoreRedirectRoutes
109
+ ]);
110
+ }
111
+ function useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang) {
112
+ useEffect(()=>{
113
+ if (!i18nInstance) return;
114
+ if (localePathRedirect) {
115
+ const currentPathname = getPathname(runtimeContextRef.current);
116
+ const pathDetection = detectLanguageFromPath(currentPathname, languages, localePathRedirect);
117
+ if (pathDetection.detected && pathDetection.language) {
118
+ const currentLang = pathDetection.language;
119
+ if (currentLang !== prevLangRef.current) {
120
+ prevLangRef.current = currentLang;
121
+ setLang(currentLang);
122
+ i18nInstance.setLang?.(currentLang);
123
+ i18nInstance.changeLanguage?.(currentLang);
124
+ if (isBrowser()) {
125
+ const detectionOptions = i18nInstance.options?.detection;
126
+ cacheUserLanguage(i18nInstance, currentLang, detectionOptions);
127
+ }
128
+ }
129
+ }
130
+ } else {
131
+ const instanceLang = i18nInstance.language;
132
+ if (instanceLang && instanceLang !== prevLangRef.current) {
133
+ prevLangRef.current = instanceLang;
134
+ setLang(instanceLang);
135
+ }
136
+ }
137
+ }, [
138
+ i18nInstance,
139
+ localePathRedirect,
140
+ languages,
141
+ runtimeContextRef,
142
+ prevLangRef,
143
+ setLang
144
+ ]);
145
+ }
146
+ export { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader };
@@ -0,0 +1,5 @@
1
+ import { deepMerge } from "../../../shared/deepMerge";
2
+ function mergeBackendOptions(defaultOptions, cliOptions = {}, userOptions) {
3
+ return deepMerge(deepMerge(defaultOptions, cliOptions), userOptions ?? {});
4
+ }
5
+ export { mergeBackendOptions };
@@ -0,0 +1,19 @@
1
+ const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
2
+ loadPath: '/locales/{{lng}}/{{ns}}.json',
3
+ addPath: '/locales/{{lng}}/{{ns}}.json'
4
+ };
5
+ function convertPath(path) {
6
+ if (!path) return path;
7
+ if (path.startsWith('/')) return `${window.__assetPrefix__ || ''}${path}`;
8
+ return path;
9
+ }
10
+ function convertBackendOptions(options) {
11
+ if (!options) return options;
12
+ const converted = {
13
+ ...options
14
+ };
15
+ if (converted.loadPath) converted.loadPath = convertPath(converted.loadPath);
16
+ if (converted.addPath) converted.addPath = convertPath(converted.addPath);
17
+ return converted;
18
+ }
19
+ export { DEFAULT_I18NEXT_BACKEND_OPTIONS, convertBackendOptions };
@@ -0,0 +1,19 @@
1
+ const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
2
+ loadPath: './locales/{{lng}}/{{ns}}.json',
3
+ addPath: './locales/{{lng}}/{{ns}}.json'
4
+ };
5
+ function convertPath(path) {
6
+ if (!path) return path;
7
+ if (path.startsWith('/')) return `.${path}`;
8
+ return path;
9
+ }
10
+ function convertBackendOptions(options) {
11
+ if (!options) return options;
12
+ const converted = {
13
+ ...options
14
+ };
15
+ if (converted.loadPath) converted.loadPath = convertPath(converted.loadPath);
16
+ if (converted.addPath) converted.addPath = convertPath(converted.addPath);
17
+ return converted;
18
+ }
19
+ export { DEFAULT_I18NEXT_BACKEND_OPTIONS, convertBackendOptions };
@@ -0,0 +1,74 @@
1
+ import { mergeBackendOptions } from "./config";
2
+ import { DEFAULT_I18NEXT_BACKEND_OPTIONS, convertBackendOptions } from "./defaults";
3
+ function hasSdkFunction(backend, userInitOptions) {
4
+ return 'function' == typeof userInitOptions?.backend?.sdk || !!backend?.enabled && !!backend?.sdk && 'function' == typeof backend.sdk;
5
+ }
6
+ function hasLoadPath(backend, userInitOptions) {
7
+ const userLoadPath = userInitOptions?.backend?.loadPath ?? backend?.loadPath;
8
+ const isExplicit = !!userLoadPath && '' !== userLoadPath;
9
+ const hasPath = isExplicit || !!backend?.enabled && void 0 === userLoadPath;
10
+ return {
11
+ hasPath,
12
+ isExplicit
13
+ };
14
+ }
15
+ function ensureDefaultLoadPath(merged, backend, isExplicitLoadPath = false) {
16
+ if (backend?.enabled && !isExplicitLoadPath && !merged.loadPath) {
17
+ merged.loadPath = DEFAULT_I18NEXT_BACKEND_OPTIONS.loadPath;
18
+ merged.addPath = DEFAULT_I18NEXT_BACKEND_OPTIONS.addPath;
19
+ }
20
+ }
21
+ function getFinalLoadPath(mergedOptions, backend, userInitOptions) {
22
+ return mergedOptions?.loadPath || userInitOptions?.backend?.loadPath || (backend?.enabled ? DEFAULT_I18NEXT_BACKEND_OPTIONS.loadPath : void 0);
23
+ }
24
+ function getFinalSdk(mergedOptions, backend, userInitOptions) {
25
+ return mergedOptions?.sdk || userInitOptions?.backend?.sdk || (backend?.sdk && 'function' == typeof backend.sdk ? backend.sdk : void 0);
26
+ }
27
+ function buildChainedBackendConfig(backend, userInitOptions) {
28
+ const merged = mergeBackendOptions(DEFAULT_I18NEXT_BACKEND_OPTIONS, backend, userInitOptions?.backend);
29
+ const { isExplicit } = hasLoadPath(backend, userInitOptions);
30
+ ensureDefaultLoadPath(merged, backend, isExplicit);
31
+ const mergedOptions = convertBackendOptions(merged);
32
+ const finalLoadPath = getFinalLoadPath(mergedOptions, backend, userInitOptions);
33
+ const finalSdk = getFinalSdk(mergedOptions, backend, userInitOptions);
34
+ const chainedBackendOptions = [
35
+ {
36
+ loadPath: finalLoadPath,
37
+ addPath: mergedOptions?.addPath || DEFAULT_I18NEXT_BACKEND_OPTIONS.addPath
38
+ },
39
+ {
40
+ sdk: finalSdk
41
+ }
42
+ ];
43
+ return {
44
+ ...mergedOptions,
45
+ loadPath: finalLoadPath,
46
+ sdk: finalSdk,
47
+ cacheHitMode: mergedOptions?.cacheHitMode || backend?.cacheHitMode || userInitOptions?.backend?.cacheHitMode || 'refreshAndUpdateStore',
48
+ _useChainedBackend: true,
49
+ _chainedBackendConfig: {
50
+ backendOptions: chainedBackendOptions
51
+ }
52
+ };
53
+ }
54
+ function buildSdkOnlyBackendConfig(backend, userInitOptions) {
55
+ const merged = mergeBackendOptions({}, backend, userInitOptions?.backend);
56
+ return convertBackendOptions(merged) || {};
57
+ }
58
+ function buildHttpFsBackendConfig(backend, userInitOptions) {
59
+ if (!backend?.enabled && !userInitOptions?.backend) return;
60
+ const mergedBackend = backend?.enabled ? mergeBackendOptions(DEFAULT_I18NEXT_BACKEND_OPTIONS, backend, userInitOptions?.backend) : userInitOptions?.backend;
61
+ if (mergedBackend) {
62
+ const { isExplicit } = hasLoadPath(backend, userInitOptions);
63
+ ensureDefaultLoadPath(mergedBackend, backend, isExplicit);
64
+ }
65
+ return convertBackendOptions(mergedBackend) || {};
66
+ }
67
+ const backend_mergeBackendOptions = (backend, userInitOptions)=>{
68
+ const sdkFunction = hasSdkFunction(backend, userInitOptions);
69
+ const { hasPath } = hasLoadPath(backend, userInitOptions);
70
+ if (hasPath && sdkFunction) return buildChainedBackendConfig(backend, userInitOptions);
71
+ if (sdkFunction) return buildSdkOnlyBackendConfig(backend, userInitOptions);
72
+ return buildHttpFsBackendConfig(backend, userInitOptions);
73
+ };
74
+ export { backend_mergeBackendOptions as mergeBackendOptions };
@@ -0,0 +1,61 @@
1
+ import i18next_chained_backend from "i18next-chained-backend";
2
+ import { getActualI18nextInstance } from "../instance";
3
+ import { SdkBackend } from "./sdk-backend";
4
+ function checkBackendConfig(backend) {
5
+ const hasSdk = backend?.sdk && 'function' == typeof backend.sdk;
6
+ const hasLoadPath = !!backend?.loadPath;
7
+ const useChained = backend?._useChainedBackend;
8
+ return {
9
+ hasSdk,
10
+ hasLoadPath,
11
+ useChained
12
+ };
13
+ }
14
+ function buildChainedBackendConfig(backend, BackendWithSave) {
15
+ const cacheHitMode = backend.cacheHitMode || 'refreshAndUpdateStore';
16
+ if (backend._chainedBackendConfig) return {
17
+ backends: [
18
+ BackendWithSave,
19
+ SdkBackend
20
+ ],
21
+ backendOptions: backend._chainedBackendConfig.backendOptions,
22
+ cacheHitMode
23
+ };
24
+ return {
25
+ backends: [
26
+ BackendWithSave,
27
+ SdkBackend
28
+ ],
29
+ backendOptions: [
30
+ {
31
+ loadPath: backend.loadPath,
32
+ addPath: backend.addPath
33
+ },
34
+ {
35
+ sdk: backend.sdk
36
+ }
37
+ ],
38
+ cacheHitMode
39
+ };
40
+ }
41
+ function setupChainedBackend(i18nInstance, backend, BackendWithSave) {
42
+ i18nInstance.use(i18next_chained_backend);
43
+ const actualInstance = getActualI18nextInstance(i18nInstance);
44
+ if (actualInstance?.options) actualInstance.options.backend = buildChainedBackendConfig(backend, BackendWithSave);
45
+ if (i18nInstance.options) i18nInstance.options.backend = buildChainedBackendConfig(backend, BackendWithSave);
46
+ }
47
+ function cleanBackendConfig(backend) {
48
+ const { _useChainedBackend, _chainedBackendConfig, ...cleanBackend } = backend;
49
+ return cleanBackend;
50
+ }
51
+ function useI18nextBackendCommon(i18nInstance, BackendWithSave, BackendBase, backend) {
52
+ if (!backend) return i18nInstance.use(BackendBase);
53
+ const { hasSdk, hasLoadPath, useChained } = checkBackendConfig(backend);
54
+ if (useChained || hasLoadPath && hasSdk) return void setupChainedBackend(i18nInstance, backend, BackendWithSave);
55
+ if (hasSdk) return i18nInstance.use(SdkBackend);
56
+ const actualInstance = getActualI18nextInstance(i18nInstance);
57
+ if (actualInstance?.options) actualInstance.options.backend = cleanBackendConfig(backend);
58
+ if (i18nInstance.options) i18nInstance.options.backend = cleanBackendConfig(backend);
59
+ return i18nInstance.use(BackendBase);
60
+ }
61
+ export { useI18nextBackendCommon };
@@ -0,0 +1,7 @@
1
+ import i18next_http_backend from "i18next-http-backend";
2
+ import { useI18nextBackendCommon } from "./middleware.common.js";
3
+ class HttpBackendWithSave extends i18next_http_backend {
4
+ save(_language, _namespace, _data) {}
5
+ }
6
+ const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, HttpBackendWithSave, i18next_http_backend, backend);
7
+ export { HttpBackendWithSave, useI18nextBackend };
@@ -0,0 +1,8 @@
1
+ import i18next_fs_backend from "i18next-fs-backend";
2
+ import { useI18nextBackendCommon } from "./middleware.common.js";
3
+ class FsBackendWithSave extends i18next_fs_backend {
4
+ save(_language, _namespace, _data) {}
5
+ }
6
+ const HttpBackendWithSave = FsBackendWithSave;
7
+ const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, FsBackendWithSave, i18next_fs_backend, backend);
8
+ export { FsBackendWithSave, HttpBackendWithSave, useI18nextBackend };
@@ -0,0 +1,137 @@
1
+ class SdkBackend {
2
+ init(services, backendOptions, _i18nextOptions) {
3
+ this.services = services;
4
+ this.sdk = backendOptions?.sdk;
5
+ if (!this.sdk) throw new Error('SdkBackend requires an SDK function to be provided in backend options');
6
+ }
7
+ read(language, namespace, callback) {
8
+ if (!this.sdk) {
9
+ console.error('[i18n] SdkBackend.read - SDK function not initialized');
10
+ callback(new Error('SDK function not initialized'), null);
11
+ return;
12
+ }
13
+ const cached = this.allResourcesCache ? this.extractFromCache(language, namespace) : null;
14
+ if (null !== cached) {
15
+ const mergedData = this.mergeWithExistingResources(language, namespace, cached);
16
+ callback(null, mergedData);
17
+ return;
18
+ }
19
+ const cacheKey = this.getCacheKey(language, namespace);
20
+ const existingPromise = this.loadingPromises.get(cacheKey);
21
+ if (existingPromise) return void this.handlePromise(existingPromise, language, namespace, callback, false);
22
+ this.loadResource(language, namespace, callback);
23
+ }
24
+ create(_languages, _namespace, _key, _fallbackValue) {}
25
+ isLoading(language, namespace) {
26
+ return this.loadingPromises.has(this.getCacheKey(language, namespace));
27
+ }
28
+ getLoadingResources() {
29
+ const loading = [];
30
+ for (const key of this.loadingPromises.keys()){
31
+ const [language, namespace] = key.split(':');
32
+ if (language && namespace) loading.push({
33
+ language,
34
+ namespace
35
+ });
36
+ }
37
+ return loading;
38
+ }
39
+ hasLoadingResources() {
40
+ return this.loadingPromises.size > 0;
41
+ }
42
+ getCacheKey(language, namespace) {
43
+ return `${language}:${namespace}`;
44
+ }
45
+ loadResource(language, namespace, callback) {
46
+ try {
47
+ const result = this.callSdk(language, namespace);
48
+ const loadPromise = result instanceof Promise ? result : Promise.resolve(result);
49
+ const cacheKey = this.getCacheKey(language, namespace);
50
+ this.loadingPromises.set(cacheKey, loadPromise);
51
+ this.handlePromise(loadPromise, language, namespace, callback, true);
52
+ } catch (error) {
53
+ callback(this.normalizeError(error), null);
54
+ }
55
+ }
56
+ handlePromise(promise, language, namespace, callback, shouldUpdateCache) {
57
+ const cacheKey = this.getCacheKey(language, namespace);
58
+ promise.then((data)=>{
59
+ const formattedData = this.formatResources(data, language, namespace);
60
+ const mergedData = this.mergeWithExistingResources(language, namespace, formattedData);
61
+ if (shouldUpdateCache) {
62
+ this.updateCache(language, namespace, mergedData);
63
+ this.loadingPromises.delete(cacheKey);
64
+ }
65
+ callback(null, mergedData);
66
+ this.triggerI18nextUpdate(language, namespace);
67
+ }).catch((error)=>{
68
+ if (shouldUpdateCache) this.loadingPromises.delete(cacheKey);
69
+ callback(this.normalizeError(error), null);
70
+ });
71
+ }
72
+ normalizeError(error) {
73
+ return error instanceof Error ? error : new Error(String(error));
74
+ }
75
+ callSdk(language, namespace) {
76
+ if (!this.sdk) throw new Error('SDK function not initialized');
77
+ const options = {
78
+ lng: language,
79
+ ns: namespace
80
+ };
81
+ return this.sdk(options);
82
+ }
83
+ extractFromCache(language, namespace) {
84
+ if (!this.allResourcesCache) return null;
85
+ const langData = this.allResourcesCache[language];
86
+ if (!this.isObject(langData)) return null;
87
+ const nsData = langData[namespace];
88
+ if (!this.isObject(nsData)) return null;
89
+ return nsData;
90
+ }
91
+ updateCache(language, namespace, data) {
92
+ if (!this.allResourcesCache) this.allResourcesCache = {};
93
+ if (!this.allResourcesCache[language]) this.allResourcesCache[language] = {};
94
+ if (this.isObject(data)) this.allResourcesCache[language][namespace] = data;
95
+ }
96
+ formatResources(data, language, namespace) {
97
+ if (!this.isObject(data)) return {};
98
+ const dataObj = data;
99
+ const langData = dataObj[language];
100
+ if (this.isObject(langData)) {
101
+ const nsData = langData[namespace];
102
+ if (this.isObject(nsData)) return nsData;
103
+ }
104
+ const hasLanguageKeys = Object.keys(dataObj).some((key)=>this.isObject(dataObj[key]));
105
+ if (!hasLanguageKeys) return dataObj;
106
+ return {};
107
+ }
108
+ isObject(value) {
109
+ return null !== value && 'object' == typeof value;
110
+ }
111
+ mergeWithExistingResources(language, namespace, sdkData) {
112
+ const store = this.services?.resourceStore || this.services?.store;
113
+ const existingData = store?.data?.[language]?.[namespace] || {};
114
+ return {
115
+ ...existingData,
116
+ ...sdkData
117
+ };
118
+ }
119
+ triggerI18nextUpdate(language, namespace) {
120
+ if ("u" > typeof window) {
121
+ const event = new CustomEvent('i18n-sdk-resources-loaded', {
122
+ detail: {
123
+ language,
124
+ namespace
125
+ }
126
+ });
127
+ window.dispatchEvent(event);
128
+ }
129
+ }
130
+ constructor(_services, _options){
131
+ this.type = 'backend';
132
+ this.allResourcesCache = null;
133
+ this.loadingPromises = new Map();
134
+ }
135
+ }
136
+ SdkBackend.type = 'backend';
137
+ export { SdkBackend };
@@ -0,0 +1,26 @@
1
+ import { deepMerge } from "../../../shared/deepMerge.js";
2
+ const DEFAULT_I18NEXT_DETECTION_OPTIONS = {
3
+ caches: [
4
+ 'cookie',
5
+ 'localStorage'
6
+ ],
7
+ order: [
8
+ 'querystring',
9
+ 'cookie',
10
+ 'localStorage',
11
+ 'header',
12
+ 'navigator',
13
+ 'htmlTag',
14
+ 'path',
15
+ 'subdomain'
16
+ ],
17
+ cookieMinutes: 525600,
18
+ lookupQuerystring: 'lng',
19
+ lookupCookie: 'i18next',
20
+ lookupLocalStorage: 'i18nextLng',
21
+ lookupHeader: 'accept-language'
22
+ };
23
+ function mergeDetectionOptions(cliOptions, userOptions, defaultOptions = DEFAULT_I18NEXT_DETECTION_OPTIONS) {
24
+ return deepMerge(deepMerge(defaultOptions, cliOptions ?? {}), userOptions ?? {});
25
+ }
26
+ export { DEFAULT_I18NEXT_DETECTION_OPTIONS, mergeDetectionOptions };