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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/dist/cjs/cli/index.js +2 -1
  2. package/dist/cjs/cli/locales.js +15 -15
  3. package/dist/cjs/runtime/Link.js +8 -89
  4. package/dist/cjs/runtime/context.js +78 -91
  5. package/dist/cjs/runtime/contextHelpers.js +143 -0
  6. package/dist/cjs/runtime/core.js +32 -132
  7. package/dist/cjs/runtime/hooks.js +54 -21
  8. package/dist/cjs/runtime/i18n/backend/defaults.node.js +7 -7
  9. package/dist/cjs/runtime/i18n/detection/cache.js +150 -0
  10. package/dist/cjs/runtime/i18n/detection/detector.js +100 -0
  11. package/dist/cjs/runtime/i18n/detection/index.js +8 -263
  12. package/dist/cjs/runtime/i18n/detection/initOptions.js +69 -0
  13. package/dist/cjs/runtime/i18n/detection/language.js +62 -0
  14. package/dist/cjs/runtime/i18n/detection/path.js +49 -0
  15. package/dist/cjs/runtime/i18n/detection/priority.js +66 -0
  16. package/dist/cjs/runtime/i18n/detection/ssr.js +70 -0
  17. package/dist/cjs/runtime/i18n/detection/types.js +18 -0
  18. package/dist/cjs/runtime/i18n/instance.js +34 -10
  19. package/dist/cjs/runtime/linkHelpers.js +148 -0
  20. package/dist/cjs/runtime/pluginSetup.js +102 -0
  21. package/dist/cjs/runtime/providerComposition.js +115 -0
  22. package/dist/cjs/runtime/reactI18next.js +43 -0
  23. package/dist/cjs/runtime/routerAdapter.js +16 -8
  24. package/dist/cjs/runtime/utils.js +1 -9
  25. package/dist/cjs/server/apiPrefix.js +72 -0
  26. package/dist/cjs/server/detectorOptions.js +97 -0
  27. package/dist/cjs/server/index.js +17 -144
  28. package/dist/cjs/server/redirectPolicy.js +96 -0
  29. package/dist/cjs/shared/detection.js +2 -104
  30. package/dist/cjs/shared/localisedUrls/config.js +49 -0
  31. package/dist/cjs/shared/localisedUrls/index.js +91 -0
  32. package/dist/cjs/shared/localisedUrls/normalise.js +87 -0
  33. package/dist/cjs/shared/localisedUrls/pathname.js +68 -0
  34. package/dist/cjs/shared/localisedUrls/patterns.js +130 -0
  35. package/dist/cjs/shared/localisedUrls/redirect.js +103 -0
  36. package/dist/cjs/shared/localisedUrls/resolve.js +88 -0
  37. package/dist/cjs/shared/localisedUrls/routes.js +130 -0
  38. package/dist/cjs/shared/localisedUrls/types.js +18 -0
  39. package/dist/cjs/shared/localisedUrls.js +28 -294
  40. package/dist/cjs/shared/utils.js +2 -8
  41. package/dist/esm/cli/index.mjs +2 -1
  42. package/dist/esm/cli/locales.mjs +13 -13
  43. package/dist/esm/runtime/Link.mjs +1 -82
  44. package/dist/esm/runtime/context.mjs +76 -92
  45. package/dist/esm/runtime/contextHelpers.mjs +87 -0
  46. package/dist/esm/runtime/core.mjs +30 -133
  47. package/dist/esm/runtime/hooks.mjs +56 -23
  48. package/dist/esm/runtime/i18n/backend/defaults.node.mjs +5 -5
  49. package/dist/esm/runtime/i18n/detection/cache.mjs +103 -0
  50. package/dist/esm/runtime/i18n/detection/detector.mjs +62 -0
  51. package/dist/esm/runtime/i18n/detection/index.mjs +4 -260
  52. package/dist/esm/runtime/i18n/detection/initOptions.mjs +28 -0
  53. package/dist/esm/runtime/i18n/detection/language.mjs +21 -0
  54. package/dist/esm/runtime/i18n/detection/path.mjs +11 -0
  55. package/dist/esm/runtime/i18n/detection/priority.mjs +28 -0
  56. package/dist/esm/runtime/i18n/detection/ssr.mjs +26 -0
  57. package/dist/esm/runtime/i18n/detection/types.mjs +0 -0
  58. package/dist/esm/runtime/i18n/instance.mjs +35 -8
  59. package/dist/esm/runtime/linkHelpers.mjs +98 -0
  60. package/dist/esm/runtime/pluginSetup.mjs +64 -0
  61. package/dist/esm/runtime/providerComposition.mjs +77 -0
  62. package/dist/esm/runtime/reactI18next.mjs +5 -0
  63. package/dist/esm/runtime/routerAdapter.mjs +16 -8
  64. package/dist/esm/runtime/utils.mjs +2 -10
  65. package/dist/esm/server/apiPrefix.mjs +31 -0
  66. package/dist/esm/server/detectorOptions.mjs +59 -0
  67. package/dist/esm/server/index.mjs +8 -135
  68. package/dist/esm/server/redirectPolicy.mjs +46 -0
  69. package/dist/esm/shared/detection.mjs +1 -71
  70. package/dist/esm/shared/localisedUrls/config.mjs +11 -0
  71. package/dist/esm/shared/localisedUrls/index.mjs +7 -0
  72. package/dist/esm/shared/localisedUrls/normalise.mjs +37 -0
  73. package/dist/esm/shared/localisedUrls/pathname.mjs +24 -0
  74. package/dist/esm/shared/localisedUrls/patterns.mjs +86 -0
  75. package/dist/esm/shared/localisedUrls/redirect.mjs +56 -0
  76. package/dist/esm/shared/localisedUrls/resolve.mjs +47 -0
  77. package/dist/esm/shared/localisedUrls/routes.mjs +89 -0
  78. package/dist/esm/shared/localisedUrls/types.mjs +0 -0
  79. package/dist/esm/shared/localisedUrls.mjs +1 -283
  80. package/dist/esm/shared/utils.mjs +1 -1
  81. package/dist/esm-node/cli/index.mjs +2 -1
  82. package/dist/esm-node/cli/locales.mjs +13 -13
  83. package/dist/esm-node/runtime/Link.mjs +1 -82
  84. package/dist/esm-node/runtime/context.mjs +76 -92
  85. package/dist/esm-node/runtime/contextHelpers.mjs +88 -0
  86. package/dist/esm-node/runtime/core.mjs +30 -133
  87. package/dist/esm-node/runtime/hooks.mjs +56 -23
  88. package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +5 -5
  89. package/dist/esm-node/runtime/i18n/detection/cache.mjs +104 -0
  90. package/dist/esm-node/runtime/i18n/detection/detector.mjs +63 -0
  91. package/dist/esm-node/runtime/i18n/detection/index.mjs +4 -260
  92. package/dist/esm-node/runtime/i18n/detection/initOptions.mjs +29 -0
  93. package/dist/esm-node/runtime/i18n/detection/language.mjs +22 -0
  94. package/dist/esm-node/runtime/i18n/detection/path.mjs +12 -0
  95. package/dist/esm-node/runtime/i18n/detection/priority.mjs +29 -0
  96. package/dist/esm-node/runtime/i18n/detection/ssr.mjs +27 -0
  97. package/dist/esm-node/runtime/i18n/detection/types.mjs +1 -0
  98. package/dist/esm-node/runtime/i18n/instance.mjs +35 -8
  99. package/dist/esm-node/runtime/linkHelpers.mjs +99 -0
  100. package/dist/esm-node/runtime/pluginSetup.mjs +65 -0
  101. package/dist/esm-node/runtime/providerComposition.mjs +78 -0
  102. package/dist/esm-node/runtime/reactI18next.mjs +6 -0
  103. package/dist/esm-node/runtime/routerAdapter.mjs +16 -8
  104. package/dist/esm-node/runtime/utils.mjs +2 -10
  105. package/dist/esm-node/server/apiPrefix.mjs +32 -0
  106. package/dist/esm-node/server/detectorOptions.mjs +60 -0
  107. package/dist/esm-node/server/index.mjs +8 -135
  108. package/dist/esm-node/server/redirectPolicy.mjs +47 -0
  109. package/dist/esm-node/shared/detection.mjs +1 -71
  110. package/dist/esm-node/shared/localisedUrls/config.mjs +12 -0
  111. package/dist/esm-node/shared/localisedUrls/index.mjs +8 -0
  112. package/dist/esm-node/shared/localisedUrls/normalise.mjs +38 -0
  113. package/dist/esm-node/shared/localisedUrls/pathname.mjs +25 -0
  114. package/dist/esm-node/shared/localisedUrls/patterns.mjs +87 -0
  115. package/dist/esm-node/shared/localisedUrls/redirect.mjs +57 -0
  116. package/dist/esm-node/shared/localisedUrls/resolve.mjs +48 -0
  117. package/dist/esm-node/shared/localisedUrls/routes.mjs +90 -0
  118. package/dist/esm-node/shared/localisedUrls/types.mjs +1 -0
  119. package/dist/esm-node/shared/localisedUrls.mjs +1 -283
  120. package/dist/esm-node/shared/utils.mjs +1 -1
  121. package/dist/types/cli/locales.d.ts +1 -1
  122. package/dist/types/runtime/Link.d.ts +3 -7
  123. package/dist/types/runtime/context.d.ts +21 -5
  124. package/dist/types/runtime/contextHelpers.d.ts +29 -0
  125. package/dist/types/runtime/core.d.ts +4 -8
  126. package/dist/types/runtime/hooks.d.ts +3 -7
  127. package/dist/types/runtime/i18n/backend/middleware.common.d.ts +2 -1
  128. package/dist/types/runtime/i18n/detection/cache.d.ts +14 -0
  129. package/dist/types/runtime/i18n/detection/detector.d.ts +8 -0
  130. package/dist/types/runtime/i18n/detection/index.d.ts +5 -50
  131. package/dist/types/runtime/i18n/detection/initOptions.d.ts +10 -0
  132. package/dist/types/runtime/i18n/detection/language.d.ts +11 -0
  133. package/dist/types/runtime/i18n/detection/middleware.d.ts +4 -4
  134. package/dist/types/runtime/i18n/detection/middleware.node.d.ts +4 -4
  135. package/dist/types/runtime/i18n/detection/path.d.ts +5 -0
  136. package/dist/types/runtime/i18n/detection/priority.d.ts +10 -0
  137. package/dist/types/runtime/i18n/detection/ssr.d.ts +9 -0
  138. package/dist/types/runtime/i18n/detection/types.d.ts +32 -0
  139. package/dist/types/runtime/i18n/instance.d.ts +31 -27
  140. package/dist/types/runtime/i18n/react-i18next.d.ts +1 -6
  141. package/dist/types/runtime/i18n/utils.d.ts +14 -5
  142. package/dist/types/runtime/linkHelpers.d.ts +17 -0
  143. package/dist/types/runtime/pluginSetup.d.ts +32 -0
  144. package/dist/types/runtime/providerComposition.d.ts +16 -0
  145. package/dist/types/runtime/reactI18next.d.ts +7 -0
  146. package/dist/types/runtime/routerAdapter.d.ts +7 -6
  147. package/dist/types/runtime/utils.d.ts +7 -2
  148. package/dist/types/server/apiPrefix.d.ts +7 -0
  149. package/dist/types/server/detectorOptions.d.ts +21 -0
  150. package/dist/types/server/index.d.ts +1 -6
  151. package/dist/types/server/redirectPolicy.d.ts +22 -0
  152. package/dist/types/shared/detection.d.ts +1 -11
  153. package/dist/types/shared/localisedUrls/config.d.ts +9 -0
  154. package/dist/types/shared/localisedUrls/index.d.ts +9 -0
  155. package/dist/types/shared/localisedUrls/normalise.d.ts +10 -0
  156. package/dist/types/shared/localisedUrls/pathname.d.ts +4 -0
  157. package/dist/types/shared/localisedUrls/patterns.d.ts +5 -0
  158. package/dist/types/shared/localisedUrls/redirect.d.ts +11 -0
  159. package/dist/types/shared/localisedUrls/resolve.d.ts +7 -0
  160. package/dist/types/shared/localisedUrls/routes.d.ts +3 -0
  161. package/dist/types/shared/localisedUrls/types.d.ts +14 -0
  162. package/dist/types/shared/localisedUrls.d.ts +2 -36
  163. package/dist/types/shared/utils.d.ts +0 -2
  164. package/package.json +17 -20
  165. package/rstest.config.mts +8 -1
  166. package/src/cli/index.ts +5 -4
  167. package/src/cli/locales.ts +3 -3
  168. package/src/runtime/Link.tsx +11 -138
  169. package/src/runtime/context.tsx +170 -207
  170. package/src/runtime/contextHelpers.ts +242 -0
  171. package/src/runtime/core.tsx +48 -275
  172. package/src/runtime/hooks.ts +86 -24
  173. package/src/runtime/i18n/backend/defaults.node.ts +2 -2
  174. package/src/runtime/i18n/backend/middleware.common.ts +5 -4
  175. package/src/runtime/i18n/detection/cache.ts +189 -0
  176. package/src/runtime/i18n/detection/detector.ts +166 -0
  177. package/src/runtime/i18n/detection/index.ts +10 -641
  178. package/src/runtime/i18n/detection/initOptions.ts +74 -0
  179. package/src/runtime/i18n/detection/language.ts +64 -0
  180. package/src/runtime/i18n/detection/middleware.node.ts +26 -7
  181. package/src/runtime/i18n/detection/middleware.ts +15 -7
  182. package/src/runtime/i18n/detection/path.ts +41 -0
  183. package/src/runtime/i18n/detection/priority.ts +82 -0
  184. package/src/runtime/i18n/detection/ssr.ts +47 -0
  185. package/src/runtime/i18n/detection/types.ts +41 -0
  186. package/src/runtime/i18n/instance.ts +98 -34
  187. package/src/runtime/i18n/react-i18next.ts +1 -6
  188. package/src/runtime/i18n/utils.ts +18 -9
  189. package/src/runtime/linkHelpers.ts +174 -0
  190. package/src/runtime/pluginSetup.ts +189 -0
  191. package/src/runtime/providerComposition.tsx +148 -0
  192. package/src/runtime/reactI18next.ts +20 -0
  193. package/src/runtime/routerAdapter.tsx +36 -15
  194. package/src/runtime/utils.ts +13 -35
  195. package/src/server/apiPrefix.ts +67 -0
  196. package/src/server/detectorOptions.ts +105 -0
  197. package/src/server/index.ts +14 -319
  198. package/src/server/redirectPolicy.ts +127 -0
  199. package/src/shared/detection.ts +1 -1
  200. package/src/shared/localisedUrls/config.ts +18 -0
  201. package/src/shared/localisedUrls/index.ts +24 -0
  202. package/src/shared/localisedUrls/normalise.ts +67 -0
  203. package/src/shared/localisedUrls/pathname.ts +60 -0
  204. package/src/shared/localisedUrls/patterns.ts +160 -0
  205. package/src/shared/localisedUrls/redirect.ts +93 -0
  206. package/src/shared/localisedUrls/resolve.ts +135 -0
  207. package/src/shared/localisedUrls/routes.ts +234 -0
  208. package/src/shared/localisedUrls/types.ts +18 -0
  209. package/src/shared/localisedUrls.ts +26 -623
  210. package/src/shared/utils.ts +2 -2
  211. package/tests/federatedI18nBoundary.client.test.tsx +112 -0
  212. package/tests/federatedI18nBoundary.test.tsx +120 -0
  213. package/tests/fixtures/localised-routes.golden.json +156 -0
  214. package/tests/link.test.tsx +111 -0
  215. package/tests/localisedUrlRewriteMatrix.fork.test.ts +147 -0
  216. package/tests/localisedUrls.test.ts +148 -2
  217. package/tests/reactI18nextRuntimeBoundary.test.ts +6 -3
  218. package/tests/redirectPolicy.test.ts +45 -0
  219. package/tests/routerAdapter.test.tsx +603 -0
  220. package/tests/type-fixture/fixture-globals.d.ts +11 -0
  221. package/tests/type-fixture/tsconfig.json +1 -0
@@ -0,0 +1,78 @@
1
+ import "node:module";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { RuntimeContext } from "@modern-js/runtime";
4
+ import { Helmet } from "@modern-js/runtime/head";
5
+ import { useContext, useEffect, useMemo, useRef, useState } from "react";
6
+ import { ModernI18nProvider } from "./context.mjs";
7
+ import { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader } from "./hooks.mjs";
8
+ import { getI18nextInstanceForProvider } from "./i18n/instance.mjs";
9
+ import { useI18nRouterAdapter } from "./routerAdapter.mjs";
10
+ const createI18nRootWrapper = (options)=>(App)=>(props)=>{
11
+ const { entryName, htmlLangAttr, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls, getLatestI18nInstance, getI18nextProvider } = options;
12
+ const runtimeContext = useContext(RuntimeContext);
13
+ const i18nInstance = runtimeContext.i18nInstance || getLatestI18nInstance();
14
+ const initialLang = useMemo(()=>i18nInstance?.language || fallbackLanguage, [
15
+ i18nInstance?.language,
16
+ fallbackLanguage
17
+ ]);
18
+ const [lang, setLang] = useState(initialLang);
19
+ const [forceUpdate, setForceUpdate] = useState(0);
20
+ const prevLangRef = useRef(lang);
21
+ const { location } = useI18nRouterAdapter();
22
+ useEffect(()=>{
23
+ if (i18nInstance?.language) {
24
+ const translator = i18nInstance.translator;
25
+ if (translator) translator.language = i18nInstance.language;
26
+ }
27
+ }, [
28
+ i18nInstance?.language
29
+ ]);
30
+ useEffect(()=>{
31
+ prevLangRef.current = lang;
32
+ }, [
33
+ lang
34
+ ]);
35
+ useSdkResourcesLoader(i18nInstance, setForceUpdate);
36
+ const synchronizeLanguage = useLanguageSync(i18nInstance, localePathRedirect, languages, location?.pathname, prevLangRef, setLang);
37
+ useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls);
38
+ const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang, synchronizeLanguage), [
39
+ lang,
40
+ i18nInstance,
41
+ entryName,
42
+ languages,
43
+ localePathRedirect,
44
+ ignoreRedirectRoutes,
45
+ localisedUrls,
46
+ forceUpdate,
47
+ synchronizeLanguage
48
+ ]);
49
+ const children = props.children;
50
+ let appContent = App ? /*#__PURE__*/ jsx(App, {
51
+ ...props,
52
+ children: children
53
+ }) : children;
54
+ if (i18nInstance) {
55
+ const I18nextProvider = getI18nextProvider();
56
+ if (I18nextProvider) {
57
+ const i18nextInstanceForProvider = getI18nextInstanceForProvider(i18nInstance, lang);
58
+ appContent = /*#__PURE__*/ jsx(I18nextProvider, {
59
+ i18n: i18nextInstanceForProvider,
60
+ children: appContent
61
+ });
62
+ }
63
+ }
64
+ return /*#__PURE__*/ jsxs(Fragment, {
65
+ children: [
66
+ Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
67
+ htmlAttributes: {
68
+ lang
69
+ }
70
+ }),
71
+ /*#__PURE__*/ jsx(ModernI18nProvider, {
72
+ value: contextValue,
73
+ children: appContent
74
+ })
75
+ ]
76
+ });
77
+ };
78
+ export { createI18nRootWrapper };
@@ -0,0 +1,6 @@
1
+ import "node:module";
2
+ const resolveReactI18nextIntegration = async (reactI18next, loadReactI18nextIntegration)=>{
3
+ if (!reactI18next) return null;
4
+ return loadReactI18nextIntegration?.() ?? null;
5
+ };
6
+ export { resolveReactI18nextIntegration };
@@ -66,28 +66,36 @@ const useI18nRouterAdapter = ()=>{
66
66
  const [, setRouterVersion] = useState(0);
67
67
  const hasRouter = 'tanstack' === framework || 'react-router' === framework || Boolean(reactRouterNavigate);
68
68
  useEffect(()=>{
69
- if ('tanstack' !== framework) return;
70
69
  const router = getRouterInstance(internalContext, contextRouter);
71
70
  if (!router) return;
72
71
  const update = ()=>setRouterVersion((version)=>version + 1);
73
72
  const unsubscribers = [];
74
- if ('function' == typeof router.stores?.location?.subscribe) {
75
- const unsubscribe = router.stores.location.subscribe(update);
73
+ if ('react-router' === framework && !inReactRouter && 'function' == typeof router.subscribe) {
74
+ const subscribe = router.subscribe;
75
+ const unsubscribe = subscribe.call(router, update);
76
76
  if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
77
77
  }
78
- if ('function' == typeof router.subscribe) for (const eventType of [
79
- 'onBeforeNavigate',
80
- 'onBeforeLoad'
81
- ]){
82
- const unsubscribe = router.subscribe(eventType, update);
78
+ if ('tanstack' === framework && 'function' == typeof router.stores?.location?.subscribe) {
79
+ const unsubscribe = router.stores.location.subscribe(update);
83
80
  if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
84
81
  }
82
+ if ('tanstack' === framework && 'function' == typeof router.subscribe) {
83
+ const subscribe = router.subscribe;
84
+ for (const eventType of [
85
+ 'onBeforeNavigate',
86
+ 'onBeforeLoad'
87
+ ]){
88
+ const unsubscribe = subscribe.call(router, eventType, update);
89
+ if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
90
+ }
91
+ }
85
92
  return ()=>{
86
93
  for (const unsubscribe of unsubscribers)unsubscribe();
87
94
  };
88
95
  }, [
89
96
  contextRouter,
90
97
  framework,
98
+ inReactRouter,
91
99
  internalContext
92
100
  ]);
93
101
  const navigate = useCallback((href, options)=>{
@@ -1,7 +1,7 @@
1
1
  import "node:module";
2
2
  import { isBrowser } from "@modern-js/runtime";
3
3
  import { getGlobalBasename } from "@modern-js/runtime/context";
4
- import { localiseTargetPathname } from "../shared/localisedUrls.mjs";
4
+ import { localiseTargetPathname, shouldSkipLocaleRedirect } from "../shared/localisedUrls.mjs";
5
5
  const getPathname = (context)=>{
6
6
  if (isBrowser()) return window.location.pathname;
7
7
  return context.ssrContext?.request?.pathname || '/';
@@ -52,13 +52,5 @@ const detectLanguageFromPath = (pathname, languages, localePathRedirect)=>{
52
52
  detected: false
53
53
  };
54
54
  };
55
- const shouldIgnoreRedirect = (pathname, languages, ignoreRedirectRoutes)=>{
56
- if (!ignoreRedirectRoutes) return false;
57
- const segments = pathname.split('/').filter(Boolean);
58
- let pathWithoutLang = pathname;
59
- if (segments.length > 0 && languages.includes(segments[0])) pathWithoutLang = `/${segments.slice(1).join('/')}`;
60
- const normalizedPath = pathWithoutLang.startsWith('/') ? pathWithoutLang : `/${pathWithoutLang}`;
61
- if ('function' == typeof ignoreRedirectRoutes) return ignoreRedirectRoutes(normalizedPath);
62
- return ignoreRedirectRoutes.some((pattern)=>normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`));
63
- };
55
+ const shouldIgnoreRedirect = (pathname, languages, ignoreRedirectRoutes)=>shouldSkipLocaleRedirect(pathname, languages, ignoreRedirectRoutes);
64
56
  export { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, getLanguageFromPath, getPathname, shouldIgnoreRedirect, splitUrlTarget };
@@ -0,0 +1,32 @@
1
+ import "node:module";
2
+ const normalizeApiPrefix = (prefix)=>{
3
+ const trimmedPrefix = prefix.trim();
4
+ if (!trimmedPrefix) return null;
5
+ const prefixedPath = trimmedPrefix.startsWith('/') ? trimmedPrefix : `/${trimmedPrefix}`;
6
+ const withoutWildcard = prefixedPath.replace(/\/\*$/, '');
7
+ const normalizedPrefix = withoutWildcard.length > 1 ? withoutWildcard.replace(/\/+$/, '') : withoutWildcard;
8
+ return '/' === normalizedPrefix ? null : normalizedPrefix;
9
+ };
10
+ const collectApiPrefixes = (routes, bffPrefix)=>{
11
+ const prefixes = new Set();
12
+ for (const route of routes){
13
+ if (!route.isApi || !route.urlPath) continue;
14
+ const normalizedPrefix = normalizeApiPrefix(route.urlPath);
15
+ if (normalizedPrefix) prefixes.add(normalizedPrefix);
16
+ }
17
+ const bffPrefixes = Array.isArray(bffPrefix) ? bffPrefix : bffPrefix ? [
18
+ bffPrefix
19
+ ] : [];
20
+ for (const prefix of bffPrefixes){
21
+ const normalizedPrefix = normalizeApiPrefix(prefix);
22
+ if (normalizedPrefix) prefixes.add(normalizedPrefix);
23
+ }
24
+ return [
25
+ ...prefixes
26
+ ];
27
+ };
28
+ const matchesApiPrefix = (pathname, apiPrefixes)=>{
29
+ const normalizedPathname = pathname.startsWith('/') ? pathname : `/${pathname}`;
30
+ return apiPrefixes.some((prefix)=>normalizedPathname === prefix || normalizedPathname.startsWith(`${prefix}/`));
31
+ };
32
+ export { collectApiPrefixes, matchesApiPrefix };
@@ -0,0 +1,60 @@
1
+ import "node:module";
2
+ import { DEFAULT_I18NEXT_DETECTION_OPTIONS, mergeDetectionOptions } from "../runtime/i18n/detection/config.mjs";
3
+ const convertToHonoLanguageDetectorOptions = (languages, fallbackLanguage, detectionOptions)=>{
4
+ const mergedDetection = detectionOptions ? mergeDetectionOptions(detectionOptions) : DEFAULT_I18NEXT_DETECTION_OPTIONS;
5
+ const order = (mergedDetection.order || []).filter((item)=>![
6
+ 'path',
7
+ 'localStorage',
8
+ 'navigator',
9
+ 'htmlTag',
10
+ 'subdomain'
11
+ ].includes(item));
12
+ const detectionOrder = order.length > 0 ? order : [
13
+ 'querystring',
14
+ 'cookie',
15
+ 'header'
16
+ ];
17
+ const honoOrder = detectionOrder.map((item)=>{
18
+ if ('querystring' === item) return 'querystring';
19
+ if ('cookie' === item) return 'cookie';
20
+ if ('header' === item) return 'header';
21
+ return item;
22
+ });
23
+ const caches = false === mergedDetection.caches ? false : Array.isArray(mergedDetection.caches) && !mergedDetection.caches.includes('cookie') ? false : [
24
+ 'cookie'
25
+ ];
26
+ const cookieMinutes = mergedDetection.cookieMinutes;
27
+ const cookieMaxAge = 'number' == typeof cookieMinutes && Number.isFinite(cookieMinutes) ? Math.max(0, Math.floor(60 * cookieMinutes)) : 60 * DEFAULT_I18NEXT_DETECTION_OPTIONS.cookieMinutes;
28
+ const cookieDomain = mergedDetection.cookieDomain;
29
+ const cookieSecure = mergedDetection.cookieSecure;
30
+ const cookieHttpOnly = mergedDetection.cookieHttpOnly;
31
+ const cookieSameSite = mergedDetection.cookieSameSite;
32
+ const normalizedCookieDomain = 'string' == typeof cookieDomain ? cookieDomain : void 0;
33
+ const cookieOptions = {
34
+ maxAge: cookieMaxAge,
35
+ sameSite: 'None' === cookieSameSite || 'none' === cookieSameSite ? 'None' : 'Lax' === cookieSameSite || 'lax' === cookieSameSite ? 'Lax' : 'Strict',
36
+ secure: 'boolean' == typeof cookieSecure ? cookieSecure : false,
37
+ httpOnly: 'boolean' == typeof cookieHttpOnly ? cookieHttpOnly : false,
38
+ ...normalizedCookieDomain ? {
39
+ domain: normalizedCookieDomain
40
+ } : {}
41
+ };
42
+ return {
43
+ supportedLanguages: languages.length > 0 ? languages : [
44
+ fallbackLanguage
45
+ ],
46
+ fallbackLanguage,
47
+ order: honoOrder,
48
+ lookupQueryString: mergedDetection.lookupQuerystring || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring || 'lng',
49
+ lookupCookie: mergedDetection.lookupCookie || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie || 'i18next',
50
+ lookupFromHeaderKey: mergedDetection.lookupHeader || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader || 'accept-language',
51
+ ...void 0 !== caches && {
52
+ caches
53
+ },
54
+ ...false !== caches && {
55
+ cookieOptions
56
+ },
57
+ ignoreCase: true
58
+ };
59
+ };
60
+ export { convertToHonoLanguageDetectorOptions };
@@ -1,138 +1,11 @@
1
1
  import "node:module";
2
- import { DEFAULT_I18NEXT_DETECTION_OPTIONS, mergeDetectionOptions } from "../runtime/i18n/detection/config.mjs";
3
- import { localiseTargetPathname, resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
2
+ import { resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
4
3
  import { getLocaleDetectionOptions } from "../shared/utils.mjs";
5
- import * as __rspack_external__modern_js_server_core_hono_a76ca254 from "@modern-js/server-core/hono";
6
- const { languageDetector: languageDetector } = __rspack_external__modern_js_server_core_hono_a76ca254;
7
- const normalizeApiPrefix = (prefix)=>{
8
- const trimmedPrefix = prefix.trim();
9
- if (!trimmedPrefix) return null;
10
- const prefixedPath = trimmedPrefix.startsWith('/') ? trimmedPrefix : `/${trimmedPrefix}`;
11
- const withoutWildcard = prefixedPath.replace(/\/\*$/, '');
12
- const normalizedPrefix = withoutWildcard.length > 1 ? withoutWildcard.replace(/\/+$/, '') : withoutWildcard;
13
- return '/' === normalizedPrefix ? null : normalizedPrefix;
14
- };
15
- const collectApiPrefixes = (routes, bffPrefix)=>{
16
- const prefixes = new Set();
17
- for (const route of routes){
18
- if (!route.isApi || !route.urlPath) continue;
19
- const normalizedPrefix = normalizeApiPrefix(route.urlPath);
20
- if (normalizedPrefix) prefixes.add(normalizedPrefix);
21
- }
22
- const bffPrefixes = Array.isArray(bffPrefix) ? bffPrefix : bffPrefix ? [
23
- bffPrefix
24
- ] : [];
25
- for (const prefix of bffPrefixes){
26
- const normalizedPrefix = normalizeApiPrefix(prefix);
27
- if (normalizedPrefix) prefixes.add(normalizedPrefix);
28
- }
29
- return [
30
- ...prefixes
31
- ];
32
- };
33
- const matchesApiPrefix = (pathname, apiPrefixes)=>{
34
- const normalizedPathname = pathname.startsWith('/') ? pathname : `/${pathname}`;
35
- return apiPrefixes.some((prefix)=>normalizedPathname === prefix || normalizedPathname.startsWith(`${prefix}/`));
36
- };
37
- const convertToHonoLanguageDetectorOptions = (languages, fallbackLanguage, detectionOptions)=>{
38
- const mergedDetection = detectionOptions ? mergeDetectionOptions(detectionOptions) : DEFAULT_I18NEXT_DETECTION_OPTIONS;
39
- const order = (mergedDetection.order || []).filter((item)=>![
40
- 'path',
41
- 'localStorage',
42
- 'navigator',
43
- 'htmlTag',
44
- 'subdomain'
45
- ].includes(item));
46
- const detectionOrder = order.length > 0 ? order : [
47
- 'querystring',
48
- 'cookie',
49
- 'header'
50
- ];
51
- const honoOrder = detectionOrder.map((item)=>{
52
- if ('querystring' === item) return 'querystring';
53
- if ('cookie' === item) return 'cookie';
54
- if ('header' === item) return 'header';
55
- return item;
56
- });
57
- const caches = false === mergedDetection.caches ? false : Array.isArray(mergedDetection.caches) && !mergedDetection.caches.includes('cookie') ? false : [
58
- 'cookie'
59
- ];
60
- const cookieMinutes = mergedDetection.cookieMinutes;
61
- const cookieMaxAge = 'number' == typeof cookieMinutes && Number.isFinite(cookieMinutes) ? Math.max(0, Math.floor(60 * cookieMinutes)) : 60 * DEFAULT_I18NEXT_DETECTION_OPTIONS.cookieMinutes;
62
- const cookieDomain = mergedDetection.cookieDomain;
63
- const cookieSecure = mergedDetection.cookieSecure;
64
- const cookieHttpOnly = mergedDetection.cookieHttpOnly;
65
- const cookieSameSite = mergedDetection.cookieSameSite;
66
- const normalizedCookieDomain = 'string' == typeof cookieDomain ? cookieDomain : void 0;
67
- const cookieOptions = {
68
- maxAge: cookieMaxAge,
69
- sameSite: 'None' === cookieSameSite || 'none' === cookieSameSite ? 'None' : 'Lax' === cookieSameSite || 'lax' === cookieSameSite ? 'Lax' : 'Strict',
70
- secure: 'boolean' == typeof cookieSecure ? cookieSecure : false,
71
- httpOnly: 'boolean' == typeof cookieHttpOnly ? cookieHttpOnly : false,
72
- ...normalizedCookieDomain ? {
73
- domain: normalizedCookieDomain
74
- } : {}
75
- };
76
- return {
77
- supportedLanguages: languages.length > 0 ? languages : [
78
- fallbackLanguage
79
- ],
80
- fallbackLanguage,
81
- order: honoOrder,
82
- lookupQueryString: mergedDetection.lookupQuerystring || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring || 'lng',
83
- lookupCookie: mergedDetection.lookupCookie || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie || 'i18next',
84
- lookupFromHeaderKey: mergedDetection.lookupHeader || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader || 'accept-language',
85
- ...void 0 !== caches && {
86
- caches
87
- },
88
- ...false !== caches && {
89
- cookieOptions
90
- },
91
- ignoreCase: true
92
- };
93
- };
94
- const shouldIgnoreRedirect = (pathname, urlPath, ignoreRedirectRoutes)=>{
95
- if (!ignoreRedirectRoutes) return false;
96
- const basePath = urlPath.replace('/*', '');
97
- const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
98
- const normalizedPath = remainingPath.startsWith('/') ? remainingPath : `/${remainingPath}`;
99
- if ('function' == typeof ignoreRedirectRoutes) return ignoreRedirectRoutes(normalizedPath);
100
- return ignoreRedirectRoutes.some((pattern)=>normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`));
101
- };
102
- const isStaticResourceRequest = (pathname, staticRoutePrefixes, languages = [])=>{
103
- if (staticRoutePrefixes.some((prefix)=>pathname.startsWith(`${prefix}/`) || pathname === prefix)) return true;
104
- const standardStaticPrefixes = [
105
- '/static/',
106
- '/upload/'
107
- ];
108
- if (standardStaticPrefixes.some((prefix)=>pathname.startsWith(prefix))) return true;
109
- const pathSegments = pathname.split('/').filter(Boolean);
110
- if (pathSegments.length > 0 && languages.includes(pathSegments[0])) {
111
- const pathWithoutLang = '/' + pathSegments.slice(1).join('/');
112
- if (standardStaticPrefixes.some((prefix)=>pathWithoutLang.startsWith(prefix)) || staticRoutePrefixes.some((prefix)=>pathWithoutLang.startsWith(`${prefix}/`) || pathWithoutLang === prefix)) return true;
113
- }
114
- return false;
115
- };
116
- const getLanguageFromPath = (req, urlPath, languages)=>{
117
- const url = new URL(req.url, `http://${req.header().host}`);
118
- const pathname = url.pathname;
119
- const basePath = urlPath.replace('/*', '');
120
- const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
121
- const segments = remainingPath.split('/').filter(Boolean);
122
- const firstSegment = segments[0];
123
- if (languages.includes(firstSegment)) return firstSegment;
124
- return null;
125
- };
126
- const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
127
- const url = new URL(req.url);
128
- const pathname = url.pathname;
129
- const basePath = urlPath.replace('/*', '');
130
- const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
131
- const newPathname = localiseTargetPathname(remainingPath, language, languages, localisedUrls);
132
- const suffix = `${url.search}${url.hash}`;
133
- const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
134
- return localizedUrl;
135
- };
4
+ import { collectApiPrefixes, matchesApiPrefix } from "./apiPrefix.mjs";
5
+ import { convertToHonoLanguageDetectorOptions } from "./detectorOptions.mjs";
6
+ import { buildLocalizedUrl, createLocaleRedirectResponse, getLanguageFromPath, isStaticResourceRequest, shouldIgnoreRedirect } from "./redirectPolicy.mjs";
7
+ import * as __rspack_external__modern_js_server_core_hono_0af3baa7 from "@modern-js/server-core/hono";
8
+ const { languageDetector: languageDetector } = __rspack_external__modern_js_server_core_hono_0af3baa7;
136
9
  const i18nServerPlugin = (options)=>({
137
10
  name: '@modern-js/plugin-i18n/server',
138
11
  setup: (api)=>{
@@ -201,12 +74,12 @@ const i18nServerPlugin = (options)=>({
201
74
  if (i18nextDetector) detectedLanguage = c.get('language') || null;
202
75
  const targetLanguage = detectedLanguage || fallbackLanguage;
203
76
  const localizedUrl = buildLocalizedUrl(c.req, originUrlPath, targetLanguage, languages, localisedUrls);
204
- return c.redirect(localizedUrl);
77
+ return createLocaleRedirectResponse(localizedUrl);
205
78
  }
206
79
  const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
207
80
  if (localisedUrlsConfig.enabled) {
208
81
  const expectedUrl = buildLocalizedUrl(c.req, originUrlPath, language, languages, localisedUrls);
209
- if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return c.redirect(expectedUrl);
82
+ if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return createLocaleRedirectResponse(expectedUrl);
210
83
  }
211
84
  await next();
212
85
  }
@@ -0,0 +1,47 @@
1
+ import "node:module";
2
+ import { isDefaultLocaleRedirectSkipPath, localiseTargetPathname, matchesPathPrefix, shouldSkipLocaleRedirect, stripLanguagePrefix } from "../shared/localisedUrls.mjs";
3
+ const stripUrlPathPrefix = (pathname, urlPath)=>{
4
+ const basePath = urlPath.replace('/*', '');
5
+ if (!basePath || '/' === basePath) return pathname;
6
+ const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
7
+ return remainingPath || '/';
8
+ };
9
+ const shouldIgnoreRedirect = (pathname, urlPath, ignoreRedirectRoutes)=>{
10
+ const remainingPath = stripUrlPathPrefix(pathname, urlPath);
11
+ return shouldSkipLocaleRedirect(remainingPath, [], ignoreRedirectRoutes);
12
+ };
13
+ const isStaticResourceRequest = (pathname, staticRoutePrefixes, languages = [])=>{
14
+ if (isDefaultLocaleRedirectSkipPath(pathname, languages)) return true;
15
+ const matchesStaticRoutePrefix = (targetPathname)=>staticRoutePrefixes.some((prefix)=>matchesPathPrefix(targetPathname, prefix));
16
+ if (matchesStaticRoutePrefix(pathname)) return true;
17
+ const pathWithoutLanguage = stripLanguagePrefix(pathname, languages);
18
+ return pathWithoutLanguage !== pathname && matchesStaticRoutePrefix(pathWithoutLanguage);
19
+ };
20
+ const getLanguageFromPath = (req, urlPath, languages)=>{
21
+ const url = new URL(req.url, `http://${req.header().host}`);
22
+ const pathname = url.pathname;
23
+ const remainingPath = stripUrlPathPrefix(pathname, urlPath);
24
+ const segments = remainingPath.split('/').filter(Boolean);
25
+ const firstSegment = segments[0];
26
+ if (languages.includes(firstSegment)) return firstSegment;
27
+ return null;
28
+ };
29
+ const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
30
+ const url = new URL(req.url);
31
+ const pathname = url.pathname;
32
+ const basePath = urlPath.replace('/*', '');
33
+ const remainingPath = stripUrlPathPrefix(pathname, urlPath);
34
+ const newPathname = localiseTargetPathname(remainingPath, language, languages, localisedUrls);
35
+ const suffix = `${url.search}${url.hash}`;
36
+ const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
37
+ return localizedUrl;
38
+ };
39
+ const createLocaleRedirectResponse = (location)=>new Response(null, {
40
+ status: 302,
41
+ headers: {
42
+ 'Cache-Control': 'private, no-store',
43
+ Location: location,
44
+ Vary: 'Accept-Language, Cookie'
45
+ }
46
+ });
47
+ export { buildLocalizedUrl, createLocaleRedirectResponse, getLanguageFromPath, isStaticResourceRequest, shouldIgnoreRedirect };
@@ -1,72 +1,2 @@
1
1
  import "node:module";
2
- import { DEFAULT_I18NEXT_DETECTION_OPTIONS, mergeDetectionOptions } from "../runtime/i18n/detection/config.mjs";
3
- function detectLanguageFromRequest(req, languages, detectionOptions) {
4
- try {
5
- const mergedDetection = detectionOptions ? mergeDetectionOptions(detectionOptions) : DEFAULT_I18NEXT_DETECTION_OPTIONS;
6
- const order = (mergedDetection.order || []).filter((item)=>![
7
- 'path',
8
- 'localStorage',
9
- 'navigator',
10
- 'htmlTag',
11
- 'subdomain'
12
- ].includes(item));
13
- const detectionOrder = order.length > 0 ? order : [
14
- 'querystring',
15
- 'cookie',
16
- 'header'
17
- ];
18
- const getHeader = (name)=>{
19
- req.headers, Headers;
20
- return req.headers.get(name);
21
- };
22
- for (const method of detectionOrder){
23
- let detectedLang = null;
24
- switch(method){
25
- case 'querystring':
26
- {
27
- const lookupKey = mergedDetection.lookupQuerystring || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring || 'lng';
28
- const host = getHeader('host') || 'localhost';
29
- const url = new URL(req.url, `http://${host}`);
30
- detectedLang = url.searchParams.get(lookupKey);
31
- break;
32
- }
33
- case 'cookie':
34
- {
35
- const lookupKey = mergedDetection.lookupCookie || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie || 'i18next';
36
- const cookieHeader = getHeader('Cookie');
37
- if (cookieHeader) {
38
- const cookies = cookieHeader.split(';').reduce((acc, item)=>{
39
- const [key, value] = item.trim().split('=');
40
- if (key && value) acc[key] = value;
41
- return acc;
42
- }, {});
43
- detectedLang = cookies[lookupKey] || null;
44
- }
45
- break;
46
- }
47
- case 'header':
48
- {
49
- const lookupKey = mergedDetection.lookupHeader || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader || 'accept-language';
50
- const acceptLanguage = getHeader(lookupKey);
51
- if (acceptLanguage) {
52
- const languagesList = acceptLanguage.split(',').map((lang)=>{
53
- const [code, q] = lang.trim().split(';');
54
- return {
55
- code: code.split('-')[0],
56
- quality: q ? parseFloat(q.split('=')[1]) : 1.0
57
- };
58
- }).sort((a, b)=>b.quality - a.quality);
59
- for (const lang of languagesList)if (0 === languages.length || languages.includes(lang.code)) {
60
- detectedLang = lang.code;
61
- break;
62
- }
63
- }
64
- break;
65
- }
66
- }
67
- if (detectedLang && (0 === languages.length || languages.includes(detectedLang))) return detectedLang;
68
- }
69
- } catch (error) {}
70
- return null;
71
- }
72
- export { detectLanguageFromRequest };
2
+ import "../runtime/i18n/detection/config.mjs";
@@ -0,0 +1,12 @@
1
+ import "node:module";
2
+ const resolveLocalisedUrlsConfig = (option)=>{
3
+ if (option && 'object' == typeof option && Object.keys(option).length > 0) return {
4
+ enabled: true,
5
+ map: option
6
+ };
7
+ return {
8
+ enabled: false,
9
+ map: {}
10
+ };
11
+ };
12
+ export { resolveLocalisedUrlsConfig };
@@ -0,0 +1,8 @@
1
+ import "node:module";
2
+ export { resolveLocalisedUrlsConfig } from "./config.mjs";
3
+ export { normalisePathPattern, normalisePathname } from "./normalise.mjs";
4
+ export { canonicalTargetPathname, localiseTargetPathname, stripLanguagePrefix } from "./pathname.mjs";
5
+ export { buildPathFromPattern, matchPathPattern } from "./patterns.mjs";
6
+ export { DEFAULT_LOCALE_REDIRECT_SKIP_RULES, isDefaultLocaleRedirectSkipPath, matchesPathPrefix, shouldSkipLocaleRedirect } from "./redirect.mjs";
7
+ export { resolveCanonicalLocalisedPath, resolveLocalisedPath } from "./resolve.mjs";
8
+ export { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from "./routes.mjs";
@@ -0,0 +1,38 @@
1
+ import "node:module";
2
+ const LOCALE_PARAM_NAMES = new Set([
3
+ 'lang',
4
+ 'locale',
5
+ 'language'
6
+ ]);
7
+ const normaliseSlashes = (path)=>{
8
+ const withoutDuplicateSlashes = path.replace(/\/+/g, '/');
9
+ const withLeadingSlash = withoutDuplicateSlashes.startsWith('/') ? withoutDuplicateSlashes : `/${withoutDuplicateSlashes}`;
10
+ return withLeadingSlash.length > 1 ? withLeadingSlash.replace(/\/+$/, '') : withLeadingSlash;
11
+ };
12
+ const normalisePathPattern = (path)=>normaliseSlashes(path).replace(/\[(.+?)\]/g, ':$1');
13
+ const normalisePathname = (pathname)=>normaliseSlashes(pathname);
14
+ const normaliseRoutePath = (path)=>{
15
+ const normalized = normalisePathPattern(path);
16
+ return '/' === normalized ? '' : normalized.slice(1);
17
+ };
18
+ const getLocaleParamSegment = (segment)=>{
19
+ if (!segment.startsWith(':')) return null;
20
+ const paramName = segment.slice(1).replace(/\?$/, '');
21
+ return LOCALE_PARAM_NAMES.has(paramName) ? segment : null;
22
+ };
23
+ const splitPathSegments = (path)=>{
24
+ if (!path) return [];
25
+ return normalisePathPattern(path).split('/').filter(Boolean);
26
+ };
27
+ const stripLeadingLocaleParam = (path)=>{
28
+ const segments = splitPathSegments(path);
29
+ const leadingLocaleParam = getLocaleParamSegment(segments[0] || '');
30
+ if (!leadingLocaleParam) return path;
31
+ const remainingPath = segments.slice(1).join('/');
32
+ return remainingPath ? `/${remainingPath}` : void 0;
33
+ };
34
+ const getLeadingLocaleParam = (path)=>{
35
+ const segments = splitPathSegments(path);
36
+ return getLocaleParamSegment(segments[0] || '');
37
+ };
38
+ export { getLeadingLocaleParam, normalisePathPattern, normalisePathname, normaliseRoutePath, stripLeadingLocaleParam };
@@ -0,0 +1,25 @@
1
+ import "node:module";
2
+ import { resolveLocalisedUrlsConfig } from "./config.mjs";
3
+ import { resolveCanonicalLocalisedPath, resolveLocalisedPath } from "./resolve.mjs";
4
+ const stripLanguagePrefix = (pathname, languages)=>{
5
+ const segments = pathname.split('/').filter(Boolean);
6
+ const firstSegment = segments[0]?.toLowerCase();
7
+ if (firstSegment && languages.some((language)=>language.toLowerCase() === firstSegment)) return `/${segments.slice(1).join('/')}`;
8
+ return pathname || '/';
9
+ };
10
+ const localiseTargetPathname = (pathname, language, languages, localisedUrls)=>{
11
+ const pathWithoutLanguage = stripLanguagePrefix(pathname, languages);
12
+ const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
13
+ const resolvedPath = localisedUrlsConfig.enabled ? resolveLocalisedPath(pathWithoutLanguage, language, languages, localisedUrlsConfig.map) : pathWithoutLanguage;
14
+ const resolvedSegments = resolvedPath.split('/').filter(Boolean);
15
+ return `/${[
16
+ language,
17
+ ...resolvedSegments
18
+ ].join('/')}`;
19
+ };
20
+ const canonicalTargetPathname = (pathname, languages, localisedUrls)=>{
21
+ const pathWithoutLanguage = stripLanguagePrefix(pathname, languages);
22
+ const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
23
+ return localisedUrlsConfig.enabled ? resolveCanonicalLocalisedPath(pathWithoutLanguage, languages, localisedUrlsConfig.map) : pathWithoutLanguage;
24
+ };
25
+ export { canonicalTargetPathname, localiseTargetPathname, stripLanguagePrefix };