nuxt-i18n-micro 1.97.0 → 1.98.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/BrUpQP6I.js">
9
9
  <link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-500.DGwSTbEi.css">
10
10
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/C0zq22yw.js">
11
- <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/ivw-WKkv.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},1758813952163,false]</script>
12
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"6376a03b-f005-4f0f-b051-3b017577666c",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
11
+ <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/ivw-WKkv.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},1759229335371,false]</script>
12
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"9bdb882b-a145-42be-bbc5-f7c9185266d5",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
@@ -8,5 +8,5 @@
8
8
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/BrUpQP6I.js">
9
9
  <link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-500.DGwSTbEi.css">
10
10
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/C0zq22yw.js">
11
- <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/ivw-WKkv.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},1758813952163,false]</script>
12
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"6376a03b-f005-4f0f-b051-3b017577666c",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
11
+ <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/ivw-WKkv.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},1759229335372,false]</script>
12
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"9bdb882b-a145-42be-bbc5-f7c9185266d5",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
@@ -1 +1 @@
1
- {"id":"6376a03b-f005-4f0f-b051-3b017577666c","timestamp":1758813945370}
1
+ {"id":"9bdb882b-a145-42be-bbc5-f7c9185266d5","timestamp":1759229326396}
@@ -0,0 +1 @@
1
+ {"id":"9bdb882b-a145-42be-bbc5-f7c9185266d5","timestamp":1759229326396,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
@@ -8,5 +8,5 @@
8
8
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/BrUpQP6I.js">
9
9
  <link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-500.DGwSTbEi.css">
10
10
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/C0zq22yw.js">
11
- <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/ivw-WKkv.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},1758813952165,false]</script>
12
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"6376a03b-f005-4f0f-b051-3b017577666c",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
11
+ <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/ivw-WKkv.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},1759229335372,false]</script>
12
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"9bdb882b-a145-42be-bbc5-f7c9185266d5",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": "1.97.0",
4
+ "version": "1.98.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,12 +1,14 @@
1
- import path, { resolve } from 'node:path';
1
+ import path, { resolve, join } from 'node:path';
2
2
  import * as fs from 'node:fs';
3
- import fs__default, { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import fs__default, { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
4
4
  import { useNuxt, defineNuxtModule, useLogger, createResolver, addTemplate, addImportsDir, addPlugin, addServerHandler, addComponentsDir, addTypeTemplate, addPrerenderRoutes } from '@nuxt/kit';
5
5
  import { watch } from 'chokidar';
6
6
  import { isPrefixAndDefaultStrategy, isPrefixStrategy, isNoPrefixStrategy, isPrefixExceptDefaultStrategy, withPrefixStrategy } from 'nuxt-i18n-micro-core';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
9
9
  import sirv from 'sirv';
10
+ import { isInternalPath } from '../dist/runtime/utils/path-utils.js';
11
+ import { globby } from 'globby';
10
12
 
11
13
  const DEVTOOLS_UI_PORT = 3030;
12
14
  const DEVTOOLS_UI_ROUTE = "/__nuxt-i18n-micro";
@@ -108,66 +110,88 @@ function setupDevToolsUI(options, resolve2) {
108
110
  });
109
111
  }
110
112
 
111
- const DEFAULT_STATIC_PATTERNS = [
112
- /^\/sitemap.*\.xml$/,
113
- /^\/sitemap\.xml$/,
114
- /^\/robots\.txt$/,
115
- /^\/favicon\.ico$/,
116
- /^\/apple-touch-icon.*\.png$/,
117
- /^\/manifest\.json$/,
118
- /^\/sw\.js$/,
119
- /^\/workbox-.*\.js$/,
120
- /\.(xml|txt|ico|json|js|css|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/
121
- ];
122
- const isInternalPath = (path2, excludePatterns) => {
123
- if (/(?:^|\/)__[^/]+/.test(path2)) {
124
- return true;
113
+ function extractDefineI18nRouteData(content, filePath) {
114
+ const defineMatch = content.match(/\$?\bdefineI18nRoute\s*\(\s*\{[\s\S]*?\}\s*\)/);
115
+ if (!defineMatch) {
116
+ return { locales: null, localeRoutes: null };
125
117
  }
126
- for (const pattern of DEFAULT_STATIC_PATTERNS) {
127
- if (pattern.test(path2)) {
128
- return true;
118
+ const defineContent = defineMatch[0];
119
+ let locales = null;
120
+ let localeRoutes = null;
121
+ let localesStr = "";
122
+ const localesStart = defineContent.indexOf("locales:");
123
+ if (localesStart !== -1) {
124
+ const afterLocales = defineContent.substring(localesStart + 8);
125
+ const trimmed = afterLocales.trim();
126
+ if (trimmed.startsWith("[")) {
127
+ let bracketCount = 0;
128
+ let i = 0;
129
+ for (; i < trimmed.length; i++) {
130
+ if (trimmed[i] === "[") bracketCount++;
131
+ if (trimmed[i] === "]") bracketCount--;
132
+ if (bracketCount === 0) break;
133
+ }
134
+ localesStr = trimmed.substring(0, i + 1);
135
+ } else if (trimmed.startsWith("{")) {
136
+ let braceCount = 0;
137
+ let i = 0;
138
+ for (; i < trimmed.length; i++) {
139
+ if (trimmed[i] === "{") braceCount++;
140
+ if (trimmed[i] === "}") braceCount--;
141
+ if (braceCount === 0) break;
142
+ }
143
+ localesStr = trimmed.substring(0, i + 1);
129
144
  }
130
145
  }
131
- if (excludePatterns) {
132
- for (const pattern of excludePatterns) {
133
- if (typeof pattern === "string") {
134
- if (pattern.includes("*") || pattern.includes("?")) {
135
- const regex = new RegExp(pattern.replace(/\*/g, ".*").replace(/\?/g, "."));
136
- if (regex.test(path2)) {
137
- return true;
138
- }
139
- } else if (path2 === pattern || path2.startsWith(pattern)) {
140
- return true;
146
+ if (localesStr) {
147
+ try {
148
+ const localesStrTrimmed = localesStr.trim();
149
+ if (localesStrTrimmed.startsWith("[") && localesStrTrimmed.endsWith("]")) {
150
+ const arrayMatch = localesStrTrimmed.match(/\[(.*?)\]/s);
151
+ if (arrayMatch && arrayMatch[1]) {
152
+ const elements = arrayMatch[1].split(",").map((el) => el.trim().replace(/['"]/g, "")).filter((el) => el.length > 0);
153
+ locales = elements;
141
154
  }
142
- } else if (pattern instanceof RegExp) {
143
- if (pattern.test(path2)) {
144
- return true;
155
+ }
156
+ if (localesStrTrimmed.startsWith("{") && localesStrTrimmed.endsWith("}")) {
157
+ const topLevelKeyMatches = localesStrTrimmed.match(/^\s*(\w+)\s*:\s*\{/gm);
158
+ if (topLevelKeyMatches) {
159
+ const keys = topLevelKeyMatches.map((match) => {
160
+ const keyMatch = match.match(/^\s*(\w+)\s*:/);
161
+ return keyMatch ? keyMatch[1] : "";
162
+ }).filter((key) => key.length > 0);
163
+ locales = keys;
164
+ } else {
165
+ const fallbackMatches = localesStrTrimmed.match(/(\w+)\s*:\s*\{/g);
166
+ if (fallbackMatches) {
167
+ const keys = fallbackMatches.map((match) => {
168
+ const keyMatch = match.match(/(\w+)\s*:/);
169
+ return keyMatch ? keyMatch[1] : "";
170
+ }).filter((key) => key.length > 0);
171
+ locales = keys;
172
+ }
145
173
  }
146
174
  }
175
+ } catch (error) {
176
+ console.error("Failed to parse locales:", error, "in file:", filePath);
147
177
  }
148
178
  }
149
- return false;
150
- };
151
- function extractLocaleRoutes(content, filePath) {
152
- const defineMatch = content.match(/\$?\bdefineI18nRoute\s*\(\s*\{[\s\S]*?\}\s*\)/);
153
- if (defineMatch) {
154
- const localeRoutesMatch = defineMatch[0].match(/localeRoutes:\s*(\{[\s\S]*?\})/);
155
- if (localeRoutesMatch && localeRoutesMatch[1]) {
156
- try {
157
- const parsedLocaleRoutes = Function('"use strict";return (' + localeRoutesMatch[1] + ")")();
158
- if (typeof parsedLocaleRoutes === "object" && parsedLocaleRoutes !== null) {
159
- if (validateDefineI18nRouteConfig(parsedLocaleRoutes)) {
160
- return parsedLocaleRoutes;
161
- }
162
- } else {
163
- console.error("localeRoutes found but it is not a valid object in file:", filePath);
179
+ const localeRoutesMatch = defineContent.match(/localeRoutes:\s*(\{[\s\S]*?\})/);
180
+ if (localeRoutesMatch && localeRoutesMatch[1]) {
181
+ try {
182
+ const parsedLocaleRoutes = Function('"use strict";return (' + localeRoutesMatch[1] + ")")();
183
+ if (typeof parsedLocaleRoutes === "object" && parsedLocaleRoutes !== null) {
184
+ if (validateDefineI18nRouteConfig(parsedLocaleRoutes)) {
185
+ localeRoutes = parsedLocaleRoutes;
164
186
  }
165
- } catch (error) {
166
- console.error("Failed to parse localeRoutes:", error, "in file:", filePath);
187
+ } else {
188
+ console.error("localeRoutes found but it is not a valid object in file:", filePath);
167
189
  }
190
+ } catch (error) {
191
+ console.error("Failed to parse localeRoutes:", error, "in file:", filePath);
168
192
  }
169
193
  }
170
- return null;
194
+ return { locales, localeRoutes };
171
195
  }
172
196
  function validateDefineI18nRouteConfig(obj) {
173
197
  if (typeof obj !== "object") return false;
@@ -215,9 +239,10 @@ class PageManager {
215
239
  localizedPaths = {};
216
240
  activeLocaleCodes;
217
241
  globalLocaleRoutes;
242
+ filesLocaleRoutes;
218
243
  noPrefixRedirect;
219
244
  excludePatterns;
220
- constructor(locales, defaultLocaleCode, strategy, globalLocaleRoutes, noPrefixRedirect, excludePatterns) {
245
+ constructor(locales, defaultLocaleCode, strategy, globalLocaleRoutes, filesLocaleRoutes, noPrefixRedirect, excludePatterns) {
221
246
  this.locales = locales;
222
247
  this.defaultLocale = this.findLocaleByCode(defaultLocaleCode) || { code: defaultLocaleCode };
223
248
  this.strategy = strategy;
@@ -225,6 +250,7 @@ class PageManager {
225
250
  this.excludePatterns = excludePatterns;
226
251
  this.activeLocaleCodes = this.computeActiveLocaleCodes();
227
252
  this.globalLocaleRoutes = globalLocaleRoutes || {};
253
+ this.filesLocaleRoutes = filesLocaleRoutes || {};
228
254
  }
229
255
  findLocaleByCode(code) {
230
256
  return this.locales.find((locale) => locale.code === code);
@@ -278,13 +304,10 @@ class PageManager {
278
304
  const pageName = buildRouteNameFromRoute(page.name, page.path);
279
305
  const globalLocalePath = this.globalLocaleRoutes[pageName];
280
306
  if (!globalLocalePath) {
281
- if (page.file) {
282
- const fileContent = readFileSync(page.file, "utf-8");
283
- const localeRoutes = extractLocaleRoutes(fileContent, page.file);
284
- if (localeRoutes) {
285
- const normalizedFullPath = normalizePath(path.posix.join(parentPath, page.path));
286
- localizedPaths[normalizedFullPath] = localeRoutes;
287
- }
307
+ const filesLocalePath = this.filesLocaleRoutes[pageName];
308
+ if (filesLocalePath && typeof filesLocalePath === "object") {
309
+ const normalizedFullPath = normalizePath(path.posix.join(parentPath, page.path));
310
+ localizedPaths[normalizedFullPath] = filesLocalePath;
288
311
  }
289
312
  } else if (typeof globalLocalePath === "object") {
290
313
  const normalizedFullPath = normalizePath(path.posix.join(parentPath, page.path));
@@ -638,7 +661,26 @@ const module = defineNuxtModule({
638
661
  const resolver = createResolver(import.meta.url);
639
662
  const rootDirs = nuxt.options._layers.map((layer) => layer.config.rootDir).reverse();
640
663
  const localeManager = new LocaleManager(options, rootDirs);
641
- const pageManager = new PageManager(localeManager.locales, defaultLocale, options.strategy, options.globalLocaleRoutes, options.noPrefixRedirect, options.excludePatterns);
664
+ const routeLocales = {};
665
+ const globalLocaleRoutes = {};
666
+ const pageFiles = await globby("pages/**/*.vue", { cwd: nuxt.options.rootDir });
667
+ for (const pageFile of pageFiles) {
668
+ const fullPath = join(nuxt.options.rootDir, pageFile);
669
+ try {
670
+ const fileContent = readFileSync(fullPath, "utf-8");
671
+ const { locales: extractedLocales, localeRoutes } = extractDefineI18nRouteData(fileContent, fullPath);
672
+ const routePath = pageFile.replace(/^pages\//, "/").replace(/\/index\.vue$/, "").replace(/\.vue$/, "").replace(/\/$/, "") || "/";
673
+ const pageName = routePath.replace(/[^a-z0-9]/gi, "-").replace(/^-+|-+$/g, "");
674
+ if (extractedLocales) {
675
+ routeLocales[routePath] = extractedLocales;
676
+ }
677
+ if (localeRoutes) {
678
+ globalLocaleRoutes[pageName] = localeRoutes;
679
+ }
680
+ } catch {
681
+ }
682
+ }
683
+ const pageManager = new PageManager(localeManager.locales, defaultLocale, options.strategy, options.globalLocaleRoutes, globalLocaleRoutes, options.noPrefixRedirect, options.excludePatterns);
642
684
  addTemplate({
643
685
  filename: "i18n.plural.mjs",
644
686
  write: true,
@@ -663,7 +705,10 @@ const module = defineNuxtModule({
663
705
  isSSG,
664
706
  disablePageLocales: options.disablePageLocales ?? false,
665
707
  canonicalQueryWhitelist: options.canonicalQueryWhitelist ?? [],
666
- excludePatterns: options.excludePatterns ?? []
708
+ excludePatterns: options.excludePatterns ?? [],
709
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
710
+ // @ts-ignore
711
+ routeLocales
667
712
  };
668
713
  if (typeof options.customRegexMatcher !== "undefined") {
669
714
  const localeCodes = localeManager.locales.map((l) => l.code);
@@ -4,7 +4,7 @@
4
4
 
5
5
  <script setup>
6
6
  import { useRoute, useI18n, createError, navigateTo, useRuntimeConfig } from "#imports";
7
- import { isInternalPath } from "../../utils";
7
+ import { isInternalPath } from "../utils/path-utils";
8
8
  const route = useRoute();
9
9
  const { $getLocales, $defaultLocale } = useI18n();
10
10
  const config = useRuntimeConfig();
@@ -22,7 +22,7 @@ export declare const useLocaleHead: ({ addDirAttribute, identifierAttribute, add
22
22
  identifierAttribute?: string | undefined;
23
23
  addSeoAttributes?: boolean | undefined;
24
24
  baseUrl?: string | undefined;
25
- }) => import("vue").Ref<{
25
+ }) => Promise<import("vue").Ref<{
26
26
  htmlAttrs: {
27
27
  lang?: string | undefined;
28
28
  dir?: "ltr" | "rtl" | "auto" | undefined;
@@ -54,5 +54,5 @@ export declare const useLocaleHead: ({ addDirAttribute, identifierAttribute, add
54
54
  property: string;
55
55
  content: string;
56
56
  }[];
57
- }>;
57
+ }>>;
58
58
  export {};
@@ -1,7 +1,7 @@
1
1
  import { joinURL, parseURL, withQuery } from "ufo";
2
2
  import { isPrefixExceptDefaultStrategy, isNoPrefixStrategy } from "nuxt-i18n-micro-core";
3
3
  import { unref, useRoute, useRuntimeConfig, watch, onUnmounted, ref, useNuxtApp } from "#imports";
4
- export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "id", addSeoAttributes = true, baseUrl = "/" } = {}) => {
4
+ export const useLocaleHead = async ({ addDirAttribute = true, identifierAttribute = "id", addSeoAttributes = true, baseUrl = "/" } = {}) => {
5
5
  const metaObject = ref({
6
6
  htmlAttrs: {},
7
7
  link: [],
@@ -19,15 +19,17 @@ export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "i
19
19
  return withQuery(pathname, filtered);
20
20
  }
21
21
  function updateMeta() {
22
- const { defaultLocale, strategy, canonicalQueryWhitelist } = useRuntimeConfig().public.i18nConfig;
22
+ const { defaultLocale, strategy, canonicalQueryWhitelist, routeLocales } = useRuntimeConfig().public.i18nConfig;
23
23
  const { $getLocales, $getLocale } = useNuxtApp();
24
24
  if (!$getLocale || !$getLocales) return;
25
25
  const route = useRoute();
26
26
  const locale = unref($getLocale());
27
- const locales = unref($getLocales());
27
+ const allLocales = unref($getLocales());
28
28
  const routeName = (route.name ?? "").toString();
29
29
  const currentLocale = unref($getLocales().find((loc) => loc.code === locale));
30
30
  if (!currentLocale) return;
31
+ const currentRouteLocales = routeLocales?.[routeName] || routeLocales?.[route.path];
32
+ const locales = currentRouteLocales ? allLocales.filter((loc) => currentRouteLocales.includes(loc.code)) : allLocales;
31
33
  const currentIso = currentLocale.iso || locale;
32
34
  const currentDir = currentLocale.dir || "auto";
33
35
  let fullPath = unref(route.fullPath);
@@ -55,7 +57,7 @@ export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "i
55
57
  meta: []
56
58
  };
57
59
  if (!addSeoAttributes) return;
58
- const alternateLocales = $getLocales() ?? [];
60
+ const alternateLocales = locales;
59
61
  const ogLocaleMeta = {
60
62
  [identifierAttribute]: "i18n-og",
61
63
  property: "og:locale",
@@ -2,20 +2,18 @@ import { useLocaleHead } from "../composables/useLocaleHead.js";
2
2
  import { useRequestURL, useHead, defineNuxtPlugin, useRuntimeConfig } from "#imports";
3
3
  const host = process.env.HOST ?? "localhost";
4
4
  const port = process.env.PORT ?? "host";
5
- export default defineNuxtPlugin((nuxtApp) => {
5
+ export default defineNuxtPlugin(async (_nuxtApp) => {
6
6
  const config = useRuntimeConfig();
7
7
  const i18nConfig = config.public.i18nConfig;
8
8
  const schema = port === "443" ? "https" : "http";
9
9
  const defaultUrl = port === "80" || port === "443" ? `${schema}://${host}` : `${schema}://${host}:${port}`;
10
- nuxtApp.hook("app:rendered", (_context) => {
11
- const url = useRequestURL();
12
- const baseUrl = (i18nConfig.metaBaseUrl || url.origin || defaultUrl).toString();
13
- const head = useLocaleHead({
14
- addDirAttribute: true,
15
- identifierAttribute: "id",
16
- addSeoAttributes: true,
17
- baseUrl
18
- });
19
- useHead(head);
10
+ const url = useRequestURL();
11
+ const baseUrl = (i18nConfig.metaBaseUrl || url.origin || defaultUrl).toString();
12
+ const head = await useLocaleHead({
13
+ addDirAttribute: true,
14
+ identifierAttribute: "id",
15
+ addSeoAttributes: true,
16
+ baseUrl
20
17
  });
18
+ useHead(head);
21
19
  });
@@ -1,10 +1,31 @@
1
1
  import { isNoPrefixStrategy, isPrefixStrategy } from "nuxt-i18n-micro-core";
2
- import { defineNuxtPlugin, useRuntimeConfig, useRoute, useRouter, navigateTo } from "#imports";
2
+ import { defineNuxtPlugin, useRuntimeConfig, useRoute, useRouter, navigateTo, createError } from "#imports";
3
3
  export default defineNuxtPlugin(async (nuxtApp) => {
4
4
  const config = useRuntimeConfig();
5
5
  const i18nConfig = config.public.i18nConfig;
6
+ const { routeLocales } = useRuntimeConfig().public.i18nConfig;
6
7
  const route = useRoute();
7
8
  const router = useRouter();
9
+ const checkRouteLocales = (to) => {
10
+ const routePath = to.path;
11
+ const routeName = to.name?.toString();
12
+ const normalizedRouteName = routeName?.replace("localized-", "");
13
+ const normalizedRoutePath = normalizedRouteName ? `/${normalizedRouteName}` : void 0;
14
+ const allowedLocales = routeName && routeLocales?.[routeName] || normalizedRouteName && routeLocales?.[normalizedRouteName] || normalizedRoutePath && routeLocales?.[normalizedRoutePath] || routeLocales?.[routePath];
15
+ if (!allowedLocales || allowedLocales.length === 0) {
16
+ return;
17
+ }
18
+ const pathSegments = routePath.split("/").filter(Boolean);
19
+ const firstSegment = pathSegments[0];
20
+ const allLocales = i18nConfig.locales?.map((l) => l.code) || [];
21
+ if (firstSegment && allLocales.includes(firstSegment) && !allowedLocales.includes(firstSegment)) {
22
+ console.log("Locale not allowed, throwing 404");
23
+ throw createError({
24
+ statusCode: 404,
25
+ statusMessage: "Page Not Found"
26
+ });
27
+ }
28
+ };
8
29
  const handleRedirect = async (to) => {
9
30
  const currentLocale = nuxtApp.$getLocale().toString();
10
31
  const name = to.name?.toString();
@@ -26,10 +47,16 @@ export default defineNuxtPlugin(async (nuxtApp) => {
26
47
  });
27
48
  }
28
49
  };
29
- if (import.meta.server && (isPrefixStrategy(i18nConfig.strategy) || isNoPrefixStrategy(i18nConfig.strategy))) {
30
- await handleRedirect(route);
50
+ if (import.meta.server) {
51
+ checkRouteLocales(route);
52
+ if (isPrefixStrategy(i18nConfig.strategy) || isNoPrefixStrategy(i18nConfig.strategy)) {
53
+ await handleRedirect(route);
54
+ }
31
55
  }
32
56
  router.beforeEach(async (to, from, next) => {
57
+ if (from.path !== to.path) {
58
+ checkRouteLocales(to);
59
+ }
33
60
  if (isPrefixStrategy(i18nConfig.strategy) || isNoPrefixStrategy(i18nConfig.strategy)) {
34
61
  await handleRedirect(to);
35
62
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Checks if a path should be excluded from i18n routing
3
+ * @param path - The path to check
4
+ * @param excludePatterns - Optional custom exclusion patterns
5
+ * @returns true if the path should be excluded
6
+ */
7
+ export declare const isInternalPath: (path: string, excludePatterns?: (string | RegExp | object)[]) => boolean;
@@ -0,0 +1,40 @@
1
+ const DEFAULT_STATIC_PATTERNS = [
2
+ /^\/sitemap.*\.xml$/,
3
+ /^\/sitemap\.xml$/,
4
+ /^\/robots\.txt$/,
5
+ /^\/favicon\.ico$/,
6
+ /^\/apple-touch-icon.*\.png$/,
7
+ /^\/manifest\.json$/,
8
+ /^\/sw\.js$/,
9
+ /^\/workbox-.*\.js$/,
10
+ /\.(xml|txt|ico|json|js|css|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/
11
+ ];
12
+ export const isInternalPath = (path, excludePatterns) => {
13
+ if (/(?:^|\/)__[^/]+/.test(path)) {
14
+ return true;
15
+ }
16
+ for (const pattern of DEFAULT_STATIC_PATTERNS) {
17
+ if (pattern.test(path)) {
18
+ return true;
19
+ }
20
+ }
21
+ if (excludePatterns) {
22
+ for (const pattern of excludePatterns) {
23
+ if (typeof pattern === "string") {
24
+ if (pattern.includes("*") || pattern.includes("?")) {
25
+ const regex = new RegExp(pattern.replace(/\*/g, ".*").replace(/\?/g, "."));
26
+ if (regex.test(path)) {
27
+ return true;
28
+ }
29
+ } else if (path === pattern || path.startsWith(pattern)) {
30
+ return true;
31
+ }
32
+ } else if (pattern instanceof RegExp) {
33
+ if (pattern.test(path)) {
34
+ return true;
35
+ }
36
+ }
37
+ }
38
+ }
39
+ return false;
40
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-i18n-micro",
3
- "version": "1.97.0",
3
+ "version": "1.98.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",
@@ -62,8 +62,8 @@
62
62
  "sirv": "^2.0.4",
63
63
  "ufo": "^1.5.4",
64
64
  "nuxt-i18n-micro-core": "1.0.18",
65
- "nuxt-i18n-micro-test-utils": "1.0.6",
66
- "nuxt-i18n-micro-types": "1.0.7"
65
+ "nuxt-i18n-micro-types": "1.0.8",
66
+ "nuxt-i18n-micro-test-utils": "1.0.6"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@nuxt/devtools": "^2.6.3",
@@ -1 +0,0 @@
1
- {"id":"6376a03b-f005-4f0f-b051-3b017577666c","timestamp":1758813945370,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}