nuxt-i18n-micro 1.96.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.
- package/README.md +1 -1
- package/dist/client/200.html +2 -2
- package/dist/client/404.html +2 -2
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/9bdb882b-a145-42be-bbc5-f7c9185266d5.json +1 -0
- package/dist/client/index.html +2 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +123 -35
- package/dist/runtime/components/locale-redirect.vue +10 -1
- package/dist/runtime/composables/useLocaleHead.d.ts +2 -2
- package/dist/runtime/composables/useLocaleHead.js +6 -4
- package/dist/runtime/plugins/02.meta.js +9 -11
- package/dist/runtime/plugins/06.redirect.js +30 -3
- package/dist/runtime/utils/path-utils.d.ts +7 -0
- package/dist/runtime/utils/path-utils.js +40 -0
- package/package.json +3 -3
- package/dist/client/_nuxt/builds/meta/2bd7945f-6cbd-45d4-9b72-30c2c2aeb875.json +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.donationalerts.com/r/s00d88)
|
|
5
5
|
|
|
6
6
|
<p align="center">
|
|
7
|
-
<img src="https://github.com/s00d/nuxt-i18n-micro/blob/main/
|
|
7
|
+
<img src="https://github.com/s00d/nuxt-i18n-micro/blob/main/branding/logo_full.png?raw=true" alt="logo">
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
# Nuxt I18n Micro
|
package/dist/client/200.html
CHANGED
|
@@ -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},
|
|
12
|
-
<script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"
|
|
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>
|
package/dist/client/404.html
CHANGED
|
@@ -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},
|
|
12
|
-
<script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"
|
|
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":"
|
|
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":[]}
|
package/dist/client/index.html
CHANGED
|
@@ -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},
|
|
12
|
-
<script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"
|
|
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
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, {
|
|
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,27 +110,88 @@ function setupDevToolsUI(options, resolve2) {
|
|
|
108
110
|
});
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
function extractLocaleRoutes(content, filePath) {
|
|
113
|
+
function extractDefineI18nRouteData(content, filePath) {
|
|
113
114
|
const defineMatch = content.match(/\$?\bdefineI18nRoute\s*\(\s*\{[\s\S]*?\}\s*\)/);
|
|
114
|
-
if (defineMatch) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
if (!defineMatch) {
|
|
116
|
+
return { locales: null, localeRoutes: null };
|
|
117
|
+
}
|
|
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);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
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;
|
|
154
|
+
}
|
|
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;
|
|
123
164
|
} else {
|
|
124
|
-
|
|
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
|
+
}
|
|
125
173
|
}
|
|
126
|
-
} catch (error) {
|
|
127
|
-
console.error("Failed to parse localeRoutes:", error, "in file:", filePath);
|
|
128
174
|
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error("Failed to parse locales:", error, "in file:", filePath);
|
|
129
177
|
}
|
|
130
178
|
}
|
|
131
|
-
|
|
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;
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
console.error("localeRoutes found but it is not a valid object in file:", filePath);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error("Failed to parse localeRoutes:", error, "in file:", filePath);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { locales, localeRoutes };
|
|
132
195
|
}
|
|
133
196
|
function validateDefineI18nRouteConfig(obj) {
|
|
134
197
|
if (typeof obj !== "object") return false;
|
|
@@ -176,14 +239,18 @@ class PageManager {
|
|
|
176
239
|
localizedPaths = {};
|
|
177
240
|
activeLocaleCodes;
|
|
178
241
|
globalLocaleRoutes;
|
|
242
|
+
filesLocaleRoutes;
|
|
179
243
|
noPrefixRedirect;
|
|
180
|
-
|
|
244
|
+
excludePatterns;
|
|
245
|
+
constructor(locales, defaultLocaleCode, strategy, globalLocaleRoutes, filesLocaleRoutes, noPrefixRedirect, excludePatterns) {
|
|
181
246
|
this.locales = locales;
|
|
182
247
|
this.defaultLocale = this.findLocaleByCode(defaultLocaleCode) || { code: defaultLocaleCode };
|
|
183
248
|
this.strategy = strategy;
|
|
184
249
|
this.noPrefixRedirect = noPrefixRedirect;
|
|
250
|
+
this.excludePatterns = excludePatterns;
|
|
185
251
|
this.activeLocaleCodes = this.computeActiveLocaleCodes();
|
|
186
252
|
this.globalLocaleRoutes = globalLocaleRoutes || {};
|
|
253
|
+
this.filesLocaleRoutes = filesLocaleRoutes || {};
|
|
187
254
|
}
|
|
188
255
|
findLocaleByCode(code) {
|
|
189
256
|
return this.locales.find((locale) => locale.code === code);
|
|
@@ -200,7 +267,7 @@ class PageManager {
|
|
|
200
267
|
this.localizedPaths = this.extractLocalizedPaths(pages);
|
|
201
268
|
const additionalRoutes = [];
|
|
202
269
|
for (const page of [...pages]) {
|
|
203
|
-
if (page.path && isInternalPath(page.path)) {
|
|
270
|
+
if (page.path && isInternalPath(page.path, this.excludePatterns)) {
|
|
204
271
|
continue;
|
|
205
272
|
}
|
|
206
273
|
if (!page.name && page.file?.endsWith(".vue")) {
|
|
@@ -222,7 +289,7 @@ class PageManager {
|
|
|
222
289
|
if (!page) continue;
|
|
223
290
|
const pagePath = page.path ?? "";
|
|
224
291
|
const pageName = page.name ?? "";
|
|
225
|
-
if (isInternalPath(pagePath)) continue;
|
|
292
|
+
if (isInternalPath(pagePath, this.excludePatterns)) continue;
|
|
226
293
|
if (this.globalLocaleRoutes[pageName] === false) continue;
|
|
227
294
|
if (!/^\/:locale/.test(pagePath) && pagePath !== "/") {
|
|
228
295
|
pages.splice(i, 1);
|
|
@@ -237,13 +304,10 @@ class PageManager {
|
|
|
237
304
|
const pageName = buildRouteNameFromRoute(page.name, page.path);
|
|
238
305
|
const globalLocalePath = this.globalLocaleRoutes[pageName];
|
|
239
306
|
if (!globalLocalePath) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
const normalizedFullPath = normalizePath(path.posix.join(parentPath, page.path));
|
|
245
|
-
localizedPaths[normalizedFullPath] = localeRoutes;
|
|
246
|
-
}
|
|
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;
|
|
247
311
|
}
|
|
248
312
|
} else if (typeof globalLocalePath === "object") {
|
|
249
313
|
const normalizedFullPath = normalizePath(path.posix.join(parentPath, page.path));
|
|
@@ -578,7 +642,8 @@ const module = defineNuxtModule({
|
|
|
578
642
|
if (!selectedForm) return null;
|
|
579
643
|
return selectedForm.trim().replace("{count}", count.toString());
|
|
580
644
|
},
|
|
581
|
-
customRegexMatcher: void 0
|
|
645
|
+
customRegexMatcher: void 0,
|
|
646
|
+
excludePatterns: void 0
|
|
582
647
|
},
|
|
583
648
|
async setup(options, nuxt) {
|
|
584
649
|
const defaultLocale = process.env.DEFAULT_LOCALE ?? options.defaultLocale ?? "en";
|
|
@@ -596,7 +661,26 @@ const module = defineNuxtModule({
|
|
|
596
661
|
const resolver = createResolver(import.meta.url);
|
|
597
662
|
const rootDirs = nuxt.options._layers.map((layer) => layer.config.rootDir).reverse();
|
|
598
663
|
const localeManager = new LocaleManager(options, rootDirs);
|
|
599
|
-
const
|
|
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);
|
|
600
684
|
addTemplate({
|
|
601
685
|
filename: "i18n.plural.mjs",
|
|
602
686
|
write: true,
|
|
@@ -620,7 +704,11 @@ const module = defineNuxtModule({
|
|
|
620
704
|
apiBaseUrl,
|
|
621
705
|
isSSG,
|
|
622
706
|
disablePageLocales: options.disablePageLocales ?? false,
|
|
623
|
-
canonicalQueryWhitelist: options.canonicalQueryWhitelist ?? []
|
|
707
|
+
canonicalQueryWhitelist: options.canonicalQueryWhitelist ?? [],
|
|
708
|
+
excludePatterns: options.excludePatterns ?? [],
|
|
709
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
710
|
+
// @ts-ignore
|
|
711
|
+
routeLocales
|
|
624
712
|
};
|
|
625
713
|
if (typeof options.customRegexMatcher !== "undefined") {
|
|
626
714
|
const localeCodes = localeManager.locales.map((l) => l.code);
|
|
@@ -734,7 +822,7 @@ const module = defineNuxtModule({
|
|
|
734
822
|
const processPageWithChildren = (page, parentPath = "") => {
|
|
735
823
|
if (!page.path) return;
|
|
736
824
|
const fullPath = path.posix.normalize(`${parentPath}/${page.path}`);
|
|
737
|
-
if (isInternalPath(fullPath)) {
|
|
825
|
+
if (isInternalPath(fullPath, options.excludePatterns)) {
|
|
738
826
|
return;
|
|
739
827
|
}
|
|
740
828
|
const routeRule = routeRules[fullPath];
|
|
@@ -753,13 +841,13 @@ const module = defineNuxtModule({
|
|
|
753
841
|
if (localizedRouteRule && localizedRouteRule.prerender === false) {
|
|
754
842
|
return;
|
|
755
843
|
}
|
|
756
|
-
if (!isInternalPath(localizedPath)) {
|
|
844
|
+
if (!isInternalPath(localizedPath, options.excludePatterns)) {
|
|
757
845
|
prerenderRoutes.push(localizedPath);
|
|
758
846
|
}
|
|
759
847
|
}
|
|
760
848
|
});
|
|
761
849
|
} else {
|
|
762
|
-
if (!isInternalPath(fullPath)) {
|
|
850
|
+
if (!isInternalPath(fullPath, options.excludePatterns)) {
|
|
763
851
|
prerenderRoutes.push(fullPath);
|
|
764
852
|
}
|
|
765
853
|
}
|
|
@@ -883,7 +971,7 @@ const module = defineNuxtModule({
|
|
|
883
971
|
const routesSet = prerenderRoutes.routes;
|
|
884
972
|
const routesToRemove = [];
|
|
885
973
|
routesSet.forEach((route) => {
|
|
886
|
-
if (isInternalPath(route)) {
|
|
974
|
+
if (isInternalPath(route, options.excludePatterns)) {
|
|
887
975
|
routesToRemove.push(route);
|
|
888
976
|
}
|
|
889
977
|
});
|
|
@@ -891,7 +979,7 @@ const module = defineNuxtModule({
|
|
|
891
979
|
const additionalRoutes = /* @__PURE__ */ new Set();
|
|
892
980
|
const routeRules = nuxt.options.routeRules || {};
|
|
893
981
|
routesSet.forEach((route) => {
|
|
894
|
-
if (!/\.[a-z0-9]+$/i.test(route) && !isInternalPath(route)) {
|
|
982
|
+
if (!/\.[a-z0-9]+$/i.test(route) && !isInternalPath(route, options.excludePatterns)) {
|
|
895
983
|
localeManager.locales.forEach((locale) => {
|
|
896
984
|
const shouldGenerate = locale.code !== defaultLocale || withPrefixStrategy(options.strategy);
|
|
897
985
|
if (shouldGenerate) {
|
|
@@ -3,13 +3,22 @@
|
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
5
|
<script setup>
|
|
6
|
-
import { useRoute, useI18n, createError, navigateTo } from "#imports";
|
|
6
|
+
import { useRoute, useI18n, createError, navigateTo, useRuntimeConfig } from "#imports";
|
|
7
|
+
import { isInternalPath } from "../utils/path-utils";
|
|
7
8
|
const route = useRoute();
|
|
8
9
|
const { $getLocales, $defaultLocale } = useI18n();
|
|
10
|
+
const config = useRuntimeConfig();
|
|
9
11
|
const locales = $getLocales().map((locale) => locale.code);
|
|
10
12
|
const defaultLocale = $defaultLocale() || "en";
|
|
11
13
|
const pathSegments = route.fullPath.split("/");
|
|
12
14
|
const firstSegment = pathSegments[1];
|
|
15
|
+
const excludePatterns = config.public.i18nConfig?.excludePatterns;
|
|
16
|
+
if (isInternalPath(route.fullPath, excludePatterns)) {
|
|
17
|
+
throw createError({
|
|
18
|
+
statusCode: 404,
|
|
19
|
+
statusMessage: "Static file - should not be processed by i18n"
|
|
20
|
+
});
|
|
21
|
+
}
|
|
13
22
|
const generateRouteName = (segments) => {
|
|
14
23
|
return segments.slice(1).map((segment) => segment.replace(/:/g, "")).join("-");
|
|
15
24
|
};
|
|
@@ -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
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
30
|
-
|
|
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.
|
|
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-
|
|
66
|
-
"nuxt-i18n-micro-
|
|
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":"2bd7945f-6cbd-45d4-9b72-30c2c2aeb875","timestamp":1758176680760,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
|