@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,33 @@
1
+ import "node:module";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { Link as router_Link, useInRouterContext, useParams } from "@modern-js/runtime/router";
4
+ import { useModernI18n } from "./context.mjs";
5
+ import { buildLocalizedUrl } from "./utils.mjs";
6
+ const useRouterHooks = ()=>{
7
+ const inRouter = useInRouterContext();
8
+ return {
9
+ Link: inRouter ? router_Link : null,
10
+ params: inRouter ? useParams() : {},
11
+ hasRouter: inRouter
12
+ };
13
+ };
14
+ const I18nLink = ({ to, children, ...props })=>{
15
+ const { Link, params, hasRouter } = useRouterHooks();
16
+ const { language, supportedLanguages } = useModernI18n();
17
+ const currentLang = language;
18
+ const localizedTo = buildLocalizedUrl(to, currentLang, supportedLanguages);
19
+ if ('development' === process.env.NODE_ENV && hasRouter && !params.lang) console.warn("I18nLink is being used outside of a :lang dynamic route context. This may cause unexpected behavior. Please ensure I18nLink is used within a route that has a :lang parameter.");
20
+ if (!hasRouter || !Link) return /*#__PURE__*/ jsx("a", {
21
+ href: localizedTo,
22
+ ...props,
23
+ children: children
24
+ });
25
+ return /*#__PURE__*/ jsx(Link, {
26
+ to: localizedTo,
27
+ ...props,
28
+ children: children
29
+ });
30
+ };
31
+ const runtime_I18nLink = I18nLink;
32
+ export default runtime_I18nLink;
33
+ export { I18nLink };
@@ -0,0 +1,106 @@
1
+ import "node:module";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { isBrowser } from "@modern-js/runtime";
4
+ import { createContext, useCallback, useContext, useMemo } from "react";
5
+ import { cacheUserLanguage } from "./i18n/detection/index.mjs";
6
+ import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, shouldIgnoreRedirect, useRouterHooks } from "./utils.mjs";
7
+ const ModernI18nContext = /*#__PURE__*/ createContext(null);
8
+ const ModernI18nProvider = ({ children, value })=>/*#__PURE__*/ jsx(ModernI18nContext.Provider, {
9
+ value: value,
10
+ children: children
11
+ });
12
+ const useModernI18n = ()=>{
13
+ const context = useContext(ModernI18nContext);
14
+ if (!context) throw new Error('useModernI18n must be used within a ModernI18nProvider');
15
+ const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, updateLanguage } = context;
16
+ const { navigate, location, hasRouter } = useRouterHooks();
17
+ const currentLanguage = contextLanguage;
18
+ const changeLanguage = useCallback(async (newLang)=>{
19
+ try {
20
+ if (!newLang || 'string' != typeof newLang) throw new Error('Language must be a non-empty string');
21
+ await i18nInstance?.setLang?.(newLang);
22
+ await i18nInstance?.changeLanguage?.(newLang);
23
+ if (isBrowser()) {
24
+ const detectionOptions = i18nInstance.options?.detection;
25
+ cacheUserLanguage(i18nInstance, newLang, detectionOptions);
26
+ }
27
+ if (localePathRedirect && isBrowser() && hasRouter && navigate && location) {
28
+ const currentPath = location.pathname;
29
+ const entryPath = getEntryPath();
30
+ const relativePath = currentPath.replace(entryPath, '');
31
+ const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
32
+ if (pathLanguage.detected && pathLanguage.language === newLang) return;
33
+ if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
34
+ const newPath = buildLocalizedUrl(relativePath, newLang, languages || []);
35
+ const newUrl = entryPath + newPath + location.search + location.hash;
36
+ await navigate(newUrl, {
37
+ replace: true
38
+ });
39
+ }
40
+ } else if (localePathRedirect && isBrowser() && !hasRouter) {
41
+ const currentPath = window.location.pathname;
42
+ const entryPath = getEntryPath();
43
+ const relativePath = currentPath.replace(entryPath, '');
44
+ const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
45
+ if (pathLanguage.detected && pathLanguage.language === newLang) return;
46
+ if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
47
+ const newPath = buildLocalizedUrl(relativePath, newLang, languages || []);
48
+ const newUrl = entryPath + newPath + window.location.search + window.location.hash;
49
+ window.history.pushState(null, '', newUrl);
50
+ }
51
+ }
52
+ if (updateLanguage) updateLanguage(newLang);
53
+ } catch (error) {
54
+ console.error('Failed to change language:', error);
55
+ throw error;
56
+ }
57
+ }, [
58
+ i18nInstance,
59
+ updateLanguage,
60
+ localePathRedirect,
61
+ ignoreRedirectRoutes,
62
+ languages,
63
+ hasRouter,
64
+ navigate,
65
+ location
66
+ ]);
67
+ const isLanguageSupported = useCallback((lang)=>languages?.includes(lang) || false, [
68
+ languages
69
+ ]);
70
+ const isResourcesReady = useMemo(()=>{
71
+ if (!i18nInstance?.isInitialized) return false;
72
+ const backend = i18nInstance?.services?.backend;
73
+ if (backend && 'function' == typeof backend.isLoading) {
74
+ const loadingResources = backend.getLoadingResources();
75
+ const isCurrentLanguageLoading = loadingResources.some(({ language })=>language === currentLanguage);
76
+ if (isCurrentLanguageLoading) return false;
77
+ }
78
+ const store = i18nInstance.store;
79
+ if (!store?.data) return false;
80
+ const langData = store.data[currentLanguage];
81
+ if (!langData || 'object' != typeof langData) return false;
82
+ const options = i18nInstance.options;
83
+ const namespaces = options?.ns || options?.defaultNS || [
84
+ 'translation'
85
+ ];
86
+ const requiredNamespaces = Array.isArray(namespaces) ? namespaces : [
87
+ namespaces
88
+ ];
89
+ return requiredNamespaces.every((ns)=>{
90
+ const nsData = langData[ns];
91
+ return nsData && 'object' == typeof nsData && Object.keys(nsData).length > 0;
92
+ });
93
+ }, [
94
+ currentLanguage,
95
+ i18nInstance
96
+ ]);
97
+ return {
98
+ language: currentLanguage,
99
+ changeLanguage,
100
+ i18nInstance,
101
+ supportedLanguages: languages || [],
102
+ isLanguageSupported,
103
+ isResourcesReady
104
+ };
105
+ };
106
+ export { ModernI18nProvider, useModernI18n };
@@ -0,0 +1,152 @@
1
+ import "node:module";
2
+ import { isBrowser } from "@modern-js/runtime";
3
+ import { useEffect, useRef } from "react";
4
+ import { I18N_SDK_RESOURCES_LOADED_EVENT, getI18nSdkBackendId } from "./i18n/backend/sdk-event.mjs";
5
+ import { cacheUserLanguage } from "./i18n/detection/index.mjs";
6
+ import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, getPathname, shouldIgnoreRedirect, useRouterHooks } from "./utils.mjs";
7
+ function createMinimalI18nInstance(language) {
8
+ const minimalInstance = {
9
+ language,
10
+ isInitialized: false,
11
+ init: ()=>Promise.resolve(void 0),
12
+ use: ()=>{},
13
+ createInstance: ()=>minimalInstance,
14
+ services: {}
15
+ };
16
+ return minimalInstance;
17
+ }
18
+ function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, setLang) {
19
+ const instance = i18nInstance || createMinimalI18nInstance(lang);
20
+ return {
21
+ language: lang,
22
+ i18nInstance: instance,
23
+ entryName,
24
+ languages,
25
+ localePathRedirect,
26
+ ignoreRedirectRoutes,
27
+ updateLanguage: setLang
28
+ };
29
+ }
30
+ function useSdkResourcesLoader(i18nInstance, setForceUpdate) {
31
+ useEffect(()=>{
32
+ if (!i18nInstance || !isBrowser()) return;
33
+ const backendId = getI18nSdkBackendId(i18nInstance.services?.resourceStore) || getI18nSdkBackendId(i18nInstance.services?.store) || getI18nSdkBackendId(i18nInstance.store);
34
+ if (!backendId) return;
35
+ const handleSdkResourcesLoaded = (event)=>{
36
+ const customEvent = event;
37
+ const { language, namespace, backendId: eventBackendId } = customEvent.detail || {};
38
+ if (!language || !namespace) return;
39
+ if (eventBackendId && eventBackendId !== backendId) return;
40
+ const triggerUpdate = (retryCount = 0)=>{
41
+ const store = i18nInstance.store;
42
+ const hasResource = store?.data?.[language]?.[namespace];
43
+ if (hasResource || retryCount >= 10) {
44
+ if (store?.data?.[language]?.[namespace]) {
45
+ if ('function' == typeof store.emit) store.emit('added', language, namespace);
46
+ }
47
+ if ('function' == typeof i18nInstance.emit) {
48
+ i18nInstance.emit('loaded', {
49
+ language,
50
+ namespace
51
+ });
52
+ i18nInstance.emit('loaded', language, namespace);
53
+ }
54
+ if ('function' == typeof i18nInstance.reloadResources) i18nInstance.reloadResources(language, namespace).then(()=>{
55
+ if ('function' == typeof i18nInstance.emit) i18nInstance.emit('loaded', {
56
+ language,
57
+ namespace
58
+ });
59
+ setForceUpdate((prev)=>prev + 1);
60
+ }).catch(()=>{});
61
+ if ('function' == typeof i18nInstance.emit) i18nInstance.emit('languageChanged', language);
62
+ setForceUpdate((prev)=>prev + 1);
63
+ } else setTimeout(()=>triggerUpdate(retryCount + 1), 10);
64
+ };
65
+ triggerUpdate();
66
+ };
67
+ window.addEventListener(I18N_SDK_RESOURCES_LOADED_EVENT, handleSdkResourcesLoaded);
68
+ return ()=>{
69
+ window.removeEventListener(I18N_SDK_RESOURCES_LOADED_EVENT, handleSdkResourcesLoaded);
70
+ };
71
+ }, [
72
+ i18nInstance,
73
+ setForceUpdate
74
+ ]);
75
+ }
76
+ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes) {
77
+ const hasRedirectedRef = useRef(false);
78
+ const { navigate, location, hasRouter } = useRouterHooks();
79
+ useEffect(()=>{
80
+ if ('browser' !== process.env.MODERN_TARGET) return;
81
+ if (!localePathRedirect || !i18nInstance) return;
82
+ try {
83
+ const ssrData = window._SSR_DATA;
84
+ if (ssrData) return;
85
+ } catch {}
86
+ if (hasRedirectedRef.current) return;
87
+ if (!i18nInstance.isInitialized) return;
88
+ const currentPathname = hasRouter && location ? location.pathname : window.location.pathname;
89
+ const currentSearch = hasRouter && location ? location.search : window.location.search;
90
+ const currentHash = hasRouter && location ? location.hash : window.location.hash;
91
+ const entryPath = getEntryPath();
92
+ const relativePath = currentPathname.replace(entryPath, '');
93
+ if (shouldIgnoreRedirect(relativePath, languages, ignoreRedirectRoutes)) return;
94
+ const pathDetection = detectLanguageFromPath(currentPathname, languages, localePathRedirect);
95
+ if (pathDetection.detected) return;
96
+ const targetLanguage = i18nInstance.language || fallbackLanguage || languages[0] || 'en';
97
+ const newPath = buildLocalizedUrl(relativePath, targetLanguage, languages);
98
+ const newUrl = entryPath + newPath + currentSearch + currentHash;
99
+ if (newUrl !== currentPathname + currentSearch + currentHash) {
100
+ hasRedirectedRef.current = true;
101
+ if (hasRouter && navigate && location) navigate(newUrl, {
102
+ replace: true
103
+ });
104
+ else window.location.replace(newUrl);
105
+ }
106
+ }, [
107
+ navigate,
108
+ location,
109
+ hasRouter,
110
+ localePathRedirect,
111
+ i18nInstance,
112
+ languages,
113
+ fallbackLanguage,
114
+ ignoreRedirectRoutes
115
+ ]);
116
+ }
117
+ function useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang) {
118
+ useEffect(()=>{
119
+ if (!i18nInstance) return;
120
+ if (localePathRedirect) {
121
+ const currentPathname = getPathname(runtimeContextRef.current);
122
+ const pathDetection = detectLanguageFromPath(currentPathname, languages, localePathRedirect);
123
+ if (pathDetection.detected && pathDetection.language) {
124
+ const currentLang = pathDetection.language;
125
+ if (currentLang !== prevLangRef.current) {
126
+ prevLangRef.current = currentLang;
127
+ setLang(currentLang);
128
+ i18nInstance.setLang?.(currentLang);
129
+ i18nInstance.changeLanguage?.(currentLang);
130
+ if (isBrowser()) {
131
+ const detectionOptions = i18nInstance.options?.detection;
132
+ cacheUserLanguage(i18nInstance, currentLang, detectionOptions);
133
+ }
134
+ }
135
+ }
136
+ } else {
137
+ const instanceLang = i18nInstance.language;
138
+ if (instanceLang && instanceLang !== prevLangRef.current) {
139
+ prevLangRef.current = instanceLang;
140
+ setLang(instanceLang);
141
+ }
142
+ }
143
+ }, [
144
+ i18nInstance,
145
+ localePathRedirect,
146
+ languages,
147
+ runtimeContextRef,
148
+ prevLangRef,
149
+ setLang
150
+ ]);
151
+ }
152
+ export { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader };
@@ -0,0 +1,6 @@
1
+ import "node:module";
2
+ import { deepMerge } from "../../../shared/deepMerge.mjs";
3
+ function mergeBackendOptions(defaultOptions, cliOptions = {}, userOptions) {
4
+ return deepMerge(deepMerge(defaultOptions, cliOptions), userOptions ?? {});
5
+ }
6
+ export { mergeBackendOptions };
@@ -0,0 +1,20 @@
1
+ import "node:module";
2
+ const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
3
+ loadPath: '/locales/{{lng}}/{{ns}}.json',
4
+ addPath: '/locales/{{lng}}/{{ns}}.json'
5
+ };
6
+ function convertPath(path) {
7
+ if (!path) return path;
8
+ if (path.startsWith('/')) return `${window.__assetPrefix__ || ''}${path}`;
9
+ return path;
10
+ }
11
+ function convertBackendOptions(options) {
12
+ if (!options) return options;
13
+ const converted = {
14
+ ...options
15
+ };
16
+ if (converted.loadPath) converted.loadPath = convertPath(converted.loadPath);
17
+ if (converted.addPath) converted.addPath = convertPath(converted.addPath);
18
+ return converted;
19
+ }
20
+ export { DEFAULT_I18NEXT_BACKEND_OPTIONS, convertBackendOptions };
@@ -0,0 +1,20 @@
1
+ import "node:module";
2
+ const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
3
+ loadPath: './locales/{{lng}}/{{ns}}.json',
4
+ addPath: './locales/{{lng}}/{{ns}}.json'
5
+ };
6
+ function convertPath(path) {
7
+ if (!path) return path;
8
+ if (path.startsWith('/')) return `.${path}`;
9
+ return path;
10
+ }
11
+ function convertBackendOptions(options) {
12
+ if (!options) return options;
13
+ const converted = {
14
+ ...options
15
+ };
16
+ if (converted.loadPath) converted.loadPath = convertPath(converted.loadPath);
17
+ if (converted.addPath) converted.addPath = convertPath(converted.addPath);
18
+ return converted;
19
+ }
20
+ export { DEFAULT_I18NEXT_BACKEND_OPTIONS, convertBackendOptions };
@@ -0,0 +1,75 @@
1
+ import "node:module";
2
+ import { mergeBackendOptions } from "./config.mjs";
3
+ import { DEFAULT_I18NEXT_BACKEND_OPTIONS, convertBackendOptions } from "./defaults.mjs";
4
+ function hasSdkFunction(backend, userInitOptions) {
5
+ return 'function' == typeof userInitOptions?.backend?.sdk || !!backend?.enabled && !!backend?.sdk && 'function' == typeof backend.sdk;
6
+ }
7
+ function hasLoadPath(backend, userInitOptions) {
8
+ const userLoadPath = userInitOptions?.backend?.loadPath ?? backend?.loadPath;
9
+ const isExplicit = !!userLoadPath && '' !== userLoadPath;
10
+ const hasPath = isExplicit || !!backend?.enabled && void 0 === userLoadPath;
11
+ return {
12
+ hasPath,
13
+ isExplicit
14
+ };
15
+ }
16
+ function ensureDefaultLoadPath(merged, backend, isExplicitLoadPath = false) {
17
+ if (backend?.enabled && !isExplicitLoadPath && !merged.loadPath) {
18
+ merged.loadPath = DEFAULT_I18NEXT_BACKEND_OPTIONS.loadPath;
19
+ merged.addPath = DEFAULT_I18NEXT_BACKEND_OPTIONS.addPath;
20
+ }
21
+ }
22
+ function getFinalLoadPath(mergedOptions, backend, userInitOptions) {
23
+ return mergedOptions?.loadPath || userInitOptions?.backend?.loadPath || (backend?.enabled ? DEFAULT_I18NEXT_BACKEND_OPTIONS.loadPath : void 0);
24
+ }
25
+ function getFinalSdk(mergedOptions, backend, userInitOptions) {
26
+ return mergedOptions?.sdk || userInitOptions?.backend?.sdk || (backend?.sdk && 'function' == typeof backend.sdk ? backend.sdk : void 0);
27
+ }
28
+ function buildChainedBackendConfig(backend, userInitOptions) {
29
+ const merged = mergeBackendOptions(DEFAULT_I18NEXT_BACKEND_OPTIONS, backend, userInitOptions?.backend);
30
+ const { isExplicit } = hasLoadPath(backend, userInitOptions);
31
+ ensureDefaultLoadPath(merged, backend, isExplicit);
32
+ const mergedOptions = convertBackendOptions(merged);
33
+ const finalLoadPath = getFinalLoadPath(mergedOptions, backend, userInitOptions);
34
+ const finalSdk = getFinalSdk(mergedOptions, backend, userInitOptions);
35
+ const chainedBackendOptions = [
36
+ {
37
+ loadPath: finalLoadPath,
38
+ addPath: mergedOptions?.addPath || DEFAULT_I18NEXT_BACKEND_OPTIONS.addPath
39
+ },
40
+ {
41
+ sdk: finalSdk
42
+ }
43
+ ];
44
+ return {
45
+ ...mergedOptions,
46
+ loadPath: finalLoadPath,
47
+ sdk: finalSdk,
48
+ cacheHitMode: mergedOptions?.cacheHitMode || backend?.cacheHitMode || userInitOptions?.backend?.cacheHitMode || 'refreshAndUpdateStore',
49
+ _useChainedBackend: true,
50
+ _chainedBackendConfig: {
51
+ backendOptions: chainedBackendOptions
52
+ }
53
+ };
54
+ }
55
+ function buildSdkOnlyBackendConfig(backend, userInitOptions) {
56
+ const merged = mergeBackendOptions({}, backend, userInitOptions?.backend);
57
+ return convertBackendOptions(merged) || {};
58
+ }
59
+ function buildHttpFsBackendConfig(backend, userInitOptions) {
60
+ if (!backend?.enabled && !userInitOptions?.backend) return;
61
+ const mergedBackend = backend?.enabled ? mergeBackendOptions(DEFAULT_I18NEXT_BACKEND_OPTIONS, backend, userInitOptions?.backend) : userInitOptions?.backend;
62
+ if (mergedBackend) {
63
+ const { isExplicit } = hasLoadPath(backend, userInitOptions);
64
+ ensureDefaultLoadPath(mergedBackend, backend, isExplicit);
65
+ }
66
+ return convertBackendOptions(mergedBackend) || {};
67
+ }
68
+ const backend_mergeBackendOptions = (backend, userInitOptions)=>{
69
+ const sdkFunction = hasSdkFunction(backend, userInitOptions);
70
+ const { hasPath } = hasLoadPath(backend, userInitOptions);
71
+ if (hasPath && sdkFunction) return buildChainedBackendConfig(backend, userInitOptions);
72
+ if (sdkFunction) return buildSdkOnlyBackendConfig(backend, userInitOptions);
73
+ return buildHttpFsBackendConfig(backend, userInitOptions);
74
+ };
75
+ export { backend_mergeBackendOptions as mergeBackendOptions };
@@ -0,0 +1,62 @@
1
+ import "node:module";
2
+ import i18next_chained_backend from "i18next-chained-backend";
3
+ import { getActualI18nextInstance } from "../instance.mjs";
4
+ import { SdkBackend } from "./sdk-backend.mjs";
5
+ function checkBackendConfig(backend) {
6
+ const hasSdk = backend?.sdk && 'function' == typeof backend.sdk;
7
+ const hasLoadPath = !!backend?.loadPath;
8
+ const useChained = backend?._useChainedBackend;
9
+ return {
10
+ hasSdk,
11
+ hasLoadPath,
12
+ useChained
13
+ };
14
+ }
15
+ function buildChainedBackendConfig(backend, BackendWithSave) {
16
+ const cacheHitMode = backend.cacheHitMode || 'refreshAndUpdateStore';
17
+ if (backend._chainedBackendConfig) return {
18
+ backends: [
19
+ BackendWithSave,
20
+ SdkBackend
21
+ ],
22
+ backendOptions: backend._chainedBackendConfig.backendOptions,
23
+ cacheHitMode
24
+ };
25
+ return {
26
+ backends: [
27
+ BackendWithSave,
28
+ SdkBackend
29
+ ],
30
+ backendOptions: [
31
+ {
32
+ loadPath: backend.loadPath,
33
+ addPath: backend.addPath
34
+ },
35
+ {
36
+ sdk: backend.sdk
37
+ }
38
+ ],
39
+ cacheHitMode
40
+ };
41
+ }
42
+ function setupChainedBackend(i18nInstance, backend, BackendWithSave) {
43
+ i18nInstance.use(i18next_chained_backend);
44
+ const actualInstance = getActualI18nextInstance(i18nInstance);
45
+ if (actualInstance?.options) actualInstance.options.backend = buildChainedBackendConfig(backend, BackendWithSave);
46
+ if (i18nInstance.options) i18nInstance.options.backend = buildChainedBackendConfig(backend, BackendWithSave);
47
+ }
48
+ function cleanBackendConfig(backend) {
49
+ const { _useChainedBackend, _chainedBackendConfig, ...cleanBackend } = backend;
50
+ return cleanBackend;
51
+ }
52
+ function useI18nextBackendCommon(i18nInstance, BackendWithSave, BackendBase, backend) {
53
+ if (!backend) return i18nInstance.use(BackendBase);
54
+ const { hasSdk, hasLoadPath, useChained } = checkBackendConfig(backend);
55
+ if (useChained || hasLoadPath && hasSdk) return void setupChainedBackend(i18nInstance, backend, BackendWithSave);
56
+ if (hasSdk) return i18nInstance.use(SdkBackend);
57
+ const actualInstance = getActualI18nextInstance(i18nInstance);
58
+ if (actualInstance?.options) actualInstance.options.backend = cleanBackendConfig(backend);
59
+ if (i18nInstance.options) i18nInstance.options.backend = cleanBackendConfig(backend);
60
+ return i18nInstance.use(BackendBase);
61
+ }
62
+ export { useI18nextBackendCommon };
@@ -0,0 +1,8 @@
1
+ import "node:module";
2
+ import i18next_http_backend from "i18next-http-backend";
3
+ import { useI18nextBackendCommon } from "./middleware.common.mjs";
4
+ class HttpBackendWithSave extends i18next_http_backend {
5
+ save(_language, _namespace, _data) {}
6
+ }
7
+ const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, HttpBackendWithSave, i18next_http_backend, backend);
8
+ export { HttpBackendWithSave, useI18nextBackend };
@@ -0,0 +1,9 @@
1
+ import "node:module";
2
+ import i18next_fs_backend from "i18next-fs-backend";
3
+ import { useI18nextBackendCommon } from "./middleware.common.mjs";
4
+ class FsBackendWithSave extends i18next_fs_backend {
5
+ save(_language, _namespace, _data) {}
6
+ }
7
+ const HttpBackendWithSave = FsBackendWithSave;
8
+ const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, FsBackendWithSave, i18next_fs_backend, backend);
9
+ export { FsBackendWithSave, HttpBackendWithSave, useI18nextBackend };
@@ -0,0 +1,142 @@
1
+ import "node:module";
2
+ import { I18N_SDK_RESOURCES_LOADED_EVENT, createI18nSdkBackendId, setI18nSdkBackendId } from "./sdk-event.mjs";
3
+ class SdkBackend {
4
+ init(services, backendOptions, _i18nextOptions) {
5
+ this.services = services;
6
+ this.sdk = backendOptions?.sdk;
7
+ setI18nSdkBackendId(services.resourceStore || services.store, this.backendId);
8
+ if (!this.sdk) throw new Error('SdkBackend requires an SDK function to be provided in backend options');
9
+ }
10
+ read(language, namespace, callback) {
11
+ if (!this.sdk) {
12
+ console.error('[i18n] SdkBackend.read - SDK function not initialized');
13
+ callback(new Error('SDK function not initialized'), null);
14
+ return;
15
+ }
16
+ const cached = this.allResourcesCache ? this.extractFromCache(language, namespace) : null;
17
+ if (null !== cached) {
18
+ const mergedData = this.mergeWithExistingResources(language, namespace, cached);
19
+ callback(null, mergedData);
20
+ return;
21
+ }
22
+ const cacheKey = this.getCacheKey(language, namespace);
23
+ const existingPromise = this.loadingPromises.get(cacheKey);
24
+ if (existingPromise) return void this.handlePromise(existingPromise, language, namespace, callback, false);
25
+ this.loadResource(language, namespace, callback);
26
+ }
27
+ create(_languages, _namespace, _key, _fallbackValue) {}
28
+ isLoading(language, namespace) {
29
+ return this.loadingPromises.has(this.getCacheKey(language, namespace));
30
+ }
31
+ getLoadingResources() {
32
+ const loading = [];
33
+ for (const key of this.loadingPromises.keys()){
34
+ const [language, namespace] = key.split(':');
35
+ if (language && namespace) loading.push({
36
+ language,
37
+ namespace
38
+ });
39
+ }
40
+ return loading;
41
+ }
42
+ hasLoadingResources() {
43
+ return this.loadingPromises.size > 0;
44
+ }
45
+ getCacheKey(language, namespace) {
46
+ return `${language}:${namespace}`;
47
+ }
48
+ loadResource(language, namespace, callback) {
49
+ try {
50
+ const result = this.callSdk(language, namespace);
51
+ const loadPromise = result instanceof Promise ? result : Promise.resolve(result);
52
+ const cacheKey = this.getCacheKey(language, namespace);
53
+ this.loadingPromises.set(cacheKey, loadPromise);
54
+ this.handlePromise(loadPromise, language, namespace, callback, true);
55
+ } catch (error) {
56
+ callback(this.normalizeError(error), null);
57
+ }
58
+ }
59
+ handlePromise(promise, language, namespace, callback, shouldUpdateCache) {
60
+ const cacheKey = this.getCacheKey(language, namespace);
61
+ promise.then((data)=>{
62
+ const formattedData = this.formatResources(data, language, namespace);
63
+ const mergedData = this.mergeWithExistingResources(language, namespace, formattedData);
64
+ if (shouldUpdateCache) {
65
+ this.updateCache(language, namespace, mergedData);
66
+ this.loadingPromises.delete(cacheKey);
67
+ }
68
+ callback(null, mergedData);
69
+ this.triggerI18nextUpdate(language, namespace);
70
+ }).catch((error)=>{
71
+ if (shouldUpdateCache) this.loadingPromises.delete(cacheKey);
72
+ callback(this.normalizeError(error), null);
73
+ });
74
+ }
75
+ normalizeError(error) {
76
+ return error instanceof Error ? error : new Error(String(error));
77
+ }
78
+ callSdk(language, namespace) {
79
+ if (!this.sdk) throw new Error('SDK function not initialized');
80
+ const options = {
81
+ lng: language,
82
+ ns: namespace
83
+ };
84
+ return this.sdk(options);
85
+ }
86
+ extractFromCache(language, namespace) {
87
+ if (!this.allResourcesCache) return null;
88
+ const langData = this.allResourcesCache[language];
89
+ if (!this.isObject(langData)) return null;
90
+ const nsData = langData[namespace];
91
+ if (!this.isObject(nsData)) return null;
92
+ return nsData;
93
+ }
94
+ updateCache(language, namespace, data) {
95
+ if (!this.allResourcesCache) this.allResourcesCache = {};
96
+ if (!this.allResourcesCache[language]) this.allResourcesCache[language] = {};
97
+ if (this.isObject(data)) this.allResourcesCache[language][namespace] = data;
98
+ }
99
+ formatResources(data, language, namespace) {
100
+ if (!this.isObject(data)) return {};
101
+ const dataObj = data;
102
+ const langData = dataObj[language];
103
+ if (this.isObject(langData)) {
104
+ const nsData = langData[namespace];
105
+ if (this.isObject(nsData)) return nsData;
106
+ }
107
+ const hasLanguageKeys = Object.keys(dataObj).some((key)=>this.isObject(dataObj[key]));
108
+ if (!hasLanguageKeys) return dataObj;
109
+ return {};
110
+ }
111
+ isObject(value) {
112
+ return null !== value && 'object' == typeof value;
113
+ }
114
+ mergeWithExistingResources(language, namespace, sdkData) {
115
+ const store = this.services?.resourceStore || this.services?.store;
116
+ const existingData = store?.data?.[language]?.[namespace] || {};
117
+ return {
118
+ ...existingData,
119
+ ...sdkData
120
+ };
121
+ }
122
+ triggerI18nextUpdate(language, namespace) {
123
+ if ("u" > typeof window) {
124
+ const event = new CustomEvent(I18N_SDK_RESOURCES_LOADED_EVENT, {
125
+ detail: {
126
+ language,
127
+ namespace,
128
+ backendId: this.backendId
129
+ }
130
+ });
131
+ window.dispatchEvent(event);
132
+ }
133
+ }
134
+ constructor(_services, _options){
135
+ this.type = 'backend';
136
+ this.allResourcesCache = null;
137
+ this.backendId = createI18nSdkBackendId();
138
+ this.loadingPromises = new Map();
139
+ }
140
+ }
141
+ SdkBackend.type = 'backend';
142
+ export { SdkBackend };
@@ -0,0 +1,22 @@
1
+ import "node:module";
2
+ const I18N_SDK_RESOURCES_LOADED_EVENT = 'i18n-sdk-resources-loaded';
3
+ const I18N_SDK_BACKEND_ID_KEY = '__modern_i18n_sdk_backend_id__';
4
+ let sdkBackendInstanceCount = 0;
5
+ function createI18nSdkBackendId() {
6
+ sdkBackendInstanceCount += 1;
7
+ return `modern-i18n-sdk-backend-${sdkBackendInstanceCount}`;
8
+ }
9
+ function setI18nSdkBackendId(target, backendId) {
10
+ if (!target || 'object' != typeof target) return;
11
+ Object.defineProperty(target, I18N_SDK_BACKEND_ID_KEY, {
12
+ configurable: true,
13
+ enumerable: false,
14
+ value: backendId,
15
+ writable: true
16
+ });
17
+ }
18
+ function getI18nSdkBackendId(target) {
19
+ if (!target || 'object' != typeof target) return;
20
+ return target[I18N_SDK_BACKEND_ID_KEY];
21
+ }
22
+ export { I18N_SDK_RESOURCES_LOADED_EVENT, createI18nSdkBackendId, getI18nSdkBackendId, setI18nSdkBackendId };