nuxt-i18n-micro 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,5 +8,5 @@
8
8
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/CBxwnKtU.js">
9
9
  <link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-500.BqKd8Zt-.css">
10
10
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/BYEpoBUk.js">
11
- <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/XZXfxmri.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1761933271955,false]</script>
12
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"df79903a-9996-4527-8269-e47de875a594",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
11
+ <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/XZXfxmri.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1762281061506,false]</script>
12
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"1f0be1c2-48e6-40e6-8faf-6f8e0f051970",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
@@ -8,5 +8,5 @@
8
8
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/CBxwnKtU.js">
9
9
  <link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-500.BqKd8Zt-.css">
10
10
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/BYEpoBUk.js">
11
- <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/XZXfxmri.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1761933271956,false]</script>
12
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"df79903a-9996-4527-8269-e47de875a594",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
11
+ <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/XZXfxmri.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1762281061507,false]</script>
12
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"1f0be1c2-48e6-40e6-8faf-6f8e0f051970",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
@@ -1 +1 @@
1
- {"id":"df79903a-9996-4527-8269-e47de875a594","timestamp":1761933265840}
1
+ {"id":"1f0be1c2-48e6-40e6-8faf-6f8e0f051970","timestamp":1762281055061}
@@ -0,0 +1 @@
1
+ {"id":"1f0be1c2-48e6-40e6-8faf-6f8e0f051970","timestamp":1762281055061,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
@@ -8,5 +8,5 @@
8
8
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/CBxwnKtU.js">
9
9
  <link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-500.BqKd8Zt-.css">
10
10
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/BYEpoBUk.js">
11
- <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/XZXfxmri.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1761933271956,false]</script>
12
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"df79903a-9996-4527-8269-e47de875a594",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
11
+ <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/XZXfxmri.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1762281061507,false]</script>
12
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"1f0be1c2-48e6-40e6-8faf-6f8e0f051970",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-i18n-micro",
3
3
  "configKey": "i18n",
4
- "version": "2.1.0",
4
+ "version": "2.3.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -199,6 +199,19 @@ function extractDefineI18nRouteData(content, _filePath) {
199
199
  return null;
200
200
  }
201
201
  }
202
+ function normalizeRouteKey(key) {
203
+ return key.split("/").map((segment) => {
204
+ if (segment.startsWith("[...") && segment.endsWith("]")) {
205
+ const paramName = segment.substring(4, segment.length - 1);
206
+ return `:${paramName}(.*)*`;
207
+ }
208
+ if (segment.startsWith("[") && segment.endsWith("]")) {
209
+ const paramName = segment.substring(1, segment.length - 1);
210
+ return `:${paramName}`;
211
+ }
212
+ return segment;
213
+ }).join("/");
214
+ }
202
215
  const normalizePath = (routePath) => {
203
216
  if (!routePath) {
204
217
  return "";
@@ -249,7 +262,22 @@ class PageManager {
249
262
  this.noPrefixRedirect = noPrefixRedirect;
250
263
  this.excludePatterns = excludePatterns;
251
264
  this.activeLocaleCodes = this.computeActiveLocaleCodes();
252
- this.globalLocaleRoutes = globalLocaleRoutes || {};
265
+ const normalizedGlobalRoutes = {};
266
+ for (const key in globalLocaleRoutes) {
267
+ const newKey = normalizeRouteKey(key);
268
+ const localePaths = globalLocaleRoutes[key];
269
+ if (typeof localePaths === "object") {
270
+ const normalizedLocalePaths = {};
271
+ for (const locale in localePaths) {
272
+ const customPath = localePaths[locale];
273
+ normalizedLocalePaths[locale] = normalizeRouteKey(customPath);
274
+ }
275
+ normalizedGlobalRoutes[newKey] = normalizedLocalePaths;
276
+ } else {
277
+ normalizedGlobalRoutes[newKey] = localePaths;
278
+ }
279
+ }
280
+ this.globalLocaleRoutes = normalizedGlobalRoutes;
253
281
  this.filesLocaleRoutes = filesLocaleRoutes || {};
254
282
  this.routeLocales = routeLocales || {};
255
283
  }
@@ -279,22 +307,76 @@ class PageManager {
279
307
  extendPages(pages, customRegex, isCloudflarePages) {
280
308
  this.localizedPaths = this.extractLocalizedPaths(pages);
281
309
  const additionalRoutes = [];
310
+ const originalPagePaths = /* @__PURE__ */ new Map();
282
311
  for (const page of [...pages]) {
312
+ if (isPageRedirectOnly(page)) {
313
+ continue;
314
+ }
283
315
  if (page.path && isInternalPath(page.path, this.excludePatterns)) {
284
316
  continue;
285
317
  }
286
- if (!page.name && page.file?.endsWith(".vue")) {
287
- console.warn(`[nuxt-i18n-next] Page name is missing for the file: ${page.file}`);
318
+ const originalPath = page.path ?? "";
319
+ originalPagePaths.set(page, originalPath);
320
+ const pageName = buildRouteNameFromRoute(page.name, page.path);
321
+ const normalizedOriginalPath = normalizeRouteKey(originalPath);
322
+ const customPaths = this.localizedPaths[originalPath] || this.localizedPaths[pageName];
323
+ const isLocalizationDisabled = this.globalLocaleRoutes[pageName] === false || this.globalLocaleRoutes[normalizedOriginalPath] === false;
324
+ if (isLocalizationDisabled) {
325
+ continue;
288
326
  }
289
- const customRoute = this.globalLocaleRoutes[page.name ?? ""];
290
- if (customRoute === false) {
327
+ const allowedLocales = this.getAllowedLocalesForPage(originalPath, pageName);
328
+ const originalChildren = cloneArray(page.children ?? []);
329
+ if (isNoPrefixStrategy(this.strategy)) {
330
+ if (customPaths) {
331
+ this.locales.forEach((locale) => {
332
+ const customPath = customPaths[locale.code];
333
+ if (customPath && allowedLocales.includes(locale.code)) {
334
+ const newRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPath, customRegex, false, locale.code, originalPath);
335
+ if (newRoute) {
336
+ additionalRoutes.push(newRoute);
337
+ if (this.noPrefixRedirect && locale.code === this.defaultLocale.code) {
338
+ page.redirect = normalizePath(customPath);
339
+ }
340
+ }
341
+ }
342
+ });
343
+ }
344
+ this.handleAliasRoutes(page, additionalRoutes, customRegex, allowedLocales);
291
345
  continue;
292
346
  }
293
- if (typeof customRoute === "object" && customRoute !== null) {
294
- this.addCustomGlobalLocalizedRoutes(page, customRoute, additionalRoutes, customRegex);
295
- } else {
296
- this.localizePage(page, additionalRoutes, customRegex);
347
+ const defaultLocaleCode = this.defaultLocale.code;
348
+ if (allowedLocales.includes(defaultLocaleCode)) {
349
+ const customPath = customPaths?.[defaultLocaleCode];
350
+ if (isPrefixExceptDefaultStrategy(this.strategy)) {
351
+ if (customPath) {
352
+ page.path = normalizePath(customPath);
353
+ }
354
+ page.children = this.createLocalizedChildren(originalChildren, originalPath, [defaultLocaleCode], false, false, false, customPath ? { [defaultLocaleCode]: customPath } : {});
355
+ }
297
356
  }
357
+ const localesToGenerate = this.locales.filter((l) => {
358
+ if (!allowedLocales.includes(l.code)) return false;
359
+ if (isPrefixExceptDefaultStrategy(this.strategy) && l.code === defaultLocaleCode) return false;
360
+ return true;
361
+ });
362
+ if (localesToGenerate.length > 0) {
363
+ if (customPaths) {
364
+ localesToGenerate.forEach((locale) => {
365
+ if (customPaths[locale.code]) {
366
+ const newRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPaths[locale.code], customRegex, false, locale.code, originalPath);
367
+ if (newRoute) additionalRoutes.push(newRoute);
368
+ } else {
369
+ const newRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, false, "", customRegex, false, locale.code, originalPath);
370
+ if (newRoute) additionalRoutes.push(newRoute);
371
+ }
372
+ });
373
+ } else {
374
+ const localeCodes = localesToGenerate.map((l) => l.code);
375
+ const newRoute = this.createLocalizedRoute(page, localeCodes, originalChildren, false, "", customRegex, false, true, originalPath);
376
+ if (newRoute) additionalRoutes.push(newRoute);
377
+ }
378
+ }
379
+ this.handleAliasRoutes(page, additionalRoutes, customRegex, allowedLocales);
298
380
  }
299
381
  if (isPrefixStrategy(this.strategy) && !isCloudflarePages) {
300
382
  for (let i = pages.length - 1; i >= 0; i--) {
@@ -316,7 +398,8 @@ class PageManager {
316
398
  pages.forEach((page) => {
317
399
  const pageName = buildRouteNameFromRoute(page.name, page.path);
318
400
  const normalizedFullPath = normalizePath(path.posix.join(parentPath, page.path));
319
- const globalLocalePath = this.globalLocaleRoutes[normalizedFullPath] || this.globalLocaleRoutes[pageName];
401
+ const normalizedKey = normalizeRouteKey(normalizedFullPath);
402
+ const globalLocalePath = this.globalLocaleRoutes[normalizedKey] || this.globalLocaleRoutes[pageName];
320
403
  if (!globalLocalePath) {
321
404
  const filesLocalePath = this.filesLocaleRoutes[pageName];
322
405
  if (filesLocalePath && typeof filesLocalePath === "object") {
@@ -564,16 +647,17 @@ class PageManager {
564
647
  }
565
648
  return baseName;
566
649
  }
567
- createLocalizedRoute(page, localeCodes, originalChildren, isCustom, customPath = "", customRegex, force = false, parentLocale = false) {
650
+ createLocalizedRoute(page, localeCodes, originalChildren, isCustom, customPath = "", customRegex, force = false, parentLocale = false, originalPagePath) {
568
651
  const routePath = this.buildRoutePath(localeCodes, page.path, encodeURI(customPath), isCustom, customRegex, force);
569
- if (!routePath || routePath == page.path) return null;
652
+ if (!routePath || routePath === page.path) return null;
570
653
  if (localeCodes.length === 0) return null;
571
654
  const firstLocale = localeCodes[0];
572
655
  if (!firstLocale) return null;
573
- const routeName = buildRouteName(buildRouteNameFromRoute(page.name ?? "", page.path ?? ""), firstLocale, isCustom);
656
+ const parentPathForChildren = originalPagePath ?? page.path ?? "";
657
+ const routeName = buildRouteName(buildRouteNameFromRoute(page.name ?? "", parentPathForChildren), firstLocale, isCustom);
574
658
  return {
575
659
  ...page,
576
- children: this.createLocalizedChildren(originalChildren, page.path, localeCodes, true, false, parentLocale, { [firstLocale]: customPath }),
660
+ children: this.createLocalizedChildren(originalChildren, parentPathForChildren, localeCodes, true, false, parentLocale, { [firstLocale]: customPath }),
577
661
  path: routePath,
578
662
  name: routeName,
579
663
  alias: [],
@@ -787,6 +871,9 @@ const module = defineNuxtModule({
787
871
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
788
872
  // @ts-ignore
789
873
  routeDisableMeta,
874
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
875
+ // @ts-ignore
876
+ globalLocaleRoutes: mergedGlobalLocaleRoutes,
790
877
  experimental: {
791
878
  i18nPreviousPageFallback: options.experimental?.i18nPreviousPageFallback ?? false,
792
879
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -947,16 +1034,33 @@ ${accepts}
947
1034
  localeManager.ensureTranslationFilesExist(pagesNames, options.translationDir, nuxt.options.rootDir);
948
1035
  }
949
1036
  pageManager.extendPages(pages, options.customRegexMatcher, isCloudflarePages);
950
- if (isPrefixStrategy(options.strategy) && !isCloudflarePages) {
951
- const fallbackRoute = {
952
- path: "/:pathMatch(.*)*",
953
- name: "custom-fallback-route",
954
- file: resolver.resolve("./runtime/components/locale-redirect.vue"),
955
- meta: {
956
- globalLocaleRoutes: options.globalLocaleRoutes
1037
+ if (!isCloudflarePages) {
1038
+ const strategy = options.strategy;
1039
+ if (isPrefixStrategy(strategy)) {
1040
+ const rootPageIndex = pages.findIndex((page) => page.name === "index" && page.path === "/");
1041
+ if (rootPageIndex > -1) {
1042
+ pages.splice(rootPageIndex, 1);
957
1043
  }
958
- };
959
- pages.push(fallbackRoute);
1044
+ const fallbackRoute = {
1045
+ path: "/:pathMatch(.*)*",
1046
+ name: "custom-fallback-route",
1047
+ file: resolver.resolve("./runtime/components/locale-redirect.vue")
1048
+ };
1049
+ pages.push(fallbackRoute);
1050
+ logger.info("Strategy 'prefix': Added fallback route to redirect all non-prefixed paths.");
1051
+ }
1052
+ const needsFallback = isPrefixStrategy(options.strategy) || isPrefixExceptDefaultStrategy(options.strategy);
1053
+ if (needsFallback) {
1054
+ const fallbackRoute = {
1055
+ path: "/:pathMatch(.*)*",
1056
+ name: "custom-fallback-route",
1057
+ file: resolver.resolve("./runtime/components/locale-redirect.vue"),
1058
+ meta: {
1059
+ globalLocaleRoutes: options.globalLocaleRoutes
1060
+ }
1061
+ };
1062
+ pages.push(fallbackRoute);
1063
+ }
960
1064
  }
961
1065
  if (!isNoPrefixStrategy(options.strategy)) {
962
1066
  if (isCloudflarePages) {
@@ -993,7 +1097,9 @@ ${accepts}
993
1097
  }
994
1098
  }
995
1099
  if (page.children && page.children.length) {
996
- page.children.forEach((childPage) => processPageWithChildren(childPage, fullPath));
1100
+ page.children.forEach((childPage) => {
1101
+ processPageWithChildren(childPage, fullPath);
1102
+ });
997
1103
  }
998
1104
  };
999
1105
  pages.forEach((page) => {
@@ -1104,7 +1210,9 @@ ${accepts}
1104
1210
  routesToRemove.push(route);
1105
1211
  }
1106
1212
  });
1107
- routesToRemove.forEach((route) => routesSet.delete(route));
1213
+ routesToRemove.forEach((route) => {
1214
+ routesSet.delete(route);
1215
+ });
1108
1216
  const additionalRoutes = /* @__PURE__ */ new Set();
1109
1217
  const routeRules = nuxt.options.routeRules || {};
1110
1218
  routesSet.forEach((route) => {
@@ -1131,7 +1239,9 @@ ${accepts}
1131
1239
  });
1132
1240
  }
1133
1241
  });
1134
- additionalRoutes.forEach((route) => routesSet.add(route));
1242
+ additionalRoutes.forEach((route) => {
1243
+ routesSet.add(route);
1244
+ });
1135
1245
  });
1136
1246
  if (nuxt.options.dev) {
1137
1247
  setupDevToolsUI(options, resolver.resolve);
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import { h, defineComponent } from "vue";
2
+ import { h as hyperscript, defineComponent } from "vue";
3
3
  import { useNuxtApp, useRoute } from "#imports";
4
4
  export default defineComponent({
5
5
  name: "I18nT",
@@ -53,18 +53,18 @@ export default defineComponent({
53
53
  const $t = $_t(route);
54
54
  if (props.number !== void 0) {
55
55
  const numberValue = Number(props.number);
56
- return h(props.tag, { ...attrs, innerHTML: $t(props.keypath, { number: $tn(numberValue) }) });
56
+ return hyperscript(props.tag, { ...attrs, innerHTML: $t(props.keypath, { number: $tn(numberValue) }) });
57
57
  }
58
58
  if (props.date !== void 0) {
59
- return h(props.tag, { ...attrs, innerHTML: $t(props.keypath, { date: $td(props.date) }) });
59
+ return hyperscript(props.tag, { ...attrs, innerHTML: $t(props.keypath, { date: $td(props.date) }) });
60
60
  }
61
61
  if (props.relativeDate !== void 0) {
62
- return h(props.tag, { ...attrs, innerHTML: $t(props.keypath, { relativeDate: $tdr(props.relativeDate) }) });
62
+ return hyperscript(props.tag, { ...attrs, innerHTML: $t(props.keypath, { relativeDate: $tdr(props.relativeDate) }) });
63
63
  }
64
64
  if (props.plural !== void 0) {
65
65
  const count = Number.parseInt(props.plural.toString());
66
66
  if (props.customPluralRule) {
67
- return h(props.tag, { ...attrs, innerHTML: props.customPluralRule(
67
+ return hyperscript(props.tag, { ...attrs, innerHTML: props.customPluralRule(
68
68
  props.keypath,
69
69
  count,
70
70
  props.params,
@@ -72,7 +72,7 @@ export default defineComponent({
72
72
  $t
73
73
  ) });
74
74
  } else {
75
- return h(props.tag, { ...attrs, innerHTML: $tc(props.keypath, { count, ...props.params }) });
75
+ return hyperscript(props.tag, { ...attrs, innerHTML: $tc(props.keypath, { count, ...props.params }) });
76
76
  }
77
77
  }
78
78
  const translation = ($t(props.keypath, { ...props.params, ...options }) ?? "").toString();
@@ -80,10 +80,10 @@ export default defineComponent({
80
80
  return props.defaultValue ?? null;
81
81
  }
82
82
  if (props.html) {
83
- return h(props.tag, { ...attrs, innerHTML: translation });
83
+ return hyperscript(props.tag, { ...attrs, innerHTML: translation });
84
84
  }
85
85
  if (slots.default) {
86
- return h(
86
+ return hyperscript(
87
87
  props.tag,
88
88
  attrs,
89
89
  slots.default({ translation })
@@ -98,7 +98,7 @@ export default defineComponent({
98
98
  if (index > lastIndex) {
99
99
  children.push(translation.slice(lastIndex, index));
100
100
  }
101
- children.push(h(slotFn));
101
+ children.push(hyperscript(slotFn));
102
102
  lastIndex = index + placeholder.length;
103
103
  }
104
104
  }
@@ -106,13 +106,13 @@ export default defineComponent({
106
106
  children.push(translation.slice(lastIndex));
107
107
  }
108
108
  if (slots.default) {
109
- return h(
109
+ return hyperscript(
110
110
  props.tag,
111
111
  attrs,
112
112
  slots.default({ children })
113
113
  );
114
114
  }
115
- return h(props.tag, attrs, children);
115
+ return hyperscript(props.tag, attrs, children);
116
116
  };
117
117
  }
118
118
  });
@@ -52,7 +52,7 @@ if (globalLocaleRoutes && globalLocaleRoutes[currentPageName]) {
52
52
  } else if (locales.includes(firstSegment) && globalLocaleRoutes && globalLocaleRoutes[currentLocalePageName]) {
53
53
  const localizedRoutes = globalLocaleRoutes[currentLocalePageName];
54
54
  if (localizedRoutes && localizedRoutes[firstSegment]) {
55
- const localizedPath = `/${firstSegment}${localizedRoutes[firstSegment]}`;
55
+ const localizedPath = `/${firstSegment}/${localizedRoutes[firstSegment]}`;
56
56
  if (route.fullPath !== localizedPath) {
57
57
  handleRedirect(localizedPath);
58
58
  }
@@ -46,7 +46,6 @@ export default defineNuxtPlugin(async (nuxtApp) => {
46
46
  const enablePreviousPageFallback = i18nConfig.experimental?.i18nPreviousPageFallback ?? false;
47
47
  nuxtApp.hook("page:finish", () => {
48
48
  if (import.meta.client) {
49
- i18nRouteParams.value = null;
50
49
  previousPageInfo.value = null;
51
50
  }
52
51
  });
@@ -83,6 +82,9 @@ export default defineNuxtPlugin(async (nuxtApp) => {
83
82
  }
84
83
  }
85
84
  router.beforeEach(async (to, from, next) => {
85
+ if (to.name !== from.name) {
86
+ i18nRouteParams.value = {};
87
+ }
86
88
  if (to.path === from.path && !isNoPrefixStrategy(i18nConfig.strategy)) {
87
89
  if (next) next();
88
90
  return;
@@ -1,30 +1,64 @@
1
1
  import { isNoPrefixStrategy, isPrefixStrategy } from "nuxt-i18n-micro-core";
2
2
  import { defineNuxtPlugin, useRuntimeConfig, useRoute, useRouter, navigateTo, createError } from "#imports";
3
3
  import { findAllowedLocalesForRoute } from "../utils/route-utils.js";
4
+ import { joinURL, withQuery } from "ufo";
5
+ function resolvePathWithParams(path, params) {
6
+ let resolvedPath = path;
7
+ for (const key in params) {
8
+ const value = params[key];
9
+ if (value) {
10
+ const stringValue = String(value);
11
+ resolvedPath = resolvedPath.replace(`:${key}()`, stringValue).replace(`:${key}`, stringValue);
12
+ }
13
+ }
14
+ return resolvedPath;
15
+ }
4
16
  export default defineNuxtPlugin(async (nuxtApp) => {
5
17
  const config = useRuntimeConfig();
6
18
  const i18nConfig = config.public.i18nConfig;
7
- const { routeLocales } = useRuntimeConfig().public.i18nConfig;
19
+ const { routeLocales, globalLocaleRoutes } = useRuntimeConfig().public.i18nConfig;
8
20
  const route = useRoute();
9
21
  const router = useRouter();
22
+ const checkGlobalLocaleRoutes = (to) => {
23
+ if (!globalLocaleRoutes || typeof globalLocaleRoutes !== "object" || Object.keys(globalLocaleRoutes).length === 0) {
24
+ return false;
25
+ }
26
+ const locales = i18nConfig.locales?.map((l) => l.code) || [];
27
+ const defaultLocale = i18nConfig.defaultLocale || "en";
28
+ const pathSegments = to.path.split("/").filter(Boolean);
29
+ const firstSegment = pathSegments[0];
30
+ const pathWithoutLocale = locales.includes(firstSegment) ? "/" + pathSegments.slice(1).join("/") : to.path;
31
+ const routeName = (typeof to.name === "string" ? to.name : "").replace("localized-", "").replace(new RegExp(`-(${locales.join("|")})$`), "");
32
+ const routeRules = globalLocaleRoutes[pathWithoutLocale] || globalLocaleRoutes[routeName];
33
+ if (routeRules && typeof routeRules === "object") {
34
+ const localeToUse = locales.includes(firstSegment) ? firstSegment : defaultLocale;
35
+ const customPathSegment = routeRules[localeToUse];
36
+ if (customPathSegment) {
37
+ const resolvedCustomPath = resolvePathWithParams(customPathSegment, to.params);
38
+ const localizedPath = locales.includes(firstSegment) ? joinURL(`/${firstSegment}`, resolvedCustomPath) : resolvedCustomPath;
39
+ if (decodeURI(to.path) !== decodeURI(localizedPath)) {
40
+ const finalUrl = withQuery(localizedPath, to.query);
41
+ navigateTo(finalUrl, { redirectCode: 301, external: true });
42
+ return true;
43
+ }
44
+ } else if (locales.includes(firstSegment)) {
45
+ throw createError({ statusCode: 404, statusMessage: "Page Not Found" });
46
+ }
47
+ }
48
+ return false;
49
+ };
10
50
  const checkRouteLocales = (to) => {
11
51
  const allowedLocales = findAllowedLocalesForRoute(to, routeLocales);
12
- if (!allowedLocales || allowedLocales.length === 0) {
13
- return;
14
- }
52
+ if (!allowedLocales || allowedLocales.length === 0) return;
15
53
  const pathSegments = to.path.split("/").filter(Boolean);
16
54
  const firstSegment = pathSegments[0];
17
55
  const allLocales = i18nConfig.locales?.map((l) => l.code) || [];
18
56
  if (firstSegment && allLocales.includes(firstSegment) && !allowedLocales.includes(firstSegment)) {
19
- console.log("Locale not allowed, throwing 404");
20
- throw createError({
21
- statusCode: 404,
22
- statusMessage: "Page Not Found"
23
- });
57
+ throw createError({ statusCode: 404, statusMessage: "Page Not Found" });
24
58
  }
25
59
  };
26
60
  const handleRedirect = async (to) => {
27
- const currentLocale = nuxtApp.$getLocale().toString();
61
+ const currentLocale = nuxtApp.$getLocale(to);
28
62
  const name = to.name?.toString();
29
63
  let defaultRouteName = name?.toString().replace("localized-", "").replace(new RegExp(`-${currentLocale}$`), "");
30
64
  if (!to.params.locale) {
@@ -45,12 +79,14 @@ export default defineNuxtPlugin(async (nuxtApp) => {
45
79
  }
46
80
  };
47
81
  if (import.meta.server) {
82
+ if (checkGlobalLocaleRoutes(route)) return;
48
83
  checkRouteLocales(route);
49
84
  if (isPrefixStrategy(i18nConfig.strategy) || isNoPrefixStrategy(i18nConfig.strategy)) {
50
85
  await handleRedirect(route);
51
86
  }
52
87
  }
53
88
  router.beforeEach(async (to, from, next) => {
89
+ if (checkGlobalLocaleRoutes(to)) return;
54
90
  if (from.path !== to.path) {
55
91
  checkRouteLocales(to);
56
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-i18n-micro",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Nuxt I18n Micro is a lightweight, high-performance internationalization module for Nuxt, designed to handle multi-language support with minimal overhead, fast build times, and efficient runtime performance.",
5
5
  "repository": "s00d/nuxt-i18n-micro",
6
6
  "license": "MIT",
@@ -61,7 +61,7 @@
61
61
  "globby": "^14.1.0",
62
62
  "sirv": "^2.0.4",
63
63
  "ufo": "^1.5.4",
64
- "nuxt-i18n-micro-core": "1.0.21",
64
+ "nuxt-i18n-micro-core": "1.0.23",
65
65
  "nuxt-i18n-micro-test-utils": "1.0.7",
66
66
  "nuxt-i18n-micro-types": "1.0.12"
67
67
  },
@@ -1 +0,0 @@
1
- {"id":"df79903a-9996-4527-8269-e47de875a594","timestamp":1761933265840,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}