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

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 (162) hide show
  1. package/dist/cjs/cli/index.js +2 -1
  2. package/dist/cjs/runtime/Link.js +8 -89
  3. package/dist/cjs/runtime/context.js +18 -82
  4. package/dist/cjs/runtime/contextHelpers.js +137 -0
  5. package/dist/cjs/runtime/core.js +29 -132
  6. package/dist/cjs/runtime/i18n/detection/cache.js +149 -0
  7. package/dist/cjs/runtime/i18n/detection/detector.js +100 -0
  8. package/dist/cjs/runtime/i18n/detection/index.js +8 -263
  9. package/dist/cjs/runtime/i18n/detection/initOptions.js +69 -0
  10. package/dist/cjs/runtime/i18n/detection/language.js +62 -0
  11. package/dist/cjs/runtime/i18n/detection/path.js +49 -0
  12. package/dist/cjs/runtime/i18n/detection/priority.js +66 -0
  13. package/dist/cjs/runtime/i18n/detection/ssr.js +70 -0
  14. package/dist/cjs/runtime/i18n/detection/types.js +18 -0
  15. package/dist/cjs/runtime/linkHelpers.js +148 -0
  16. package/dist/cjs/runtime/pluginSetup.js +102 -0
  17. package/dist/cjs/runtime/providerComposition.js +113 -0
  18. package/dist/cjs/runtime/reactI18next.js +43 -0
  19. package/dist/cjs/server/apiPrefix.js +72 -0
  20. package/dist/cjs/server/detectorOptions.js +97 -0
  21. package/dist/cjs/server/index.js +17 -152
  22. package/dist/cjs/server/redirectPolicy.js +102 -0
  23. package/dist/cjs/shared/localisedUrls/config.js +49 -0
  24. package/dist/cjs/shared/localisedUrls/index.js +75 -0
  25. package/dist/cjs/shared/localisedUrls/normalise.js +87 -0
  26. package/dist/cjs/shared/localisedUrls/pathname.js +65 -0
  27. package/dist/cjs/shared/localisedUrls/patterns.js +130 -0
  28. package/dist/cjs/shared/localisedUrls/resolve.js +91 -0
  29. package/dist/cjs/shared/localisedUrls/routes.js +129 -0
  30. package/dist/cjs/shared/localisedUrls/types.js +18 -0
  31. package/dist/cjs/shared/localisedUrls.js +13 -294
  32. package/dist/esm/cli/index.mjs +2 -1
  33. package/dist/esm/runtime/Link.mjs +1 -82
  34. package/dist/esm/runtime/context.mjs +18 -82
  35. package/dist/esm/runtime/contextHelpers.mjs +84 -0
  36. package/dist/esm/runtime/core.mjs +30 -133
  37. package/dist/esm/runtime/i18n/detection/cache.mjs +102 -0
  38. package/dist/esm/runtime/i18n/detection/detector.mjs +62 -0
  39. package/dist/esm/runtime/i18n/detection/index.mjs +4 -260
  40. package/dist/esm/runtime/i18n/detection/initOptions.mjs +28 -0
  41. package/dist/esm/runtime/i18n/detection/language.mjs +21 -0
  42. package/dist/esm/runtime/i18n/detection/path.mjs +11 -0
  43. package/dist/esm/runtime/i18n/detection/priority.mjs +28 -0
  44. package/dist/esm/runtime/i18n/detection/ssr.mjs +26 -0
  45. package/dist/esm/runtime/i18n/detection/types.mjs +0 -0
  46. package/dist/esm/runtime/linkHelpers.mjs +98 -0
  47. package/dist/esm/runtime/pluginSetup.mjs +64 -0
  48. package/dist/esm/runtime/providerComposition.mjs +75 -0
  49. package/dist/esm/runtime/reactI18next.mjs +5 -0
  50. package/dist/esm/server/apiPrefix.mjs +31 -0
  51. package/dist/esm/server/detectorOptions.mjs +59 -0
  52. package/dist/esm/server/index.mjs +4 -139
  53. package/dist/esm/server/redirectPolicy.mjs +52 -0
  54. package/dist/esm/shared/localisedUrls/config.mjs +11 -0
  55. package/dist/esm/shared/localisedUrls/index.mjs +6 -0
  56. package/dist/esm/shared/localisedUrls/normalise.mjs +37 -0
  57. package/dist/esm/shared/localisedUrls/pathname.mjs +24 -0
  58. package/dist/esm/shared/localisedUrls/patterns.mjs +86 -0
  59. package/dist/esm/shared/localisedUrls/resolve.mjs +50 -0
  60. package/dist/esm/shared/localisedUrls/routes.mjs +88 -0
  61. package/dist/esm/shared/localisedUrls/types.mjs +0 -0
  62. package/dist/esm/shared/localisedUrls.mjs +1 -283
  63. package/dist/esm-node/cli/index.mjs +2 -1
  64. package/dist/esm-node/runtime/Link.mjs +1 -82
  65. package/dist/esm-node/runtime/context.mjs +18 -82
  66. package/dist/esm-node/runtime/contextHelpers.mjs +85 -0
  67. package/dist/esm-node/runtime/core.mjs +30 -133
  68. package/dist/esm-node/runtime/i18n/detection/cache.mjs +103 -0
  69. package/dist/esm-node/runtime/i18n/detection/detector.mjs +63 -0
  70. package/dist/esm-node/runtime/i18n/detection/index.mjs +4 -260
  71. package/dist/esm-node/runtime/i18n/detection/initOptions.mjs +29 -0
  72. package/dist/esm-node/runtime/i18n/detection/language.mjs +22 -0
  73. package/dist/esm-node/runtime/i18n/detection/path.mjs +12 -0
  74. package/dist/esm-node/runtime/i18n/detection/priority.mjs +29 -0
  75. package/dist/esm-node/runtime/i18n/detection/ssr.mjs +27 -0
  76. package/dist/esm-node/runtime/i18n/detection/types.mjs +1 -0
  77. package/dist/esm-node/runtime/linkHelpers.mjs +99 -0
  78. package/dist/esm-node/runtime/pluginSetup.mjs +65 -0
  79. package/dist/esm-node/runtime/providerComposition.mjs +76 -0
  80. package/dist/esm-node/runtime/reactI18next.mjs +6 -0
  81. package/dist/esm-node/server/apiPrefix.mjs +32 -0
  82. package/dist/esm-node/server/detectorOptions.mjs +60 -0
  83. package/dist/esm-node/server/index.mjs +4 -139
  84. package/dist/esm-node/server/redirectPolicy.mjs +53 -0
  85. package/dist/esm-node/shared/localisedUrls/config.mjs +12 -0
  86. package/dist/esm-node/shared/localisedUrls/index.mjs +7 -0
  87. package/dist/esm-node/shared/localisedUrls/normalise.mjs +38 -0
  88. package/dist/esm-node/shared/localisedUrls/pathname.mjs +25 -0
  89. package/dist/esm-node/shared/localisedUrls/patterns.mjs +87 -0
  90. package/dist/esm-node/shared/localisedUrls/resolve.mjs +51 -0
  91. package/dist/esm-node/shared/localisedUrls/routes.mjs +89 -0
  92. package/dist/esm-node/shared/localisedUrls/types.mjs +1 -0
  93. package/dist/esm-node/shared/localisedUrls.mjs +1 -283
  94. package/dist/types/runtime/Link.d.ts +3 -7
  95. package/dist/types/runtime/context.d.ts +2 -2
  96. package/dist/types/runtime/contextHelpers.d.ts +28 -0
  97. package/dist/types/runtime/core.d.ts +3 -7
  98. package/dist/types/runtime/i18n/detection/cache.d.ts +14 -0
  99. package/dist/types/runtime/i18n/detection/detector.d.ts +8 -0
  100. package/dist/types/runtime/i18n/detection/index.d.ts +5 -50
  101. package/dist/types/runtime/i18n/detection/initOptions.d.ts +10 -0
  102. package/dist/types/runtime/i18n/detection/language.d.ts +11 -0
  103. package/dist/types/runtime/i18n/detection/path.d.ts +5 -0
  104. package/dist/types/runtime/i18n/detection/priority.d.ts +10 -0
  105. package/dist/types/runtime/i18n/detection/ssr.d.ts +9 -0
  106. package/dist/types/runtime/i18n/detection/types.d.ts +29 -0
  107. package/dist/types/runtime/i18n/react-i18next.d.ts +1 -6
  108. package/dist/types/runtime/linkHelpers.d.ts +17 -0
  109. package/dist/types/runtime/pluginSetup.d.ts +32 -0
  110. package/dist/types/runtime/providerComposition.d.ts +15 -0
  111. package/dist/types/runtime/reactI18next.d.ts +7 -0
  112. package/dist/types/server/apiPrefix.d.ts +7 -0
  113. package/dist/types/server/detectorOptions.d.ts +21 -0
  114. package/dist/types/server/index.d.ts +1 -6
  115. package/dist/types/server/redirectPolicy.d.ts +16 -0
  116. package/dist/types/shared/localisedUrls/config.d.ts +9 -0
  117. package/dist/types/shared/localisedUrls/index.d.ts +7 -0
  118. package/dist/types/shared/localisedUrls/normalise.d.ts +10 -0
  119. package/dist/types/shared/localisedUrls/pathname.d.ts +3 -0
  120. package/dist/types/shared/localisedUrls/patterns.d.ts +5 -0
  121. package/dist/types/shared/localisedUrls/resolve.d.ts +8 -0
  122. package/dist/types/shared/localisedUrls/routes.d.ts +3 -0
  123. package/dist/types/shared/localisedUrls/types.d.ts +14 -0
  124. package/dist/types/shared/localisedUrls.d.ts +2 -42
  125. package/package.json +10 -10
  126. package/src/cli/index.ts +3 -2
  127. package/src/runtime/Link.tsx +11 -138
  128. package/src/runtime/context.tsx +42 -196
  129. package/src/runtime/contextHelpers.ts +232 -0
  130. package/src/runtime/core.tsx +42 -273
  131. package/src/runtime/i18n/detection/cache.ts +175 -0
  132. package/src/runtime/i18n/detection/detector.ts +157 -0
  133. package/src/runtime/i18n/detection/index.ts +10 -641
  134. package/src/runtime/i18n/detection/initOptions.ts +74 -0
  135. package/src/runtime/i18n/detection/language.ts +64 -0
  136. package/src/runtime/i18n/detection/path.ts +41 -0
  137. package/src/runtime/i18n/detection/priority.ts +82 -0
  138. package/src/runtime/i18n/detection/ssr.ts +47 -0
  139. package/src/runtime/i18n/detection/types.ts +33 -0
  140. package/src/runtime/i18n/react-i18next.ts +1 -6
  141. package/src/runtime/linkHelpers.ts +174 -0
  142. package/src/runtime/pluginSetup.ts +189 -0
  143. package/src/runtime/providerComposition.tsx +142 -0
  144. package/src/runtime/reactI18next.ts +20 -0
  145. package/src/server/apiPrefix.ts +67 -0
  146. package/src/server/detectorOptions.ts +105 -0
  147. package/src/server/index.ts +12 -327
  148. package/src/server/redirectPolicy.ts +152 -0
  149. package/src/shared/localisedUrls/config.ts +18 -0
  150. package/src/shared/localisedUrls/index.ts +13 -0
  151. package/src/shared/localisedUrls/normalise.ts +67 -0
  152. package/src/shared/localisedUrls/pathname.ts +57 -0
  153. package/src/shared/localisedUrls/patterns.ts +160 -0
  154. package/src/shared/localisedUrls/resolve.ts +118 -0
  155. package/src/shared/localisedUrls/routes.ts +226 -0
  156. package/src/shared/localisedUrls/types.ts +18 -0
  157. package/src/shared/localisedUrls.ts +20 -629
  158. package/tests/fixtures/localised-routes.golden.json +156 -0
  159. package/tests/link.test.tsx +111 -0
  160. package/tests/localisedUrls.test.ts +125 -0
  161. package/tests/reactI18nextRuntimeBoundary.test.ts +5 -2
  162. package/tests/routerAdapter.test.tsx +56 -0
@@ -0,0 +1,28 @@
1
+ import { isI18nWrapperInstance } from "../instance.mjs";
2
+ import { detectLanguageFromI18nextDetector } from "./detector.mjs";
3
+ import { mergeDetectionOptions } from "./initOptions.mjs";
4
+ import { readLanguageFromStorage } from "./middleware.mjs";
5
+ import { detectLanguageFromPathPriority } from "./path.mjs";
6
+ import { detectLanguageFromSSR } from "./ssr.mjs";
7
+ const detectLanguageWithPriority = async (i18nInstance, options)=>{
8
+ const { languages, fallbackLanguage, localePathRedirect, i18nextDetector, detection, userInitOptions, pathname, ssrContext } = options;
9
+ let detectedLanguage;
10
+ detectedLanguage = detectLanguageFromPathPriority(pathname, languages, localePathRedirect);
11
+ if (!detectedLanguage) detectedLanguage = detectLanguageFromSSR(languages);
12
+ if (!detectedLanguage && i18nextDetector) detectedLanguage = isI18nWrapperInstance(i18nInstance) ? readLanguageFromStorage(mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions)) : await detectLanguageFromI18nextDetector(i18nInstance, {
13
+ languages,
14
+ fallbackLanguage,
15
+ localePathRedirect,
16
+ i18nextDetector,
17
+ detection,
18
+ userInitOptions,
19
+ mergedBackend: options.mergedBackend,
20
+ ssrContext
21
+ });
22
+ const finalLanguage = detectedLanguage || userInitOptions?.lng || fallbackLanguage;
23
+ return {
24
+ detectedLanguage,
25
+ finalLanguage
26
+ };
27
+ };
28
+ export { detectLanguageWithPriority };
@@ -0,0 +1,26 @@
1
+ import { isBrowser } from "@modern-js/runtime";
2
+ import { getSupportedLanguage } from "./language.mjs";
3
+ function exportServerLngToWindow(context, lng) {
4
+ context.__i18nData__ = {
5
+ lng
6
+ };
7
+ }
8
+ const getLanguageFromSSRData = (window1)=>{
9
+ try {
10
+ const ssrData = window1._SSR_DATA;
11
+ if (!ssrData || !ssrData.data || !ssrData.data.i18nData) return;
12
+ const lng = ssrData.data.i18nData.lng;
13
+ return 'string' == typeof lng && '' !== lng.trim() ? lng : void 0;
14
+ } catch (error) {
15
+ return;
16
+ }
17
+ };
18
+ const detectLanguageFromSSR = (languages)=>{
19
+ if (!isBrowser()) return;
20
+ try {
21
+ const ssrLanguage = getLanguageFromSSRData(window);
22
+ const supportedLanguage = getSupportedLanguage(ssrLanguage, languages);
23
+ if (supportedLanguage) return supportedLanguage;
24
+ } catch (error) {}
25
+ };
26
+ export { detectLanguageFromSSR, exportServerLngToWindow, getLanguageFromSSRData };
File without changes
@@ -0,0 +1,98 @@
1
+ const warnedTargets = new Set();
2
+ const warnOnce = (key, message)=>{
3
+ if ('development' !== process.env.NODE_ENV || warnedTargets.has(key)) return;
4
+ warnedTargets.add(key);
5
+ console.warn(message);
6
+ };
7
+ const interpolateRouteParams = (pathname, params)=>{
8
+ if (!/[$:*{]/.test(pathname)) return pathname;
9
+ const resolveParam = (name)=>{
10
+ const value = params?.[name];
11
+ return void 0 === value ? void 0 : String(value);
12
+ };
13
+ const segments = pathname.split('/').map((segment)=>{
14
+ if (!segment) return segment;
15
+ if (segment.startsWith('{-$') && segment.endsWith('}')) {
16
+ const value = resolveParam(segment.slice(3, -1));
17
+ return void 0 === value ? null : encodeURIComponent(value);
18
+ }
19
+ if ('$' === segment || '*' === segment) {
20
+ const value = resolveParam('_splat') ?? resolveParam('*');
21
+ return void 0 === value ? null : value.split('/').map(encodeURIComponent).join('/');
22
+ }
23
+ if (segment.startsWith('$')) {
24
+ const value = resolveParam(segment.slice(1));
25
+ if (void 0 === value) {
26
+ warnOnce(`missing-param:${pathname}:${segment}`, `[plugin-i18n] <Link to="${pathname}"> is missing required param "${segment.slice(1)}".`);
27
+ return segment;
28
+ }
29
+ return encodeURIComponent(value);
30
+ }
31
+ if (segment.startsWith(':')) {
32
+ const optional = segment.endsWith('?');
33
+ const name = segment.slice(1, optional ? -1 : void 0);
34
+ const value = resolveParam(name);
35
+ if (void 0 === value) {
36
+ if (optional) return null;
37
+ warnOnce(`missing-param:${pathname}:${segment}`, `[plugin-i18n] <Link to="${pathname}"> is missing required param "${name}".`);
38
+ return segment;
39
+ }
40
+ return encodeURIComponent(value);
41
+ }
42
+ return segment;
43
+ }).filter((segment)=>null !== segment);
44
+ return segments.join('/') || '/';
45
+ };
46
+ const normalizeSearch = (search, searchFromTo)=>{
47
+ if (search && 'object' == typeof search) {
48
+ const searchObject = {};
49
+ const params = new URLSearchParams();
50
+ for (const [key, value] of Object.entries(search)){
51
+ if (null == value) continue;
52
+ if (Array.isArray(value)) {
53
+ const values = value.filter((item)=>null != item).map(String);
54
+ if (0 === values.length) continue;
55
+ searchObject[key] = values;
56
+ for (const item of values)params.append(key, item);
57
+ continue;
58
+ }
59
+ const stringValue = String(value);
60
+ searchObject[key] = stringValue;
61
+ params.append(key, stringValue);
62
+ }
63
+ const serialized = params.toString();
64
+ return {
65
+ searchString: serialized ? `?${serialized}` : '',
66
+ searchObject: Object.keys(searchObject).length > 0 ? searchObject : void 0
67
+ };
68
+ }
69
+ const raw = 'string' == typeof search ? search : searchFromTo;
70
+ if (!raw) return {
71
+ searchString: '',
72
+ searchObject: void 0
73
+ };
74
+ const searchString = raw.startsWith('?') ? raw : `?${raw}`;
75
+ const searchObject = {};
76
+ new URLSearchParams(searchString).forEach((value, key)=>{
77
+ const existing = searchObject[key];
78
+ if (void 0 === existing) searchObject[key] = value;
79
+ else if (Array.isArray(existing)) existing.push(value);
80
+ else searchObject[key] = [
81
+ existing,
82
+ value
83
+ ];
84
+ });
85
+ return {
86
+ searchString,
87
+ searchObject
88
+ };
89
+ };
90
+ const splitActiveProps = (active, activeProps)=>{
91
+ if (!active || !activeProps) return {};
92
+ return activeProps;
93
+ };
94
+ const mergeClassNames = (...values)=>{
95
+ const classNames = values.filter((value)=>'string' == typeof value && value.length > 0);
96
+ return classNames.length > 0 ? classNames.join(' ') : void 0;
97
+ };
98
+ export { interpolateRouteParams, mergeClassNames, normalizeSearch, splitActiveProps, warnOnce };
@@ -0,0 +1,64 @@
1
+ import { isBrowser } from "@modern-js/runtime";
2
+ import { merge } from "@modern-js/runtime-utils/merge";
3
+ import { getI18nInstance } from "./i18n/index.mjs";
4
+ import { mergeBackendOptions } from "./i18n/backend/index.mjs";
5
+ import { detectLanguageWithPriority, exportServerLngToWindow, mergeDetectionOptions } from "./i18n/detection/index.mjs";
6
+ import { useI18nextLanguageDetector } from "./i18n/detection/middleware.mjs";
7
+ import { getPathname } from "./utils.mjs";
8
+ let i18nLifecycleHelpersPromise;
9
+ function loadI18nLifecycleHelpers() {
10
+ i18nLifecycleHelpersPromise ??= Promise.all([
11
+ import("./i18n/backend/middleware.mjs"),
12
+ import("./i18n/utils.mjs")
13
+ ]).then(([backendMiddleware, utils])=>({
14
+ useI18nextBackend: backendMiddleware.useI18nextBackend,
15
+ changeI18nLanguage: utils.changeI18nLanguage,
16
+ ensureLanguageMatch: utils.ensureLanguageMatch,
17
+ initializeI18nInstance: utils.initializeI18nInstance,
18
+ setupClonedInstance: utils.setupClonedInstance
19
+ }));
20
+ return i18nLifecycleHelpersPromise;
21
+ }
22
+ async function setupI18nBeforeRender(context, options) {
23
+ const { api, userI18nInstance, initOptions, backend, backendEnabled, i18nextDetector, detection, localePathRedirect, languages, fallbackLanguage, resolveReactI18nextIntegration, setI18nextProvider } = options;
24
+ const { useI18nextBackend, changeI18nLanguage, ensureLanguageMatch, initializeI18nInstance, setupClonedInstance } = await loadI18nLifecycleHelpers();
25
+ let i18nInstance = await getI18nInstance(userI18nInstance);
26
+ const { i18n: otherConfig } = api.getRuntimeConfig();
27
+ const { initOptions: otherInitOptions } = otherConfig || {};
28
+ const userInitOptions = merge(otherInitOptions || {}, initOptions || {});
29
+ const reactI18nextIntegration = await resolveReactI18nextIntegration();
30
+ setI18nextProvider(reactI18nextIntegration?.I18nextProvider ?? null);
31
+ if (reactI18nextIntegration?.initReactI18next) i18nInstance.use(reactI18nextIntegration.initReactI18next);
32
+ const pathname = getPathname(context);
33
+ if (i18nextDetector) useI18nextLanguageDetector(i18nInstance);
34
+ const mergedDetection = mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions);
35
+ const mergedBackend = mergeBackendOptions(backend, userInitOptions);
36
+ const hasSdkConfig = 'function' == typeof userInitOptions?.backend?.sdk || mergedBackend?.sdk && 'function' == typeof mergedBackend.sdk;
37
+ if (mergedBackend && (backendEnabled || hasSdkConfig)) useI18nextBackend(i18nInstance, mergedBackend);
38
+ const { finalLanguage } = await detectLanguageWithPriority(i18nInstance, {
39
+ languages,
40
+ fallbackLanguage,
41
+ localePathRedirect,
42
+ i18nextDetector,
43
+ detection,
44
+ userInitOptions,
45
+ mergedBackend,
46
+ pathname,
47
+ ssrContext: context.ssrContext
48
+ });
49
+ await initializeI18nInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, mergedDetection, mergedBackend, userInitOptions);
50
+ if (!isBrowser() && i18nInstance.cloneInstance) {
51
+ i18nInstance = i18nInstance.cloneInstance();
52
+ await setupClonedInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, backendEnabled, backend, i18nextDetector, detection, localePathRedirect, userInitOptions);
53
+ }
54
+ if (localePathRedirect) await ensureLanguageMatch(i18nInstance, finalLanguage);
55
+ if (!isBrowser()) exportServerLngToWindow(context, finalLanguage);
56
+ context.i18nInstance = i18nInstance;
57
+ context.changeLanguage = async (newLang)=>{
58
+ await changeI18nLanguage(i18nInstance, newLang, {
59
+ detectionOptions: mergedDetection
60
+ });
61
+ };
62
+ return i18nInstance;
63
+ }
64
+ export { setupI18nBeforeRender };
@@ -0,0 +1,75 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { RuntimeContext } from "@modern-js/runtime";
3
+ import { Helmet } from "@modern-js/runtime/head";
4
+ import { useContext, useEffect, useMemo, useRef, useState } from "react";
5
+ import { ModernI18nProvider } from "./context.mjs";
6
+ import { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader } from "./hooks.mjs";
7
+ import { getI18nextInstanceForProvider } from "./i18n/instance.mjs";
8
+ const createI18nRootWrapper = (options)=>(App)=>(props)=>{
9
+ const { entryName, htmlLangAttr, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls, getLatestI18nInstance, getI18nextProvider } = options;
10
+ const runtimeContext = useContext(RuntimeContext);
11
+ const i18nInstance = runtimeContext.i18nInstance || getLatestI18nInstance();
12
+ const initialLang = useMemo(()=>i18nInstance?.language || fallbackLanguage, [
13
+ i18nInstance?.language,
14
+ fallbackLanguage
15
+ ]);
16
+ const [lang, setLang] = useState(initialLang);
17
+ const [forceUpdate, setForceUpdate] = useState(0);
18
+ const prevLangRef = useRef(lang);
19
+ const runtimeContextRef = useRef(runtimeContext);
20
+ runtimeContextRef.current = runtimeContext;
21
+ useEffect(()=>{
22
+ if (i18nInstance?.language) {
23
+ const translator = i18nInstance.translator;
24
+ if (translator) translator.language = i18nInstance.language;
25
+ }
26
+ }, [
27
+ i18nInstance?.language
28
+ ]);
29
+ useEffect(()=>{
30
+ prevLangRef.current = lang;
31
+ }, [
32
+ lang
33
+ ]);
34
+ useSdkResourcesLoader(i18nInstance, setForceUpdate);
35
+ useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang);
36
+ useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls);
37
+ const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang), [
38
+ lang,
39
+ i18nInstance,
40
+ entryName,
41
+ languages,
42
+ localePathRedirect,
43
+ ignoreRedirectRoutes,
44
+ localisedUrls,
45
+ forceUpdate
46
+ ]);
47
+ const children = props.children;
48
+ const appContent = /*#__PURE__*/ jsxs(Fragment, {
49
+ children: [
50
+ Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
51
+ htmlAttributes: {
52
+ lang
53
+ }
54
+ }),
55
+ /*#__PURE__*/ jsx(ModernI18nProvider, {
56
+ value: contextValue,
57
+ children: App ? /*#__PURE__*/ jsx(App, {
58
+ ...props,
59
+ children: children
60
+ }) : children
61
+ })
62
+ ]
63
+ });
64
+ if (!i18nInstance) return appContent;
65
+ const I18nextProvider = getI18nextProvider();
66
+ if (I18nextProvider) {
67
+ const i18nextInstanceForProvider = getI18nextInstanceForProvider(i18nInstance);
68
+ return /*#__PURE__*/ jsx(I18nextProvider, {
69
+ i18n: i18nextInstanceForProvider,
70
+ children: appContent
71
+ });
72
+ }
73
+ return appContent;
74
+ };
75
+ export { createI18nRootWrapper };
@@ -0,0 +1,5 @@
1
+ const resolveReactI18nextIntegration = async (reactI18next, loadReactI18nextIntegration)=>{
2
+ if (!reactI18next) return null;
3
+ return loadReactI18nextIntegration?.() ?? null;
4
+ };
5
+ export { resolveReactI18nextIntegration };
@@ -0,0 +1,31 @@
1
+ const normalizeApiPrefix = (prefix)=>{
2
+ const trimmedPrefix = prefix.trim();
3
+ if (!trimmedPrefix) return null;
4
+ const prefixedPath = trimmedPrefix.startsWith('/') ? trimmedPrefix : `/${trimmedPrefix}`;
5
+ const withoutWildcard = prefixedPath.replace(/\/\*$/, '');
6
+ const normalizedPrefix = withoutWildcard.length > 1 ? withoutWildcard.replace(/\/+$/, '') : withoutWildcard;
7
+ return '/' === normalizedPrefix ? null : normalizedPrefix;
8
+ };
9
+ const collectApiPrefixes = (routes, bffPrefix)=>{
10
+ const prefixes = new Set();
11
+ for (const route of routes){
12
+ if (!route.isApi || !route.urlPath) continue;
13
+ const normalizedPrefix = normalizeApiPrefix(route.urlPath);
14
+ if (normalizedPrefix) prefixes.add(normalizedPrefix);
15
+ }
16
+ const bffPrefixes = Array.isArray(bffPrefix) ? bffPrefix : bffPrefix ? [
17
+ bffPrefix
18
+ ] : [];
19
+ for (const prefix of bffPrefixes){
20
+ const normalizedPrefix = normalizeApiPrefix(prefix);
21
+ if (normalizedPrefix) prefixes.add(normalizedPrefix);
22
+ }
23
+ return [
24
+ ...prefixes
25
+ ];
26
+ };
27
+ const matchesApiPrefix = (pathname, apiPrefixes)=>{
28
+ const normalizedPathname = pathname.startsWith('/') ? pathname : `/${pathname}`;
29
+ return apiPrefixes.some((prefix)=>normalizedPathname === prefix || normalizedPathname.startsWith(`${prefix}/`));
30
+ };
31
+ export { collectApiPrefixes, matchesApiPrefix };
@@ -0,0 +1,59 @@
1
+ import { DEFAULT_I18NEXT_DETECTION_OPTIONS, mergeDetectionOptions } from "../runtime/i18n/detection/config.mjs";
2
+ const convertToHonoLanguageDetectorOptions = (languages, fallbackLanguage, detectionOptions)=>{
3
+ const mergedDetection = detectionOptions ? mergeDetectionOptions(detectionOptions) : DEFAULT_I18NEXT_DETECTION_OPTIONS;
4
+ const order = (mergedDetection.order || []).filter((item)=>![
5
+ 'path',
6
+ 'localStorage',
7
+ 'navigator',
8
+ 'htmlTag',
9
+ 'subdomain'
10
+ ].includes(item));
11
+ const detectionOrder = order.length > 0 ? order : [
12
+ 'querystring',
13
+ 'cookie',
14
+ 'header'
15
+ ];
16
+ const honoOrder = detectionOrder.map((item)=>{
17
+ if ('querystring' === item) return 'querystring';
18
+ if ('cookie' === item) return 'cookie';
19
+ if ('header' === item) return 'header';
20
+ return item;
21
+ });
22
+ const caches = false === mergedDetection.caches ? false : Array.isArray(mergedDetection.caches) && !mergedDetection.caches.includes('cookie') ? false : [
23
+ 'cookie'
24
+ ];
25
+ const cookieMinutes = mergedDetection.cookieMinutes;
26
+ const cookieMaxAge = 'number' == typeof cookieMinutes && Number.isFinite(cookieMinutes) ? Math.max(0, Math.floor(60 * cookieMinutes)) : 60 * DEFAULT_I18NEXT_DETECTION_OPTIONS.cookieMinutes;
27
+ const cookieDomain = mergedDetection.cookieDomain;
28
+ const cookieSecure = mergedDetection.cookieSecure;
29
+ const cookieHttpOnly = mergedDetection.cookieHttpOnly;
30
+ const cookieSameSite = mergedDetection.cookieSameSite;
31
+ const normalizedCookieDomain = 'string' == typeof cookieDomain ? cookieDomain : void 0;
32
+ const cookieOptions = {
33
+ maxAge: cookieMaxAge,
34
+ sameSite: 'None' === cookieSameSite || 'none' === cookieSameSite ? 'None' : 'Lax' === cookieSameSite || 'lax' === cookieSameSite ? 'Lax' : 'Strict',
35
+ secure: 'boolean' == typeof cookieSecure ? cookieSecure : false,
36
+ httpOnly: 'boolean' == typeof cookieHttpOnly ? cookieHttpOnly : false,
37
+ ...normalizedCookieDomain ? {
38
+ domain: normalizedCookieDomain
39
+ } : {}
40
+ };
41
+ return {
42
+ supportedLanguages: languages.length > 0 ? languages : [
43
+ fallbackLanguage
44
+ ],
45
+ fallbackLanguage,
46
+ order: honoOrder,
47
+ lookupQueryString: mergedDetection.lookupQuerystring || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring || 'lng',
48
+ lookupCookie: mergedDetection.lookupCookie || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie || 'i18next',
49
+ lookupFromHeaderKey: mergedDetection.lookupHeader || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader || 'accept-language',
50
+ ...void 0 !== caches && {
51
+ caches
52
+ },
53
+ ...false !== caches && {
54
+ cookieOptions
55
+ },
56
+ ignoreCase: true
57
+ };
58
+ };
59
+ export { convertToHonoLanguageDetectorOptions };
@@ -1,145 +1,10 @@
1
- import { DEFAULT_I18NEXT_DETECTION_OPTIONS, mergeDetectionOptions } from "../runtime/i18n/detection/config.mjs";
2
- import { localiseTargetPathname, resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
1
+ import { resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
3
2
  import { getLocaleDetectionOptions } from "../shared/utils.mjs";
3
+ import { collectApiPrefixes, matchesApiPrefix } from "./apiPrefix.mjs";
4
+ import { convertToHonoLanguageDetectorOptions } from "./detectorOptions.mjs";
5
+ import { buildLocalizedUrl, createLocaleRedirectResponse, getLanguageFromPath, isStaticResourceRequest, shouldIgnoreRedirect } from "./redirectPolicy.mjs";
4
6
  import * as __rspack_external__modern_js_server_core_hono_a76ca254 from "@modern-js/server-core/hono";
5
7
  const { languageDetector: languageDetector } = __rspack_external__modern_js_server_core_hono_a76ca254;
6
- const normalizeApiPrefix = (prefix)=>{
7
- const trimmedPrefix = prefix.trim();
8
- if (!trimmedPrefix) return null;
9
- const prefixedPath = trimmedPrefix.startsWith('/') ? trimmedPrefix : `/${trimmedPrefix}`;
10
- const withoutWildcard = prefixedPath.replace(/\/\*$/, '');
11
- const normalizedPrefix = withoutWildcard.length > 1 ? withoutWildcard.replace(/\/+$/, '') : withoutWildcard;
12
- return '/' === normalizedPrefix ? null : normalizedPrefix;
13
- };
14
- const collectApiPrefixes = (routes, bffPrefix)=>{
15
- const prefixes = new Set();
16
- for (const route of routes){
17
- if (!route.isApi || !route.urlPath) continue;
18
- const normalizedPrefix = normalizeApiPrefix(route.urlPath);
19
- if (normalizedPrefix) prefixes.add(normalizedPrefix);
20
- }
21
- const bffPrefixes = Array.isArray(bffPrefix) ? bffPrefix : bffPrefix ? [
22
- bffPrefix
23
- ] : [];
24
- for (const prefix of bffPrefixes){
25
- const normalizedPrefix = normalizeApiPrefix(prefix);
26
- if (normalizedPrefix) prefixes.add(normalizedPrefix);
27
- }
28
- return [
29
- ...prefixes
30
- ];
31
- };
32
- const matchesApiPrefix = (pathname, apiPrefixes)=>{
33
- const normalizedPathname = pathname.startsWith('/') ? pathname : `/${pathname}`;
34
- return apiPrefixes.some((prefix)=>normalizedPathname === prefix || normalizedPathname.startsWith(`${prefix}/`));
35
- };
36
- const convertToHonoLanguageDetectorOptions = (languages, fallbackLanguage, detectionOptions)=>{
37
- const mergedDetection = detectionOptions ? mergeDetectionOptions(detectionOptions) : DEFAULT_I18NEXT_DETECTION_OPTIONS;
38
- const order = (mergedDetection.order || []).filter((item)=>![
39
- 'path',
40
- 'localStorage',
41
- 'navigator',
42
- 'htmlTag',
43
- 'subdomain'
44
- ].includes(item));
45
- const detectionOrder = order.length > 0 ? order : [
46
- 'querystring',
47
- 'cookie',
48
- 'header'
49
- ];
50
- const honoOrder = detectionOrder.map((item)=>{
51
- if ('querystring' === item) return 'querystring';
52
- if ('cookie' === item) return 'cookie';
53
- if ('header' === item) return 'header';
54
- return item;
55
- });
56
- const caches = false === mergedDetection.caches ? false : Array.isArray(mergedDetection.caches) && !mergedDetection.caches.includes('cookie') ? false : [
57
- 'cookie'
58
- ];
59
- const cookieMinutes = mergedDetection.cookieMinutes;
60
- const cookieMaxAge = 'number' == typeof cookieMinutes && Number.isFinite(cookieMinutes) ? Math.max(0, Math.floor(60 * cookieMinutes)) : 60 * DEFAULT_I18NEXT_DETECTION_OPTIONS.cookieMinutes;
61
- const cookieDomain = mergedDetection.cookieDomain;
62
- const cookieSecure = mergedDetection.cookieSecure;
63
- const cookieHttpOnly = mergedDetection.cookieHttpOnly;
64
- const cookieSameSite = mergedDetection.cookieSameSite;
65
- const normalizedCookieDomain = 'string' == typeof cookieDomain ? cookieDomain : void 0;
66
- const cookieOptions = {
67
- maxAge: cookieMaxAge,
68
- sameSite: 'None' === cookieSameSite || 'none' === cookieSameSite ? 'None' : 'Lax' === cookieSameSite || 'lax' === cookieSameSite ? 'Lax' : 'Strict',
69
- secure: 'boolean' == typeof cookieSecure ? cookieSecure : false,
70
- httpOnly: 'boolean' == typeof cookieHttpOnly ? cookieHttpOnly : false,
71
- ...normalizedCookieDomain ? {
72
- domain: normalizedCookieDomain
73
- } : {}
74
- };
75
- return {
76
- supportedLanguages: languages.length > 0 ? languages : [
77
- fallbackLanguage
78
- ],
79
- fallbackLanguage,
80
- order: honoOrder,
81
- lookupQueryString: mergedDetection.lookupQuerystring || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring || 'lng',
82
- lookupCookie: mergedDetection.lookupCookie || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie || 'i18next',
83
- lookupFromHeaderKey: mergedDetection.lookupHeader || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader || 'accept-language',
84
- ...void 0 !== caches && {
85
- caches
86
- },
87
- ...false !== caches && {
88
- cookieOptions
89
- },
90
- ignoreCase: true
91
- };
92
- };
93
- const shouldIgnoreRedirect = (pathname, urlPath, ignoreRedirectRoutes)=>{
94
- if (!ignoreRedirectRoutes) return false;
95
- const basePath = urlPath.replace('/*', '');
96
- const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
97
- const normalizedPath = remainingPath.startsWith('/') ? remainingPath : `/${remainingPath}`;
98
- if ('function' == typeof ignoreRedirectRoutes) return ignoreRedirectRoutes(normalizedPath);
99
- return ignoreRedirectRoutes.some((pattern)=>normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`));
100
- };
101
- const isStaticResourceRequest = (pathname, staticRoutePrefixes, languages = [])=>{
102
- if (staticRoutePrefixes.some((prefix)=>pathname.startsWith(`${prefix}/`) || pathname === prefix)) return true;
103
- const standardStaticPrefixes = [
104
- '/static/',
105
- '/upload/'
106
- ];
107
- if (standardStaticPrefixes.some((prefix)=>pathname.startsWith(prefix))) return true;
108
- const pathSegments = pathname.split('/').filter(Boolean);
109
- if (pathSegments.length > 0 && languages.includes(pathSegments[0])) {
110
- const pathWithoutLang = '/' + pathSegments.slice(1).join('/');
111
- if (standardStaticPrefixes.some((prefix)=>pathWithoutLang.startsWith(prefix)) || staticRoutePrefixes.some((prefix)=>pathWithoutLang.startsWith(`${prefix}/`) || pathWithoutLang === prefix)) return true;
112
- }
113
- return false;
114
- };
115
- const getLanguageFromPath = (req, urlPath, languages)=>{
116
- const url = new URL(req.url, `http://${req.header().host}`);
117
- const pathname = url.pathname;
118
- const basePath = urlPath.replace('/*', '');
119
- const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
120
- const segments = remainingPath.split('/').filter(Boolean);
121
- const firstSegment = segments[0];
122
- if (languages.includes(firstSegment)) return firstSegment;
123
- return null;
124
- };
125
- const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
126
- const url = new URL(req.url);
127
- const pathname = url.pathname;
128
- const basePath = urlPath.replace('/*', '');
129
- const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
130
- const newPathname = localiseTargetPathname(remainingPath, language, languages, localisedUrls);
131
- const suffix = `${url.search}${url.hash}`;
132
- const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
133
- return localizedUrl;
134
- };
135
- const createLocaleRedirectResponse = (location)=>new Response(null, {
136
- status: 302,
137
- headers: {
138
- 'Cache-Control': 'private, no-store',
139
- Location: location,
140
- Vary: 'Accept-Language, Cookie'
141
- }
142
- });
143
8
  const i18nServerPlugin = (options)=>({
144
9
  name: '@modern-js/plugin-i18n/server',
145
10
  setup: (api)=>{
@@ -0,0 +1,52 @@
1
+ import { localiseTargetPathname } from "../shared/localisedUrls.mjs";
2
+ const shouldIgnoreRedirect = (pathname, urlPath, ignoreRedirectRoutes)=>{
3
+ if (!ignoreRedirectRoutes) return false;
4
+ const basePath = urlPath.replace('/*', '');
5
+ const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
6
+ const normalizedPath = remainingPath.startsWith('/') ? remainingPath : `/${remainingPath}`;
7
+ if ('function' == typeof ignoreRedirectRoutes) return ignoreRedirectRoutes(normalizedPath);
8
+ return ignoreRedirectRoutes.some((pattern)=>normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`));
9
+ };
10
+ const isStaticResourceRequest = (pathname, staticRoutePrefixes, languages = [])=>{
11
+ if (staticRoutePrefixes.some((prefix)=>pathname.startsWith(`${prefix}/`) || pathname === prefix)) return true;
12
+ const standardStaticPrefixes = [
13
+ '/static/',
14
+ '/upload/'
15
+ ];
16
+ if (standardStaticPrefixes.some((prefix)=>pathname.startsWith(prefix))) return true;
17
+ const pathSegments = pathname.split('/').filter(Boolean);
18
+ if (pathSegments.length > 0 && languages.includes(pathSegments[0])) {
19
+ const pathWithoutLang = '/' + pathSegments.slice(1).join('/');
20
+ if (standardStaticPrefixes.some((prefix)=>pathWithoutLang.startsWith(prefix)) || staticRoutePrefixes.some((prefix)=>pathWithoutLang.startsWith(`${prefix}/`) || pathWithoutLang === prefix)) return true;
21
+ }
22
+ return false;
23
+ };
24
+ const getLanguageFromPath = (req, urlPath, languages)=>{
25
+ const url = new URL(req.url, `http://${req.header().host}`);
26
+ const pathname = url.pathname;
27
+ const basePath = urlPath.replace('/*', '');
28
+ const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
29
+ const segments = remainingPath.split('/').filter(Boolean);
30
+ const firstSegment = segments[0];
31
+ if (languages.includes(firstSegment)) return firstSegment;
32
+ return null;
33
+ };
34
+ const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
35
+ const url = new URL(req.url);
36
+ const pathname = url.pathname;
37
+ const basePath = urlPath.replace('/*', '');
38
+ const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
39
+ const newPathname = localiseTargetPathname(remainingPath, language, languages, localisedUrls);
40
+ const suffix = `${url.search}${url.hash}`;
41
+ const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
42
+ return localizedUrl;
43
+ };
44
+ const createLocaleRedirectResponse = (location)=>new Response(null, {
45
+ status: 302,
46
+ headers: {
47
+ 'Cache-Control': 'private, no-store',
48
+ Location: location,
49
+ Vary: 'Accept-Language, Cookie'
50
+ }
51
+ });
52
+ export { buildLocalizedUrl, createLocaleRedirectResponse, getLanguageFromPath, isStaticResourceRequest, shouldIgnoreRedirect };
@@ -0,0 +1,11 @@
1
+ const resolveLocalisedUrlsConfig = (option)=>{
2
+ if (option && 'object' == typeof option && Object.keys(option).length > 0) return {
3
+ enabled: true,
4
+ map: option
5
+ };
6
+ return {
7
+ enabled: false,
8
+ map: {}
9
+ };
10
+ };
11
+ export { resolveLocalisedUrlsConfig };
@@ -0,0 +1,6 @@
1
+ export { resolveLocalisedUrlsConfig } from "./config.mjs";
2
+ export { normalisePathPattern, normalisePathname } from "./normalise.mjs";
3
+ export { canonicalTargetPathname, localiseTargetPathname } from "./pathname.mjs";
4
+ export { buildPathFromPattern, matchPathPattern } from "./patterns.mjs";
5
+ export { resolveCanonicalLocalisedPath, resolveLocalisedPath } from "./resolve.mjs";
6
+ export { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from "./routes.mjs";
@@ -0,0 +1,37 @@
1
+ const LOCALE_PARAM_NAMES = new Set([
2
+ 'lang',
3
+ 'locale',
4
+ 'language'
5
+ ]);
6
+ const normaliseSlashes = (path)=>{
7
+ const withoutDuplicateSlashes = path.replace(/\/+/g, '/');
8
+ const withLeadingSlash = withoutDuplicateSlashes.startsWith('/') ? withoutDuplicateSlashes : `/${withoutDuplicateSlashes}`;
9
+ return withLeadingSlash.length > 1 ? withLeadingSlash.replace(/\/+$/, '') : withLeadingSlash;
10
+ };
11
+ const normalisePathPattern = (path)=>normaliseSlashes(path).replace(/\[(.+?)\]/g, ':$1');
12
+ const normalisePathname = (pathname)=>normaliseSlashes(pathname);
13
+ const normaliseRoutePath = (path)=>{
14
+ const normalized = normalisePathPattern(path);
15
+ return '/' === normalized ? '' : normalized.slice(1);
16
+ };
17
+ const getLocaleParamSegment = (segment)=>{
18
+ if (!segment.startsWith(':')) return null;
19
+ const paramName = segment.slice(1).replace(/\?$/, '');
20
+ return LOCALE_PARAM_NAMES.has(paramName) ? segment : null;
21
+ };
22
+ const splitPathSegments = (path)=>{
23
+ if (!path) return [];
24
+ return normalisePathPattern(path).split('/').filter(Boolean);
25
+ };
26
+ const stripLeadingLocaleParam = (path)=>{
27
+ const segments = splitPathSegments(path);
28
+ const leadingLocaleParam = getLocaleParamSegment(segments[0] || '');
29
+ if (!leadingLocaleParam) return path;
30
+ const remainingPath = segments.slice(1).join('/');
31
+ return remainingPath ? `/${remainingPath}` : void 0;
32
+ };
33
+ const getLeadingLocaleParam = (path)=>{
34
+ const segments = splitPathSegments(path);
35
+ return getLocaleParamSegment(segments[0] || '');
36
+ };
37
+ export { getLeadingLocaleParam, normalisePathPattern, normalisePathname, normaliseRoutePath, stripLeadingLocaleParam };