@bleedingdev/modern-js-plugin-i18n 3.2.0-ultramodern.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/dist/cjs/cli/index.js +154 -0
  4. package/dist/cjs/runtime/I18nLink.js +68 -0
  5. package/dist/cjs/runtime/context.js +142 -0
  6. package/dist/cjs/runtime/hooks.js +194 -0
  7. package/dist/cjs/runtime/i18n/backend/config.js +39 -0
  8. package/dist/cjs/runtime/i18n/backend/defaults.js +56 -0
  9. package/dist/cjs/runtime/i18n/backend/defaults.node.js +56 -0
  10. package/dist/cjs/runtime/i18n/backend/index.js +108 -0
  11. package/dist/cjs/runtime/i18n/backend/middleware.common.js +105 -0
  12. package/dist/cjs/runtime/i18n/backend/middleware.js +54 -0
  13. package/dist/cjs/runtime/i18n/backend/middleware.node.js +58 -0
  14. package/dist/cjs/runtime/i18n/backend/sdk-backend.js +175 -0
  15. package/dist/cjs/runtime/i18n/backend/sdk-event.js +64 -0
  16. package/dist/cjs/runtime/i18n/detection/config.js +63 -0
  17. package/dist/cjs/runtime/i18n/detection/index.js +309 -0
  18. package/dist/cjs/runtime/i18n/detection/middleware.js +185 -0
  19. package/dist/cjs/runtime/i18n/detection/middleware.node.js +74 -0
  20. package/dist/cjs/runtime/i18n/index.js +43 -0
  21. package/dist/cjs/runtime/i18n/instance.js +132 -0
  22. package/dist/cjs/runtime/i18n/utils.js +189 -0
  23. package/dist/cjs/runtime/index.js +174 -0
  24. package/dist/cjs/runtime/types.js +18 -0
  25. package/dist/cjs/runtime/utils.js +136 -0
  26. package/dist/cjs/server/index.js +218 -0
  27. package/dist/cjs/shared/deepMerge.js +54 -0
  28. package/dist/cjs/shared/detection.js +105 -0
  29. package/dist/cjs/shared/type.js +18 -0
  30. package/dist/cjs/shared/utils.js +78 -0
  31. package/dist/esm/cli/index.mjs +107 -0
  32. package/dist/esm/rslib-runtime.mjs +18 -0
  33. package/dist/esm/runtime/I18nLink.mjs +32 -0
  34. package/dist/esm/runtime/context.mjs +105 -0
  35. package/dist/esm/runtime/hooks.mjs +151 -0
  36. package/dist/esm/runtime/i18n/backend/config.mjs +5 -0
  37. package/dist/esm/runtime/i18n/backend/defaults.mjs +19 -0
  38. package/dist/esm/runtime/i18n/backend/defaults.node.mjs +19 -0
  39. package/dist/esm/runtime/i18n/backend/index.mjs +74 -0
  40. package/dist/esm/runtime/i18n/backend/middleware.common.mjs +61 -0
  41. package/dist/esm/runtime/i18n/backend/middleware.mjs +7 -0
  42. package/dist/esm/runtime/i18n/backend/middleware.node.mjs +8 -0
  43. package/dist/esm/runtime/i18n/backend/sdk-backend.mjs +141 -0
  44. package/dist/esm/runtime/i18n/backend/sdk-event.mjs +21 -0
  45. package/dist/esm/runtime/i18n/detection/config.mjs +26 -0
  46. package/dist/esm/runtime/i18n/detection/index.mjs +260 -0
  47. package/dist/esm/runtime/i18n/detection/middleware.mjs +132 -0
  48. package/dist/esm/runtime/i18n/detection/middleware.node.mjs +31 -0
  49. package/dist/esm/runtime/i18n/index.mjs +2 -0
  50. package/dist/esm/runtime/i18n/instance.mjs +77 -0
  51. package/dist/esm/runtime/i18n/utils.mjs +140 -0
  52. package/dist/esm/runtime/index.mjs +132 -0
  53. package/dist/esm/runtime/types.mjs +0 -0
  54. package/dist/esm/runtime/utils.mjs +75 -0
  55. package/dist/esm/server/index.mjs +182 -0
  56. package/dist/esm/shared/deepMerge.mjs +20 -0
  57. package/dist/esm/shared/detection.mjs +71 -0
  58. package/dist/esm/shared/type.mjs +0 -0
  59. package/dist/esm/shared/utils.mjs +35 -0
  60. package/dist/esm-node/cli/index.mjs +108 -0
  61. package/dist/esm-node/rslib-runtime.mjs +19 -0
  62. package/dist/esm-node/runtime/I18nLink.mjs +33 -0
  63. package/dist/esm-node/runtime/context.mjs +106 -0
  64. package/dist/esm-node/runtime/hooks.mjs +152 -0
  65. package/dist/esm-node/runtime/i18n/backend/config.mjs +6 -0
  66. package/dist/esm-node/runtime/i18n/backend/defaults.mjs +20 -0
  67. package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +20 -0
  68. package/dist/esm-node/runtime/i18n/backend/index.mjs +75 -0
  69. package/dist/esm-node/runtime/i18n/backend/middleware.common.mjs +62 -0
  70. package/dist/esm-node/runtime/i18n/backend/middleware.mjs +8 -0
  71. package/dist/esm-node/runtime/i18n/backend/middleware.node.mjs +9 -0
  72. package/dist/esm-node/runtime/i18n/backend/sdk-backend.mjs +142 -0
  73. package/dist/esm-node/runtime/i18n/backend/sdk-event.mjs +22 -0
  74. package/dist/esm-node/runtime/i18n/detection/config.mjs +27 -0
  75. package/dist/esm-node/runtime/i18n/detection/index.mjs +261 -0
  76. package/dist/esm-node/runtime/i18n/detection/middleware.mjs +133 -0
  77. package/dist/esm-node/runtime/i18n/detection/middleware.node.mjs +32 -0
  78. package/dist/esm-node/runtime/i18n/index.mjs +3 -0
  79. package/dist/esm-node/runtime/i18n/instance.mjs +78 -0
  80. package/dist/esm-node/runtime/i18n/utils.mjs +141 -0
  81. package/dist/esm-node/runtime/index.mjs +133 -0
  82. package/dist/esm-node/runtime/types.mjs +1 -0
  83. package/dist/esm-node/runtime/utils.mjs +76 -0
  84. package/dist/esm-node/server/index.mjs +183 -0
  85. package/dist/esm-node/shared/deepMerge.mjs +21 -0
  86. package/dist/esm-node/shared/detection.mjs +72 -0
  87. package/dist/esm-node/shared/type.mjs +1 -0
  88. package/dist/esm-node/shared/utils.mjs +36 -0
  89. package/dist/types/cli/index.d.ts +21 -0
  90. package/dist/types/runtime/I18nLink.d.ts +8 -0
  91. package/dist/types/runtime/context.d.ts +38 -0
  92. package/dist/types/runtime/hooks.d.ts +28 -0
  93. package/dist/types/runtime/i18n/backend/config.d.ts +2 -0
  94. package/dist/types/runtime/i18n/backend/defaults.d.ts +13 -0
  95. package/dist/types/runtime/i18n/backend/defaults.node.d.ts +8 -0
  96. package/dist/types/runtime/i18n/backend/index.d.ts +3 -0
  97. package/dist/types/runtime/i18n/backend/middleware.common.d.ts +14 -0
  98. package/dist/types/runtime/i18n/backend/middleware.d.ts +12 -0
  99. package/dist/types/runtime/i18n/backend/middleware.node.d.ts +13 -0
  100. package/dist/types/runtime/i18n/backend/sdk-backend.d.ts +53 -0
  101. package/dist/types/runtime/i18n/backend/sdk-event.d.ts +9 -0
  102. package/dist/types/runtime/i18n/detection/config.d.ts +11 -0
  103. package/dist/types/runtime/i18n/detection/index.d.ts +50 -0
  104. package/dist/types/runtime/i18n/detection/middleware.d.ts +24 -0
  105. package/dist/types/runtime/i18n/detection/middleware.node.d.ts +17 -0
  106. package/dist/types/runtime/i18n/index.d.ts +3 -0
  107. package/dist/types/runtime/i18n/instance.d.ts +93 -0
  108. package/dist/types/runtime/i18n/utils.d.ts +29 -0
  109. package/dist/types/runtime/index.d.ts +20 -0
  110. package/dist/types/runtime/types.d.ts +15 -0
  111. package/dist/types/runtime/utils.d.ts +33 -0
  112. package/dist/types/server/index.d.ts +8 -0
  113. package/dist/types/shared/deepMerge.d.ts +1 -0
  114. package/dist/types/shared/detection.d.ts +11 -0
  115. package/dist/types/shared/type.d.ts +156 -0
  116. package/dist/types/shared/utils.d.ts +5 -0
  117. package/package.json +136 -0
  118. package/rslib.config.mts +4 -0
  119. package/src/cli/index.ts +245 -0
  120. package/src/runtime/I18nLink.tsx +76 -0
  121. package/src/runtime/context.tsx +281 -0
  122. package/src/runtime/hooks.ts +298 -0
  123. package/src/runtime/i18n/backend/config.ts +10 -0
  124. package/src/runtime/i18n/backend/defaults.node.ts +31 -0
  125. package/src/runtime/i18n/backend/defaults.ts +37 -0
  126. package/src/runtime/i18n/backend/index.ts +181 -0
  127. package/src/runtime/i18n/backend/middleware.common.ts +116 -0
  128. package/src/runtime/i18n/backend/middleware.node.ts +32 -0
  129. package/src/runtime/i18n/backend/middleware.ts +28 -0
  130. package/src/runtime/i18n/backend/sdk-backend.ts +306 -0
  131. package/src/runtime/i18n/backend/sdk-event.ts +39 -0
  132. package/src/runtime/i18n/detection/config.ts +32 -0
  133. package/src/runtime/i18n/detection/index.ts +641 -0
  134. package/src/runtime/i18n/detection/middleware.node.ts +84 -0
  135. package/src/runtime/i18n/detection/middleware.ts +251 -0
  136. package/src/runtime/i18n/index.ts +8 -0
  137. package/src/runtime/i18n/instance.ts +227 -0
  138. package/src/runtime/i18n/utils.ts +351 -0
  139. package/src/runtime/index.tsx +285 -0
  140. package/src/runtime/types.ts +17 -0
  141. package/src/runtime/utils.ts +163 -0
  142. package/src/server/index.ts +406 -0
  143. package/src/shared/deepMerge.ts +38 -0
  144. package/src/shared/detection.ts +131 -0
  145. package/src/shared/type.ts +170 -0
  146. package/src/shared/utils.ts +82 -0
  147. package/tsconfig.json +13 -0
@@ -0,0 +1,105 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { isBrowser } from "@modern-js/runtime";
3
+ import { createContext, useCallback, useContext, useMemo } from "react";
4
+ import { cacheUserLanguage } from "./i18n/detection/index.mjs";
5
+ import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, shouldIgnoreRedirect, useRouterHooks } from "./utils.mjs";
6
+ const ModernI18nContext = /*#__PURE__*/ createContext(null);
7
+ const ModernI18nProvider = ({ children, value })=>/*#__PURE__*/ jsx(ModernI18nContext.Provider, {
8
+ value: value,
9
+ children: children
10
+ });
11
+ const useModernI18n = ()=>{
12
+ const context = useContext(ModernI18nContext);
13
+ if (!context) throw new Error('useModernI18n must be used within a ModernI18nProvider');
14
+ const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, updateLanguage } = context;
15
+ const { navigate, location, hasRouter } = useRouterHooks();
16
+ const currentLanguage = contextLanguage;
17
+ const changeLanguage = useCallback(async (newLang)=>{
18
+ try {
19
+ if (!newLang || 'string' != typeof newLang) throw new Error('Language must be a non-empty string');
20
+ await i18nInstance?.setLang?.(newLang);
21
+ await i18nInstance?.changeLanguage?.(newLang);
22
+ if (isBrowser()) {
23
+ const detectionOptions = i18nInstance.options?.detection;
24
+ cacheUserLanguage(i18nInstance, newLang, detectionOptions);
25
+ }
26
+ if (localePathRedirect && isBrowser() && hasRouter && navigate && location) {
27
+ const currentPath = location.pathname;
28
+ const entryPath = getEntryPath();
29
+ const relativePath = currentPath.replace(entryPath, '');
30
+ const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
31
+ if (pathLanguage.detected && pathLanguage.language === newLang) return;
32
+ if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
33
+ const newPath = buildLocalizedUrl(relativePath, newLang, languages || []);
34
+ const newUrl = entryPath + newPath + location.search + location.hash;
35
+ await navigate(newUrl, {
36
+ replace: true
37
+ });
38
+ }
39
+ } else if (localePathRedirect && isBrowser() && !hasRouter) {
40
+ const currentPath = window.location.pathname;
41
+ const entryPath = getEntryPath();
42
+ const relativePath = currentPath.replace(entryPath, '');
43
+ const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
44
+ if (pathLanguage.detected && pathLanguage.language === newLang) return;
45
+ if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
46
+ const newPath = buildLocalizedUrl(relativePath, newLang, languages || []);
47
+ const newUrl = entryPath + newPath + window.location.search + window.location.hash;
48
+ window.history.pushState(null, '', newUrl);
49
+ }
50
+ }
51
+ if (updateLanguage) updateLanguage(newLang);
52
+ } catch (error) {
53
+ console.error('Failed to change language:', error);
54
+ throw error;
55
+ }
56
+ }, [
57
+ i18nInstance,
58
+ updateLanguage,
59
+ localePathRedirect,
60
+ ignoreRedirectRoutes,
61
+ languages,
62
+ hasRouter,
63
+ navigate,
64
+ location
65
+ ]);
66
+ const isLanguageSupported = useCallback((lang)=>languages?.includes(lang) || false, [
67
+ languages
68
+ ]);
69
+ const isResourcesReady = useMemo(()=>{
70
+ if (!i18nInstance?.isInitialized) return false;
71
+ const backend = i18nInstance?.services?.backend;
72
+ if (backend && 'function' == typeof backend.isLoading) {
73
+ const loadingResources = backend.getLoadingResources();
74
+ const isCurrentLanguageLoading = loadingResources.some(({ language })=>language === currentLanguage);
75
+ if (isCurrentLanguageLoading) return false;
76
+ }
77
+ const store = i18nInstance.store;
78
+ if (!store?.data) return false;
79
+ const langData = store.data[currentLanguage];
80
+ if (!langData || 'object' != typeof langData) return false;
81
+ const options = i18nInstance.options;
82
+ const namespaces = options?.ns || options?.defaultNS || [
83
+ 'translation'
84
+ ];
85
+ const requiredNamespaces = Array.isArray(namespaces) ? namespaces : [
86
+ namespaces
87
+ ];
88
+ return requiredNamespaces.every((ns)=>{
89
+ const nsData = langData[ns];
90
+ return nsData && 'object' == typeof nsData && Object.keys(nsData).length > 0;
91
+ });
92
+ }, [
93
+ currentLanguage,
94
+ i18nInstance
95
+ ]);
96
+ return {
97
+ language: currentLanguage,
98
+ changeLanguage,
99
+ i18nInstance,
100
+ supportedLanguages: languages || [],
101
+ isLanguageSupported,
102
+ isResourcesReady
103
+ };
104
+ };
105
+ export { ModernI18nProvider, useModernI18n };
@@ -0,0 +1,151 @@
1
+ import { isBrowser } from "@modern-js/runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { I18N_SDK_RESOURCES_LOADED_EVENT, getI18nSdkBackendId } from "./i18n/backend/sdk-event.mjs";
4
+ import { cacheUserLanguage } from "./i18n/detection/index.mjs";
5
+ import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, getPathname, shouldIgnoreRedirect, useRouterHooks } from "./utils.mjs";
6
+ function createMinimalI18nInstance(language) {
7
+ const minimalInstance = {
8
+ language,
9
+ isInitialized: false,
10
+ init: ()=>Promise.resolve(void 0),
11
+ use: ()=>{},
12
+ createInstance: ()=>minimalInstance,
13
+ services: {}
14
+ };
15
+ return minimalInstance;
16
+ }
17
+ function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, setLang) {
18
+ const instance = i18nInstance || createMinimalI18nInstance(lang);
19
+ return {
20
+ language: lang,
21
+ i18nInstance: instance,
22
+ entryName,
23
+ languages,
24
+ localePathRedirect,
25
+ ignoreRedirectRoutes,
26
+ updateLanguage: setLang
27
+ };
28
+ }
29
+ function useSdkResourcesLoader(i18nInstance, setForceUpdate) {
30
+ useEffect(()=>{
31
+ if (!i18nInstance || !isBrowser()) return;
32
+ const backendId = getI18nSdkBackendId(i18nInstance.services?.resourceStore) || getI18nSdkBackendId(i18nInstance.services?.store) || getI18nSdkBackendId(i18nInstance.store);
33
+ if (!backendId) return;
34
+ const handleSdkResourcesLoaded = (event)=>{
35
+ const customEvent = event;
36
+ const { language, namespace, backendId: eventBackendId } = customEvent.detail || {};
37
+ if (!language || !namespace) return;
38
+ if (eventBackendId && eventBackendId !== backendId) return;
39
+ const triggerUpdate = (retryCount = 0)=>{
40
+ const store = i18nInstance.store;
41
+ const hasResource = store?.data?.[language]?.[namespace];
42
+ if (hasResource || retryCount >= 10) {
43
+ if (store?.data?.[language]?.[namespace]) {
44
+ if ('function' == typeof store.emit) store.emit('added', language, namespace);
45
+ }
46
+ if ('function' == typeof i18nInstance.emit) {
47
+ i18nInstance.emit('loaded', {
48
+ language,
49
+ namespace
50
+ });
51
+ i18nInstance.emit('loaded', language, namespace);
52
+ }
53
+ if ('function' == typeof i18nInstance.reloadResources) i18nInstance.reloadResources(language, namespace).then(()=>{
54
+ if ('function' == typeof i18nInstance.emit) i18nInstance.emit('loaded', {
55
+ language,
56
+ namespace
57
+ });
58
+ setForceUpdate((prev)=>prev + 1);
59
+ }).catch(()=>{});
60
+ if ('function' == typeof i18nInstance.emit) i18nInstance.emit('languageChanged', language);
61
+ setForceUpdate((prev)=>prev + 1);
62
+ } else setTimeout(()=>triggerUpdate(retryCount + 1), 10);
63
+ };
64
+ triggerUpdate();
65
+ };
66
+ window.addEventListener(I18N_SDK_RESOURCES_LOADED_EVENT, handleSdkResourcesLoaded);
67
+ return ()=>{
68
+ window.removeEventListener(I18N_SDK_RESOURCES_LOADED_EVENT, handleSdkResourcesLoaded);
69
+ };
70
+ }, [
71
+ i18nInstance,
72
+ setForceUpdate
73
+ ]);
74
+ }
75
+ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes) {
76
+ const hasRedirectedRef = useRef(false);
77
+ const { navigate, location, hasRouter } = useRouterHooks();
78
+ useEffect(()=>{
79
+ if ('browser' !== process.env.MODERN_TARGET) return;
80
+ if (!localePathRedirect || !i18nInstance) return;
81
+ try {
82
+ const ssrData = window._SSR_DATA;
83
+ if (ssrData) return;
84
+ } catch {}
85
+ if (hasRedirectedRef.current) return;
86
+ if (!i18nInstance.isInitialized) return;
87
+ const currentPathname = hasRouter && location ? location.pathname : window.location.pathname;
88
+ const currentSearch = hasRouter && location ? location.search : window.location.search;
89
+ const currentHash = hasRouter && location ? location.hash : window.location.hash;
90
+ const entryPath = getEntryPath();
91
+ const relativePath = currentPathname.replace(entryPath, '');
92
+ if (shouldIgnoreRedirect(relativePath, languages, ignoreRedirectRoutes)) return;
93
+ const pathDetection = detectLanguageFromPath(currentPathname, languages, localePathRedirect);
94
+ if (pathDetection.detected) return;
95
+ const targetLanguage = i18nInstance.language || fallbackLanguage || languages[0] || 'en';
96
+ const newPath = buildLocalizedUrl(relativePath, targetLanguage, languages);
97
+ const newUrl = entryPath + newPath + currentSearch + currentHash;
98
+ if (newUrl !== currentPathname + currentSearch + currentHash) {
99
+ hasRedirectedRef.current = true;
100
+ if (hasRouter && navigate && location) navigate(newUrl, {
101
+ replace: true
102
+ });
103
+ else window.location.replace(newUrl);
104
+ }
105
+ }, [
106
+ navigate,
107
+ location,
108
+ hasRouter,
109
+ localePathRedirect,
110
+ i18nInstance,
111
+ languages,
112
+ fallbackLanguage,
113
+ ignoreRedirectRoutes
114
+ ]);
115
+ }
116
+ function useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang) {
117
+ useEffect(()=>{
118
+ if (!i18nInstance) return;
119
+ if (localePathRedirect) {
120
+ const currentPathname = getPathname(runtimeContextRef.current);
121
+ const pathDetection = detectLanguageFromPath(currentPathname, languages, localePathRedirect);
122
+ if (pathDetection.detected && pathDetection.language) {
123
+ const currentLang = pathDetection.language;
124
+ if (currentLang !== prevLangRef.current) {
125
+ prevLangRef.current = currentLang;
126
+ setLang(currentLang);
127
+ i18nInstance.setLang?.(currentLang);
128
+ i18nInstance.changeLanguage?.(currentLang);
129
+ if (isBrowser()) {
130
+ const detectionOptions = i18nInstance.options?.detection;
131
+ cacheUserLanguage(i18nInstance, currentLang, detectionOptions);
132
+ }
133
+ }
134
+ }
135
+ } else {
136
+ const instanceLang = i18nInstance.language;
137
+ if (instanceLang && instanceLang !== prevLangRef.current) {
138
+ prevLangRef.current = instanceLang;
139
+ setLang(instanceLang);
140
+ }
141
+ }
142
+ }, [
143
+ i18nInstance,
144
+ localePathRedirect,
145
+ languages,
146
+ runtimeContextRef,
147
+ prevLangRef,
148
+ setLang
149
+ ]);
150
+ }
151
+ export { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader };
@@ -0,0 +1,5 @@
1
+ import { deepMerge } from "../../../shared/deepMerge.mjs";
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.mjs";
2
+ import { DEFAULT_I18NEXT_BACKEND_OPTIONS, convertBackendOptions } from "./defaults.mjs";
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.mjs";
3
+ import { SdkBackend } from "./sdk-backend.mjs";
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.mjs";
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.mjs";
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,141 @@
1
+ import { I18N_SDK_RESOURCES_LOADED_EVENT, createI18nSdkBackendId, setI18nSdkBackendId } from "./sdk-event.mjs";
2
+ class SdkBackend {
3
+ init(services, backendOptions, _i18nextOptions) {
4
+ this.services = services;
5
+ this.sdk = backendOptions?.sdk;
6
+ setI18nSdkBackendId(services.resourceStore || services.store, this.backendId);
7
+ if (!this.sdk) throw new Error('SdkBackend requires an SDK function to be provided in backend options');
8
+ }
9
+ read(language, namespace, callback) {
10
+ if (!this.sdk) {
11
+ console.error('[i18n] SdkBackend.read - SDK function not initialized');
12
+ callback(new Error('SDK function not initialized'), null);
13
+ return;
14
+ }
15
+ const cached = this.allResourcesCache ? this.extractFromCache(language, namespace) : null;
16
+ if (null !== cached) {
17
+ const mergedData = this.mergeWithExistingResources(language, namespace, cached);
18
+ callback(null, mergedData);
19
+ return;
20
+ }
21
+ const cacheKey = this.getCacheKey(language, namespace);
22
+ const existingPromise = this.loadingPromises.get(cacheKey);
23
+ if (existingPromise) return void this.handlePromise(existingPromise, language, namespace, callback, false);
24
+ this.loadResource(language, namespace, callback);
25
+ }
26
+ create(_languages, _namespace, _key, _fallbackValue) {}
27
+ isLoading(language, namespace) {
28
+ return this.loadingPromises.has(this.getCacheKey(language, namespace));
29
+ }
30
+ getLoadingResources() {
31
+ const loading = [];
32
+ for (const key of this.loadingPromises.keys()){
33
+ const [language, namespace] = key.split(':');
34
+ if (language && namespace) loading.push({
35
+ language,
36
+ namespace
37
+ });
38
+ }
39
+ return loading;
40
+ }
41
+ hasLoadingResources() {
42
+ return this.loadingPromises.size > 0;
43
+ }
44
+ getCacheKey(language, namespace) {
45
+ return `${language}:${namespace}`;
46
+ }
47
+ loadResource(language, namespace, callback) {
48
+ try {
49
+ const result = this.callSdk(language, namespace);
50
+ const loadPromise = result instanceof Promise ? result : Promise.resolve(result);
51
+ const cacheKey = this.getCacheKey(language, namespace);
52
+ this.loadingPromises.set(cacheKey, loadPromise);
53
+ this.handlePromise(loadPromise, language, namespace, callback, true);
54
+ } catch (error) {
55
+ callback(this.normalizeError(error), null);
56
+ }
57
+ }
58
+ handlePromise(promise, language, namespace, callback, shouldUpdateCache) {
59
+ const cacheKey = this.getCacheKey(language, namespace);
60
+ promise.then((data)=>{
61
+ const formattedData = this.formatResources(data, language, namespace);
62
+ const mergedData = this.mergeWithExistingResources(language, namespace, formattedData);
63
+ if (shouldUpdateCache) {
64
+ this.updateCache(language, namespace, mergedData);
65
+ this.loadingPromises.delete(cacheKey);
66
+ }
67
+ callback(null, mergedData);
68
+ this.triggerI18nextUpdate(language, namespace);
69
+ }).catch((error)=>{
70
+ if (shouldUpdateCache) this.loadingPromises.delete(cacheKey);
71
+ callback(this.normalizeError(error), null);
72
+ });
73
+ }
74
+ normalizeError(error) {
75
+ return error instanceof Error ? error : new Error(String(error));
76
+ }
77
+ callSdk(language, namespace) {
78
+ if (!this.sdk) throw new Error('SDK function not initialized');
79
+ const options = {
80
+ lng: language,
81
+ ns: namespace
82
+ };
83
+ return this.sdk(options);
84
+ }
85
+ extractFromCache(language, namespace) {
86
+ if (!this.allResourcesCache) return null;
87
+ const langData = this.allResourcesCache[language];
88
+ if (!this.isObject(langData)) return null;
89
+ const nsData = langData[namespace];
90
+ if (!this.isObject(nsData)) return null;
91
+ return nsData;
92
+ }
93
+ updateCache(language, namespace, data) {
94
+ if (!this.allResourcesCache) this.allResourcesCache = {};
95
+ if (!this.allResourcesCache[language]) this.allResourcesCache[language] = {};
96
+ if (this.isObject(data)) this.allResourcesCache[language][namespace] = data;
97
+ }
98
+ formatResources(data, language, namespace) {
99
+ if (!this.isObject(data)) return {};
100
+ const dataObj = data;
101
+ const langData = dataObj[language];
102
+ if (this.isObject(langData)) {
103
+ const nsData = langData[namespace];
104
+ if (this.isObject(nsData)) return nsData;
105
+ }
106
+ const hasLanguageKeys = Object.keys(dataObj).some((key)=>this.isObject(dataObj[key]));
107
+ if (!hasLanguageKeys) return dataObj;
108
+ return {};
109
+ }
110
+ isObject(value) {
111
+ return null !== value && 'object' == typeof value;
112
+ }
113
+ mergeWithExistingResources(language, namespace, sdkData) {
114
+ const store = this.services?.resourceStore || this.services?.store;
115
+ const existingData = store?.data?.[language]?.[namespace] || {};
116
+ return {
117
+ ...existingData,
118
+ ...sdkData
119
+ };
120
+ }
121
+ triggerI18nextUpdate(language, namespace) {
122
+ if ("u" > typeof window) {
123
+ const event = new CustomEvent(I18N_SDK_RESOURCES_LOADED_EVENT, {
124
+ detail: {
125
+ language,
126
+ namespace,
127
+ backendId: this.backendId
128
+ }
129
+ });
130
+ window.dispatchEvent(event);
131
+ }
132
+ }
133
+ constructor(_services, _options){
134
+ this.type = 'backend';
135
+ this.allResourcesCache = null;
136
+ this.backendId = createI18nSdkBackendId();
137
+ this.loadingPromises = new Map();
138
+ }
139
+ }
140
+ SdkBackend.type = 'backend';
141
+ export { SdkBackend };
@@ -0,0 +1,21 @@
1
+ const I18N_SDK_RESOURCES_LOADED_EVENT = 'i18n-sdk-resources-loaded';
2
+ const I18N_SDK_BACKEND_ID_KEY = '__modern_i18n_sdk_backend_id__';
3
+ let sdkBackendInstanceCount = 0;
4
+ function createI18nSdkBackendId() {
5
+ sdkBackendInstanceCount += 1;
6
+ return `modern-i18n-sdk-backend-${sdkBackendInstanceCount}`;
7
+ }
8
+ function setI18nSdkBackendId(target, backendId) {
9
+ if (!target || 'object' != typeof target) return;
10
+ Object.defineProperty(target, I18N_SDK_BACKEND_ID_KEY, {
11
+ configurable: true,
12
+ enumerable: false,
13
+ value: backendId,
14
+ writable: true
15
+ });
16
+ }
17
+ function getI18nSdkBackendId(target) {
18
+ if (!target || 'object' != typeof target) return;
19
+ return target[I18N_SDK_BACKEND_ID_KEY];
20
+ }
21
+ export { I18N_SDK_RESOURCES_LOADED_EVENT, createI18nSdkBackendId, getI18nSdkBackendId, setI18nSdkBackendId };
@@ -0,0 +1,26 @@
1
+ import { deepMerge } from "../../../shared/deepMerge.mjs";
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 };