@bleedingdev/modern-js-plugin-i18n 3.2.0-ultramodern.120 → 3.2.0-ultramodern.122

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 (41) hide show
  1. package/dist/cjs/runtime/Link.js +33 -21
  2. package/dist/cjs/runtime/i18n/backend/defaults.node.js +42 -8
  3. package/dist/cjs/runtime/localizedPaths.js +1 -4
  4. package/dist/cjs/runtime/routerAdapter.js +2 -2
  5. package/dist/cjs/runtime/utils.js +2 -9
  6. package/dist/cjs/server/index.js +1 -9
  7. package/dist/cjs/shared/localisedUrls.js +107 -27
  8. package/dist/esm/runtime/Link.mjs +33 -21
  9. package/dist/esm/runtime/i18n/backend/defaults.node.mjs +24 -3
  10. package/dist/esm/runtime/localizedPaths.mjs +2 -5
  11. package/dist/esm/runtime/routerAdapter.mjs +3 -3
  12. package/dist/esm/runtime/utils.mjs +3 -10
  13. package/dist/esm/server/index.mjs +2 -10
  14. package/dist/esm/shared/localisedUrls.mjs +99 -28
  15. package/dist/esm-node/runtime/Link.mjs +33 -21
  16. package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +24 -3
  17. package/dist/esm-node/runtime/localizedPaths.mjs +2 -5
  18. package/dist/esm-node/runtime/routerAdapter.mjs +3 -3
  19. package/dist/esm-node/runtime/utils.mjs +3 -10
  20. package/dist/esm-node/server/index.mjs +2 -10
  21. package/dist/esm-node/shared/localisedUrls.mjs +99 -28
  22. package/dist/types/runtime/Link.d.ts +10 -0
  23. package/dist/types/runtime/i18n/backend/defaults.node.d.ts +3 -2
  24. package/dist/types/runtime/utils.d.ts +2 -2
  25. package/dist/types/shared/localisedUrls.d.ts +15 -0
  26. package/dist/types/shared/type.d.ts +7 -5
  27. package/package.json +16 -12
  28. package/rstest.config.mts +6 -1
  29. package/src/runtime/Link.tsx +28 -12
  30. package/src/runtime/i18n/backend/defaults.node.ts +40 -2
  31. package/src/runtime/localizedPaths.ts +6 -17
  32. package/src/runtime/routerAdapter.tsx +4 -5
  33. package/src/runtime/utils.ts +11 -23
  34. package/src/server/index.ts +7 -17
  35. package/src/shared/localisedUrls.ts +212 -42
  36. package/src/shared/type.ts +7 -5
  37. package/tests/backendDefaults.test.ts +51 -0
  38. package/tests/i18nUtils.test.ts +10 -3
  39. package/tests/link.test.tsx +51 -1
  40. package/tests/localisedUrls.test.ts +224 -0
  41. package/tests/routerAdapter.test.tsx +12 -8
@@ -122,7 +122,7 @@ const mergeClassNames = (...values)=>{
122
122
  return classNames.length > 0 ? classNames.join(' ') : void 0;
123
123
  };
124
124
  const Link = (props)=>{
125
- const { to, params, children, hash: hashProp, search: searchProp, hashScrollIntoView, activeOptions, activeProps, ...rest } = props;
125
+ const { to, params, children, hash: hashProp, search: searchProp, hashScrollIntoView, activeOptions, activeProps, prefetch, preload, ...rest } = props;
126
126
  const adapter = (0, external_routerAdapter_js_namespaceObject.useI18nRouterAdapter)();
127
127
  const { language, supportedLanguages, localisedUrls } = (0, external_context_js_namespaceObject.useModernI18n)();
128
128
  const config = {
@@ -180,7 +180,7 @@ const Link = (props)=>{
180
180
  'aria-current': rest['aria-current'] ?? resolvedActiveProps['aria-current'] ?? 'page'
181
181
  } : {};
182
182
  if (!target) {
183
- const { prefetch: _prefetch, preload: _preload, replace: _replace, ...anchorProps } = rest;
183
+ const { replace: _replace, ...anchorProps } = rest;
184
184
  return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("a", {
185
185
  href: to,
186
186
  ...anchorProps,
@@ -189,7 +189,7 @@ const Link = (props)=>{
189
189
  }
190
190
  const { Link: RouterLink, hasRouter, framework } = adapter;
191
191
  if (!hasRouter || !RouterLink) {
192
- const { prefetch: _prefetch, preload: _preload, replace: _replace, ...anchorProps } = rest;
192
+ const { replace: _replace, ...anchorProps } = rest;
193
193
  const { className: activeClassName, style: activeStyle, ...activeRest } = resolvedActiveProps;
194
194
  return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("a", {
195
195
  href: target.href,
@@ -210,26 +210,38 @@ const Link = (props)=>{
210
210
  ...rest.style,
211
211
  ...activeStyle
212
212
  };
213
- if ('tanstack' === framework) return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(RouterLink, {
214
- to: target.localizedPathname,
215
- ...target.searchObject ? {
216
- search: target.searchObject
217
- } : {},
218
- ...target.hash ? {
219
- hash: target.hash
220
- } : {},
221
- ...void 0 === hashScrollIntoView ? {} : {
222
- hashScrollIntoView
223
- },
224
- ...rest,
225
- ...activeRest,
226
- ...activeAttributes,
227
- className: mergedClassName,
228
- style: mergedStyle,
229
- children: children
230
- });
213
+ if ('tanstack' === framework) {
214
+ const tanstackPreload = void 0 !== preload ? preload : void 0 === prefetch ? void 0 : 'none' === prefetch ? false : prefetch;
215
+ return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(RouterLink, {
216
+ to: target.localizedPathname,
217
+ ...target.searchObject ? {
218
+ search: target.searchObject
219
+ } : {},
220
+ ...target.hash ? {
221
+ hash: target.hash
222
+ } : {},
223
+ ...void 0 === hashScrollIntoView ? {} : {
224
+ hashScrollIntoView
225
+ },
226
+ ...void 0 === tanstackPreload ? {} : {
227
+ preload: tanstackPreload
228
+ },
229
+ ...rest,
230
+ ...activeRest,
231
+ ...activeAttributes,
232
+ className: mergedClassName,
233
+ style: mergedStyle,
234
+ children: children
235
+ });
236
+ }
231
237
  return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(RouterLink, {
232
238
  to: target.href,
239
+ ...void 0 === prefetch ? {} : {
240
+ prefetch
241
+ },
242
+ ...void 0 === preload ? {} : {
243
+ preload
244
+ },
233
245
  ...rest,
234
246
  ...activeRest,
235
247
  ...activeAttributes,
@@ -1,5 +1,14 @@
1
1
  "use strict";
2
2
  var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.n = (module)=>{
5
+ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
6
+ __webpack_require__.d(getter, {
7
+ a: getter
8
+ });
9
+ return getter;
10
+ };
11
+ })();
3
12
  (()=>{
4
13
  __webpack_require__.d = (exports1, getters, values)=>{
5
14
  var define = (defs, kind)=>{
@@ -27,9 +36,37 @@ var __webpack_require__ = {};
27
36
  })();
28
37
  var __webpack_exports__ = {};
29
38
  __webpack_require__.r(__webpack_exports__);
39
+ __webpack_require__.d(__webpack_exports__, {
40
+ DEFAULT_I18NEXT_BACKEND_OPTIONS: ()=>DEFAULT_I18NEXT_BACKEND_OPTIONS,
41
+ convertBackendOptions: ()=>convertBackendOptions,
42
+ resolveDefaultLocalesDir: ()=>resolveDefaultLocalesDir
43
+ });
44
+ const external_fs_namespaceObject = require("fs");
45
+ var external_fs_default = /*#__PURE__*/ __webpack_require__.n(external_fs_namespaceObject);
46
+ const external_path_namespaceObject = require("path");
47
+ var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_namespaceObject);
48
+ const CONVENTIONAL_LOCALES_DIRS = [
49
+ './locales',
50
+ './config/public/locales'
51
+ ];
52
+ const isDirectory = (dirPath)=>{
53
+ try {
54
+ return external_fs_default().statSync(dirPath).isDirectory();
55
+ } catch {
56
+ return false;
57
+ }
58
+ };
59
+ const resolveDefaultLocalesDir = (cwd = process.cwd())=>{
60
+ for (const dir of CONVENTIONAL_LOCALES_DIRS)if (isDirectory(external_path_default().resolve(cwd, dir))) return dir;
61
+ return CONVENTIONAL_LOCALES_DIRS[0];
62
+ };
30
63
  const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
31
- loadPath: './config/public/locales/{{lng}}/{{ns}}.json',
32
- addPath: './config/public/locales/{{lng}}/{{ns}}.json'
64
+ get loadPath () {
65
+ return `${resolveDefaultLocalesDir()}/{{lng}}/{{ns}}.json`;
66
+ },
67
+ get addPath () {
68
+ return `${resolveDefaultLocalesDir()}/{{lng}}/{{ns}}.json`;
69
+ }
33
70
  };
34
71
  function convertPath(path) {
35
72
  if (!path) return path;
@@ -45,16 +82,13 @@ function convertBackendOptions(options) {
45
82
  if (converted.addPath) converted.addPath = convertPath(converted.addPath);
46
83
  return converted;
47
84
  }
48
- __webpack_require__.d(__webpack_exports__, {
49
- convertBackendOptions: ()=>convertBackendOptions
50
- }, {
51
- DEFAULT_I18NEXT_BACKEND_OPTIONS: DEFAULT_I18NEXT_BACKEND_OPTIONS
52
- });
53
85
  exports.DEFAULT_I18NEXT_BACKEND_OPTIONS = __webpack_exports__.DEFAULT_I18NEXT_BACKEND_OPTIONS;
54
86
  exports.convertBackendOptions = __webpack_exports__.convertBackendOptions;
87
+ exports.resolveDefaultLocalesDir = __webpack_exports__.resolveDefaultLocalesDir;
55
88
  for(var __rspack_i in __webpack_exports__)if (-1 === [
56
89
  "DEFAULT_I18NEXT_BACKEND_OPTIONS",
57
- "convertBackendOptions"
90
+ "convertBackendOptions",
91
+ "resolveDefaultLocalesDir"
58
92
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
59
93
  Object.defineProperty(exports, '__esModule', {
60
94
  value: true
@@ -41,10 +41,7 @@ const external_utils_js_namespaceObject = require("./utils.js");
41
41
  const localizePath = (pathname, language, config)=>(0, external_utils_js_namespaceObject.buildLocalizedUrl)(pathname, language, config.languages, config.localisedUrls);
42
42
  const canonicalPath = (target, config)=>{
43
43
  const { pathname, search, hash } = (0, external_utils_js_namespaceObject.splitUrlTarget)(target);
44
- const segments = pathname.split('/').filter(Boolean);
45
- const pathWithoutLanguage = segments.length > 0 && config.languages.includes(segments[0]) ? `/${segments.slice(1).join('/')}` : pathname || '/';
46
- const localisedUrlsConfig = (0, localisedUrls_js_namespaceObject.resolveLocalisedUrlsConfig)(config.localisedUrls);
47
- const resolvedPath = localisedUrlsConfig.enabled ? (0, localisedUrls_js_namespaceObject.resolveCanonicalLocalisedPath)(pathWithoutLanguage, config.languages, localisedUrlsConfig.map) : pathWithoutLanguage;
44
+ const resolvedPath = (0, localisedUrls_js_namespaceObject.canonicalTargetPathname)(pathname, config.languages, config.localisedUrls);
48
45
  return `${resolvedPath}${search}${hash}`;
49
46
  };
50
47
  const useLocalizedPaths = ()=>{
@@ -57,7 +57,7 @@ const getWindowLocation = ()=>{
57
57
  };
58
58
  };
59
59
  const getRouterFramework = (runtimeContext, internalContext, inReactRouter)=>{
60
- const framework = internalContext.routerFramework || internalContext.routerRuntime?.framework || runtimeContext.routerFramework;
60
+ const framework = (0, context_namespaceObject.getRouterRuntimeState)(internalContext)?.framework || (0, context_namespaceObject.getRouterRuntimeState)(runtimeContext)?.framework;
61
61
  if (framework) return framework;
62
62
  if (internalContext.router?.useRouter || runtimeContext.router?.useRouter) return 'tanstack';
63
63
  if (internalContext.router?.useLocation || internalContext.router?.useHref || runtimeContext.router?.useLocation || runtimeContext.router?.useHref) return 'react-router';
@@ -65,7 +65,7 @@ const getRouterFramework = (runtimeContext, internalContext, inReactRouter)=>{
65
65
  };
66
66
  const getRouterInstance = (internalContext, contextRouter)=>{
67
67
  if (contextRouter) return contextRouter;
68
- const router = internalContext.routerInstance || internalContext.routerRuntime?.instance;
68
+ const router = (0, context_namespaceObject.getRouterRuntimeState)(internalContext)?.instance;
69
69
  if (!router || 'object' != typeof router) return null;
70
70
  return router;
71
71
  };
@@ -69,15 +69,8 @@ const splitUrlTarget = (target)=>{
69
69
  };
70
70
  const buildLocalizedUrl = (target, language, languages, localisedUrls)=>{
71
71
  const { pathname, search, hash } = splitUrlTarget(target);
72
- const segments = pathname.split('/').filter(Boolean);
73
- const localisedUrlsConfig = (0, localisedUrls_js_namespaceObject.resolveLocalisedUrlsConfig)(localisedUrls);
74
- const pathWithoutLanguage = segments.length > 0 && languages.includes(segments[0]) ? `/${segments.slice(1).join('/')}` : pathname || '/';
75
- const resolvedPath = localisedUrlsConfig.enabled ? (0, localisedUrls_js_namespaceObject.resolveLocalisedPath)(pathWithoutLanguage, language, languages, localisedUrlsConfig.map) : pathWithoutLanguage;
76
- const resolvedSegments = resolvedPath.split('/').filter(Boolean);
77
- return `/${[
78
- language,
79
- ...resolvedSegments
80
- ].join('/')}${search}${hash}`;
72
+ const localizedPathname = (0, localisedUrls_js_namespaceObject.localiseTargetPathname)(pathname, language, languages, localisedUrls);
73
+ return `${localizedPathname}${search}${hash}`;
81
74
  };
82
75
  const detectLanguageFromPath = (pathname, languages, localePathRedirect)=>{
83
76
  if (!localePathRedirect) return {
@@ -162,15 +162,7 @@ const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
162
162
  const pathname = url.pathname;
163
163
  const basePath = urlPath.replace('/*', '');
164
164
  const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
165
- const segments = remainingPath.split('/').filter(Boolean);
166
- const localisedUrlsConfig = (0, localisedUrls_js_namespaceObject.resolveLocalisedUrlsConfig)(localisedUrls);
167
- const pathWithoutLanguage = segments.length > 0 && languages.includes(segments[0]) ? `/${segments.slice(1).join('/')}` : remainingPath;
168
- const resolvedPath = localisedUrlsConfig.enabled ? (0, localisedUrls_js_namespaceObject.resolveLocalisedPath)(pathWithoutLanguage, language, languages, localisedUrlsConfig.map) : pathWithoutLanguage;
169
- const resolvedSegments = resolvedPath.split('/').filter(Boolean);
170
- const newPathname = `/${[
171
- language,
172
- ...resolvedSegments
173
- ].join('/')}`;
165
+ const newPathname = (0, localisedUrls_js_namespaceObject.localiseTargetPathname)(remainingPath, language, languages, localisedUrls);
174
166
  const suffix = `${url.search}${url.hash}`;
175
167
  const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
176
168
  return localizedUrl;
@@ -32,12 +32,13 @@ const LOCALE_PARAM_NAMES = new Set([
32
32
  'locale',
33
33
  'language'
34
34
  ]);
35
- const normalisePathPattern = (path)=>{
35
+ const normaliseSlashes = (path)=>{
36
36
  const withoutDuplicateSlashes = path.replace(/\/+/g, '/');
37
37
  const withLeadingSlash = withoutDuplicateSlashes.startsWith('/') ? withoutDuplicateSlashes : `/${withoutDuplicateSlashes}`;
38
- const withoutTrailingSlash = withLeadingSlash.length > 1 ? withLeadingSlash.replace(/\/+$/, '') : withLeadingSlash;
39
- return withoutTrailingSlash.replace(/\[(.+?)\]/g, ':$1');
38
+ return withLeadingSlash.length > 1 ? withLeadingSlash.replace(/\/+$/, '') : withLeadingSlash;
40
39
  };
40
+ const normalisePathPattern = (path)=>normaliseSlashes(path).replace(/\[(.+?)\]/g, ':$1');
41
+ const normalisePathname = (pathname)=>normaliseSlashes(pathname);
41
42
  const normaliseRoutePath = (path)=>{
42
43
  const normalized = normalisePathPattern(path);
43
44
  return '/' === normalized ? '' : normalized.slice(1);
@@ -63,16 +64,12 @@ const getLeadingLocaleParam = (path)=>{
63
64
  return getLocaleParamSegment(segments[0] || '');
64
65
  };
65
66
  const resolveLocalisedUrlsConfig = (option)=>{
66
- if (false === option) return {
67
- enabled: false,
68
- map: {}
69
- };
70
- if (option && 'object' == typeof option) return {
67
+ if (option && 'object' == typeof option && Object.keys(option).length > 0) return {
71
68
  enabled: true,
72
69
  map: option
73
70
  };
74
71
  return {
75
- enabled: true,
72
+ enabled: false,
76
73
  map: {}
77
74
  };
78
75
  };
@@ -164,9 +161,13 @@ const applyLocalisedUrlsToRoutes = (routes, languages, localisedUrls)=>{
164
161
  };
165
162
  const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
166
163
  const getParamName = (segment)=>segment.slice(1).replace(/\?$/, '');
164
+ const compiledPathPatternCache = new Map();
167
165
  const compilePathPattern = (pattern)=>{
166
+ const normalizedPattern = normalisePathPattern(pattern);
167
+ const cached = compiledPathPatternCache.get(normalizedPattern);
168
+ if (cached) return cached;
168
169
  const names = [];
169
- const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
170
+ const segments = normalizedPattern.split('/').filter(Boolean);
170
171
  const source = segments.map((segment)=>{
171
172
  if (segment.startsWith(':')) {
172
173
  names.push(getParamName(segment));
@@ -179,19 +180,55 @@ const compilePathPattern = (pattern)=>{
179
180
  }
180
181
  return `/${escapeRegExp(segment)}`;
181
182
  }).join('');
182
- return {
183
+ const compiled = {
183
184
  names,
184
185
  regexp: new RegExp(`^${source || '/'}$`)
185
186
  };
187
+ compiledPathPatternCache.set(normalizedPattern, compiled);
188
+ return compiled;
189
+ };
190
+ const getPatternSpecificity = (pattern)=>{
191
+ const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
192
+ let staticSegments = 0;
193
+ let dynamicSegments = 0;
194
+ let splatSegments = 0;
195
+ for (const segment of segments)if ('*' === segment) splatSegments++;
196
+ else if (segment.startsWith(':')) dynamicSegments++;
197
+ else staticSegments++;
198
+ return {
199
+ staticSegments,
200
+ dynamicSegments,
201
+ splatSegments,
202
+ totalSegments: segments.length
203
+ };
204
+ };
205
+ const comparePatternSpecificity = (left, right)=>{
206
+ const a = getPatternSpecificity(left);
207
+ const b = getPatternSpecificity(right);
208
+ return b.staticSegments - a.staticSegments || b.totalSegments - a.totalSegments || a.splatSegments - b.splatSegments || a.dynamicSegments - b.dynamicSegments;
209
+ };
210
+ const sortPatternsBySpecificity = (patterns)=>patterns.map((pattern, index)=>({
211
+ pattern,
212
+ index
213
+ })).sort((left, right)=>comparePatternSpecificity(left.pattern.pattern, right.pattern.pattern) || left.index - right.index).map(({ pattern })=>pattern);
214
+ const decodePathParam = (value)=>{
215
+ try {
216
+ return decodeURIComponent(value);
217
+ } catch {
218
+ return null;
219
+ }
186
220
  };
187
221
  const matchPathPattern = (pathname, pattern)=>{
188
222
  const { names, regexp } = compilePathPattern(pattern);
189
- const match = regexp.exec(normalisePathPattern(pathname));
223
+ const match = regexp.exec(normalisePathname(pathname));
190
224
  if (!match) return null;
191
- return names.reduce((params, name, index)=>{
192
- params[name] = decodeURIComponent(match[index + 1] || '');
193
- return params;
194
- }, {});
225
+ const params = {};
226
+ for(let index = 0; index < names.length; index++){
227
+ const decoded = decodePathParam(match[index + 1] || '');
228
+ if (null === decoded) return null;
229
+ params[names[index]] = decoded;
230
+ }
231
+ return params;
195
232
  };
196
233
  const buildPathFromPattern = (pattern, params)=>{
197
234
  const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
@@ -206,27 +243,41 @@ const buildPathFromPattern = (pattern, params)=>{
206
243
  return `/${path}`;
207
244
  };
208
245
  const resolveLocalisedPath = (pathname, targetLanguage, languages, localisedUrls)=>{
209
- const normalizedPathname = normalisePathPattern(pathname);
210
- for (const [canonicalPattern, localisedUrlEntry] of Object.entries(localisedUrls)){
246
+ const normalizedPathname = normalisePathname(pathname);
247
+ const canonicalCandidates = sortPatternsBySpecificity(Object.entries(localisedUrls).map(([canonicalPattern, localisedUrlEntry])=>({
248
+ pattern: canonicalPattern,
249
+ canonicalPattern,
250
+ localisedUrlEntry
251
+ })));
252
+ for (const { canonicalPattern, localisedUrlEntry } of canonicalCandidates){
211
253
  const targetPattern = localisedUrlEntry[targetLanguage];
212
254
  if (!targetPattern) continue;
213
255
  const params = matchPathPattern(normalizedPathname, canonicalPattern);
214
256
  if (params) return buildPathFromPattern(targetPattern, params);
215
257
  }
216
- for (const localisedUrlEntry of Object.values(localisedUrls)){
258
+ const localisedCandidates = sortPatternsBySpecificity(Object.values(localisedUrls).flatMap((localisedUrlEntry)=>{
217
259
  const targetPattern = localisedUrlEntry[targetLanguage];
218
- if (targetPattern) for (const language of languages){
219
- const sourcePattern = localisedUrlEntry[language];
220
- if (!sourcePattern) continue;
221
- const params = matchPathPattern(normalizedPathname, sourcePattern);
222
- if (params) return buildPathFromPattern(targetPattern, params);
223
- }
260
+ if (!targetPattern) return [];
261
+ return languages.map((language)=>localisedUrlEntry[language]).filter((sourcePattern)=>Boolean(sourcePattern)).map((sourcePattern)=>({
262
+ pattern: sourcePattern,
263
+ sourcePattern,
264
+ targetPattern
265
+ }));
266
+ }));
267
+ for (const { sourcePattern, targetPattern } of localisedCandidates){
268
+ const params = matchPathPattern(normalizedPathname, sourcePattern);
269
+ if (params) return buildPathFromPattern(targetPattern, params);
224
270
  }
225
271
  return normalizedPathname;
226
272
  };
227
273
  const resolveCanonicalLocalisedPath = (pathname, languages, localisedUrls)=>{
228
- const normalizedPathname = normalisePathPattern(pathname);
229
- for (const [canonicalPattern, localisedUrlEntry] of Object.entries(localisedUrls)){
274
+ const normalizedPathname = normalisePathname(pathname);
275
+ const canonicalCandidates = sortPatternsBySpecificity(Object.entries(localisedUrls).map(([canonicalPattern, localisedUrlEntry])=>({
276
+ pattern: canonicalPattern,
277
+ canonicalPattern,
278
+ localisedUrlEntry
279
+ })));
280
+ for (const { canonicalPattern, localisedUrlEntry } of canonicalCandidates){
230
281
  const canonicalParams = matchPathPattern(normalizedPathname, canonicalPattern);
231
282
  if (canonicalParams) return buildPathFromPattern(canonicalPattern, canonicalParams);
232
283
  for (const language of languages){
@@ -238,11 +289,34 @@ const resolveCanonicalLocalisedPath = (pathname, languages, localisedUrls)=>{
238
289
  }
239
290
  return normalizedPathname;
240
291
  };
292
+ const stripLanguagePrefix = (pathname, languages)=>{
293
+ const segments = pathname.split('/').filter(Boolean);
294
+ if (segments.length > 0 && languages.includes(segments[0])) return `/${segments.slice(1).join('/')}`;
295
+ return pathname || '/';
296
+ };
297
+ const localiseTargetPathname = (pathname, language, languages, localisedUrls)=>{
298
+ const pathWithoutLanguage = stripLanguagePrefix(pathname, languages);
299
+ const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
300
+ const resolvedPath = localisedUrlsConfig.enabled ? resolveLocalisedPath(pathWithoutLanguage, language, languages, localisedUrlsConfig.map) : pathWithoutLanguage;
301
+ const resolvedSegments = resolvedPath.split('/').filter(Boolean);
302
+ return `/${[
303
+ language,
304
+ ...resolvedSegments
305
+ ].join('/')}`;
306
+ };
307
+ const canonicalTargetPathname = (pathname, languages, localisedUrls)=>{
308
+ const pathWithoutLanguage = stripLanguagePrefix(pathname, languages);
309
+ const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
310
+ return localisedUrlsConfig.enabled ? resolveCanonicalLocalisedPath(pathWithoutLanguage, languages, localisedUrlsConfig.map) : pathWithoutLanguage;
311
+ };
241
312
  __webpack_require__.d(__webpack_exports__, {}, {
242
313
  applyLocalisedUrlsToRoutes: applyLocalisedUrlsToRoutes,
243
314
  buildPathFromPattern: buildPathFromPattern,
315
+ canonicalTargetPathname: canonicalTargetPathname,
316
+ localiseTargetPathname: localiseTargetPathname,
244
317
  matchPathPattern: matchPathPattern,
245
318
  normalisePathPattern: normalisePathPattern,
319
+ normalisePathname: normalisePathname,
246
320
  resolveCanonicalLocalisedPath: resolveCanonicalLocalisedPath,
247
321
  resolveLocalisedPath: resolveLocalisedPath,
248
322
  resolveLocalisedUrlsConfig: resolveLocalisedUrlsConfig,
@@ -250,8 +324,11 @@ __webpack_require__.d(__webpack_exports__, {}, {
250
324
  });
251
325
  exports.applyLocalisedUrlsToRoutes = __webpack_exports__.applyLocalisedUrlsToRoutes;
252
326
  exports.buildPathFromPattern = __webpack_exports__.buildPathFromPattern;
327
+ exports.canonicalTargetPathname = __webpack_exports__.canonicalTargetPathname;
328
+ exports.localiseTargetPathname = __webpack_exports__.localiseTargetPathname;
253
329
  exports.matchPathPattern = __webpack_exports__.matchPathPattern;
254
330
  exports.normalisePathPattern = __webpack_exports__.normalisePathPattern;
331
+ exports.normalisePathname = __webpack_exports__.normalisePathname;
255
332
  exports.resolveCanonicalLocalisedPath = __webpack_exports__.resolveCanonicalLocalisedPath;
256
333
  exports.resolveLocalisedPath = __webpack_exports__.resolveLocalisedPath;
257
334
  exports.resolveLocalisedUrlsConfig = __webpack_exports__.resolveLocalisedUrlsConfig;
@@ -259,8 +336,11 @@ exports.validateLocalisedUrls = __webpack_exports__.validateLocalisedUrls;
259
336
  for(var __rspack_i in __webpack_exports__)if (-1 === [
260
337
  "applyLocalisedUrlsToRoutes",
261
338
  "buildPathFromPattern",
339
+ "canonicalTargetPathname",
340
+ "localiseTargetPathname",
262
341
  "matchPathPattern",
263
342
  "normalisePathPattern",
343
+ "normalisePathname",
264
344
  "resolveCanonicalLocalisedPath",
265
345
  "resolveLocalisedPath",
266
346
  "resolveLocalisedUrlsConfig",
@@ -88,7 +88,7 @@ const mergeClassNames = (...values)=>{
88
88
  return classNames.length > 0 ? classNames.join(' ') : void 0;
89
89
  };
90
90
  const Link = (props)=>{
91
- const { to, params, children, hash: hashProp, search: searchProp, hashScrollIntoView, activeOptions, activeProps, ...rest } = props;
91
+ const { to, params, children, hash: hashProp, search: searchProp, hashScrollIntoView, activeOptions, activeProps, prefetch, preload, ...rest } = props;
92
92
  const adapter = useI18nRouterAdapter();
93
93
  const { language, supportedLanguages, localisedUrls } = useModernI18n();
94
94
  const config = {
@@ -146,7 +146,7 @@ const Link = (props)=>{
146
146
  'aria-current': rest['aria-current'] ?? resolvedActiveProps['aria-current'] ?? 'page'
147
147
  } : {};
148
148
  if (!target) {
149
- const { prefetch: _prefetch, preload: _preload, replace: _replace, ...anchorProps } = rest;
149
+ const { replace: _replace, ...anchorProps } = rest;
150
150
  return /*#__PURE__*/ jsx("a", {
151
151
  href: to,
152
152
  ...anchorProps,
@@ -155,7 +155,7 @@ const Link = (props)=>{
155
155
  }
156
156
  const { Link: RouterLink, hasRouter, framework } = adapter;
157
157
  if (!hasRouter || !RouterLink) {
158
- const { prefetch: _prefetch, preload: _preload, replace: _replace, ...anchorProps } = rest;
158
+ const { replace: _replace, ...anchorProps } = rest;
159
159
  const { className: activeClassName, style: activeStyle, ...activeRest } = resolvedActiveProps;
160
160
  return /*#__PURE__*/ jsx("a", {
161
161
  href: target.href,
@@ -176,26 +176,38 @@ const Link = (props)=>{
176
176
  ...rest.style,
177
177
  ...activeStyle
178
178
  };
179
- if ('tanstack' === framework) return /*#__PURE__*/ jsx(RouterLink, {
180
- to: target.localizedPathname,
181
- ...target.searchObject ? {
182
- search: target.searchObject
183
- } : {},
184
- ...target.hash ? {
185
- hash: target.hash
186
- } : {},
187
- ...void 0 === hashScrollIntoView ? {} : {
188
- hashScrollIntoView
189
- },
190
- ...rest,
191
- ...activeRest,
192
- ...activeAttributes,
193
- className: mergedClassName,
194
- style: mergedStyle,
195
- children: children
196
- });
179
+ if ('tanstack' === framework) {
180
+ const tanstackPreload = void 0 !== preload ? preload : void 0 === prefetch ? void 0 : 'none' === prefetch ? false : prefetch;
181
+ return /*#__PURE__*/ jsx(RouterLink, {
182
+ to: target.localizedPathname,
183
+ ...target.searchObject ? {
184
+ search: target.searchObject
185
+ } : {},
186
+ ...target.hash ? {
187
+ hash: target.hash
188
+ } : {},
189
+ ...void 0 === hashScrollIntoView ? {} : {
190
+ hashScrollIntoView
191
+ },
192
+ ...void 0 === tanstackPreload ? {} : {
193
+ preload: tanstackPreload
194
+ },
195
+ ...rest,
196
+ ...activeRest,
197
+ ...activeAttributes,
198
+ className: mergedClassName,
199
+ style: mergedStyle,
200
+ children: children
201
+ });
202
+ }
197
203
  return /*#__PURE__*/ jsx(RouterLink, {
198
204
  to: target.href,
205
+ ...void 0 === prefetch ? {} : {
206
+ prefetch
207
+ },
208
+ ...void 0 === preload ? {} : {
209
+ preload
210
+ },
199
211
  ...rest,
200
212
  ...activeRest,
201
213
  ...activeAttributes,
@@ -1,6 +1,27 @@
1
+ import fs from "fs";
2
+ import path_0 from "path";
3
+ const CONVENTIONAL_LOCALES_DIRS = [
4
+ './locales',
5
+ './config/public/locales'
6
+ ];
7
+ const isDirectory = (dirPath)=>{
8
+ try {
9
+ return fs.statSync(dirPath).isDirectory();
10
+ } catch {
11
+ return false;
12
+ }
13
+ };
14
+ const resolveDefaultLocalesDir = (cwd = process.cwd())=>{
15
+ for (const dir of CONVENTIONAL_LOCALES_DIRS)if (isDirectory(path_0.resolve(cwd, dir))) return dir;
16
+ return CONVENTIONAL_LOCALES_DIRS[0];
17
+ };
1
18
  const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
2
- loadPath: './config/public/locales/{{lng}}/{{ns}}.json',
3
- addPath: './config/public/locales/{{lng}}/{{ns}}.json'
19
+ get loadPath () {
20
+ return `${resolveDefaultLocalesDir()}/{{lng}}/{{ns}}.json`;
21
+ },
22
+ get addPath () {
23
+ return `${resolveDefaultLocalesDir()}/{{lng}}/{{ns}}.json`;
24
+ }
4
25
  };
5
26
  function convertPath(path) {
6
27
  if (!path) return path;
@@ -16,4 +37,4 @@ function convertBackendOptions(options) {
16
37
  if (converted.addPath) converted.addPath = convertPath(converted.addPath);
17
38
  return converted;
18
39
  }
19
- export { DEFAULT_I18NEXT_BACKEND_OPTIONS, convertBackendOptions };
40
+ export { DEFAULT_I18NEXT_BACKEND_OPTIONS, convertBackendOptions, resolveDefaultLocalesDir };
@@ -1,15 +1,12 @@
1
1
  import { useMemo } from "react";
2
- import { resolveCanonicalLocalisedPath, resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
2
+ import { canonicalTargetPathname } from "../shared/localisedUrls.mjs";
3
3
  import { useModernI18n } from "./context.mjs";
4
4
  import { useI18nRouterAdapter } from "./routerAdapter.mjs";
5
5
  import { buildLocalizedUrl, splitUrlTarget } from "./utils.mjs";
6
6
  const localizePath = (pathname, language, config)=>buildLocalizedUrl(pathname, language, config.languages, config.localisedUrls);
7
7
  const canonicalPath = (target, config)=>{
8
8
  const { pathname, search, hash } = splitUrlTarget(target);
9
- const segments = pathname.split('/').filter(Boolean);
10
- const pathWithoutLanguage = segments.length > 0 && config.languages.includes(segments[0]) ? `/${segments.slice(1).join('/')}` : pathname || '/';
11
- const localisedUrlsConfig = resolveLocalisedUrlsConfig(config.localisedUrls);
12
- const resolvedPath = localisedUrlsConfig.enabled ? resolveCanonicalLocalisedPath(pathWithoutLanguage, config.languages, localisedUrlsConfig.map) : pathWithoutLanguage;
9
+ const resolvedPath = canonicalTargetPathname(pathname, config.languages, config.localisedUrls);
13
10
  return `${resolvedPath}${search}${hash}`;
14
11
  };
15
12
  const useLocalizedPaths = ()=>{
@@ -1,5 +1,5 @@
1
1
  import { RuntimeContext, isBrowser } from "@modern-js/runtime";
2
- import { InternalRuntimeContext } from "@modern-js/runtime/context";
2
+ import { InternalRuntimeContext, getRouterRuntimeState } from "@modern-js/runtime/context";
3
3
  import { Link as router_Link, useInRouterContext, useLocation, useNavigate, useParams } from "@modern-js/runtime/router";
4
4
  import { useCallback, useContext, useEffect, useState } from "react";
5
5
  const normalizeUrlPart = (value, prefix)=>{
@@ -25,7 +25,7 @@ const getWindowLocation = ()=>{
25
25
  };
26
26
  };
27
27
  const getRouterFramework = (runtimeContext, internalContext, inReactRouter)=>{
28
- const framework = internalContext.routerFramework || internalContext.routerRuntime?.framework || runtimeContext.routerFramework;
28
+ const framework = getRouterRuntimeState(internalContext)?.framework || getRouterRuntimeState(runtimeContext)?.framework;
29
29
  if (framework) return framework;
30
30
  if (internalContext.router?.useRouter || runtimeContext.router?.useRouter) return 'tanstack';
31
31
  if (internalContext.router?.useLocation || internalContext.router?.useHref || runtimeContext.router?.useLocation || runtimeContext.router?.useHref) return 'react-router';
@@ -33,7 +33,7 @@ const getRouterFramework = (runtimeContext, internalContext, inReactRouter)=>{
33
33
  };
34
34
  const getRouterInstance = (internalContext, contextRouter)=>{
35
35
  if (contextRouter) return contextRouter;
36
- const router = internalContext.routerInstance || internalContext.routerRuntime?.instance;
36
+ const router = getRouterRuntimeState(internalContext)?.instance;
37
37
  if (!router || 'object' != typeof router) return null;
38
38
  return router;
39
39
  };
@@ -1,6 +1,6 @@
1
1
  import { isBrowser } from "@modern-js/runtime";
2
2
  import { getGlobalBasename } from "@modern-js/runtime/context";
3
- import { resolveLocalisedPath, resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
3
+ import { localiseTargetPathname } from "../shared/localisedUrls.mjs";
4
4
  const getPathname = (context)=>{
5
5
  if (isBrowser()) return window.location.pathname;
6
6
  return context.ssrContext?.request?.pathname || '/';
@@ -31,15 +31,8 @@ const splitUrlTarget = (target)=>{
31
31
  };
32
32
  const buildLocalizedUrl = (target, language, languages, localisedUrls)=>{
33
33
  const { pathname, search, hash } = splitUrlTarget(target);
34
- const segments = pathname.split('/').filter(Boolean);
35
- const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
36
- const pathWithoutLanguage = segments.length > 0 && languages.includes(segments[0]) ? `/${segments.slice(1).join('/')}` : pathname || '/';
37
- const resolvedPath = localisedUrlsConfig.enabled ? resolveLocalisedPath(pathWithoutLanguage, language, languages, localisedUrlsConfig.map) : pathWithoutLanguage;
38
- const resolvedSegments = resolvedPath.split('/').filter(Boolean);
39
- return `/${[
40
- language,
41
- ...resolvedSegments
42
- ].join('/')}${search}${hash}`;
34
+ const localizedPathname = localiseTargetPathname(pathname, language, languages, localisedUrls);
35
+ return `${localizedPathname}${search}${hash}`;
43
36
  };
44
37
  const detectLanguageFromPath = (pathname, languages, localePathRedirect)=>{
45
38
  if (!localePathRedirect) return {