@bleedingdev/modern-js-plugin-i18n 3.2.0-ultramodern.11 → 3.2.0-ultramodern.110

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 (97) hide show
  1. package/dist/cjs/cli/index.js +32 -5
  2. package/dist/cjs/runtime/I18nLink.js +21 -22
  3. package/dist/cjs/runtime/context.js +41 -10
  4. package/dist/cjs/runtime/hooks.js +17 -10
  5. package/dist/cjs/runtime/i18n/backend/config.js +9 -5
  6. package/dist/cjs/runtime/i18n/backend/defaults.js +15 -10
  7. package/dist/cjs/runtime/i18n/backend/defaults.node.js +16 -11
  8. package/dist/cjs/runtime/i18n/backend/index.js +9 -5
  9. package/dist/cjs/runtime/i18n/backend/middleware.common.js +9 -5
  10. package/dist/cjs/runtime/i18n/backend/middleware.js +9 -5
  11. package/dist/cjs/runtime/i18n/backend/middleware.node.js +13 -9
  12. package/dist/cjs/runtime/i18n/backend/sdk-backend.js +9 -5
  13. package/dist/cjs/runtime/i18n/backend/sdk-event.js +16 -11
  14. package/dist/cjs/runtime/i18n/detection/config.js +9 -5
  15. package/dist/cjs/runtime/i18n/detection/index.js +9 -5
  16. package/dist/cjs/runtime/i18n/detection/middleware.js +9 -5
  17. package/dist/cjs/runtime/i18n/detection/middleware.node.js +9 -5
  18. package/dist/cjs/runtime/i18n/index.js +9 -5
  19. package/dist/cjs/runtime/i18n/instance.js +17 -37
  20. package/dist/cjs/runtime/i18n/react-i18next.js +53 -0
  21. package/dist/cjs/runtime/i18n/utils.js +9 -17
  22. package/dist/cjs/runtime/index.js +27 -15
  23. package/dist/cjs/runtime/routerAdapter.js +167 -0
  24. package/dist/cjs/runtime/utils.js +72 -99
  25. package/dist/cjs/server/index.js +69 -13
  26. package/dist/cjs/shared/deepMerge.js +12 -8
  27. package/dist/cjs/shared/detection.js +9 -5
  28. package/dist/cjs/shared/localisedUrls.js +241 -0
  29. package/dist/cjs/shared/utils.js +15 -11
  30. package/dist/esm/cli/index.mjs +23 -0
  31. package/dist/esm/runtime/I18nLink.mjs +12 -17
  32. package/dist/esm/runtime/context.mjs +34 -7
  33. package/dist/esm/runtime/hooks.mjs +9 -6
  34. package/dist/esm/runtime/i18n/backend/defaults.mjs +1 -1
  35. package/dist/esm/runtime/i18n/backend/defaults.node.mjs +2 -2
  36. package/dist/esm/runtime/i18n/backend/middleware.node.mjs +3 -3
  37. package/dist/esm/runtime/i18n/instance.mjs +1 -19
  38. package/dist/esm/runtime/i18n/react-i18next.mjs +15 -0
  39. package/dist/esm/runtime/i18n/utils.mjs +0 -12
  40. package/dist/esm/runtime/index.mjs +19 -11
  41. package/dist/esm/runtime/routerAdapter.mjs +129 -0
  42. package/dist/esm/runtime/utils.mjs +11 -30
  43. package/dist/esm/server/index.mjs +53 -7
  44. package/dist/esm/shared/localisedUrls.mjs +191 -0
  45. package/dist/esm-node/cli/index.mjs +23 -0
  46. package/dist/esm-node/runtime/I18nLink.mjs +12 -17
  47. package/dist/esm-node/runtime/context.mjs +34 -7
  48. package/dist/esm-node/runtime/hooks.mjs +9 -6
  49. package/dist/esm-node/runtime/i18n/backend/defaults.mjs +1 -1
  50. package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +2 -2
  51. package/dist/esm-node/runtime/i18n/backend/middleware.node.mjs +3 -3
  52. package/dist/esm-node/runtime/i18n/instance.mjs +1 -19
  53. package/dist/esm-node/runtime/i18n/react-i18next.mjs +16 -0
  54. package/dist/esm-node/runtime/i18n/utils.mjs +0 -12
  55. package/dist/esm-node/runtime/index.mjs +19 -11
  56. package/dist/esm-node/runtime/routerAdapter.mjs +130 -0
  57. package/dist/esm-node/runtime/utils.mjs +11 -30
  58. package/dist/esm-node/server/index.mjs +53 -7
  59. package/dist/esm-node/shared/localisedUrls.mjs +192 -0
  60. package/dist/types/cli/index.d.ts +1 -0
  61. package/dist/types/runtime/I18nLink.d.ts +15 -0
  62. package/dist/types/runtime/context.d.ts +3 -0
  63. package/dist/types/runtime/hooks.d.ts +4 -2
  64. package/dist/types/runtime/i18n/backend/middleware.node.d.ts +1 -1
  65. package/dist/types/runtime/i18n/instance.d.ts +4 -6
  66. package/dist/types/runtime/i18n/react-i18next.d.ts +7 -0
  67. package/dist/types/runtime/index.d.ts +1 -0
  68. package/dist/types/runtime/routerAdapter.d.ts +26 -0
  69. package/dist/types/runtime/types.d.ts +1 -1
  70. package/dist/types/runtime/utils.d.ts +2 -7
  71. package/dist/types/server/index.d.ts +6 -0
  72. package/dist/types/shared/localisedUrls.d.ts +13 -0
  73. package/dist/types/shared/type.d.ts +12 -0
  74. package/package.json +23 -27
  75. package/rstest.config.mts +39 -0
  76. package/src/cli/index.ts +44 -1
  77. package/src/runtime/I18nLink.tsx +13 -17
  78. package/src/runtime/context.tsx +45 -7
  79. package/src/runtime/hooks.ts +13 -4
  80. package/src/runtime/i18n/backend/defaults.node.ts +2 -2
  81. package/src/runtime/i18n/backend/defaults.ts +3 -1
  82. package/src/runtime/i18n/backend/middleware.node.ts +1 -1
  83. package/src/runtime/i18n/instance.ts +3 -30
  84. package/src/runtime/i18n/react-i18next.ts +25 -0
  85. package/src/runtime/i18n/utils.ts +4 -26
  86. package/src/runtime/index.tsx +23 -10
  87. package/src/runtime/routerAdapter.tsx +333 -0
  88. package/src/runtime/types.ts +1 -1
  89. package/src/runtime/utils.ts +22 -34
  90. package/src/server/index.ts +117 -10
  91. package/src/shared/localisedUrls.ts +393 -0
  92. package/src/shared/type.ts +12 -0
  93. package/tests/i18nUtils.test.ts +52 -0
  94. package/tests/localisedUrls.test.ts +312 -0
  95. package/tests/routerAdapter.test.tsx +452 -0
  96. package/dist/esm/rslib-runtime.mjs +0 -18
  97. package/dist/esm-node/rslib-runtime.mjs +0 -19
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
+ })();
18
+ (()=>{
19
+ __webpack_require__.r = (exports1)=>{
20
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
+ value: 'Module'
22
+ });
23
+ Object.defineProperty(exports1, '__esModule', {
24
+ value: true
25
+ });
26
+ };
27
+ })();
28
+ var __webpack_exports__ = {};
29
+ __webpack_require__.r(__webpack_exports__);
30
+ const LOCALE_PARAM_NAMES = new Set([
31
+ 'lang',
32
+ 'locale',
33
+ 'language'
34
+ ]);
35
+ const normalisePathPattern = (path)=>{
36
+ const withoutDuplicateSlashes = path.replace(/\/+/g, '/');
37
+ const withLeadingSlash = withoutDuplicateSlashes.startsWith('/') ? withoutDuplicateSlashes : `/${withoutDuplicateSlashes}`;
38
+ const withoutTrailingSlash = withLeadingSlash.length > 1 ? withLeadingSlash.replace(/\/+$/, '') : withLeadingSlash;
39
+ return withoutTrailingSlash.replace(/\[(.+?)\]/g, ':$1');
40
+ };
41
+ const normaliseRoutePath = (path)=>{
42
+ const normalized = normalisePathPattern(path);
43
+ return '/' === normalized ? '' : normalized.slice(1);
44
+ };
45
+ const getLocaleParamSegment = (segment)=>{
46
+ if (!segment.startsWith(':')) return null;
47
+ const paramName = segment.slice(1).replace(/\?$/, '');
48
+ return LOCALE_PARAM_NAMES.has(paramName) ? segment : null;
49
+ };
50
+ const splitPathSegments = (path)=>{
51
+ if (!path) return [];
52
+ return normalisePathPattern(path).split('/').filter(Boolean);
53
+ };
54
+ const stripLeadingLocaleParam = (path)=>{
55
+ const segments = splitPathSegments(path);
56
+ const leadingLocaleParam = getLocaleParamSegment(segments[0] || '');
57
+ if (!leadingLocaleParam) return path;
58
+ const remainingPath = segments.slice(1).join('/');
59
+ return remainingPath ? `/${remainingPath}` : void 0;
60
+ };
61
+ const getLeadingLocaleParam = (path)=>{
62
+ const segments = splitPathSegments(path);
63
+ return getLocaleParamSegment(segments[0] || '');
64
+ };
65
+ const resolveLocalisedUrlsConfig = (option)=>{
66
+ if (false === option) return {
67
+ enabled: false,
68
+ map: {}
69
+ };
70
+ if (option && 'object' == typeof option) return {
71
+ enabled: true,
72
+ map: option
73
+ };
74
+ return {
75
+ enabled: true,
76
+ map: {}
77
+ };
78
+ };
79
+ const isLocalisableRoutePath = (path)=>{
80
+ const pathWithoutLocale = stripLeadingLocaleParam(path);
81
+ if (!pathWithoutLocale || '/' === pathWithoutLocale || '*' === pathWithoutLocale) return false;
82
+ return true;
83
+ };
84
+ const joinPath = (parentPath, routePath)=>{
85
+ if (!isLocalisableRoutePath(routePath)) return parentPath;
86
+ const segment = normaliseRoutePath(stripLeadingLocaleParam(routePath) || '');
87
+ return normalisePathPattern(`${parentPath}/${segment}`);
88
+ };
89
+ const ensureLocalisedUrlsForPath = (canonicalPath, languages, localisedUrls)=>{
90
+ const entry = localisedUrls[canonicalPath];
91
+ if (!entry) throw new Error(`localisedUrls is enabled, but route "${canonicalPath}" does not define localised URLs for languages: ${languages.join(', ')}. Add localisedUrls["${canonicalPath}"] or set localeDetection.localisedUrls to false.`);
92
+ const missingLanguages = languages.filter((language)=>!entry[language]);
93
+ if (missingLanguages.length > 0) throw new Error(`localisedUrls["${canonicalPath}"] is missing languages: ${missingLanguages.join(', ')}. Every configured language must have a localised URL.`);
94
+ return entry;
95
+ };
96
+ const validateLocalisedUrls = (routes, languages, localisedUrls)=>{
97
+ const visit = (route, parentPath)=>{
98
+ const canonicalPath = joinPath(parentPath, route.path);
99
+ if (isLocalisableRoutePath(route.path)) ensureLocalisedUrlsForPath(canonicalPath, languages, localisedUrls);
100
+ if ('children' in route && route.children) route.children.forEach((child)=>visit(child, canonicalPath));
101
+ };
102
+ routes.forEach((route)=>visit(route, ''));
103
+ };
104
+ const getLocalisedRoutePaths = (canonicalPath, parentLocalisedPaths, languages, entry)=>{
105
+ const paths = languages.map((language)=>{
106
+ const fullPath = normalisePathPattern(entry[language]);
107
+ const parentPath = normalisePathPattern(parentLocalisedPaths[language] || '/');
108
+ if ('/' === parentPath) return normaliseRoutePath(fullPath) || void 0;
109
+ const parentPrefix = `${parentPath}/`;
110
+ if (!fullPath.startsWith(parentPrefix)) throw new Error(`localisedUrls["${canonicalPath}"].${language} must be nested under "${parentPath}" because its parent route is localised there.`);
111
+ return normaliseRoutePath(fullPath.slice(parentPath.length));
112
+ });
113
+ return Array.from(new Set(paths.filter(Boolean)));
114
+ };
115
+ const transformLocalisedRoute = (route, parentCanonicalPath, parentLocalisedPaths, languages, localisedUrls)=>{
116
+ const canonicalPath = joinPath(parentCanonicalPath, route.path);
117
+ const localisedUrlEntry = isLocalisableRoutePath(route.path) ? ensureLocalisedUrlsForPath(canonicalPath, languages, localisedUrls) : void 0;
118
+ const routeLocalisedPaths = localisedUrlEntry ? languages.reduce((acc, language)=>{
119
+ acc[language] = normalisePathPattern(localisedUrlEntry[language]);
120
+ return acc;
121
+ }, {}) : parentLocalisedPaths;
122
+ const children = 'children' in route && route.children ? route.children.flatMap((child)=>transformLocalisedRoute(child, canonicalPath, routeLocalisedPaths, languages, localisedUrls)) : void 0;
123
+ const baseRoute = {
124
+ ...route,
125
+ ...children ? {
126
+ children
127
+ } : {}
128
+ };
129
+ if (!localisedUrlEntry) return [
130
+ baseRoute
131
+ ];
132
+ return getLocalisedRoutePaths(canonicalPath, parentLocalisedPaths, languages, localisedUrlEntry).map((localisedPath, index)=>cloneRouteWithLocalisedPath(baseRoute, localisedPath, index));
133
+ };
134
+ const legalRouteIdPart = (value)=>value.replace(/[^a-zA-Z0-9_$-]+/g, '_').replace(/^_+|_+$/g, '') || 'index';
135
+ const suffixRouteIds = (route, suffix)=>{
136
+ const children = 'children' in route && route.children ? route.children.map((child)=>suffixRouteIds(child, suffix)) : void 0;
137
+ return {
138
+ ...route,
139
+ ...route.id ? {
140
+ id: `${route.id}__localised_${suffix}`
141
+ } : {},
142
+ ...children ? {
143
+ children
144
+ } : {}
145
+ };
146
+ };
147
+ const cloneRouteWithLocalisedPath = (route, path, index)=>{
148
+ const leadingLocaleParam = getLeadingLocaleParam(route.path);
149
+ const localisedPath = leadingLocaleParam ? normaliseRoutePath(`${leadingLocaleParam}/${path}`) : path;
150
+ const routeWithPath = {
151
+ ...route,
152
+ path: localisedPath
153
+ };
154
+ return 0 === index ? routeWithPath : suffixRouteIds(routeWithPath, legalRouteIdPart(localisedPath));
155
+ };
156
+ const applyLocalisedUrlsToRoutes = (routes, languages, localisedUrls)=>{
157
+ const rootLocalisedPaths = languages.reduce((acc, language)=>{
158
+ acc[language] = '/';
159
+ return acc;
160
+ }, {});
161
+ validateLocalisedUrls(routes, languages, localisedUrls);
162
+ return routes.flatMap((route)=>transformLocalisedRoute(route, '', rootLocalisedPaths, languages, localisedUrls));
163
+ };
164
+ const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
165
+ const getParamName = (segment)=>segment.slice(1).replace(/\?$/, '');
166
+ const compilePathPattern = (pattern)=>{
167
+ const names = [];
168
+ const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
169
+ const source = segments.map((segment)=>{
170
+ if (segment.startsWith(':')) {
171
+ names.push(getParamName(segment));
172
+ const paramPattern = '([^/]+)';
173
+ return segment.endsWith('?') ? `(?:/${paramPattern})?` : `/${paramPattern}`;
174
+ }
175
+ if ('*' === segment) {
176
+ names.push('*');
177
+ return '/(.*)';
178
+ }
179
+ return `/${escapeRegExp(segment)}`;
180
+ }).join('');
181
+ return {
182
+ names,
183
+ regexp: new RegExp(`^${source || '/'}$`)
184
+ };
185
+ };
186
+ const matchPathPattern = (pathname, pattern)=>{
187
+ const { names, regexp } = compilePathPattern(pattern);
188
+ const match = regexp.exec(normalisePathPattern(pathname));
189
+ if (!match) return null;
190
+ return names.reduce((params, name, index)=>{
191
+ params[name] = decodeURIComponent(match[index + 1] || '');
192
+ return params;
193
+ }, {});
194
+ };
195
+ const buildPathFromPattern = (pattern, params)=>{
196
+ const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
197
+ const path = segments.map((segment)=>{
198
+ if (segment.startsWith(':')) {
199
+ const param = params[getParamName(segment)];
200
+ return param ? encodeURIComponent(param) : '';
201
+ }
202
+ if ('*' === segment) return params['*'] || '';
203
+ return segment;
204
+ }).filter(Boolean).join('/');
205
+ return `/${path}`;
206
+ };
207
+ const resolveLocalisedPath = (pathname, targetLanguage, languages, localisedUrls)=>{
208
+ const normalizedPathname = normalisePathPattern(pathname);
209
+ for (const localisedUrlEntry of Object.values(localisedUrls)){
210
+ const targetPattern = localisedUrlEntry[targetLanguage];
211
+ if (targetPattern) for (const language of languages){
212
+ const sourcePattern = localisedUrlEntry[language];
213
+ if (!sourcePattern) continue;
214
+ const params = matchPathPattern(normalizedPathname, sourcePattern);
215
+ if (params) return buildPathFromPattern(targetPattern, params);
216
+ }
217
+ }
218
+ return normalizedPathname;
219
+ };
220
+ __webpack_require__.d(__webpack_exports__, {}, {
221
+ applyLocalisedUrlsToRoutes: applyLocalisedUrlsToRoutes,
222
+ normalisePathPattern: normalisePathPattern,
223
+ resolveLocalisedPath: resolveLocalisedPath,
224
+ resolveLocalisedUrlsConfig: resolveLocalisedUrlsConfig,
225
+ validateLocalisedUrls: validateLocalisedUrls
226
+ });
227
+ exports.applyLocalisedUrlsToRoutes = __webpack_exports__.applyLocalisedUrlsToRoutes;
228
+ exports.normalisePathPattern = __webpack_exports__.normalisePathPattern;
229
+ exports.resolveLocalisedPath = __webpack_exports__.resolveLocalisedPath;
230
+ exports.resolveLocalisedUrlsConfig = __webpack_exports__.resolveLocalisedUrlsConfig;
231
+ exports.validateLocalisedUrls = __webpack_exports__.validateLocalisedUrls;
232
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
233
+ "applyLocalisedUrlsToRoutes",
234
+ "normalisePathPattern",
235
+ "resolveLocalisedPath",
236
+ "resolveLocalisedUrlsConfig",
237
+ "validateLocalisedUrls"
238
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
239
+ Object.defineProperty(exports, '__esModule', {
240
+ value: true
241
+ });
@@ -1,11 +1,15 @@
1
1
  "use strict";
2
2
  var __webpack_require__ = {};
3
3
  (()=>{
4
- __webpack_require__.d = (exports1, definition)=>{
5
- for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
- enumerable: true,
7
- get: definition[key]
8
- });
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
9
13
  };
10
14
  })();
11
15
  (()=>{
@@ -23,12 +27,6 @@ var __webpack_require__ = {};
23
27
  })();
24
28
  var __webpack_exports__ = {};
25
29
  __webpack_require__.r(__webpack_exports__);
26
- __webpack_require__.d(__webpack_exports__, {
27
- getBackendOptions: ()=>getBackendOptions,
28
- getEntryConfig: ()=>getEntryConfig,
29
- getLocaleDetectionOptions: ()=>getLocaleDetectionOptions,
30
- removeEntryConfigKey: ()=>removeEntryConfigKey
31
- });
32
30
  function getEntryConfig(entryName, config, entryKey) {
33
31
  const entryConfigMap = config[entryKey];
34
32
  return entryConfigMap?.[entryName];
@@ -63,6 +61,12 @@ function getBackendOptions(entryName, backend) {
63
61
  if ('backendOptionsByEntry' in fullConfig) return removeEntryConfigKey(fullConfig, 'backendOptionsByEntry');
64
62
  return backend;
65
63
  }
64
+ __webpack_require__.d(__webpack_exports__, {
65
+ getBackendOptions: ()=>getBackendOptions,
66
+ getEntryConfig: ()=>getEntryConfig,
67
+ getLocaleDetectionOptions: ()=>getLocaleDetectionOptions,
68
+ removeEntryConfigKey: ()=>removeEntryConfigKey
69
+ });
66
70
  exports.getBackendOptions = __webpack_exports__.getBackendOptions;
67
71
  exports.getEntryConfig = __webpack_exports__.getEntryConfig;
68
72
  exports.getLocaleDetectionOptions = __webpack_exports__.getLocaleDetectionOptions;
@@ -1,7 +1,9 @@
1
1
  import { getPublicDirRoutePrefixes } from "@modern-js/server-core";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
+ import { applyLocalisedUrlsToRoutes, resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
4
5
  import { getBackendOptions, getLocaleDetectionOptions } from "../shared/utils.mjs";
6
+ import "../runtime/types.mjs";
5
7
  function hasJsonFiles(dirPath) {
6
8
  try {
7
9
  if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) return false;
@@ -80,6 +82,27 @@ const i18nPlugin = (options = {})=>({
80
82
  plugins
81
83
  };
82
84
  });
85
+ api.modifyFileSystemRoutes(({ entrypoint, routes })=>{
86
+ if (!localeDetection) return {
87
+ entrypoint,
88
+ routes
89
+ };
90
+ const localeDetectionOptions = getLocaleDetectionOptions(entrypoint.entryName, localeDetection);
91
+ const { localePathRedirect, languages = [], localisedUrls } = localeDetectionOptions;
92
+ if (!localePathRedirect || 0 === languages.length) return {
93
+ entrypoint,
94
+ routes
95
+ };
96
+ const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
97
+ if (!localisedUrlsConfig.enabled) return {
98
+ entrypoint,
99
+ routes
100
+ };
101
+ return {
102
+ entrypoint,
103
+ routes: applyLocalisedUrlsToRoutes(routes, languages, localisedUrlsConfig.map)
104
+ };
105
+ });
83
106
  api._internalServerPlugins(({ plugins })=>{
84
107
  const { serverRoutes, metaName } = api.getAppContext();
85
108
  const normalizedConfig = api.getNormalizedConfig();
@@ -1,26 +1,21 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { Link as router_Link, useInRouterContext, useParams } from "@modern-js/runtime/router";
3
2
  import { useModernI18n } from "./context.mjs";
3
+ import { useI18nRouterAdapter } from "./routerAdapter.mjs";
4
4
  import { buildLocalizedUrl } from "./utils.mjs";
5
- const useRouterHooks = ()=>{
6
- const inRouter = useInRouterContext();
7
- return {
8
- Link: inRouter ? router_Link : null,
9
- params: inRouter ? useParams() : {},
10
- hasRouter: inRouter
11
- };
12
- };
13
5
  const I18nLink = ({ to, children, ...props })=>{
14
- const { Link, params, hasRouter } = useRouterHooks();
15
- const { language, supportedLanguages } = useModernI18n();
6
+ const { Link, params, hasRouter } = useI18nRouterAdapter();
7
+ const { language, supportedLanguages, localisedUrls } = useModernI18n();
16
8
  const currentLang = language;
17
- const localizedTo = buildLocalizedUrl(to, currentLang, supportedLanguages);
9
+ const localizedTo = buildLocalizedUrl(to, currentLang, supportedLanguages, localisedUrls);
18
10
  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.");
19
- if (!hasRouter || !Link) return /*#__PURE__*/ jsx("a", {
20
- href: localizedTo,
21
- ...props,
22
- children: children
23
- });
11
+ if (!hasRouter || !Link) {
12
+ const { prefetch: _prefetch, preload: _preload, ...anchorProps } = props;
13
+ return /*#__PURE__*/ jsx("a", {
14
+ href: localizedTo,
15
+ ...anchorProps,
16
+ children: children
17
+ });
18
+ }
24
19
  return /*#__PURE__*/ jsx(Link, {
25
20
  to: localizedTo,
26
21
  ...props,
@@ -1,8 +1,9 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { isBrowser } from "@modern-js/runtime";
3
- import { createContext, useCallback, useContext, useMemo } from "react";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo } from "react";
4
4
  import { cacheUserLanguage } from "./i18n/detection/index.mjs";
5
- import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, shouldIgnoreRedirect, useRouterHooks } from "./utils.mjs";
5
+ import { useI18nRouterAdapter } from "./routerAdapter.mjs";
6
+ import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, shouldIgnoreRedirect } from "./utils.mjs";
6
7
  const ModernI18nContext = /*#__PURE__*/ createContext(null);
7
8
  const ModernI18nProvider = ({ children, value })=>/*#__PURE__*/ jsx(ModernI18nContext.Provider, {
8
9
  value: value,
@@ -11,9 +12,33 @@ const ModernI18nProvider = ({ children, value })=>/*#__PURE__*/ jsx(ModernI18nCo
11
12
  const useModernI18n = ()=>{
12
13
  const context = useContext(ModernI18nContext);
13
14
  if (!context) throw new Error('useModernI18n must be used within a ModernI18nProvider');
14
- const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, updateLanguage } = context;
15
- const { navigate, location, hasRouter } = useRouterHooks();
16
- const currentLanguage = contextLanguage;
15
+ const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, updateLanguage } = context;
16
+ const { navigate, location, hasRouter } = useI18nRouterAdapter();
17
+ const pathLanguage = useMemo(()=>{
18
+ if (!localePathRedirect || !location?.pathname) return;
19
+ const detected = detectLanguageFromPath(location.pathname, languages || [], localePathRedirect);
20
+ return detected.detected ? detected.language : void 0;
21
+ }, [
22
+ languages,
23
+ localePathRedirect,
24
+ location?.pathname
25
+ ]);
26
+ const currentLanguage = pathLanguage || contextLanguage;
27
+ useEffect(()=>{
28
+ if (!pathLanguage || pathLanguage === contextLanguage) return;
29
+ updateLanguage?.(pathLanguage);
30
+ i18nInstance?.setLang?.(pathLanguage);
31
+ i18nInstance?.changeLanguage?.(pathLanguage);
32
+ if (isBrowser()) {
33
+ const detectionOptions = i18nInstance.options?.detection;
34
+ cacheUserLanguage(i18nInstance, pathLanguage, detectionOptions);
35
+ }
36
+ }, [
37
+ contextLanguage,
38
+ i18nInstance,
39
+ pathLanguage,
40
+ updateLanguage
41
+ ]);
17
42
  const changeLanguage = useCallback(async (newLang)=>{
18
43
  try {
19
44
  if (!newLang || 'string' != typeof newLang) throw new Error('Language must be a non-empty string');
@@ -30,7 +55,7 @@ const useModernI18n = ()=>{
30
55
  const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
31
56
  if (pathLanguage.detected && pathLanguage.language === newLang) return;
32
57
  if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
33
- const newPath = buildLocalizedUrl(relativePath, newLang, languages || []);
58
+ const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
34
59
  const newUrl = entryPath + newPath + location.search + location.hash;
35
60
  await navigate(newUrl, {
36
61
  replace: true
@@ -43,7 +68,7 @@ const useModernI18n = ()=>{
43
68
  const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
44
69
  if (pathLanguage.detected && pathLanguage.language === newLang) return;
45
70
  if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
46
- const newPath = buildLocalizedUrl(relativePath, newLang, languages || []);
71
+ const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
47
72
  const newUrl = entryPath + newPath + window.location.search + window.location.hash;
48
73
  window.history.pushState(null, '', newUrl);
49
74
  }
@@ -58,6 +83,7 @@ const useModernI18n = ()=>{
58
83
  updateLanguage,
59
84
  localePathRedirect,
60
85
  ignoreRedirectRoutes,
86
+ localisedUrls,
61
87
  languages,
62
88
  hasRouter,
63
89
  navigate,
@@ -98,6 +124,7 @@ const useModernI18n = ()=>{
98
124
  changeLanguage,
99
125
  i18nInstance,
100
126
  supportedLanguages: languages || [],
127
+ localisedUrls,
101
128
  isLanguageSupported,
102
129
  isResourcesReady
103
130
  };
@@ -2,7 +2,8 @@ import { isBrowser } from "@modern-js/runtime";
2
2
  import { useEffect, useRef } from "react";
3
3
  import { I18N_SDK_RESOURCES_LOADED_EVENT, getI18nSdkBackendId } from "./i18n/backend/sdk-event.mjs";
4
4
  import { cacheUserLanguage } from "./i18n/detection/index.mjs";
5
- import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, getPathname, shouldIgnoreRedirect, useRouterHooks } from "./utils.mjs";
5
+ import { useI18nRouterAdapter } from "./routerAdapter.mjs";
6
+ import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, getPathname, shouldIgnoreRedirect } from "./utils.mjs";
6
7
  function createMinimalI18nInstance(language) {
7
8
  const minimalInstance = {
8
9
  language,
@@ -14,7 +15,7 @@ function createMinimalI18nInstance(language) {
14
15
  };
15
16
  return minimalInstance;
16
17
  }
17
- function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, setLang) {
18
+ function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang) {
18
19
  const instance = i18nInstance || createMinimalI18nInstance(lang);
19
20
  return {
20
21
  language: lang,
@@ -23,6 +24,7 @@ function createContextValue(lang, i18nInstance, entryName, languages, localePath
23
24
  languages,
24
25
  localePathRedirect,
25
26
  ignoreRedirectRoutes,
27
+ localisedUrls,
26
28
  updateLanguage: setLang
27
29
  };
28
30
  }
@@ -72,9 +74,9 @@ function useSdkResourcesLoader(i18nInstance, setForceUpdate) {
72
74
  setForceUpdate
73
75
  ]);
74
76
  }
75
- function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes) {
77
+ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls) {
76
78
  const hasRedirectedRef = useRef(false);
77
- const { navigate, location, hasRouter } = useRouterHooks();
79
+ const { navigate, location, hasRouter } = useI18nRouterAdapter();
78
80
  useEffect(()=>{
79
81
  if ('browser' !== process.env.MODERN_TARGET) return;
80
82
  if (!localePathRedirect || !i18nInstance) return;
@@ -93,7 +95,7 @@ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fall
93
95
  const pathDetection = detectLanguageFromPath(currentPathname, languages, localePathRedirect);
94
96
  if (pathDetection.detected) return;
95
97
  const targetLanguage = i18nInstance.language || fallbackLanguage || languages[0] || 'en';
96
- const newPath = buildLocalizedUrl(relativePath, targetLanguage, languages);
98
+ const newPath = buildLocalizedUrl(relativePath, targetLanguage, languages, localisedUrls);
97
99
  const newUrl = entryPath + newPath + currentSearch + currentHash;
98
100
  if (newUrl !== currentPathname + currentSearch + currentHash) {
99
101
  hasRedirectedRef.current = true;
@@ -110,7 +112,8 @@ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fall
110
112
  i18nInstance,
111
113
  languages,
112
114
  fallbackLanguage,
113
- ignoreRedirectRoutes
115
+ ignoreRedirectRoutes,
116
+ localisedUrls
114
117
  ]);
115
118
  }
116
119
  function useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang) {
@@ -4,7 +4,7 @@ const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
4
4
  };
5
5
  function convertPath(path) {
6
6
  if (!path) return path;
7
- if (path.startsWith('/')) return `${window.__assetPrefix__ || ''}${path}`;
7
+ if (path.startsWith('/')) return "u" < typeof window ? path : `${window.__assetPrefix__ || ''}${path}`;
8
8
  return path;
9
9
  }
10
10
  function convertBackendOptions(options) {
@@ -1,6 +1,6 @@
1
1
  const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
2
- loadPath: './locales/{{lng}}/{{ns}}.json',
3
- addPath: './locales/{{lng}}/{{ns}}.json'
2
+ loadPath: './config/public/locales/{{lng}}/{{ns}}.json',
3
+ addPath: './config/public/locales/{{lng}}/{{ns}}.json'
4
4
  };
5
5
  function convertPath(path) {
6
6
  if (!path) return path;
@@ -1,8 +1,8 @@
1
- import i18next_fs_backend from "i18next-fs-backend";
1
+ import cjs from "i18next-fs-backend/cjs";
2
2
  import { useI18nextBackendCommon } from "./middleware.common.mjs";
3
- class FsBackendWithSave extends i18next_fs_backend {
3
+ class FsBackendWithSave extends cjs {
4
4
  save(_language, _namespace, _data) {}
5
5
  }
6
6
  const HttpBackendWithSave = FsBackendWithSave;
7
- const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, FsBackendWithSave, i18next_fs_backend, backend);
7
+ const useI18nextBackend = (i18nInstance, backend)=>useI18nextBackendCommon(i18nInstance, FsBackendWithSave, cjs, backend);
8
8
  export { FsBackendWithSave, HttpBackendWithSave, useI18nextBackend };
@@ -40,14 +40,6 @@ async function createI18nextInstance() {
40
40
  return null;
41
41
  }
42
42
  }
43
- async function tryImportReactI18next() {
44
- try {
45
- const reactI18next = await import("react-i18next");
46
- return reactI18next;
47
- } catch (error) {
48
- return null;
49
- }
50
- }
51
43
  function getI18nextInstanceForProvider(instance) {
52
44
  if (isI18nWrapperInstance(instance)) {
53
45
  const i18nextInstance = getI18nWrapperI18nextInstance(instance);
@@ -64,14 +56,4 @@ async function getI18nInstance(userInstance) {
64
56
  if (i18nextInstance) return i18nextInstance;
65
57
  throw new Error('No i18n instance found');
66
58
  }
67
- async function getInitReactI18next() {
68
- const reactI18nextModule = await tryImportReactI18next();
69
- if (reactI18nextModule) return reactI18nextModule.initReactI18next;
70
- return null;
71
- }
72
- async function getI18nextProvider() {
73
- const reactI18nextModule = await tryImportReactI18next();
74
- if (reactI18nextModule) return reactI18nextModule.I18nextProvider;
75
- return null;
76
- }
77
- export { getActualI18nextInstance, getI18nInstance, getI18nWrapperI18nextInstance, getI18nextInstanceForProvider, getI18nextProvider, getInitReactI18next, isI18nInstance, isI18nWrapperInstance };
59
+ export { getActualI18nextInstance, getI18nInstance, getI18nWrapperI18nextInstance, getI18nextInstanceForProvider, isI18nInstance, isI18nWrapperInstance };
@@ -0,0 +1,15 @@
1
+ async function tryImportReactI18next() {
2
+ try {
3
+ return await import("react-i18next");
4
+ } catch (error) {
5
+ return null;
6
+ }
7
+ }
8
+ async function getReactI18nextIntegration() {
9
+ const reactI18nextModule = await tryImportReactI18next();
10
+ return {
11
+ I18nextProvider: reactI18nextModule?.I18nextProvider ?? null,
12
+ initReactI18next: reactI18nextModule?.initReactI18next ?? null
13
+ };
14
+ }
15
+ export { getReactI18nextIntegration };
@@ -100,18 +100,6 @@ const initializeI18nInstance = async (i18nInstance, finalLanguage, fallbackLangu
100
100
  };
101
101
  }
102
102
  }
103
- if (mergedBackend && hasOptions(i18nInstance)) {
104
- const defaultNS = initOptions.defaultNS || initOptions.ns || 'translation';
105
- const ns = Array.isArray(defaultNS) ? defaultNS[0] : defaultNS;
106
- let retries = 20;
107
- while(retries > 0){
108
- const actualInstance = getActualI18nextInstance(i18nInstance);
109
- const store = actualInstance.store;
110
- if (store?.data?.[finalLanguage]?.[ns]) break;
111
- await new Promise((resolve)=>setTimeout(resolve, 100));
112
- retries--;
113
- }
114
- }
115
103
  }
116
104
  };
117
105
  function hasOptions(instance) {