nuxt-i18n-micro 2.2.0 → 2.4.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},1762153620044,false]</script>
12
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"761c8107-0390-4ed8-9b33-a10824c775b2",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},1762341835524,false]</script>
12
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"2d4fb144-8539-4f7d-a7d3-5dedb810c640",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},1762153620044,false]</script>
12
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"761c8107-0390-4ed8-9b33-a10824c775b2",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},1762341835524,false]</script>
12
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"2d4fb144-8539-4f7d-a7d3-5dedb810c640",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
@@ -1 +1 @@
1
- {"id":"761c8107-0390-4ed8-9b33-a10824c775b2","timestamp":1762153612543}
1
+ {"id":"2d4fb144-8539-4f7d-a7d3-5dedb810c640","timestamp":1762341830059}
@@ -0,0 +1 @@
1
+ {"id":"2d4fb144-8539-4f7d-a7d3-5dedb810c640","timestamp":1762341830059,"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},1762153620045,false]</script>
12
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"761c8107-0390-4ed8-9b33-a10824c775b2",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},1762341835524,false]</script>
12
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"2d4fb144-8539-4f7d-a7d3-5dedb810c640",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.2.0",
4
+ "version": "2.4.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -194,11 +194,52 @@ function extractDefineI18nRouteData(content, _filePath) {
194
194
  if (!configObject) {
195
195
  return null;
196
196
  }
197
+ if (configObject.locales && typeof configObject.locales === "object" && !Array.isArray(configObject.locales)) {
198
+ const localesObj = configObject.locales;
199
+ const normalizedLocales = [];
200
+ const normalizedLocaleRoutes = {};
201
+ for (const [locale, value] of Object.entries(localesObj)) {
202
+ normalizedLocales.push(locale);
203
+ if (value && typeof value === "object" && "path" in value && typeof value.path === "string") {
204
+ normalizedLocaleRoutes[locale] = value.path;
205
+ }
206
+ }
207
+ return {
208
+ ...configObject,
209
+ locales: normalizedLocales,
210
+ localeRoutes: configObject.localeRoutes || Object.keys(normalizedLocaleRoutes).length > 0 ? { ...configObject.localeRoutes, ...normalizedLocaleRoutes } : void 0
211
+ };
212
+ }
213
+ if (Array.isArray(configObject.locales) && configObject.locales.length > 0 && typeof configObject.locales[0] === "object") {
214
+ const normalizedLocales = configObject.locales.map((item) => {
215
+ if (item && typeof item === "object" && "code" in item) {
216
+ return item.code;
217
+ }
218
+ return String(item);
219
+ });
220
+ return {
221
+ ...configObject,
222
+ locales: normalizedLocales
223
+ };
224
+ }
197
225
  return configObject;
198
226
  } catch {
199
227
  return null;
200
228
  }
201
229
  }
230
+ function normalizeRouteKey(key) {
231
+ return key.split("/").map((segment) => {
232
+ if (segment.startsWith("[...") && segment.endsWith("]")) {
233
+ const paramName = segment.substring(4, segment.length - 1);
234
+ return `:${paramName}(.*)*`;
235
+ }
236
+ if (segment.startsWith("[") && segment.endsWith("]")) {
237
+ const paramName = segment.substring(1, segment.length - 1);
238
+ return `:${paramName}`;
239
+ }
240
+ return segment;
241
+ }).join("/");
242
+ }
202
243
  const normalizePath = (routePath) => {
203
244
  if (!routePath) {
204
245
  return "";
@@ -249,7 +290,22 @@ class PageManager {
249
290
  this.noPrefixRedirect = noPrefixRedirect;
250
291
  this.excludePatterns = excludePatterns;
251
292
  this.activeLocaleCodes = this.computeActiveLocaleCodes();
252
- this.globalLocaleRoutes = globalLocaleRoutes || {};
293
+ const normalizedGlobalRoutes = {};
294
+ for (const key in globalLocaleRoutes) {
295
+ const newKey = normalizeRouteKey(key);
296
+ const localePaths = globalLocaleRoutes[key];
297
+ if (typeof localePaths === "object") {
298
+ const normalizedLocalePaths = {};
299
+ for (const locale in localePaths) {
300
+ const customPath = localePaths[locale];
301
+ normalizedLocalePaths[locale] = normalizeRouteKey(customPath);
302
+ }
303
+ normalizedGlobalRoutes[newKey] = normalizedLocalePaths;
304
+ } else {
305
+ normalizedGlobalRoutes[newKey] = localePaths;
306
+ }
307
+ }
308
+ this.globalLocaleRoutes = normalizedGlobalRoutes;
253
309
  this.filesLocaleRoutes = filesLocaleRoutes || {};
254
310
  this.routeLocales = routeLocales || {};
255
311
  }
@@ -279,22 +335,84 @@ class PageManager {
279
335
  extendPages(pages, customRegex, isCloudflarePages) {
280
336
  this.localizedPaths = this.extractLocalizedPaths(pages);
281
337
  const additionalRoutes = [];
338
+ const originalPagePaths = /* @__PURE__ */ new Map();
282
339
  for (const page of [...pages]) {
340
+ if (isPageRedirectOnly(page)) {
341
+ continue;
342
+ }
283
343
  if (page.path && isInternalPath(page.path, this.excludePatterns)) {
284
344
  continue;
285
345
  }
286
- if (!page.name && page.file?.endsWith(".vue")) {
287
- console.warn(`[nuxt-i18n-next] Page name is missing for the file: ${page.file}`);
346
+ const originalPath = page.path ?? "";
347
+ originalPagePaths.set(page, originalPath);
348
+ const pageName = buildRouteNameFromRoute(page.name, page.path);
349
+ const normalizedOriginalPath = normalizeRouteKey(originalPath);
350
+ const customPaths = this.localizedPaths[originalPath] || this.localizedPaths[pageName];
351
+ const isLocalizationDisabled = this.globalLocaleRoutes[pageName] === false || this.globalLocaleRoutes[normalizedOriginalPath] === false;
352
+ if (isLocalizationDisabled) {
353
+ continue;
288
354
  }
289
- const customRoute = this.globalLocaleRoutes[page.name ?? ""];
290
- if (customRoute === false) {
355
+ const allowedLocales = this.getAllowedLocalesForPage(originalPath, pageName);
356
+ const originalChildren = cloneArray(page.children ?? []);
357
+ if (isNoPrefixStrategy(this.strategy)) {
358
+ if (customPaths) {
359
+ this.locales.forEach((locale) => {
360
+ const customPath = customPaths[locale.code];
361
+ if (customPath && allowedLocales.includes(locale.code)) {
362
+ const newRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPath, customRegex, false, locale.code, originalPath);
363
+ if (newRoute) {
364
+ additionalRoutes.push(newRoute);
365
+ if (this.noPrefixRedirect && locale.code === this.defaultLocale.code) {
366
+ page.redirect = normalizePath(customPath);
367
+ }
368
+ }
369
+ }
370
+ });
371
+ }
372
+ this.handleAliasRoutes(page, additionalRoutes, customRegex, allowedLocales);
291
373
  continue;
292
374
  }
293
- if (typeof customRoute === "object" && customRoute !== null) {
294
- this.addCustomGlobalLocalizedRoutes(page, customRoute, additionalRoutes, customRegex);
295
- } else {
296
- this.localizePage(page, additionalRoutes, customRegex);
375
+ const defaultLocaleCode = this.defaultLocale.code;
376
+ if (allowedLocales.includes(defaultLocaleCode)) {
377
+ const customPath = customPaths?.[defaultLocaleCode];
378
+ if (isPrefixExceptDefaultStrategy(this.strategy)) {
379
+ if (customPath) {
380
+ page.path = normalizePath(customPath);
381
+ }
382
+ page.children = this.createLocalizedChildren(originalChildren, originalPath, [defaultLocaleCode], false, false, false, customPath ? { [defaultLocaleCode]: customPath } : {});
383
+ }
297
384
  }
385
+ const localesToGenerate = this.locales.filter((l) => {
386
+ if (!allowedLocales.includes(l.code)) return false;
387
+ if (isPrefixExceptDefaultStrategy(this.strategy) && l.code === defaultLocaleCode) return false;
388
+ return true;
389
+ });
390
+ if (localesToGenerate.length > 0) {
391
+ if (customPaths) {
392
+ localesToGenerate.forEach((locale) => {
393
+ if (customPaths[locale.code]) {
394
+ if (isPrefixAndDefaultStrategy(this.strategy) && locale.code === defaultLocaleCode) {
395
+ const nonPrefixedRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPaths[locale.code], customRegex, false, locale.code, originalPath);
396
+ if (nonPrefixedRoute) additionalRoutes.push(nonPrefixedRoute);
397
+ const prefixedRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPaths[locale.code], customRegex, true, locale.code, originalPath);
398
+ if (prefixedRoute) additionalRoutes.push(prefixedRoute);
399
+ } else {
400
+ const shouldAddPrefix = isPrefixAndDefaultStrategy(this.strategy) && locale.code === defaultLocaleCode;
401
+ const newRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPaths[locale.code], customRegex, shouldAddPrefix, locale.code, originalPath);
402
+ if (newRoute) additionalRoutes.push(newRoute);
403
+ }
404
+ } else {
405
+ const newRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, false, "", customRegex, false, locale.code, originalPath);
406
+ if (newRoute) additionalRoutes.push(newRoute);
407
+ }
408
+ });
409
+ } else {
410
+ const localeCodes = localesToGenerate.map((l) => l.code);
411
+ const newRoute = this.createLocalizedRoute(page, localeCodes, originalChildren, false, "", customRegex, false, true, originalPath);
412
+ if (newRoute) additionalRoutes.push(newRoute);
413
+ }
414
+ }
415
+ this.handleAliasRoutes(page, additionalRoutes, customRegex, allowedLocales);
298
416
  }
299
417
  if (isPrefixStrategy(this.strategy) && !isCloudflarePages) {
300
418
  for (let i = pages.length - 1; i >= 0; i--) {
@@ -316,7 +434,8 @@ class PageManager {
316
434
  pages.forEach((page) => {
317
435
  const pageName = buildRouteNameFromRoute(page.name, page.path);
318
436
  const normalizedFullPath = normalizePath(path.posix.join(parentPath, page.path));
319
- const globalLocalePath = this.globalLocaleRoutes[normalizedFullPath] || this.globalLocaleRoutes[pageName];
437
+ const normalizedKey = normalizeRouteKey(normalizedFullPath);
438
+ const globalLocalePath = this.globalLocaleRoutes[normalizedKey] || this.globalLocaleRoutes[pageName];
320
439
  if (!globalLocalePath) {
321
440
  const filesLocalePath = this.filesLocaleRoutes[pageName];
322
441
  if (filesLocalePath && typeof filesLocalePath === "object") {
@@ -412,6 +531,9 @@ class PageManager {
412
531
  if (isNoPrefixStrategy(this.strategy)) {
413
532
  return;
414
533
  }
534
+ if (isPrefixAndDefaultStrategy(this.strategy)) {
535
+ return;
536
+ }
415
537
  const defaultLocalePath = this.localizedPaths[page.path]?.[this.defaultLocale.code];
416
538
  if (defaultLocalePath) {
417
539
  page.path = normalizePath(defaultLocalePath);
@@ -450,16 +572,22 @@ class PageManager {
450
572
  if (isDefaultLocale && isPrefixExceptDefaultStrategy(this.strategy)) {
451
573
  page.path = normalizePath(customPath);
452
574
  page.children = this.createLocalizedChildren(originalChildren, "", [locale.code], false, false, false, { [locale.code]: customPath });
575
+ } else if (isPrefixAndDefaultStrategy(this.strategy) && locale === this.defaultLocale) {
576
+ const nonPrefixedRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPath, customRegex, false, locale.code);
577
+ if (nonPrefixedRoute) {
578
+ additionalRoutes.push(nonPrefixedRoute);
579
+ }
580
+ const prefixedRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPath, customRegex, true, locale.code);
581
+ if (prefixedRoute) {
582
+ additionalRoutes.push(prefixedRoute);
583
+ }
453
584
  } else {
454
- const newRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPath, customRegex, false, locale.code);
585
+ const shouldAddPrefix = !isDefaultLocale;
586
+ const newRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPath, customRegex, shouldAddPrefix, locale.code);
455
587
  if (newRoute) {
456
588
  additionalRoutes.push(newRoute);
457
589
  }
458
590
  }
459
- if (isPrefixAndDefaultStrategy(this.strategy) && locale === this.defaultLocale) {
460
- const newRoute = this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPath, customRegex, true, locale.code);
461
- if (newRoute) additionalRoutes.push(newRoute);
462
- }
463
591
  });
464
592
  }
465
593
  createLocalizedChildren(routes, parentPath, localeCodes, modifyName = true, addLocalePrefix = false, parentLocale = false, localizedParentPaths = {}) {
@@ -564,16 +692,18 @@ class PageManager {
564
692
  }
565
693
  return baseName;
566
694
  }
567
- createLocalizedRoute(page, localeCodes, originalChildren, isCustom, customPath = "", customRegex, force = false, parentLocale = false) {
695
+ createLocalizedRoute(page, localeCodes, originalChildren, isCustom, customPath = "", customRegex, force = false, parentLocale = false, originalPagePath) {
568
696
  const routePath = this.buildRoutePath(localeCodes, page.path, encodeURI(customPath), isCustom, customRegex, force);
569
- if (!routePath || routePath == page.path) return null;
697
+ const isPrefixAndDefaultWithCustomPath = isPrefixAndDefaultStrategy(this.strategy) && isCustom && customPath;
698
+ if (!routePath || !isPrefixAndDefaultWithCustomPath && routePath === page.path) return null;
570
699
  if (localeCodes.length === 0) return null;
571
700
  const firstLocale = localeCodes[0];
572
701
  if (!firstLocale) return null;
573
- const routeName = buildRouteName(buildRouteNameFromRoute(page.name ?? "", page.path ?? ""), firstLocale, isCustom);
702
+ const parentPathForChildren = originalPagePath ?? page.path ?? "";
703
+ const routeName = buildRouteName(buildRouteNameFromRoute(page.name ?? "", parentPathForChildren), firstLocale, isCustom);
574
704
  return {
575
705
  ...page,
576
- children: this.createLocalizedChildren(originalChildren, page.path, localeCodes, true, false, parentLocale, { [firstLocale]: customPath }),
706
+ children: this.createLocalizedChildren(originalChildren, parentPathForChildren, localeCodes, true, false, parentLocale, { [firstLocale]: customPath }),
577
707
  path: routePath,
578
708
  name: routeName,
579
709
  alias: [],
@@ -598,7 +728,8 @@ class PageManager {
598
728
  return buildFullPathNoPrefix(isCustom ? customPath : originalPath);
599
729
  }
600
730
  if (isCustom) {
601
- return force || isPrefixStrategy(this.strategy) || !localeCodes.includes(this.defaultLocale.code) ? buildFullPath(localeCodes, customPath, customRegex) : normalizePath(customPath);
731
+ const shouldAddPrefix = force || isPrefixStrategy(this.strategy) || isPrefixAndDefaultStrategy(this.strategy) && !localeCodes.includes(this.defaultLocale.code) || !localeCodes.includes(this.defaultLocale.code);
732
+ return shouldAddPrefix ? buildFullPath(localeCodes, customPath, customRegex) : normalizePath(customPath);
602
733
  }
603
734
  return buildFullPath(localeCodes, originalPath, customRegex);
604
735
  }
@@ -950,16 +1081,33 @@ ${accepts}
950
1081
  localeManager.ensureTranslationFilesExist(pagesNames, options.translationDir, nuxt.options.rootDir);
951
1082
  }
952
1083
  pageManager.extendPages(pages, options.customRegexMatcher, isCloudflarePages);
953
- if (isPrefixStrategy(options.strategy) && !isCloudflarePages) {
954
- const fallbackRoute = {
955
- path: "/:pathMatch(.*)*",
956
- name: "custom-fallback-route",
957
- file: resolver.resolve("./runtime/components/locale-redirect.vue"),
958
- meta: {
959
- globalLocaleRoutes: options.globalLocaleRoutes
1084
+ if (!isCloudflarePages) {
1085
+ const strategy = options.strategy;
1086
+ if (isPrefixStrategy(strategy)) {
1087
+ const rootPageIndex = pages.findIndex((page) => page.name === "index" && page.path === "/");
1088
+ if (rootPageIndex > -1) {
1089
+ pages.splice(rootPageIndex, 1);
960
1090
  }
961
- };
962
- pages.push(fallbackRoute);
1091
+ const fallbackRoute = {
1092
+ path: "/:pathMatch(.*)*",
1093
+ name: "custom-fallback-route",
1094
+ file: resolver.resolve("./runtime/components/locale-redirect.vue")
1095
+ };
1096
+ pages.push(fallbackRoute);
1097
+ logger.info("Strategy 'prefix': Added fallback route to redirect all non-prefixed paths.");
1098
+ }
1099
+ const needsFallback = isPrefixStrategy(options.strategy) || isPrefixExceptDefaultStrategy(options.strategy);
1100
+ if (needsFallback) {
1101
+ const fallbackRoute = {
1102
+ path: "/:pathMatch(.*)*",
1103
+ name: "custom-fallback-route",
1104
+ file: resolver.resolve("./runtime/components/locale-redirect.vue"),
1105
+ meta: {
1106
+ globalLocaleRoutes: options.globalLocaleRoutes
1107
+ }
1108
+ };
1109
+ pages.push(fallbackRoute);
1110
+ }
963
1111
  }
964
1112
  if (!isNoPrefixStrategy(options.strategy)) {
965
1113
  if (isCloudflarePages) {
@@ -996,7 +1144,9 @@ ${accepts}
996
1144
  }
997
1145
  }
998
1146
  if (page.children && page.children.length) {
999
- page.children.forEach((childPage) => processPageWithChildren(childPage, fullPath));
1147
+ page.children.forEach((childPage) => {
1148
+ processPageWithChildren(childPage, fullPath);
1149
+ });
1000
1150
  }
1001
1151
  };
1002
1152
  pages.forEach((page) => {
@@ -1107,7 +1257,9 @@ ${accepts}
1107
1257
  routesToRemove.push(route);
1108
1258
  }
1109
1259
  });
1110
- routesToRemove.forEach((route) => routesSet.delete(route));
1260
+ routesToRemove.forEach((route) => {
1261
+ routesSet.delete(route);
1262
+ });
1111
1263
  const additionalRoutes = /* @__PURE__ */ new Set();
1112
1264
  const routeRules = nuxt.options.routeRules || {};
1113
1265
  routesSet.forEach((route) => {
@@ -1134,7 +1286,9 @@ ${accepts}
1134
1286
  });
1135
1287
  }
1136
1288
  });
1137
- additionalRoutes.forEach((route) => routesSet.add(route));
1289
+ additionalRoutes.forEach((route) => {
1290
+ routesSet.add(route);
1291
+ });
1138
1292
  });
1139
1293
  if (nuxt.options.dev) {
1140
1294
  setupDevToolsUI(options, resolver.resolve);
@@ -19,7 +19,8 @@
19
19
  </template>
20
20
 
21
21
  <script setup>
22
- import { useNuxtApp, computed, useRoute, useRouter } from "#imports";
22
+ import { useNuxtApp, useRoute, useRouter } from "#imports";
23
+ import { computed } from "vue";
23
24
  const { $localeRoute } = useNuxtApp();
24
25
  const props = defineProps({
25
26
  to: { type: [Object, String], required: true },
@@ -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
  }
@@ -1,7 +1,8 @@
1
1
  import { joinURL, parseURL, withQuery } from "ufo";
2
- import { isPrefixExceptDefaultStrategy, isNoPrefixStrategy } from "nuxt-i18n-micro-core";
3
- import { unref, useRoute, useRuntimeConfig, ref, useNuxtApp } from "#imports";
2
+ import { isNoPrefixStrategy } from "nuxt-i18n-micro-core";
3
+ import { useRoute, useRuntimeConfig, useNuxtApp } from "#imports";
4
4
  import { findAllowedLocalesForRoute } from "../utils/route-utils.js";
5
+ import { ref, unref } from "vue";
5
6
  export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "id", addSeoAttributes = true, baseUrl = "/" } = {}) => {
6
7
  const metaObject = ref({
7
8
  htmlAttrs: {},
@@ -20,8 +21,8 @@ export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "i
20
21
  return withQuery(pathname, filtered);
21
22
  }
22
23
  function updateMeta() {
23
- const { defaultLocale, strategy, canonicalQueryWhitelist, routeLocales, globalLocaleRoutes } = useRuntimeConfig().public.i18nConfig;
24
- const { $getLocales, $getLocale, $getRouteName } = useNuxtApp();
24
+ const { strategy, canonicalQueryWhitelist, routeLocales } = useRuntimeConfig().public.i18nConfig;
25
+ const { $getLocales, $getLocale, $switchLocalePath } = useNuxtApp();
25
26
  if (!$getLocale || !$getLocales) return;
26
27
  const route = useRoute();
27
28
  const locale = unref($getLocale());
@@ -38,17 +39,6 @@ export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "i
38
39
  fullPath = `/${fullPath}`;
39
40
  }
40
41
  const matchedLocale = locales.find((locale2) => fullPath.startsWith(`/${locale2.code}`));
41
- const baseRouteName = $getRouteName ? unref($getRouteName(route, locale)) : routeName.replace(/^localized-/, "").replace(new RegExp(`-${locale}$`), "").replace(new RegExp(`-${defaultLocale}$`), "");
42
- let normalizedPath = route.path;
43
- if (matchedLocale && normalizedPath.startsWith(`/${matchedLocale.code}`)) {
44
- normalizedPath = normalizedPath.slice(matchedLocale.code.length + 1);
45
- }
46
- if (!normalizedPath.startsWith("/")) {
47
- normalizedPath = `/${normalizedPath}`;
48
- }
49
- if (normalizedPath !== "/" && normalizedPath.endsWith("/")) {
50
- normalizedPath = normalizedPath.slice(0, -1);
51
- }
52
42
  let localizedPath = fullPath;
53
43
  let ogUrl;
54
44
  let canonicalPath;
@@ -60,22 +50,6 @@ export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "i
60
50
  canonicalPath = filterQuery(fullPath, canonicalQueryWhitelist ?? []);
61
51
  ogUrl = joinURL(unref(baseUrl), canonicalPath);
62
52
  }
63
- let routeLocalizedPaths = globalLocaleRoutes?.[baseRouteName] || globalLocaleRoutes?.[normalizedPath] || null;
64
- if (!routeLocalizedPaths && globalLocaleRoutes) {
65
- for (const [, localizedPaths] of Object.entries(globalLocaleRoutes)) {
66
- if (typeof localizedPaths === "object" && localizedPaths !== null) {
67
- const normalizedCurrentPath = canonicalPath.replace(/^\/+/, "/");
68
- const matchingLocale = Object.entries(localizedPaths).find(([_, localizedPathValue]) => {
69
- const normalizedLocalizedPath = String(localizedPathValue).replace(/^\/+/, "/");
70
- return normalizedCurrentPath === normalizedLocalizedPath || matchedLocale && normalizedCurrentPath === `/${matchedLocale.code}/${normalizedLocalizedPath}`;
71
- });
72
- if (matchingLocale) {
73
- routeLocalizedPaths = localizedPaths;
74
- break;
75
- }
76
- }
77
- }
78
- }
79
53
  metaObject.value = {
80
54
  htmlAttrs: {
81
55
  lang: currentIso,
@@ -107,14 +81,16 @@ export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "i
107
81
  href: ogUrl
108
82
  };
109
83
  const alternateLinks = isNoPrefixStrategy(strategy) ? [] : alternateLocales.flatMap((loc) => {
110
- let pathForLocale;
111
- if (routeLocalizedPaths && routeLocalizedPaths[loc.code]) {
112
- pathForLocale = routeLocalizedPaths[loc.code];
84
+ const switchedPath = $switchLocalePath(loc.code);
85
+ if (!switchedPath) {
86
+ return [];
87
+ }
88
+ let href;
89
+ if (switchedPath.startsWith("http://") || switchedPath.startsWith("https://")) {
90
+ href = switchedPath;
113
91
  } else {
114
- pathForLocale = defaultLocale === loc.code && isPrefixExceptDefaultStrategy(strategy) ? canonicalPath : canonicalPath;
92
+ href = joinURL(unref(baseUrl), switchedPath.startsWith("/") ? switchedPath : `/${switchedPath}`);
115
93
  }
116
- const localizedPath2 = defaultLocale === loc.code && isPrefixExceptDefaultStrategy(strategy) ? pathForLocale : joinURL(loc.code, pathForLocale);
117
- const href = joinURL(unref(baseUrl), localizedPath2);
118
94
  const links = [{
119
95
  [identifierAttribute]: `i18n-alternate-${loc.code}`,
120
96
  rel: "alternate",
@@ -1,5 +1,6 @@
1
1
  import { useTranslationHelper, interpolate, isNoPrefixStrategy, RouteService, FormatService } from "nuxt-i18n-micro-core";
2
- import { useRouter, useCookie, unref, navigateTo, defineNuxtPlugin, useRuntimeConfig, createError } from "#imports";
2
+ import { useRouter, useCookie, navigateTo, defineNuxtPlugin, useRuntimeConfig, createError } from "#imports";
3
+ import { unref } from "vue";
3
4
  import { useState } from "#app";
4
5
  import { plural } from "#build/i18n.plural.mjs";
5
6
  const isDev = process.env.NODE_ENV !== "production";
@@ -46,7 +47,6 @@ export default defineNuxtPlugin(async (nuxtApp) => {
46
47
  const enablePreviousPageFallback = i18nConfig.experimental?.i18nPreviousPageFallback ?? false;
47
48
  nuxtApp.hook("page:finish", () => {
48
49
  if (import.meta.client) {
49
- i18nRouteParams.value = null;
50
50
  previousPageInfo.value = null;
51
51
  }
52
52
  });
@@ -83,6 +83,9 @@ export default defineNuxtPlugin(async (nuxtApp) => {
83
83
  }
84
84
  }
85
85
  router.beforeEach(async (to, from, next) => {
86
+ if (to.name !== from.name) {
87
+ i18nRouteParams.value = {};
88
+ }
86
89
  if (to.path === from.path && !isNoPrefixStrategy(i18nConfig.strategy)) {
87
90
  if (next) next();
88
91
  return;
@@ -1,5 +1,6 @@
1
1
  import { useLocaleHead } from "../composables/useLocaleHead.js";
2
- import { useRequestURL, useHead, defineNuxtPlugin, useRuntimeConfig, useRoute, watch } from "#imports";
2
+ import { useRequestURL, useHead, defineNuxtPlugin, useRuntimeConfig, useRoute } from "#imports";
3
+ import { watch } from "vue";
3
4
  import { isMetaDisabledForRoute } from "../utils/route-utils.js";
4
5
  const host = process.env.HOST ?? "localhost";
5
6
  const port = process.env.PORT ?? "host";
@@ -1,4 +1,5 @@
1
- import { defineNuxtPlugin, useNuxtApp, computed, watch, onUnmounted, unref } from "#imports";
1
+ import { defineNuxtPlugin, useNuxtApp } from "#imports";
2
+ import { watch, unref, computed, onUnmounted } from "vue";
2
3
  const normalizeLocales = (locales) => {
3
4
  if (Array.isArray(locales)) {
4
5
  return locales.reduce((acc, locale) => {
@@ -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.2.0",
3
+ "version": "2.4.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,9 +61,9 @@
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.22",
64
+ "nuxt-i18n-micro-types": "1.0.12",
65
65
  "nuxt-i18n-micro-test-utils": "1.0.7",
66
- "nuxt-i18n-micro-types": "1.0.12"
66
+ "nuxt-i18n-micro-core": "1.0.23"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@nuxt/devtools": "^2.6.3",
@@ -100,7 +100,7 @@
100
100
  "dev:build": "nuxi build playground",
101
101
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
102
102
  "dev:generate": "nuxi generate playground",
103
- "release": "pnpm run lint && pnpm run typecheck && pnpm run test:types && pnpm run test && pnpm run test:workspaces && pnpm run prepack && changelogen --release && pnpm publish -r && git push --follow-tags",
103
+ "release": "pnpm run lint && pnpm run typecheck && pnpm run test:types && pnpm run test && pnpm run test:vitest && pnpm run test:workspaces && pnpm run prepack && changelogen --release && pnpm publish -r && git push --follow-tags",
104
104
  "lint": "eslint .",
105
105
  "lint:fix": "eslint . --fix",
106
106
  "test": "playwright test",
@@ -1 +0,0 @@
1
- {"id":"761c8107-0390-4ed8-9b33-a10824c775b2","timestamp":1762153612543,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}