nuxt-i18n-micro 3.30.2 → 4.0.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/LICENSE +1 -1
- package/README.md +2 -2
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/BVna_KKx.js +3 -0
- package/dist/client/_nuxt/Bw81RhAv.js +1 -0
- package/dist/client/_nuxt/CPz_jhsD.js +1 -0
- package/dist/client/_nuxt/CmvObVGq.js +1 -0
- package/dist/client/_nuxt/DKUOBR6F.js +5 -0
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/f0c8d4bf-40d2-4915-8b4a-ebe46bde61c8.json +1 -0
- package/dist/client/_nuxt/{entry.BM-mexAv.css → entry.PSWtHhay.css} +1 -1
- package/dist/client/_nuxt/error-404.xs3fxhVJ.css +1 -0
- package/dist/client/_nuxt/error-500.C2S-4iLg.css +1 -0
- package/dist/client/index.html +1 -1
- package/dist/module.d.mts +14 -5
- package/dist/module.json +4 -1
- package/dist/module.mjs +850 -475
- package/dist/runtime/components/i18n-group.d.vue.ts +17 -9
- package/dist/runtime/components/i18n-group.vue +2 -4
- package/dist/runtime/components/i18n-group.vue.d.ts +17 -9
- package/dist/runtime/components/i18n-link.d.vue.ts +9 -6
- package/dist/runtime/components/i18n-link.vue +7 -28
- package/dist/runtime/components/i18n-link.vue.d.ts +9 -6
- package/dist/runtime/components/i18n-switcher.d.vue.ts +128 -54
- package/dist/runtime/components/i18n-switcher.vue +56 -156
- package/dist/runtime/components/i18n-switcher.vue.d.ts +128 -54
- package/dist/runtime/components/i18n-t.d.vue.ts +59 -116
- package/dist/runtime/components/i18n-t.vue +24 -71
- package/dist/runtime/components/i18n-t.vue.d.ts +59 -116
- package/dist/runtime/composables/useI18n.js +2 -38
- package/dist/runtime/composables/useI18nHead.js +21 -6
- package/dist/runtime/composables/useI18nLocale.d.ts +4 -1
- package/dist/runtime/composables/useI18nLocale.js +20 -22
- package/dist/runtime/composables/useLocaleHead.d.ts +26 -31
- package/dist/runtime/composables/useLocaleHead.js +47 -167
- package/dist/runtime/devtools/bridge.d.ts +2 -2
- package/dist/runtime/devtools/vue-devtools.d.ts +2 -2
- package/dist/runtime/devtools/vue-devtools.js +5 -5
- package/dist/runtime/middleware/i18n-redirect.global.d.ts +3 -2
- package/dist/runtime/middleware/i18n-redirect.global.js +30 -41
- package/dist/runtime/plugins/01.plugin.d.ts +156 -34
- package/dist/runtime/plugins/01.plugin.js +184 -138
- package/dist/runtime/plugins/02.meta.js +36 -43
- package/dist/runtime/plugins/04.vue-devtools.js +1 -1
- package/dist/runtime/plugins/06.redirect.d.ts +3 -3
- package/dist/runtime/plugins/06.redirect.js +65 -91
- package/dist/runtime/server/{utils → internal}/dev-live-translations.js +4 -3
- package/dist/runtime/server/internal/server-translation-cache.d.ts +9 -0
- package/dist/runtime/server/internal/server-translation-cache.js +10 -0
- package/dist/runtime/server/internal/shared-cache.d.ts +24 -0
- package/dist/runtime/server/internal/shared-cache.js +71 -0
- package/dist/runtime/server/internal/translation-watcher.d.ts +79 -0
- package/dist/runtime/server/internal/translation-watcher.js +105 -0
- package/dist/runtime/server/middleware/i18n.global.js +24 -37
- package/dist/runtime/server/payload-source.assets.d.ts +1 -0
- package/dist/runtime/server/payload-source.assets.js +3 -8
- package/dist/runtime/server/payload-source.fs.d.ts +1 -0
- package/dist/runtime/server/payload-source.fs.js +18 -7
- package/dist/runtime/server/plugins/failed-read.d.ts +7 -0
- package/dist/runtime/server/plugins/failed-read.js +23 -0
- package/dist/runtime/server/plugins/payload-cache.d.ts +6 -0
- package/dist/runtime/server/plugins/payload-cache.js +16 -0
- package/dist/runtime/server/plugins/watcher.dev.js +15 -79
- package/dist/runtime/server/routes/i18n.js +13 -14
- package/dist/runtime/server/utils/locale-detector.d.ts +32 -2
- package/dist/runtime/server/utils/locale-detector.js +48 -27
- package/dist/runtime/server/utils/locale-server-middleware.js +14 -33
- package/dist/runtime/server/utils/payload-cache.d.ts +4 -0
- package/dist/runtime/server/utils/payload-cache.js +13 -0
- package/dist/runtime/server/utils/server-loader.d.ts +17 -6
- package/dist/runtime/server/utils/server-loader.js +105 -51
- package/dist/runtime/server/utils/translation-server-middleware.js +18 -35
- package/dist/runtime/utils/cache-keys.d.ts +6 -0
- package/dist/runtime/utils/cache-keys.js +2 -0
- package/dist/runtime/utils/i18n-api.d.ts +185 -0
- package/dist/runtime/utils/i18n-api.js +196 -0
- package/dist/runtime/utils/i18n-config.d.ts +18 -0
- package/dist/runtime/utils/i18n-config.js +47 -0
- package/dist/runtime/utils/i18n-navigation.d.ts +26 -0
- package/dist/runtime/utils/i18n-navigation.js +96 -0
- package/dist/runtime/utils/nuxt-i18n-server.d.ts +26 -0
- package/dist/runtime/utils/nuxt-i18n-server.js +83 -0
- package/dist/runtime/utils/nuxt-i18n.d.ts +81 -96
- package/dist/runtime/utils/nuxt-i18n.js +180 -252
- package/dist/runtime/utils/page-head.d.ts +12 -0
- package/dist/runtime/utils/page-head.js +6 -0
- package/dist/runtime/utils/request-url.d.ts +6 -0
- package/dist/runtime/utils/request-url.js +11 -0
- package/dist/runtime/utils/storage.d.ts +31 -19
- package/dist/runtime/utils/storage.js +100 -49
- package/dist/runtime/utils/translation-entry.d.ts +6 -0
- package/dist/runtime/utils/translation-entry.js +16 -0
- package/dist/types.d.mts +4 -6
- package/internals.d.mts +9 -2
- package/package.json +78 -79
- package/dist/client/_nuxt/BOxWElQP.js +0 -21
- package/dist/client/_nuxt/CUWB4CwN.js +0 -3
- package/dist/client/_nuxt/CmUbe-uv.js +0 -1
- package/dist/client/_nuxt/DAzShkZF.js +0 -1
- package/dist/client/_nuxt/MokPP5zi.js +0 -1
- package/dist/client/_nuxt/builds/meta/f020fec8-462b-480b-82d8-6b0cb65617e8.json +0 -1
- package/dist/client/_nuxt/error-404.D9Svp6Hk.css +0 -1
- package/dist/client/_nuxt/error-500.B-mjI9_9.css +0 -1
- package/dist/runtime/plugins/03.define.d.ts +0 -7
- package/dist/runtime/plugins/03.define.js +0 -42
- package/dist/runtime/plugins/05.hooks.d.ts +0 -2
- package/dist/runtime/plugins/05.hooks.js +0 -47
- package/dist/runtime/server/utils/server-translation-cache.d.ts +0 -11
- package/dist/runtime/server/utils/server-translation-cache.js +0 -20
- /package/dist/runtime/server/{utils → internal}/dev-live-translations.d.ts +0 -0
package/dist/module.mjs
CHANGED
|
@@ -1,24 +1,377 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
|
-
import fs__default, {
|
|
3
|
-
import path, { dirname,
|
|
2
|
+
import fs__default, { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, readFileSync } from 'node:fs';
|
|
3
|
+
import path, { join, dirname, relative, basename, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
-
import { defaultPlural, isNoPrefixStrategy
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
import { hashTranslationSources, scanTranslationPayloadDirectory, compressTranslationPayloads } from '@i18n-micro/utils/payload-stats';
|
|
12
|
-
import { buildTranslationPayloadCacheControl } from '@i18n-micro/utils/payload-url';
|
|
13
|
-
import { useNuxt, addDevServerHandler, defineNuxtModule, useLogger, createResolver, addImportsDir, addPlugin, addRouteMiddleware, addServerImportsDir, addServerHandler, addComponentsDir, addTemplate, addTypeTemplate, addVitePlugin, resolvePath, addPrerenderRoutes } from '@nuxt/kit';
|
|
5
|
+
import { defaultPlural, isNoPrefixStrategy } from '@i18n-micro/core';
|
|
6
|
+
import { forwardSlashes, trimLeadingSlashes, isInternalPath } from '@i18n-micro/utils/app-path';
|
|
7
|
+
import { readTranslationFile, resolveLiveTranslationDirs, translationRelativePath } from '../dist/runtime/server/internal/translation-watcher.js';
|
|
8
|
+
import { isLocaleAllowedForUnlocalizedRoute, normalizePath, routeKey, nameOfPath, RouteGenerator, localesOutsideMatcher, pageNames } from '@i18n-micro/route-strategy';
|
|
9
|
+
import { configForHost } from '@i18n-micro/utils/domains';
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
14
11
|
import { globby } from 'globby';
|
|
12
|
+
import { deepMergeTranslationsRecursive } from '@i18n-micro/utils/deep-merge';
|
|
13
|
+
import { PREMERGED_ROOT_PAGE, mergeSourceTranslations, PREMERGED_ROOT_MARK, mergeOverRoot, buildFallbackLocaleChain } from '@i18n-micro/utils/merge-source';
|
|
14
|
+
import { gzipSync, brotliCompressSync, constants } from 'node:zlib';
|
|
15
|
+
import { resolveTranslationPayloadCachePolicy } from '@i18n-micro/utils/payload-url';
|
|
16
|
+
import { useNuxt, addDevServerHandler, defineNuxtModule, useLogger, createResolver, addImportsDir, addPlugin, addRouteMiddleware, addPluginTemplate, addServerImports, addServerHandler, addComponentsDir, addVitePlugin, addTemplate, addTypeTemplate, resolvePath, addPrerenderRoutes } from '@nuxt/kit';
|
|
15
17
|
import sirv from 'sirv';
|
|
16
18
|
import { startSubprocess, addCustomTab, onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
|
|
19
|
+
import { isInsideDir, resolveTranslationFile } from '@i18n-micro/utils/load-dir';
|
|
17
20
|
import { fromNodeMiddleware, defineEventHandler, proxyRequest } from 'h3';
|
|
18
21
|
import { joinURL } from 'ufo';
|
|
19
|
-
import {
|
|
22
|
+
import { DomainPathStrategy, createPathStrategy } from '@i18n-micro/path-strategy';
|
|
20
23
|
import { parse } from '@vue/compiler-sfc';
|
|
21
24
|
|
|
25
|
+
const TRANSLATIONS_HMR_EVENT = "i18n-micro:update";
|
|
26
|
+
function generateHmrPlugin() {
|
|
27
|
+
return `
|
|
28
|
+
import { defineNuxtPlugin } from '#imports'
|
|
29
|
+
|
|
30
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
31
|
+
if (!import.meta.hot) return
|
|
32
|
+
import.meta.hot.on(${JSON.stringify(TRANSLATIONS_HMR_EVENT)}, () => {
|
|
33
|
+
try {
|
|
34
|
+
nuxtApp.$clearCache()
|
|
35
|
+
console.log('[i18n HMR] Translations reloaded')
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
console.warn('[i18n HMR] Failed to reload translations', e)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
`.trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildFullConfigSource(config) {
|
|
46
|
+
const { excludePatterns = [], ...rest } = config;
|
|
47
|
+
const patterns = excludePatterns.map((pattern) => pattern instanceof RegExp ? String(pattern) : JSON.stringify(pattern));
|
|
48
|
+
return `{ ...${JSON.stringify(rest)}, excludePatterns: [${patterns.join(", ")}] }`;
|
|
49
|
+
}
|
|
50
|
+
function buildPrivateConfigSource(config, dev) {
|
|
51
|
+
const { rootDirs, payloadFsDir, ...rest } = config;
|
|
52
|
+
const read = `export function getI18nPrivateConfig() { return __privateConfig }
|
|
53
|
+
`;
|
|
54
|
+
if (dev) return `const __privateConfig = ${JSON.stringify(config)}
|
|
55
|
+
${read}`;
|
|
56
|
+
return `const __privateConfig = ${JSON.stringify(rest)}
|
|
57
|
+
if (import.meta.prerender) Object.assign(__privateConfig, ${JSON.stringify({ rootDirs, payloadFsDir })})
|
|
58
|
+
${read}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function mergeAdditionalRootLocaleFiles(rootDirs, additionalDirNames, locale, primaryContent = {}) {
|
|
62
|
+
if (!additionalDirNames.length) return primaryContent;
|
|
63
|
+
let content = {};
|
|
64
|
+
for (const dirName of additionalDirNames) {
|
|
65
|
+
for (const rootDir of rootDirs) {
|
|
66
|
+
content = deepMergeTranslationsRecursive(content, readTranslationFile(join(rootDir, dirName, `${locale}.json`)));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return deepMergeTranslationsRecursive(content, primaryContent);
|
|
70
|
+
}
|
|
71
|
+
function listAdditionalRootLocales(rootDirs, additionalDirNames) {
|
|
72
|
+
const locales = /* @__PURE__ */ new Set();
|
|
73
|
+
for (const dirName of additionalDirNames) {
|
|
74
|
+
for (const rootDir of rootDirs) {
|
|
75
|
+
const dir = join(rootDir, dirName);
|
|
76
|
+
if (!existsSync(dir)) continue;
|
|
77
|
+
for (const name of readdirSync(dir)) {
|
|
78
|
+
if (!name.endsWith(".json")) continue;
|
|
79
|
+
locales.add(name.slice(0, -".json".length));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return [...locales];
|
|
84
|
+
}
|
|
85
|
+
const isTree = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
86
|
+
function diffTree(page, root) {
|
|
87
|
+
const delta = {};
|
|
88
|
+
for (const key of Object.keys(page)) {
|
|
89
|
+
const value = page[key];
|
|
90
|
+
const base = root[key];
|
|
91
|
+
if (isTree(value) && isTree(base)) {
|
|
92
|
+
const nested = diffTree(value, base);
|
|
93
|
+
if (Object.keys(nested).length) delta[key] = nested;
|
|
94
|
+
} else if (!Object.hasOwn(root, key) || JSON.stringify(value) !== JSON.stringify(base)) delta[key] = value;
|
|
95
|
+
}
|
|
96
|
+
return delta;
|
|
97
|
+
}
|
|
98
|
+
function pageFileOverRoot(page, root, mark) {
|
|
99
|
+
const delta = diffTree(page, root);
|
|
100
|
+
const merged = mergeOverRoot(root, delta);
|
|
101
|
+
const whole = Object.keys(merged).length !== Object.keys(page).length;
|
|
102
|
+
if (whole || Object.keys(page).some((key) => JSON.stringify(merged[key]) !== JSON.stringify(page[key]))) return page;
|
|
103
|
+
return { ...delta, [PREMERGED_ROOT_MARK]: mark };
|
|
104
|
+
}
|
|
105
|
+
async function mergedFiles(rootDirs, translationDirName, outputDir, additionalTranslationDirs = []) {
|
|
106
|
+
if (existsSync(outputDir)) fs__default.rmSync(outputDir, { recursive: true, force: true });
|
|
107
|
+
mkdirSync(outputDir, { recursive: true });
|
|
108
|
+
const layerPaths = rootDirs.map((dir) => join(dir, translationDirName));
|
|
109
|
+
const filesByLayer = await Promise.all(layerPaths.filter((lp) => existsSync(lp)).map((lp) => globby("**/*.json", { cwd: lp })));
|
|
110
|
+
const merged = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const file of new Set(filesByLayer.flat())) {
|
|
112
|
+
let content = {};
|
|
113
|
+
for (const lp of layerPaths) content = deepMergeTranslationsRecursive(content, readTranslationFile(join(lp, file)));
|
|
114
|
+
merged.set(file, content);
|
|
115
|
+
}
|
|
116
|
+
for (const locale of listAdditionalRootLocales(rootDirs, additionalTranslationDirs))
|
|
117
|
+
if (!merged.has(`${locale}.json`)) merged.set(`${locale}.json`, {});
|
|
118
|
+
for (const file of [...merged.keys()].filter((f) => dirname(f) === "."))
|
|
119
|
+
merged.set(file, mergeAdditionalRootLocaleFiles(rootDirs, additionalTranslationDirs, file.slice(0, -".json".length), merged.get(file)));
|
|
120
|
+
return merged;
|
|
121
|
+
}
|
|
122
|
+
function depthOf(tree) {
|
|
123
|
+
let depth = 0;
|
|
124
|
+
for (let level = [tree]; level.length > 0; depth++)
|
|
125
|
+
level = level.flatMap((node) => node !== null && typeof node === "object" ? Object.values(node) : []);
|
|
126
|
+
return depth - 1;
|
|
127
|
+
}
|
|
128
|
+
async function namingDeepFile(rootDirs, dirNames, step) {
|
|
129
|
+
try {
|
|
130
|
+
await step();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (!(error instanceof RangeError) || !error.message.includes("call stack")) throw error;
|
|
133
|
+
const dirs = rootDirs.flatMap((root) => dirNames.map((name) => join(root, name))).filter((dir) => existsSync(dir));
|
|
134
|
+
const files = (await Promise.all(dirs.map((cwd) => globby("**/*.json", { cwd, absolute: true })))).flat();
|
|
135
|
+
const [deepest] = files.map((file) => ({ file, depth: depthOf(readTranslationFile(file)) })).sort((a, b) => b.depth - a.depth);
|
|
136
|
+
if (!deepest) throw error;
|
|
137
|
+
throw new Error(`[i18n] ${deepest.file} is nested ${deepest.depth} levels deep, too deep to build. Flatten it.`, { cause: error });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function preMergeLocales(options) {
|
|
141
|
+
await namingDeepFile(options.rootDirs, [options.translationDirName, ...options.additionalTranslationDirs ?? []], () => writeChunks(options));
|
|
142
|
+
}
|
|
143
|
+
async function writeChunks(options) {
|
|
144
|
+
const { rootDirs, translationDirName, outputDir, locales, globalFallbackLocale, disablePageLocales, additionalTranslationDirs } = options;
|
|
145
|
+
const { routeNames = [], rootDelta = false } = options;
|
|
146
|
+
const merged = await mergedFiles(rootDirs, translationDirName, outputDir, additionalTranslationDirs);
|
|
147
|
+
const pages = /* @__PURE__ */ new Set([...[...merged.keys()].filter((f) => f.startsWith("pages/")).map((f) => dirname(f).slice("pages/".length)), ...routeNames]);
|
|
148
|
+
const targets = disablePageLocales || pages.size === 0 ? ["index"] : [...pages, ...pages.has("index") ? [] : ["index"]];
|
|
149
|
+
const chunk = (page, locale) => mergeSourceTranslations({
|
|
150
|
+
locale,
|
|
151
|
+
pageName: page,
|
|
152
|
+
locales,
|
|
153
|
+
globalFallbackLocale,
|
|
154
|
+
disablePageLocales,
|
|
155
|
+
readLocaleFile: (relativePath) => merged.get(relativePath) ?? {}
|
|
156
|
+
});
|
|
157
|
+
const write = (page, locale, data) => {
|
|
158
|
+
const targetPath = join(outputDir, page, locale, "data.json");
|
|
159
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
160
|
+
writeFileSync(targetPath, JSON.stringify(data));
|
|
161
|
+
};
|
|
162
|
+
const split = rootDelta && !disablePageLocales;
|
|
163
|
+
await Promise.all(
|
|
164
|
+
locales.map(async ({ code: locale }) => {
|
|
165
|
+
const root = await chunk(PREMERGED_ROOT_PAGE, locale);
|
|
166
|
+
const mark = createHash("sha256").update(JSON.stringify(root)).digest("hex").slice(0, 8);
|
|
167
|
+
write(PREMERGED_ROOT_PAGE, locale, split ? { ...root, [PREMERGED_ROOT_MARK]: mark } : root);
|
|
168
|
+
await Promise.all(
|
|
169
|
+
targets.map(async (page) => {
|
|
170
|
+
const data = await chunk(page, locale);
|
|
171
|
+
write(page, locale, split ? pageFileOverRoot(data, root, mark) : data);
|
|
172
|
+
})
|
|
173
|
+
);
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
async function buildTranslationSourceLayers(rootDirs, translationDirName, outputDir, additionalTranslationDirs) {
|
|
178
|
+
await namingDeepFile(rootDirs, [translationDirName, ...additionalTranslationDirs ?? []], async () => {
|
|
179
|
+
for (const [file, content] of await mergedFiles(rootDirs, translationDirName, outputDir, additionalTranslationDirs)) {
|
|
180
|
+
const targetPath = join(outputDir, file);
|
|
181
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
182
|
+
writeFileSync(targetPath, JSON.stringify(content));
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const DEFAULT_TRANSLATION_PAYLOAD_WARN_FILE_COUNT = 500;
|
|
188
|
+
const DEFAULT_TRANSLATION_PAYLOAD_WARN_SIZE_BYTES = 10 * 1024 * 1024;
|
|
189
|
+
function resolveTranslationPayloadOptions(options) {
|
|
190
|
+
const isSourceMode = options.translationPayloads?.mode === "source";
|
|
191
|
+
return {
|
|
192
|
+
mode: isSourceMode ? "source" : "premerged",
|
|
193
|
+
serverAssets: options.translationPayloads?.serverAssets !== false,
|
|
194
|
+
serverHandler: options.translationPayloads?.serverHandler !== false,
|
|
195
|
+
publicAssets: isSourceMode ? options.translationPayloads?.publicAssets === true : options.translationPayloads?.publicAssets !== false,
|
|
196
|
+
// Off by default: prerendering `/_locales` without a live handler writes empty public stubs.
|
|
197
|
+
// Opt in when you need static `/{apiBaseUrl}/.../data.json` and payloads are already available.
|
|
198
|
+
prerenderRoutes: options.translationPayloads?.prerenderRoutes === true,
|
|
199
|
+
rootDelta: !isSourceMode && options.translationPayloads?.rootDelta !== false
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const pathSegments = (path) => path.split("/").filter(Boolean).join("/");
|
|
203
|
+
function resolveTranslationPayloadPublicRel(options, apiBaseUrl) {
|
|
204
|
+
return pathSegments(options.translationPayloads?.publicDir || apiBaseUrl) || "_locales";
|
|
205
|
+
}
|
|
206
|
+
function resolveTranslationPayloadPublicDir(outputPublicDir, options, apiBaseUrl) {
|
|
207
|
+
return join(outputPublicDir ?? "./dist", resolveTranslationPayloadPublicRel(options, apiBaseUrl));
|
|
208
|
+
}
|
|
209
|
+
function domainPayloadFilter(options) {
|
|
210
|
+
const { mode, root, served, linkLocales = served, fallbackLocale } = options;
|
|
211
|
+
const needed = served.flatMap((locale) => mode === "source" ? buildFallbackLocaleChain(locale.code, linkLocales, fallbackLocale) : [locale.code]);
|
|
212
|
+
const foreign = new Set(linkLocales.map((locale) => locale.code).filter((code) => !needed.includes(code)));
|
|
213
|
+
return (source) => {
|
|
214
|
+
const name = basename(source);
|
|
215
|
+
if (mode === "source") return !name.endsWith(".json") || !foreign.has(name.slice(0, -".json".length));
|
|
216
|
+
return !foreign.has(name) || dirname(source) === root;
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function shouldCopyTranslationPayloadsToPublic(translationPayloads, readsPublicDir) {
|
|
220
|
+
if (translationPayloads.publicAssets) return true;
|
|
221
|
+
return readsPublicDir && translationPayloads.serverAssets;
|
|
222
|
+
}
|
|
223
|
+
function shouldReadPayloadsFromPublicDir(nitroOptions) {
|
|
224
|
+
const { node, serveStatic, output } = nitroOptions;
|
|
225
|
+
return node && !!serveStatic && serveStatic !== "inline" && relative(output.serverDir, output.publicDir) === join("..", "public");
|
|
226
|
+
}
|
|
227
|
+
function shouldRegisterNitroServerAssets(translationPayloads, readsPublicDir) {
|
|
228
|
+
return !readsPublicDir && translationPayloads.serverAssets;
|
|
229
|
+
}
|
|
230
|
+
function hasLocalTranslationPayloadOutput(translationPayloads) {
|
|
231
|
+
return translationPayloads.serverAssets || translationPayloads.serverHandler || translationPayloads.publicAssets || translationPayloads.prerenderRoutes;
|
|
232
|
+
}
|
|
233
|
+
function getTranslationPayloadMisconfigurationWarnings(input) {
|
|
234
|
+
const warnings = [];
|
|
235
|
+
if (!input.translationPayloads.serverAssets && !input.translationPayloads.publicAssets && !input.apiBaseServerHost) {
|
|
236
|
+
warnings.push(
|
|
237
|
+
"translationPayloads.serverAssets and publicAssets are false and apiBaseServerHost is not set. SSR will load empty translations unless you provide an external server payload host."
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (hasLocalTranslationPayloadOutput(input.translationPayloads)) {
|
|
241
|
+
return warnings;
|
|
242
|
+
}
|
|
243
|
+
if (!input.apiBaseServerHost) {
|
|
244
|
+
warnings.push(
|
|
245
|
+
"translationPayloads disabled all local outputs but apiBaseServerHost is not set. SSR will load empty translations unless you provide an external server payload host."
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (!input.apiBaseClientHost) {
|
|
249
|
+
warnings.push(
|
|
250
|
+
"translationPayloads disabled all local outputs but apiBaseClientHost is not set. Client-side navigation may load empty translations unless you provide an external client payload host."
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
return warnings;
|
|
254
|
+
}
|
|
255
|
+
function getStaticPayloadWarning(input) {
|
|
256
|
+
const { mode, publicAssets, prerenderRoutes } = input.translationPayloads;
|
|
257
|
+
if (input.apiBaseClientHost || prerenderRoutes || mode === "premerged" && publicAssets) return null;
|
|
258
|
+
return `Static output has no server route, and this build writes no /_locales/<page>/<locale>/data.json (mode: '${mode}', publicAssets: ${publicAssets}): the browser gets a 404 and raw keys, unless something else serves that path. Use the default premerged mode with public assets, translationPayloads.prerenderRoutes, or apiBaseClientHost.`;
|
|
259
|
+
}
|
|
260
|
+
function formatBytes(bytes) {
|
|
261
|
+
if (bytes >= 1024 * 1024) {
|
|
262
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
263
|
+
}
|
|
264
|
+
if (bytes >= 1024) {
|
|
265
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
266
|
+
}
|
|
267
|
+
return `${bytes} B`;
|
|
268
|
+
}
|
|
269
|
+
function getTranslationPayloadSizeWarning(stats, thresholds) {
|
|
270
|
+
const warnFileCount = thresholds?.warnFileCount ?? DEFAULT_TRANSLATION_PAYLOAD_WARN_FILE_COUNT;
|
|
271
|
+
const warnSizeBytes = thresholds?.warnSizeBytes ?? DEFAULT_TRANSLATION_PAYLOAD_WARN_SIZE_BYTES;
|
|
272
|
+
if (stats.fileCount < warnFileCount && stats.totalBytes < warnSizeBytes) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
return `Generated translation payloads are large (${stats.fileCount} files, ${formatBytes(stats.totalBytes)}). Consider translationPayloads.mode: 'source' for public output, hosting payloads externally, or disabling unused public/prerender outputs.`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function collectJsonFiles(dir) {
|
|
279
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
280
|
+
const fullPath = join(dir, entry.name);
|
|
281
|
+
if (entry.isDirectory()) return collectJsonFiles(fullPath);
|
|
282
|
+
return entry.name.endsWith(".json") ? [fullPath] : [];
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function scanTranslationPayloadDirectory(dir) {
|
|
286
|
+
const stats = { fileCount: 0, totalBytes: 0 };
|
|
287
|
+
if (!existsSync(dir)) return stats;
|
|
288
|
+
for (const file of collectJsonFiles(dir)) {
|
|
289
|
+
stats.fileCount += 1;
|
|
290
|
+
stats.totalBytes += statSync(file).size;
|
|
291
|
+
}
|
|
292
|
+
return stats;
|
|
293
|
+
}
|
|
294
|
+
const MiB = 1024 * 1024;
|
|
295
|
+
const EMBED_LIMITS = [
|
|
296
|
+
{
|
|
297
|
+
preset: (name) => name === "vercel-edge",
|
|
298
|
+
bytes: 1e6,
|
|
299
|
+
compressed: true,
|
|
300
|
+
text: "Vercel Edge allows 1 MB gzip on Hobby, 2 MB on Pro and 4 MB on Enterprise"
|
|
301
|
+
},
|
|
302
|
+
{ preset: (name) => name === "netlify-edge", bytes: 2e7, compressed: true, text: "Netlify Edge allows 20 MB compressed" },
|
|
303
|
+
{ preset: (name) => name.startsWith("cloudflare-"), bytes: 64 * MiB, compressed: false, text: "Cloudflare Workers allow 64 MiB uncompressed" }
|
|
304
|
+
];
|
|
305
|
+
function getEmbeddedPayloadSizeWarning(preset, dir, mode) {
|
|
306
|
+
const limit = EMBED_LIMITS.find((entry) => entry.preset(preset));
|
|
307
|
+
if (!limit) return null;
|
|
308
|
+
const { totalBytes } = scanTranslationPayloadDirectory(dir);
|
|
309
|
+
if (totalBytes <= limit.bytes) return null;
|
|
310
|
+
if (limit.compressed) {
|
|
311
|
+
const gzipBytes = collectJsonFiles(dir).reduce((sum, file) => sum + gzipSync(readFileSync(file)).length, 0);
|
|
312
|
+
if (gzipBytes <= limit.bytes) return null;
|
|
313
|
+
return embeddedSizeMessage(preset, `${(gzipBytes / 1e6).toFixed(1)} MB gzip`, limit.text, mode);
|
|
314
|
+
}
|
|
315
|
+
return embeddedSizeMessage(preset, `${(totalBytes / MiB).toFixed(1)} MiB`, limit.text, mode);
|
|
316
|
+
}
|
|
317
|
+
function embeddedSizeMessage(preset, size, limitText, mode) {
|
|
318
|
+
const advice = mode === "premerged" ? "Use translationPayloads.mode: 'source'" : "Load dictionaries from apiBaseServerHost";
|
|
319
|
+
return `Preset "${preset}" embeds ${size} of dictionaries in the server bundle, and ${limitText}. ${advice}, or the deploy may be refused.`;
|
|
320
|
+
}
|
|
321
|
+
function hashTranslationSources(rootDirs, translationDirName, additionalTranslationDirs = [], salt = "") {
|
|
322
|
+
const hash = createHash("sha256").update(salt);
|
|
323
|
+
let seen = 0;
|
|
324
|
+
const hashFiles = (baseDir, files) => {
|
|
325
|
+
files.sort();
|
|
326
|
+
for (const file of files) {
|
|
327
|
+
hash.update(relative(baseDir, file));
|
|
328
|
+
hash.update("\0");
|
|
329
|
+
hash.update(readFileSync(file));
|
|
330
|
+
seen += 1;
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
const hashDir = (dir) => {
|
|
334
|
+
if (!existsSync(dir)) return;
|
|
335
|
+
hashFiles(dir, collectJsonFiles(dir));
|
|
336
|
+
};
|
|
337
|
+
const hashAdditionalRootLocales = (dir) => {
|
|
338
|
+
if (!existsSync(dir)) return;
|
|
339
|
+
const files = [];
|
|
340
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
341
|
+
if (entry.isFile() && entry.name.endsWith(".json")) files.push(join(dir, entry.name));
|
|
342
|
+
}
|
|
343
|
+
hashFiles(dir, files);
|
|
344
|
+
};
|
|
345
|
+
for (const rootDir of rootDirs) {
|
|
346
|
+
hashDir(join(rootDir, translationDirName));
|
|
347
|
+
for (const extra of additionalTranslationDirs) {
|
|
348
|
+
hashAdditionalRootLocales(join(rootDir, extra));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return seen > 0 ? hash.digest("hex").slice(0, 16) : null;
|
|
352
|
+
}
|
|
353
|
+
const NITRO_MIN_COMPRESS_BYTES = 1024;
|
|
354
|
+
function compressTranslationPayloads(dir, compression) {
|
|
355
|
+
if (!compression || !existsSync(dir)) return 0;
|
|
356
|
+
const gzip = compression === true || compression.gzip !== false;
|
|
357
|
+
const brotli = compression === true || compression.brotli !== false;
|
|
358
|
+
if (!gzip && !brotli) return 0;
|
|
359
|
+
let compressed = 0;
|
|
360
|
+
for (const file of collectJsonFiles(dir)) {
|
|
361
|
+
const raw = readFileSync(file);
|
|
362
|
+
if (raw.length < NITRO_MIN_COMPRESS_BYTES) continue;
|
|
363
|
+
if (gzip) writeFileSync(`${file}.gz`, gzipSync(raw, { level: 9 }));
|
|
364
|
+
if (brotli) {
|
|
365
|
+
writeFileSync(
|
|
366
|
+
`${file}.br`,
|
|
367
|
+
brotliCompressSync(raw, { params: { [constants.BROTLI_PARAM_QUALITY]: 11, [constants.BROTLI_PARAM_SIZE_HINT]: raw.length } })
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
compressed += 1;
|
|
371
|
+
}
|
|
372
|
+
return compressed;
|
|
373
|
+
}
|
|
374
|
+
|
|
22
375
|
function buildI18nPluralTemplate(plural, helpersImportPath) {
|
|
23
376
|
if (!plural || plural === defaultPlural) {
|
|
24
377
|
return `export { defaultPlural as plural } from '${helpersImportPath}'
|
|
@@ -90,45 +443,30 @@ function setupDevToolsUI(options, rootDirs) {
|
|
|
90
443
|
}
|
|
91
444
|
});
|
|
92
445
|
onDevToolsInitialized(() => {
|
|
93
|
-
const
|
|
94
|
-
|
|
446
|
+
const functions = {
|
|
447
|
+
// Writes only an existing `.json` inside a translation directory: `file` comes from the
|
|
448
|
+
// client, so `../` and absolute paths must not reach the filesystem. The project layer (listed
|
|
449
|
+
// last, shown in the tab) wins over extended layers.
|
|
95
450
|
async saveTranslationContent(file, content) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const candidatePath = path.join(rootDir, extraDir, relFile);
|
|
103
|
-
if (fs.existsSync(candidatePath)) {
|
|
104
|
-
filePath = candidatePath;
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
for (const rootDir of dirs) {
|
|
110
|
-
const localesDir = path.join(rootDir, options.translationDir || "locales");
|
|
111
|
-
const candidatePath = path.join(localesDir, file);
|
|
112
|
-
if (fs.existsSync(candidatePath)) {
|
|
113
|
-
filePath = candidatePath;
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (!filePath) {
|
|
119
|
-
filePath = path.resolve(file);
|
|
120
|
-
}
|
|
121
|
-
if (fs.existsSync(filePath)) {
|
|
122
|
-
fs.writeFileSync(filePath, JSON.stringify(content, null, 2), "utf-8");
|
|
123
|
-
} else {
|
|
124
|
-
throw new Error(`File not found: ${filePath}`);
|
|
125
|
-
}
|
|
451
|
+
const extraDir = file.startsWith("additional:") ? (options.additionalTranslationDirs ?? []).find((dir) => file.startsWith(`additional:${dir}/`)) : void 0;
|
|
452
|
+
if (file.startsWith("additional:") && !extraDir) throw new Error(`Not a configured additional translation directory: ${file}`);
|
|
453
|
+
const relativeFile = extraDir ? file.slice(`additional:${extraDir}/`.length) : file;
|
|
454
|
+
const target = [...rootDirs].reverse().map((rootDir) => resolveTranslationFile(path.join(rootDir, extraDir ?? (options.translationDir || "locales")), relativeFile)).find((candidate) => candidate !== null);
|
|
455
|
+
if (!target) throw new Error(`File not found in the translation directories: ${file}`);
|
|
456
|
+
fs.writeFileSync(target, JSON.stringify(content, null, 2), "utf-8");
|
|
126
457
|
},
|
|
127
458
|
async getConfigs() {
|
|
128
459
|
return Promise.resolve(options);
|
|
129
460
|
},
|
|
130
461
|
async getLocalesAndTranslations() {
|
|
131
462
|
const filesList = {};
|
|
463
|
+
const readFile = (key, filePath) => {
|
|
464
|
+
try {
|
|
465
|
+
filesList[key] = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
466
|
+
} catch (e) {
|
|
467
|
+
console.error(`Error parsing locale file ${filePath}:`, e);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
132
470
|
const processDirectory = (dir, baseDir) => {
|
|
133
471
|
if (!fs.existsSync(dir)) return;
|
|
134
472
|
fs.readdirSync(dir).forEach((file) => {
|
|
@@ -137,16 +475,11 @@ function setupDevToolsUI(options, rootDirs) {
|
|
|
137
475
|
if (stat.isDirectory()) {
|
|
138
476
|
processDirectory(filePath, baseDir);
|
|
139
477
|
} else if (file.endsWith(".json")) {
|
|
140
|
-
|
|
141
|
-
const relativePath = path.relative(baseDir, filePath).replace(/\\/g, "/");
|
|
142
|
-
filesList[relativePath] = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
143
|
-
} catch (e) {
|
|
144
|
-
console.error(`Error parsing locale file ${filePath}:`, e);
|
|
145
|
-
}
|
|
478
|
+
readFile(forwardSlashes(path.relative(baseDir, filePath)), filePath);
|
|
146
479
|
}
|
|
147
480
|
});
|
|
148
481
|
};
|
|
149
|
-
for (const rootDir of
|
|
482
|
+
for (const rootDir of rootDirs) {
|
|
150
483
|
const localesDir = path.join(rootDir, options.translationDir || "locales");
|
|
151
484
|
const pagesDir = path.join(localesDir, "pages");
|
|
152
485
|
processDirectory(localesDir, localesDir);
|
|
@@ -158,35 +491,147 @@ function setupDevToolsUI(options, rootDirs) {
|
|
|
158
491
|
if (!name.endsWith(".json")) continue;
|
|
159
492
|
const filePath = path.join(extraDir, name);
|
|
160
493
|
if (!fs.statSync(filePath).isFile()) continue;
|
|
161
|
-
|
|
162
|
-
filesList[`additional:${extra}/${name}`] = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
163
|
-
} catch (e) {
|
|
164
|
-
console.error(`Error parsing locale file ${filePath}:`, e);
|
|
165
|
-
}
|
|
494
|
+
readFile(`additional:${extra}/${name}`, filePath);
|
|
166
495
|
}
|
|
167
496
|
}
|
|
168
497
|
}
|
|
169
498
|
return filesList;
|
|
170
499
|
}
|
|
500
|
+
};
|
|
501
|
+
const rpc = extendServerRpc(DEVTOOLS_RPC_NAMESPACE, functions);
|
|
502
|
+
const directories = [options.translationDir || "locales", ...options.additionalTranslationDirs ?? []];
|
|
503
|
+
const roots = rootDirs.flatMap((root) => directories.map((dir) => resolve(root, dir)));
|
|
504
|
+
let timer;
|
|
505
|
+
nuxt.hook("builder:watch", (_event, watched) => {
|
|
506
|
+
const file = resolve(nuxt.options.srcDir, watched);
|
|
507
|
+
if (!file.endsWith(".json") || !roots.some((root) => isInsideDir(file, root))) return;
|
|
508
|
+
clearTimeout(timer);
|
|
509
|
+
timer = setTimeout(() => {
|
|
510
|
+
functions.getLocalesAndTranslations().then((locales) => rpc.broadcast.localesUpdated.asEvent(locales)).catch((error) => console.error("[i18n-devtools] Failed to push locales:", error));
|
|
511
|
+
}, 100);
|
|
171
512
|
});
|
|
172
513
|
});
|
|
173
514
|
}
|
|
174
515
|
|
|
175
|
-
function shouldLocalizeRouteRulePath(originalPath) {
|
|
516
|
+
function shouldLocalizeRouteRulePath(originalPath, localeCodes = []) {
|
|
176
517
|
const routeRulePath = originalPath.trim();
|
|
177
518
|
if (!routeRulePath) return true;
|
|
178
519
|
const path = routeRulePath.startsWith("/") ? routeRulePath : `/${routeRulePath}`;
|
|
179
520
|
if (path === "/api" || path.startsWith("/api/")) return false;
|
|
180
|
-
const firstSegment = path
|
|
181
|
-
return !firstSegment.startsWith("_");
|
|
521
|
+
const firstSegment = trimLeadingSlashes(path).split("/")[0] ?? "";
|
|
522
|
+
return !firstSegment.startsWith("_") && !localeCodes.includes(firstSegment);
|
|
523
|
+
}
|
|
524
|
+
function localizedRulePaths(path, generator) {
|
|
525
|
+
return generator.locales.filter(({ code }) => isLocaleAllowedForUnlocalizedRoute(generator.routeLocales, generator.locales, path, code)).flatMap(({ code }) => generator.resolveLocalizedRulePaths(path, code)).filter((localized) => localized !== path && localized !== normalizePath(path));
|
|
526
|
+
}
|
|
527
|
+
function localizeRouteRules(rules, target, generator, onCopy, noDocuments = false) {
|
|
528
|
+
const localeCodes = [...generator.locales, ...generator.disabledLocales].map((locale) => locale.code);
|
|
529
|
+
for (const [path, value] of Object.entries(rules)) {
|
|
530
|
+
const { redirect: _redirect, ...rule } = value;
|
|
531
|
+
if (noDocuments) delete rule.prerender;
|
|
532
|
+
if (!Object.keys(rule).length || !shouldLocalizeRouteRulePath(path, localeCodes)) continue;
|
|
533
|
+
for (const localized of localizedRulePaths(path, generator)) {
|
|
534
|
+
target[localized] = { ...target[localized], ...rule };
|
|
535
|
+
onCopy(localized, rule);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function varyRouteCacheByHost(rule, trustForwardedHost) {
|
|
540
|
+
if (rule.cache === false || !(rule.swr || rule.cache)) return rule;
|
|
541
|
+
const hosts = trustForwardedHost ? ["host", "x-forwarded-host"] : ["host"];
|
|
542
|
+
return { ...rule, cache: { ...rule.cache, varies: [.../* @__PURE__ */ new Set([...rule.cache?.varies ?? [], ...hosts])] } };
|
|
182
543
|
}
|
|
183
|
-
const PAYLOAD_ROUTE_CACHE_MAX_AGE = 60;
|
|
184
544
|
function buildTranslationPayloadRouteRule(input) {
|
|
185
|
-
if (input.dev) return { cors: true };
|
|
545
|
+
if (input.dev || !input.cacheControl) return { cors: true };
|
|
546
|
+
return { cors: true, headers: { "Cache-Control": input.cacheControl } };
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function staticPagePaths(pages, parent = "/") {
|
|
550
|
+
return pages.flatMap((page) => {
|
|
551
|
+
if (page.redirect || page.path.includes(":")) return [];
|
|
552
|
+
const path = joinURL(parent, page.path);
|
|
553
|
+
return [path, ...staticPagePaths(page.children ?? [], path)];
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
function unfixedOriginWarning(locales, defaultLocale, metaBaseUrl, siteUrl) {
|
|
557
|
+
if (metaBaseUrl || siteUrl || !locales.some((locale) => locale.baseUrl)) return null;
|
|
558
|
+
if (locales.find((locale) => locale.code === defaultLocale)?.baseUrl) return null;
|
|
559
|
+
return "locales[].baseUrl is set, but no public origin is fixed: an unknown host reaches canonical and hreflang from the request. Set metaBaseUrl, site.url, or a baseUrl on the default locale.";
|
|
560
|
+
}
|
|
561
|
+
function seedStaticRoutes(routes, staticPaths, options) {
|
|
562
|
+
const { localize, prerendered } = options;
|
|
563
|
+
if (prerendered("/")) routes.add("/");
|
|
564
|
+
for (const path of staticPaths) {
|
|
565
|
+
if (!prerendered(path)) continue;
|
|
566
|
+
if (localize.isServed(path)) routes.add(path);
|
|
567
|
+
addLocalized(routes, path, options);
|
|
568
|
+
}
|
|
569
|
+
addLocalized(routes, "/", options);
|
|
570
|
+
}
|
|
571
|
+
function addLocalized(routes, path, { localize, locales, prerendered }) {
|
|
572
|
+
for (const { code } of locales) {
|
|
573
|
+
const localized = localize.path(path, code);
|
|
574
|
+
if (localized && prerendered(localized)) routes.add(localized);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function dropUnprefixed(routes, localize) {
|
|
578
|
+
const removed = [...routes].filter(
|
|
579
|
+
(route) => route !== "/" && route !== "" && !localize.localeOf(route) && !localize.isUnlocalized(route) && localize.isPage(route)
|
|
580
|
+
);
|
|
581
|
+
for (const route of removed) routes.delete(route);
|
|
582
|
+
return removed;
|
|
583
|
+
}
|
|
584
|
+
function seedPrerenderRoutes(routes, options) {
|
|
585
|
+
const { localize, staticPaths, prerendered, isInternal } = options;
|
|
586
|
+
if (staticPaths) seedStaticRoutes(routes, staticPaths, options);
|
|
587
|
+
for (const route of routes) if (isInternal(route)) routes.delete(route);
|
|
588
|
+
const additional = /* @__PURE__ */ new Set();
|
|
589
|
+
for (const route of routes) {
|
|
590
|
+
if (/\.[a-z0-9]+$/i.test(route) || !prerendered(route) || localize.localeOf(route)) continue;
|
|
591
|
+
addLocalized(additional, route, options);
|
|
592
|
+
}
|
|
593
|
+
for (const route of additional) routes.add(route);
|
|
594
|
+
const { strategy, locales } = options;
|
|
595
|
+
const prefixedOnly = strategy === "prefix" || strategy === "prefix_and_default" && !locales.some((locale) => locale.baseDefault);
|
|
596
|
+
return { added: [...additional], removed: prefixedOnly ? dropUnprefixed(routes, localize) : [] };
|
|
597
|
+
}
|
|
598
|
+
const toRecord = (page) => ({
|
|
599
|
+
path: page.path,
|
|
600
|
+
name: page.name,
|
|
601
|
+
meta: page.meta,
|
|
602
|
+
...page.redirect ? { redirect: page.redirect } : { component: {} },
|
|
603
|
+
...page.alias?.length ? { alias: page.alias } : {},
|
|
604
|
+
...page.children?.length ? { children: page.children.map(toRecord) } : {}
|
|
605
|
+
});
|
|
606
|
+
async function createLocalizedPathResolver(pages, ctx) {
|
|
607
|
+
const { createMemoryHistory, createRouter } = await import('vue-router');
|
|
608
|
+
const router = createRouter({ history: createMemoryHistory(), routes: pages.map(toRecord) });
|
|
609
|
+
const strategy = ctx.locales.some((locale) => locale.baseUrl) ? new DomainPathStrategy({ ...ctx, router }) : createPathStrategy({ ...ctx, router });
|
|
610
|
+
const seen = /* @__PURE__ */ new Map();
|
|
611
|
+
const matched = (path) => {
|
|
612
|
+
let records = seen.get(path);
|
|
613
|
+
if (records) return records;
|
|
614
|
+
records = strategy.probe(path)?.matched ?? [];
|
|
615
|
+
seen.set(path, records);
|
|
616
|
+
return records;
|
|
617
|
+
};
|
|
618
|
+
const isPage = (path) => matched(path).length > 0 || strategy.pageLocales({ path }) !== null;
|
|
619
|
+
const isUnlocalized = (path) => {
|
|
620
|
+
const records = matched(path);
|
|
621
|
+
return records.length > 0 && !records.at(-1).meta?.i18n;
|
|
622
|
+
};
|
|
186
623
|
return {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
624
|
+
path(path, locale) {
|
|
625
|
+
const served = strategy.pageLocales({ path });
|
|
626
|
+
if (!isPage(path) || served && !served.includes(locale) || isUnlocalized(path)) return null;
|
|
627
|
+
const target = strategy.localeRoute(locale, path).fullPath;
|
|
628
|
+
return target && target !== path && !target.startsWith("https:") && !target.startsWith("http:") ? target : null;
|
|
629
|
+
},
|
|
630
|
+
localeOf: (path) => strategy.getLocaleFromPath(path),
|
|
631
|
+
isUnlocalized,
|
|
632
|
+
isPage,
|
|
633
|
+
// a known domain also leaves out what it answers with a 404 or a redirect (another host's URL)
|
|
634
|
+
isServed: (path) => matched(path).length > 0 && (!ctx.domain || !strategy.shouldReturn404(path) && !strategy.getClientRedirect(path, ctx.defaultLocale))
|
|
190
635
|
};
|
|
191
636
|
}
|
|
192
637
|
|
|
@@ -365,9 +810,12 @@ function evaluateConfig(scriptContent, configStr) {
|
|
|
365
810
|
const safeScript = `
|
|
366
811
|
const $defineI18nRoute = () => {}
|
|
367
812
|
const defineI18nRoute = () => {}
|
|
813
|
+
const useNuxtApp = () => ({ $defineI18nRoute })
|
|
368
814
|
const process = { env: { NODE_ENV: 'development' } }
|
|
815
|
+
{
|
|
369
816
|
${removeTypeScriptTypes(scriptWithoutImports)}
|
|
370
817
|
return (${cleanConfigStr})
|
|
818
|
+
}
|
|
371
819
|
`;
|
|
372
820
|
try {
|
|
373
821
|
return { ok: true, config: toPlainConfig(Function(`"use strict";${safeScript}`)()) };
|
|
@@ -438,54 +886,48 @@ function extractDefineI18nRouteData(content, filePath) {
|
|
|
438
886
|
return null;
|
|
439
887
|
}
|
|
440
888
|
}
|
|
889
|
+
const isSeparator = (char) => char === "/" || char === "\\";
|
|
890
|
+
const isIndexPage = (file) => file.endsWith("/index.vue") || file.endsWith("\\index.vue");
|
|
891
|
+
function parentPagePaths(pageFiles, rootDirs) {
|
|
892
|
+
const candidates = pageFiles.filter((file) => !isIndexPage(file));
|
|
893
|
+
return candidates.map((file) => pageFilePathToRoutePath(file, resolveRootDir(file, rootDirs) ?? ""));
|
|
894
|
+
}
|
|
895
|
+
function resolveRootDir(filePath, rootDirs) {
|
|
896
|
+
const beneath = (root) => filePath.startsWith(root) && (isSeparator(root[root.length - 1]) || isSeparator(filePath[root.length]));
|
|
897
|
+
return [...rootDirs].sort((a, b) => b.length - a.length).find(beneath);
|
|
898
|
+
}
|
|
441
899
|
function pageFilePathToRoutePath(pageFile, rootDir) {
|
|
442
|
-
const
|
|
443
|
-
|
|
900
|
+
const withoutRoot = pageFile.replace(rootDir, "");
|
|
901
|
+
let start = 0;
|
|
902
|
+
while (isSeparator(withoutRoot[start])) start++;
|
|
903
|
+
const relative = withoutRoot.slice(start);
|
|
904
|
+
const pagesAt = relative.startsWith("app/") || relative.startsWith("app\\") ? 4 : 0;
|
|
905
|
+
const inPages = relative.startsWith("pages", pagesAt) && isSeparator(relative[pagesAt + 5]);
|
|
906
|
+
const withoutPagesPrefix = inPages ? relative.slice(pagesAt + 6) : relative;
|
|
444
907
|
if (withoutPagesPrefix === "index.vue") {
|
|
445
908
|
return "/";
|
|
446
909
|
}
|
|
447
|
-
|
|
910
|
+
let page = isIndexPage(withoutPagesPrefix) ? withoutPagesPrefix.slice(0, -10) : withoutPagesPrefix;
|
|
911
|
+
if (page.endsWith(".vue")) page = page.slice(0, -4);
|
|
912
|
+
if (isSeparator(page[page.length - 1])) page = page.slice(0, -1);
|
|
913
|
+
const raw = forwardSlashes(page).split("/").filter((segment) => !(segment.length > 2 && segment[0] === "(" && segment.endsWith(")"))).join("/");
|
|
448
914
|
return raw === "" ? "/" : raw;
|
|
449
915
|
}
|
|
450
916
|
|
|
451
|
-
function
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
return
|
|
465
|
-
name: "nuxt-i18n-micro-define-route",
|
|
466
|
-
enforce: "pre",
|
|
467
|
-
transformInclude(id) {
|
|
468
|
-
return id.endsWith(".vue") && !id.includes("node_modules");
|
|
469
|
-
},
|
|
470
|
-
transform(code, id) {
|
|
471
|
-
const rootDir = resolveRootDir(id, options.rootDirs);
|
|
472
|
-
if (!rootDir) return null;
|
|
473
|
-
const config = extractDefineI18nRouteData(code, id);
|
|
474
|
-
if (!config) {
|
|
475
|
-
metaByFile.delete(id);
|
|
476
|
-
} else {
|
|
477
|
-
metaByFile.set(id, {
|
|
478
|
-
routePath: pageFilePathToRoutePath(id, rootDir),
|
|
479
|
-
config
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
writeMetaFile();
|
|
483
|
-
return null;
|
|
484
|
-
},
|
|
485
|
-
buildEnd() {
|
|
486
|
-
writeMetaFile();
|
|
487
|
-
}
|
|
488
|
-
}));
|
|
917
|
+
function mergeDefineI18nRouteMeta(entries, config) {
|
|
918
|
+
const routeLocales = { ...config.routeLocales };
|
|
919
|
+
const routeDisableMeta = { ...config.routeDisableMeta };
|
|
920
|
+
const fromPages = {};
|
|
921
|
+
for (const { routePath, config: page } of entries) {
|
|
922
|
+
if (Array.isArray(page.locales)) routeLocales[routePath] = page.locales;
|
|
923
|
+
else if (page.locales) routeLocales[routePath] = Object.keys(page.locales);
|
|
924
|
+
if (page.localeRoutes) fromPages[routePath] = page.localeRoutes;
|
|
925
|
+
if (page.disableMeta !== void 0) routeDisableMeta[routePath] = page.disableMeta;
|
|
926
|
+
}
|
|
927
|
+
const configured = config.globalLocaleRoutes ?? {};
|
|
928
|
+
const configuredKeys = new Set(Object.keys(configured).map(routeKey));
|
|
929
|
+
for (const key of Object.keys(fromPages)) if (configuredKeys.has(routeKey(key)) || configuredKeys.has(nameOfPath(key))) delete fromPages[key];
|
|
930
|
+
return { routeLocales, globalLocaleRoutes: { ...fromPages, ...configured }, routeDisableMeta };
|
|
489
931
|
}
|
|
490
932
|
function collectDefineI18nRouteMetaFromFiles(pageFiles, rootDirs) {
|
|
491
933
|
const entries = [];
|
|
@@ -506,46 +948,19 @@ function collectDefineI18nRouteMetaFromFiles(pageFiles, rootDirs) {
|
|
|
506
948
|
return entries;
|
|
507
949
|
}
|
|
508
950
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
prefix: "prefix-strategy.mjs",
|
|
512
|
-
prefix_except_default: "prefix-except-default-strategy.mjs",
|
|
513
|
-
prefix_and_default: "prefix-and-default-strategy.mjs"
|
|
514
|
-
};
|
|
515
|
-
async function resolveStrategyPath(strategy) {
|
|
516
|
-
const strategyFile = strategyFiles[strategy] ?? strategyFiles.prefix_except_default;
|
|
517
|
-
const specifier = `@i18n-micro/path-strategy/dist/${strategyFile}`;
|
|
518
|
-
let absoluteStrategyPath;
|
|
519
|
-
try {
|
|
520
|
-
absoluteStrategyPath = fileURLToPath(import.meta.resolve(specifier));
|
|
521
|
-
} catch {
|
|
522
|
-
absoluteStrategyPath = await resolvePath(specifier);
|
|
523
|
-
}
|
|
524
|
-
return process.platform === "win32" ? pathToFileURL(absoluteStrategyPath).href : absoluteStrategyPath.replace(/\\/g, "/");
|
|
525
|
-
}
|
|
526
|
-
async function resolveHelpersPath() {
|
|
527
|
-
const specifier = "@i18n-micro/core/helpers";
|
|
528
|
-
let absoluteHelpersPath;
|
|
951
|
+
async function resolveEntry(specifier) {
|
|
952
|
+
let absolutePath;
|
|
529
953
|
try {
|
|
530
|
-
|
|
954
|
+
absolutePath = fileURLToPath(import.meta.resolve(specifier));
|
|
531
955
|
} catch {
|
|
532
|
-
|
|
956
|
+
absolutePath = await resolvePath(specifier);
|
|
533
957
|
}
|
|
534
|
-
return process.platform === "win32" ? pathToFileURL(
|
|
958
|
+
return process.platform === "win32" ? pathToFileURL(absolutePath).href : forwardSlashes(absolutePath);
|
|
535
959
|
}
|
|
536
960
|
function generateI18nTypes() {
|
|
537
961
|
return `
|
|
538
962
|
import type { PluginsInjections } from "nuxt-i18n-micro";
|
|
539
963
|
|
|
540
|
-
declare module 'vue/types/vue' {
|
|
541
|
-
interface Vue extends PluginsInjections { }
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
declare module '@nuxt/types' {
|
|
545
|
-
interface NuxtAppOptions extends PluginsInjections { }
|
|
546
|
-
interface Context extends PluginsInjections { }
|
|
547
|
-
}
|
|
548
|
-
|
|
549
964
|
declare module '#app' {
|
|
550
965
|
interface NuxtApp extends PluginsInjections { }
|
|
551
966
|
}
|
|
@@ -553,87 +968,71 @@ declare module '#app' {
|
|
|
553
968
|
export {}`;
|
|
554
969
|
}
|
|
555
970
|
const DEFAULT_CANONICAL_QUERY_WHITELIST = ["page", "sort", "filter", "search", "q", "query", "tag"];
|
|
556
|
-
function
|
|
557
|
-
return
|
|
971
|
+
function isLiveDictionary(options, mode, serverHost) {
|
|
972
|
+
return mode === "source" || Boolean(options.cacheTtl) || Boolean(serverHost);
|
|
558
973
|
}
|
|
559
974
|
function buildFullConfig(params) {
|
|
560
|
-
const {
|
|
561
|
-
|
|
562
|
-
nuxt,
|
|
563
|
-
defaultLocale,
|
|
564
|
-
isSSG,
|
|
565
|
-
apiConfig,
|
|
566
|
-
translationPayloadMode,
|
|
567
|
-
routeLocales,
|
|
568
|
-
routeDisableMeta,
|
|
569
|
-
mergedGlobalLocaleRoutes,
|
|
570
|
-
locales,
|
|
571
|
-
translationsHash
|
|
572
|
-
} = params;
|
|
573
|
-
const dateBuild = resolveDateBuild(options, translationsHash);
|
|
975
|
+
const { options, nuxt, defaultLocale, apiConfig, translationPayloadMode, locales, translationsHash, publicPayloads, rootDelta } = params;
|
|
976
|
+
const noScripts = nuxt.options.features.noScripts;
|
|
574
977
|
return {
|
|
575
978
|
locales: locales ?? [],
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
979
|
+
// an unknown host of a build whose default has a domain takes that origin, not the request's
|
|
980
|
+
metaBaseUrl: options.metaBaseUrl || locales?.find((locale) => locale.code === defaultLocale)?.baseUrl || void 0,
|
|
981
|
+
metaTrustForwardedHost: options.metaTrustForwardedHost,
|
|
982
|
+
metaTrustForwardedProto: options.metaTrustForwardedProto,
|
|
983
|
+
trustForwardedHost: options.trustForwardedHost,
|
|
984
|
+
hreflangBaseLanguage: options.hreflangBaseLanguage,
|
|
580
985
|
defaultLocale,
|
|
581
986
|
fallbackLocale: options.fallbackLocale ?? void 0,
|
|
582
987
|
localeCookie: options.localeCookie ?? null,
|
|
583
|
-
autoDetectLanguage: options.autoDetectLanguage
|
|
584
|
-
autoDetectPath: options.autoDetectPath
|
|
585
|
-
strategy: options.strategy
|
|
586
|
-
|
|
988
|
+
autoDetectLanguage: options.autoDetectLanguage,
|
|
989
|
+
autoDetectPath: options.autoDetectPath || "/",
|
|
990
|
+
strategy: options.strategy,
|
|
991
|
+
// the `?v=` of payload URLs: a fingerprint of the translations, as a timestamp would make clients re-download
|
|
992
|
+
// what never changed. `options.dateBuild` wins even when it is `0`/`''`: that opts out of busting.
|
|
993
|
+
dateBuild: options.dateBuild ?? translationsHash ?? Date.now(),
|
|
587
994
|
hashMode: nuxt.options?.router?.options?.hashMode ?? false,
|
|
588
995
|
trailingSlash: options.trailingSlash ?? nuxt.options.experimental?.defaults?.nuxtLink?.trailingSlash,
|
|
589
996
|
apiBaseUrl: apiConfig.apiBaseUrl,
|
|
590
997
|
apiBaseClientHost: apiConfig.apiBaseClientHost,
|
|
591
|
-
|
|
592
|
-
disablePageLocales: options.disablePageLocales ?? false,
|
|
998
|
+
disablePageLocales: options.disablePageLocales,
|
|
593
999
|
canonicalQueryWhitelist: options.canonicalQueryWhitelist ?? DEFAULT_CANONICAL_QUERY_WHITELIST,
|
|
594
1000
|
excludePatterns: options.excludePatterns ?? [],
|
|
595
|
-
|
|
596
|
-
routeDisableMeta,
|
|
597
|
-
globalLocaleRoutes: mergedGlobalLocaleRoutes,
|
|
598
|
-
missingWarn: options.missingWarn ?? true,
|
|
1001
|
+
missingWarn: options.missingWarn,
|
|
599
1002
|
redirects: options.redirects !== false,
|
|
600
|
-
|
|
601
|
-
hmr: options.hmr ?? true,
|
|
602
|
-
localizedRouteNamePrefix: options.localizedRouteNamePrefix ?? "localized-",
|
|
1003
|
+
localizedRouteNamePrefix: options.localizedRouteNamePrefix,
|
|
603
1004
|
routesLocaleLinks: options.routesLocaleLinks ?? {},
|
|
604
|
-
|
|
605
|
-
debug: options.debug ?? false,
|
|
1005
|
+
debug: options.debug,
|
|
606
1006
|
customRegexMatcher: options.customRegexMatcher instanceof RegExp ? options.customRegexMatcher.source : options.customRegexMatcher,
|
|
607
1007
|
cacheMaxSize: options.cacheMaxSize ?? 0,
|
|
608
1008
|
cacheTtl: options.cacheTtl ?? 0,
|
|
609
1009
|
httpCacheDuration: options.httpCacheDuration,
|
|
1010
|
+
httpCacheControl: options.httpCacheControl,
|
|
610
1011
|
numberFormats: options.numberFormats ?? {},
|
|
611
1012
|
datetimeFormats: options.datetimeFormats ?? {},
|
|
612
|
-
|
|
1013
|
+
timeZone: options.timeZone,
|
|
1014
|
+
translationPayloadMode,
|
|
1015
|
+
publicPayloads,
|
|
1016
|
+
rootDelta,
|
|
1017
|
+
noScripts: noScripts === "all" || !!noScripts && !nuxt.options.dev || void 0
|
|
613
1018
|
};
|
|
614
1019
|
}
|
|
615
|
-
function buildPrivateConfig(options, nuxt, apiConfig, locales, payloadFsDir, payloadPublicRel) {
|
|
1020
|
+
function buildPrivateConfig(options, nuxt, rootDirs, apiConfig, locales, payloadFsDir, payloadPublicRel) {
|
|
616
1021
|
return {
|
|
617
|
-
|
|
618
|
-
// Same order as build translation merge: extends first, project last (wins).
|
|
619
|
-
rootDirs: nuxt.options._layers.map((layer) => layer.config.rootDir).reverse(),
|
|
1022
|
+
rootDirs,
|
|
620
1023
|
debug: options.debug ?? false,
|
|
621
1024
|
locales: locales ?? [],
|
|
622
|
-
fallbackLocale: options.fallbackLocale ?? void 0,
|
|
623
1025
|
translationDir: options.translationDir ?? "locales",
|
|
624
1026
|
additionalTranslationDirs: options.additionalTranslationDirs ?? [],
|
|
625
|
-
customRegexMatcher: options.customRegexMatcher instanceof RegExp ? options.customRegexMatcher.source : options.customRegexMatcher,
|
|
626
|
-
routesLocaleLinks: options.routesLocaleLinks ?? {},
|
|
627
|
-
apiBaseUrl: apiConfig.apiBaseUrl,
|
|
628
|
-
apiBaseClientHost: apiConfig.apiBaseClientHost,
|
|
629
1027
|
apiBaseServerHost: apiConfig.apiBaseServerHost,
|
|
630
1028
|
serverTranslationPreload: options.serverTranslationPreload ?? false,
|
|
1029
|
+
cacheStorage: options.cacheStorage,
|
|
1030
|
+
cacheStorageBuild: nuxt.options.buildId,
|
|
631
1031
|
payloadFsDir,
|
|
632
1032
|
payloadPublicRel
|
|
633
1033
|
};
|
|
634
1034
|
}
|
|
635
|
-
function registerI18nTemplates(options, resolvedStrategyPath, resolvedHelpersPath, fullConfig, privateConfig) {
|
|
636
|
-
let runtimeFullConfig = fullConfig;
|
|
1035
|
+
function registerI18nTemplates(options, resolvedStrategyPath, resolvedHelpersPath, fullConfig, privateConfig, disabledLocales, dev, serverPathFallback) {
|
|
637
1036
|
const pluralTemplate = addTemplate({
|
|
638
1037
|
filename: "i18n.plural.mjs",
|
|
639
1038
|
write: true,
|
|
@@ -643,42 +1042,40 @@ function registerI18nTemplates(options, resolvedStrategyPath, resolvedHelpersPat
|
|
|
643
1042
|
filename: "i18n.strategy.mjs",
|
|
644
1043
|
write: true,
|
|
645
1044
|
getContents: () => {
|
|
646
|
-
const
|
|
647
|
-
return `import { Strategy } from '${resolvedStrategyPath}'
|
|
1045
|
+
const domains = fullConfig.locales.some((locale) => locale.baseUrl);
|
|
1046
|
+
return `import { ${domains ? "DomainPathStrategy" : "I18nPathStrategy"} as Strategy } from '${resolvedStrategyPath}'
|
|
648
1047
|
|
|
649
|
-
const __fullConfig = ${
|
|
1048
|
+
const __fullConfig = ${buildFullConfigSource(fullConfig)}
|
|
1049
|
+
// disabled at build: no routes and not in the config, but routing must answer for their prefix
|
|
1050
|
+
const __disabledLocales = ${JSON.stringify(disabledLocales)}
|
|
650
1051
|
|
|
651
1052
|
export function getI18nConfig() { return __fullConfig }
|
|
652
1053
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
params: r.params || {},
|
|
663
|
-
query: r.query || {},
|
|
664
|
-
hash: r.hash || '',
|
|
665
|
-
}
|
|
666
|
-
},
|
|
667
|
-
}
|
|
1054
|
+
// a constant, so a build without locale domains drops their code
|
|
1055
|
+
export const domains = ${domains}
|
|
1056
|
+
|
|
1057
|
+
// a constant, so a build that keeps the locale in no cookie drops useCookie (no runtime override reaches these two)
|
|
1058
|
+
export const cookies = ${fullConfig.localeCookie !== null || fullConfig.hashMode}
|
|
1059
|
+
|
|
1060
|
+
// constants too, so a build with these options off drops their code from the loader plugin
|
|
1061
|
+
export const hooks = ${options.hooks !== false}
|
|
1062
|
+
export const defineRoute = ${options.define !== false}
|
|
668
1063
|
|
|
1064
|
+
// Nuxt 4.6+: a client navigation to a URL with no record becomes a document load (experimental.serverPathFallback)
|
|
1065
|
+
export const serverPathFallback = ${serverPathFallback}
|
|
1066
|
+
|
|
1067
|
+
// vue-router's Router is the adapter as is: no wrapper copying every resolve result.
|
|
1068
|
+
// config: the config with runtime overrides (defaultLocale, disabledLocales) \u2014 routing follows them.
|
|
1069
|
+
export function createI18nStrategy(router, config = __fullConfig) {
|
|
669
1070
|
return new Strategy({
|
|
670
|
-
strategy:
|
|
671
|
-
defaultLocale:
|
|
672
|
-
locales:
|
|
673
|
-
localizedRouteNamePrefix:
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
debug: __fullConfig.debug,
|
|
679
|
-
router: routerAdapter,
|
|
680
|
-
hashMode: __fullConfig.hashMode,
|
|
681
|
-
disablePageLocales: __fullConfig.disablePageLocales,
|
|
1071
|
+
strategy: config.strategy,
|
|
1072
|
+
defaultLocale: config.defaultLocale,
|
|
1073
|
+
locales: [...config.locales, ...__disabledLocales],
|
|
1074
|
+
localizedRouteNamePrefix: config.localizedRouteNamePrefix,
|
|
1075
|
+
router,
|
|
1076
|
+
hashMode: config.hashMode,
|
|
1077
|
+
disablePageLocales: config.disablePageLocales,
|
|
1078
|
+
customRegexMatcher: config.customRegexMatcher,${domains ? "\n domain: config.domain,\n linkLocales: config.linkLocales," : ""}
|
|
682
1079
|
})
|
|
683
1080
|
}
|
|
684
1081
|
`;
|
|
@@ -687,23 +1084,20 @@ export function createI18nStrategy(router) {
|
|
|
687
1084
|
const configTemplate = addTemplate({
|
|
688
1085
|
filename: "i18n.config.mjs",
|
|
689
1086
|
write: true,
|
|
690
|
-
getContents: () =>
|
|
691
|
-
export function getI18nPrivateConfig() { return __privateConfig }
|
|
692
|
-
`
|
|
1087
|
+
getContents: () => buildPrivateConfigSource(privateConfig, dev)
|
|
693
1088
|
});
|
|
694
1089
|
return {
|
|
695
1090
|
pluralTemplate,
|
|
696
1091
|
strategyTemplate,
|
|
697
|
-
configTemplate
|
|
698
|
-
setFullConfig(next) {
|
|
699
|
-
runtimeFullConfig = next;
|
|
700
|
-
}
|
|
1092
|
+
configTemplate
|
|
701
1093
|
};
|
|
702
1094
|
}
|
|
703
1095
|
const module$1 = defineNuxtModule({
|
|
704
1096
|
meta: {
|
|
705
1097
|
name: "nuxt-i18n-micro",
|
|
706
|
-
configKey: "i18n"
|
|
1098
|
+
configKey: "i18n",
|
|
1099
|
+
// Vite environment hotUpdate (HMR) and NuxtPage's ~transitionPromise
|
|
1100
|
+
compatibility: { nuxt: ">=3.18.0" }
|
|
707
1101
|
},
|
|
708
1102
|
defaults: {
|
|
709
1103
|
locales: [],
|
|
@@ -724,11 +1118,8 @@ const module$1 = defineNuxtModule({
|
|
|
724
1118
|
disablePageLocales: false,
|
|
725
1119
|
disableWatcher: false,
|
|
726
1120
|
noPrefixRedirect: false,
|
|
727
|
-
fallbackLocale: void 0,
|
|
728
1121
|
localeCookie: null,
|
|
729
1122
|
apiBaseUrl: "_locales",
|
|
730
|
-
apiBaseClientHost: void 0,
|
|
731
|
-
apiBaseServerHost: void 0,
|
|
732
1123
|
translationPayloads: {
|
|
733
1124
|
mode: "premerged",
|
|
734
1125
|
serverAssets: true,
|
|
@@ -740,20 +1131,18 @@ const module$1 = defineNuxtModule({
|
|
|
740
1131
|
},
|
|
741
1132
|
routesLocaleLinks: {},
|
|
742
1133
|
globalLocaleRoutes: {},
|
|
743
|
-
canonicalQueryWhitelist: void 0,
|
|
744
1134
|
plural: defaultPlural,
|
|
745
|
-
customRegexMatcher: void 0,
|
|
746
|
-
excludePatterns: void 0,
|
|
747
1135
|
localizedRouteNamePrefix: "localized-",
|
|
748
1136
|
missingWarn: true,
|
|
749
1137
|
metaTrustForwardedHost: true,
|
|
750
1138
|
metaTrustForwardedProto: true,
|
|
1139
|
+
trustForwardedHost: false,
|
|
751
1140
|
hreflangBaseLanguage: false,
|
|
752
1141
|
httpCacheDuration: 31536e3
|
|
753
1142
|
},
|
|
754
1143
|
async setup(options, nuxt) {
|
|
755
|
-
const defaultLocale = process.env.DEFAULT_LOCALE
|
|
756
|
-
|
|
1144
|
+
const defaultLocale = process.env.DEFAULT_LOCALE || options.defaultLocale || "en";
|
|
1145
|
+
let isSSG = false;
|
|
757
1146
|
const logger = useLogger("nuxt-i18n-micro");
|
|
758
1147
|
nuxt.options.typescript.hoist ||= [];
|
|
759
1148
|
for (const pkg of ["@i18n-micro/types", "@i18n-micro/path-strategy"]) {
|
|
@@ -761,12 +1150,13 @@ const module$1 = defineNuxtModule({
|
|
|
761
1150
|
nuxt.options.typescript.hoist.push(pkg);
|
|
762
1151
|
}
|
|
763
1152
|
}
|
|
764
|
-
|
|
1153
|
+
const cookieOptOut = nuxt.options.i18n?.localeCookie === null;
|
|
1154
|
+
if (options.strategy === "no_prefix" && !options.localeCookie && !cookieOptOut) {
|
|
765
1155
|
options.localeCookie = "user-locale";
|
|
766
1156
|
logger.info("Strategy 'no_prefix': localeCookie automatically set to 'user-locale' for locale persistence.");
|
|
767
1157
|
}
|
|
768
1158
|
if (options.strategy !== "no_prefix" && options.redirects !== false && !options.localeCookie) {
|
|
769
|
-
logger.
|
|
1159
|
+
logger.info(
|
|
770
1160
|
"Redirects are enabled but localeCookie is not set. Locale-based redirects will not remember user's locale preference across page reloads. Set `localeCookie: 'user-locale'` to enable cookie-based locale persistence for redirects."
|
|
771
1161
|
);
|
|
772
1162
|
}
|
|
@@ -775,7 +1165,12 @@ const module$1 = defineNuxtModule({
|
|
|
775
1165
|
const mergedLocalesDir = resolve(nuxt.options.buildDir, "i18n-merged");
|
|
776
1166
|
const sourceLocalesDir = resolve(nuxt.options.buildDir, "i18n-source");
|
|
777
1167
|
const translationDirName = options.translationDir || "locales";
|
|
778
|
-
const translationsHash = options.dateBuild === void 0 ? hashTranslationSources(
|
|
1168
|
+
const translationsHash = options.dateBuild === void 0 ? hashTranslationSources(
|
|
1169
|
+
rootDirs,
|
|
1170
|
+
translationDirName,
|
|
1171
|
+
options.additionalTranslationDirs,
|
|
1172
|
+
JSON.stringify([options.fallbackLocale, options.disablePageLocales, (options.locales ?? []).map((l) => [l.code, l.fallbackLocale])])
|
|
1173
|
+
) : null;
|
|
779
1174
|
const translationPayloads = resolveTranslationPayloadOptions(options);
|
|
780
1175
|
const publicAssetsDir = translationPayloads.mode === "source" ? sourceLocalesDir : mergedLocalesDir;
|
|
781
1176
|
for (const warning of getTranslationPayloadMisconfigurationWarnings({
|
|
@@ -783,9 +1178,10 @@ const module$1 = defineNuxtModule({
|
|
|
783
1178
|
apiBaseClientHost: options.apiBaseClientHost,
|
|
784
1179
|
apiBaseServerHost: options.apiBaseServerHost
|
|
785
1180
|
})) {
|
|
786
|
-
logger.warn(warning
|
|
1181
|
+
logger.warn(warning);
|
|
787
1182
|
}
|
|
788
|
-
|
|
1183
|
+
const rawServerHost = process.env.NUXT_I18N_APP_BASE_SERVER_HOST ?? options.apiBaseServerHost ?? void 0;
|
|
1184
|
+
if (!translationPayloads.serverAssets && !translationPayloads.publicAssets && !rawServerHost) {
|
|
789
1185
|
throw new Error(
|
|
790
1186
|
"nuxt-i18n-micro: local SSR payloads are disabled (serverAssets/publicAssets false) and apiBaseServerHost is not set. SSR would load empty translations."
|
|
791
1187
|
);
|
|
@@ -799,79 +1195,53 @@ const module$1 = defineNuxtModule({
|
|
|
799
1195
|
const prev = localeInfosByCode.get(locale.code);
|
|
800
1196
|
localeInfosByCode.set(locale.code, {
|
|
801
1197
|
code: locale.code,
|
|
802
|
-
fallbackLocale:
|
|
803
|
-
disabled:
|
|
1198
|
+
fallbackLocale: prev?.fallbackLocale ?? locale.fallbackLocale,
|
|
1199
|
+
disabled: prev?.disabled ?? locale.disabled
|
|
804
1200
|
});
|
|
805
1201
|
}
|
|
806
1202
|
const localeInfos = [...localeInfosByCode.values()].filter((locale) => !locale.disabled).map(({ code, fallbackLocale }) => ({ code, fallbackLocale }));
|
|
1203
|
+
let routeNames = [];
|
|
807
1204
|
nuxt.hook("build:before", async () => {
|
|
808
1205
|
if (translationPayloads.mode === "source") {
|
|
809
1206
|
await buildTranslationSourceLayers(rootDirs, translationDirName, sourceLocalesDir, options.additionalTranslationDirs);
|
|
810
1207
|
logger.info(`Built compact translation source from ${rootDirs.length} layer(s) into ${sourceLocalesDir}`);
|
|
811
1208
|
} else {
|
|
812
|
-
await preMergeLocales(
|
|
1209
|
+
await preMergeLocales({
|
|
813
1210
|
rootDirs,
|
|
814
1211
|
translationDirName,
|
|
815
|
-
mergedLocalesDir,
|
|
816
|
-
localeInfos,
|
|
817
|
-
options.fallbackLocale,
|
|
818
|
-
options.disablePageLocales,
|
|
819
|
-
options.additionalTranslationDirs
|
|
820
|
-
|
|
1212
|
+
outputDir: mergedLocalesDir,
|
|
1213
|
+
locales: localeInfos,
|
|
1214
|
+
globalFallbackLocale: options.fallbackLocale,
|
|
1215
|
+
disablePageLocales: options.disablePageLocales,
|
|
1216
|
+
additionalTranslationDirs: options.additionalTranslationDirs,
|
|
1217
|
+
routeNames,
|
|
1218
|
+
// dev reads live files, and a prerender merges what it reads like any server
|
|
1219
|
+
rootDelta: !nuxt.options.dev && translationPayloads.rootDelta
|
|
1220
|
+
});
|
|
821
1221
|
logger.info(`Pre-merged translations from ${rootDirs.length} layer(s) into ${mergedLocalesDir}`);
|
|
822
1222
|
}
|
|
823
1223
|
const payloadStats = scanTranslationPayloadDirectory(publicAssetsDir);
|
|
824
|
-
const sizeWarning = getTranslationPayloadSizeWarning(payloadStats,
|
|
825
|
-
if (sizeWarning)
|
|
826
|
-
logger.warn(sizeWarning.replace("[nuxt-i18n-micro] ", ""));
|
|
827
|
-
}
|
|
1224
|
+
const sizeWarning = getTranslationPayloadSizeWarning(payloadStats, options.translationPayloads);
|
|
1225
|
+
if (sizeWarning) logger.warn(sizeWarning);
|
|
828
1226
|
});
|
|
829
|
-
const
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
if (extractedLocales) {
|
|
838
|
-
if (Array.isArray(extractedLocales)) {
|
|
839
|
-
routeLocales[routePath] = extractedLocales;
|
|
840
|
-
} else if (typeof extractedLocales === "object") {
|
|
841
|
-
routeLocales[routePath] = Object.keys(extractedLocales);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
if (localeRoutes) {
|
|
845
|
-
globalLocaleRoutes[routePath] = localeRoutes;
|
|
846
|
-
}
|
|
847
|
-
if (disableMeta !== void 0) {
|
|
848
|
-
routeDisableMeta[routePath] = disableMeta;
|
|
849
|
-
}
|
|
850
|
-
} catch (error) {
|
|
851
|
-
logger.debug(`Failed to parse defineI18nRoute config for ${routePath}:`, error);
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
const mergedGlobalLocaleRoutes = { ...globalLocaleRoutes, ...options.globalLocaleRoutes };
|
|
855
|
-
if (options.debug) {
|
|
856
|
-
logger.debug("[i18n module] mergedGlobalLocaleRoutes keys:", Object.keys(mergedGlobalLocaleRoutes));
|
|
857
|
-
logger.debug('[i18n module] mergedGlobalLocaleRoutes["unlocalized"]:', mergedGlobalLocaleRoutes["unlocalized"]);
|
|
858
|
-
logger.debug("[i18n module] strategy:", options.strategy);
|
|
859
|
-
}
|
|
860
|
-
const resolvedStrategyPath = await resolveStrategyPath(options.strategy);
|
|
861
|
-
const resolvedHelpersPath = await resolveHelpersPath();
|
|
1227
|
+
const pageFiles = (await Promise.all(rootDirs.map((cwd) => globby(["pages/**/*.vue", "app/pages/**/*.vue"], { cwd, absolute: true })))).flat();
|
|
1228
|
+
const {
|
|
1229
|
+
routeLocales,
|
|
1230
|
+
routeDisableMeta,
|
|
1231
|
+
globalLocaleRoutes: mergedGlobalLocaleRoutes
|
|
1232
|
+
} = mergeDefineI18nRouteMeta(collectDefineI18nRouteMetaFromFiles(pageFiles, rootDirs), options);
|
|
1233
|
+
const resolvedStrategyPath = await resolveEntry("@i18n-micro/path-strategy");
|
|
1234
|
+
const resolvedHelpersPath = await resolveEntry("@i18n-micro/core/helpers");
|
|
862
1235
|
let apiBaseClientHost = process.env.NUXT_I18N_APP_BASE_CLIENT_HOST ?? options.apiBaseClientHost ?? void 0;
|
|
863
1236
|
if (apiBaseClientHost?.endsWith("/")) {
|
|
864
1237
|
apiBaseClientHost = apiBaseClientHost.slice(0, -1);
|
|
865
1238
|
}
|
|
866
|
-
|
|
867
|
-
if (apiBaseServerHost?.endsWith("/")) {
|
|
868
|
-
apiBaseServerHost = apiBaseServerHost.slice(0, -1);
|
|
869
|
-
}
|
|
1239
|
+
const apiBaseServerHost = rawServerHost?.endsWith("/") ? rawServerHost.slice(0, -1) : rawServerHost;
|
|
870
1240
|
const rawUrl = process.env.NUXT_I18N_APP_BASE_URL ?? options.apiBaseUrl ?? "_locales";
|
|
871
1241
|
if (rawUrl.startsWith("http://") || rawUrl.startsWith("https://")) {
|
|
872
1242
|
throw new Error("Nuxt-i18n-micro: Please use NUXT_I18N_APP_BASE_CLIENT_HOST or NUXT_I18N_APP_BASE_SERVER_HOST instead.");
|
|
873
1243
|
}
|
|
874
|
-
const apiBaseUrl = rawUrl
|
|
1244
|
+
const apiBaseUrl = pathSegments(rawUrl);
|
|
875
1245
|
const apiConfig = { apiBaseUrl, apiBaseClientHost, apiBaseServerHost };
|
|
876
1246
|
const payloadPublicRel = resolveTranslationPayloadPublicRel(options, apiBaseUrl);
|
|
877
1247
|
const routeGenerator = new RouteGenerator({
|
|
@@ -879,31 +1249,50 @@ const module$1 = defineNuxtModule({
|
|
|
879
1249
|
defaultLocaleCode: defaultLocale,
|
|
880
1250
|
strategy: options.strategy,
|
|
881
1251
|
globalLocaleRoutes: mergedGlobalLocaleRoutes,
|
|
882
|
-
filesLocaleRoutes: globalLocaleRoutes,
|
|
883
1252
|
routeLocales,
|
|
1253
|
+
routeDisableMeta,
|
|
884
1254
|
noPrefixRedirect: options.noPrefixRedirect,
|
|
1255
|
+
redirects: options.redirects,
|
|
885
1256
|
excludePatterns: options.excludePatterns,
|
|
1257
|
+
apiBaseUrl,
|
|
886
1258
|
localizedRouteNamePrefix: options.localizedRouteNamePrefix,
|
|
887
|
-
customRegexMatcher: options.customRegexMatcher
|
|
1259
|
+
customRegexMatcher: options.customRegexMatcher,
|
|
1260
|
+
parentPaths: parentPagePaths(pageFiles, rootDirs)
|
|
888
1261
|
});
|
|
889
1262
|
const fullConfig = buildFullConfig({
|
|
890
1263
|
options,
|
|
891
1264
|
nuxt,
|
|
892
1265
|
defaultLocale,
|
|
893
|
-
isSSG,
|
|
894
1266
|
apiConfig,
|
|
895
1267
|
translationPayloadMode: translationPayloads.mode,
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
mergedGlobalLocaleRoutes,
|
|
1268
|
+
publicPayloads: translationPayloads.mode === "premerged" && translationPayloads.publicAssets,
|
|
1269
|
+
rootDelta: !nuxt.options.dev && translationPayloads.mode === "premerged" && translationPayloads.rootDelta && !options.disablePageLocales && (translationPayloads.publicAssets || Boolean(options.apiBaseClientHost)),
|
|
899
1270
|
locales: routeGenerator.locales ?? [],
|
|
900
1271
|
translationsHash
|
|
901
1272
|
});
|
|
902
|
-
const
|
|
903
|
-
const
|
|
1273
|
+
const generateDomain = process.env.NUXT_I18N_DOMAIN;
|
|
1274
|
+
const buildConfig = generateDomain ? configForHost(fullConfig, generateDomain) : fullConfig;
|
|
1275
|
+
if (generateDomain && !buildConfig.domain)
|
|
1276
|
+
logger.warn(`NUXT_I18N_DOMAIN "${generateDomain}" is no host of locales[].baseUrl: the build config applies.`);
|
|
1277
|
+
const siteUrl = nuxt.options.site?.url || process.env.NUXT_PUBLIC_SITE_URL || process.env.NUXT_SITE_URL;
|
|
1278
|
+
const originWarning = buildConfig.domain ? null : unfixedOriginWarning(routeGenerator.locales, defaultLocale, options.metaBaseUrl, siteUrl);
|
|
1279
|
+
if (originWarning) logger.warn(originWarning);
|
|
1280
|
+
const privateConfig = buildPrivateConfig(options, nuxt, rootDirs, apiConfig, routeGenerator.locales ?? [], publicAssetsDir, payloadPublicRel);
|
|
1281
|
+
const templates = registerI18nTemplates(
|
|
1282
|
+
options,
|
|
1283
|
+
resolvedStrategyPath,
|
|
1284
|
+
resolvedHelpersPath,
|
|
1285
|
+
fullConfig,
|
|
1286
|
+
privateConfig,
|
|
1287
|
+
routeGenerator.disabledLocales,
|
|
1288
|
+
nuxt.options.dev,
|
|
1289
|
+
nuxt.options.experimental.serverPathFallback === true && !nuxt.options.router?.options?.hashMode
|
|
1290
|
+
);
|
|
904
1291
|
if (typeof options.customRegexMatcher !== "undefined") {
|
|
905
|
-
const
|
|
906
|
-
|
|
1292
|
+
const failedCodes = localesOutsideMatcher(
|
|
1293
|
+
routeGenerator.locales.map((l) => l.code),
|
|
1294
|
+
options.customRegexMatcher
|
|
1295
|
+
);
|
|
907
1296
|
if (failedCodes.length > 0) {
|
|
908
1297
|
throw new Error(
|
|
909
1298
|
"Nuxt-i18n-micro: customRegexMatcher does not match the following locale codes: " + failedCodes.join(", ") + ". The regex must match ALL locale codes in your configuration."
|
|
@@ -917,51 +1306,48 @@ const module$1 = defineNuxtModule({
|
|
|
917
1306
|
name: "i18n-plugin-loader",
|
|
918
1307
|
order: -5
|
|
919
1308
|
});
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
if (options.define) {
|
|
936
|
-
addPlugin({
|
|
937
|
-
src: resolver.resolve("./runtime/plugins/03.define"),
|
|
938
|
-
name: "i18n-plugin-define",
|
|
939
|
-
mode: "all",
|
|
940
|
-
order: 3
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
|
-
if (nuxt.options.dev && options.plugin !== false && options.vueDevtools !== false) {
|
|
1309
|
+
if (options.meta) {
|
|
1310
|
+
addPlugin({
|
|
1311
|
+
src: resolver.resolve("./runtime/plugins/02.meta"),
|
|
1312
|
+
name: "i18n-plugin-meta",
|
|
1313
|
+
order: 2
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
if (nuxt.options.dev && options.vueDevtools !== false) {
|
|
1317
|
+
addPlugin({
|
|
1318
|
+
src: resolver.resolve("./runtime/plugins/04.vue-devtools"),
|
|
1319
|
+
name: "i18n-vue-devtools",
|
|
1320
|
+
mode: "client",
|
|
1321
|
+
order: 5
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
944
1324
|
addPlugin({
|
|
945
|
-
src: resolver.resolve("./runtime/plugins/
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
order:
|
|
1325
|
+
src: resolver.resolve("./runtime/plugins/06.redirect"),
|
|
1326
|
+
mode: "server",
|
|
1327
|
+
name: "i18n-plugin-redirect",
|
|
1328
|
+
order: 10
|
|
949
1329
|
});
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
if (options.
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1330
|
+
if (options.redirects !== false && options.strategy !== "no_prefix") {
|
|
1331
|
+
addRouteMiddleware({
|
|
1332
|
+
name: "i18n-redirect",
|
|
1333
|
+
path: resolver.resolve("./runtime/middleware/i18n-redirect.global"),
|
|
1334
|
+
global: true
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
} else if (options.define) {
|
|
1338
|
+
addPluginTemplate({
|
|
1339
|
+
filename: "i18n.define.mjs",
|
|
1340
|
+
getContents: () => `import { defineNuxtPlugin } from '#imports'
|
|
1341
|
+
export default defineNuxtPlugin(() => ({ provide: { defineI18nRoute: async () => {} } }))
|
|
1342
|
+
`
|
|
962
1343
|
});
|
|
963
1344
|
}
|
|
964
|
-
|
|
1345
|
+
addServerImports([
|
|
1346
|
+
{ name: "useLocaleServerMiddleware", from: resolver.resolve("./runtime/server/utils/locale-server-middleware") },
|
|
1347
|
+
{ name: "useTranslationServerMiddleware", from: resolver.resolve("./runtime/server/utils/translation-server-middleware") },
|
|
1348
|
+
{ name: "loadTranslationsFromServer", from: resolver.resolve("./runtime/server/utils/server-loader") },
|
|
1349
|
+
{ name: "detectCurrentLocale", from: resolver.resolve("./runtime/server/utils/locale-detector") }
|
|
1350
|
+
]);
|
|
965
1351
|
if (translationPayloads.serverHandler) {
|
|
966
1352
|
addServerHandler({
|
|
967
1353
|
route: `/${apiBaseUrl}/:page/:locale/data.json`,
|
|
@@ -976,30 +1362,26 @@ const module$1 = defineNuxtModule({
|
|
|
976
1362
|
});
|
|
977
1363
|
}
|
|
978
1364
|
if (nuxt.options.dev && (options.hmr ?? true)) {
|
|
979
|
-
const
|
|
980
|
-
rootDirs
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
)).flat();
|
|
1365
|
+
const translationDirs = resolveLiveTranslationDirs({
|
|
1366
|
+
rootDirs,
|
|
1367
|
+
translationDir: translationDirName,
|
|
1368
|
+
additionalTranslationDirs: options.additionalTranslationDirs
|
|
1369
|
+
});
|
|
1370
|
+
addVitePlugin(
|
|
1371
|
+
{
|
|
1372
|
+
name: "i18n-micro:translations-hmr",
|
|
1373
|
+
hotUpdate({ file }) {
|
|
1374
|
+
if (this.environment.name !== "client" || !translationRelativePath(file, translationDirs)) return;
|
|
1375
|
+
this.environment.hot.send({ type: "custom", event: TRANSLATIONS_HMR_EVENT });
|
|
1376
|
+
return [];
|
|
1377
|
+
}
|
|
1378
|
+
},
|
|
1379
|
+
{ server: false }
|
|
1380
|
+
);
|
|
996
1381
|
const tpl = addTemplate({
|
|
997
1382
|
filename: "i18n.hmr.mjs",
|
|
998
1383
|
write: true,
|
|
999
|
-
getContents: () => generateHmrPlugin(
|
|
1000
|
-
files.map((f) => f.replace(/\\/g, "/")),
|
|
1001
|
-
{ additionalRootFiles }
|
|
1002
|
-
)
|
|
1384
|
+
getContents: () => generateHmrPlugin()
|
|
1003
1385
|
});
|
|
1004
1386
|
addPlugin({
|
|
1005
1387
|
src: tpl.dst,
|
|
@@ -1015,13 +1397,19 @@ const module$1 = defineNuxtModule({
|
|
|
1015
1397
|
});
|
|
1016
1398
|
addTypeTemplate({
|
|
1017
1399
|
filename: "types/h3.d.ts",
|
|
1018
|
-
getContents: () => `import type { Translations } from '@i18n-micro/types'
|
|
1400
|
+
getContents: () => `import type { ResolvedI18nConfig, Translations } from '@i18n-micro/types'
|
|
1019
1401
|
|
|
1020
1402
|
declare module 'h3' {
|
|
1021
1403
|
interface H3EventContext {
|
|
1022
1404
|
i18n?: {
|
|
1023
1405
|
locale: string
|
|
1024
1406
|
translations: Translations
|
|
1407
|
+
/** Loads a chunk for this request's render (the server loader, no local HTTP round trip), or \`null\` when the host has no file for it. */
|
|
1408
|
+
load?: (locale: string, routeName: string) => Promise<Translations | null>
|
|
1409
|
+
/** The module config with runtime overrides, resolved once for this request. */
|
|
1410
|
+
config?: ResolvedI18nConfig
|
|
1411
|
+
/** A dictionary of this page render could not be read: no cache may keep the page. */
|
|
1412
|
+
failedRead?: boolean
|
|
1025
1413
|
}
|
|
1026
1414
|
}
|
|
1027
1415
|
}
|
|
@@ -1033,60 +1421,77 @@ export {}
|
|
|
1033
1421
|
addTypeTemplate({
|
|
1034
1422
|
filename: "types/i18n-internal.d.ts",
|
|
1035
1423
|
getContents: () => {
|
|
1036
|
-
|
|
1424
|
+
const strategyDeclaration = `
|
|
1425
|
+
export const domains: boolean
|
|
1426
|
+
export const cookies: boolean
|
|
1427
|
+
export const hooks: boolean
|
|
1428
|
+
export const defineRoute: boolean
|
|
1429
|
+
export const serverPathFallback: boolean
|
|
1430
|
+
export function getI18nConfig(): ResolvedI18nConfig
|
|
1431
|
+
export function createI18nStrategy(router: I18nStrategyRouter, config?: ModuleOptionsExtend): PathStrategy
|
|
1432
|
+
`;
|
|
1433
|
+
return `import type { ModuleOptionsExtend, ModulePrivateOptionsExtend, Params, Getter, PluralFunc, ResolvedI18nConfig } from '@i18n-micro/types'
|
|
1037
1434
|
import type { PathStrategy } from '@i18n-micro/path-strategy'
|
|
1435
|
+
import type { Router } from 'vue-router'
|
|
1436
|
+
|
|
1437
|
+
/** The app's vue-router: createI18nStrategy reads hasRoute, resolve and getRoutes from it. */
|
|
1438
|
+
type I18nStrategyRouter = Pick<Router, 'hasRoute' | 'resolve' | 'getRoutes'>
|
|
1038
1439
|
|
|
1039
1440
|
declare module '#build/i18n.plural.mjs' {
|
|
1040
1441
|
export function plural(key: string, count: number, params: Params, locale: string, getter: Getter): string | null
|
|
1041
1442
|
}
|
|
1042
1443
|
|
|
1043
|
-
declare module '#build/i18n.strategy.mjs' {
|
|
1044
|
-
export function getI18nConfig(): ModuleOptionsExtend
|
|
1045
|
-
export function createI18nStrategy(router: { hasRoute: (name: string) => boolean, resolve: (to: unknown) => { name: string | null, path: string, fullPath: string, params?: Record<string, unknown>, query?: Record<string, unknown>, hash?: string } }): PathStrategy
|
|
1046
|
-
}
|
|
1444
|
+
declare module '#build/i18n.strategy.mjs' {${strategyDeclaration}}
|
|
1047
1445
|
|
|
1048
1446
|
declare module '#i18n-internal/config' {
|
|
1049
1447
|
export function getI18nPrivateConfig(): ModulePrivateOptionsExtend
|
|
1050
1448
|
}
|
|
1051
1449
|
|
|
1052
|
-
declare module '#i18n-internal/strategy' {
|
|
1053
|
-
export function getI18nConfig(): ModuleOptionsExtend
|
|
1054
|
-
export function createI18nStrategy(router: { hasRoute: (name: string) => boolean, resolve: (to: unknown) => { name: string | null, path: string, fullPath: string, params?: Record<string, unknown>, query?: Record<string, unknown>, hash?: string } }): PathStrategy
|
|
1055
|
-
}
|
|
1450
|
+
declare module '#i18n-internal/strategy' {${strategyDeclaration}}
|
|
1056
1451
|
|
|
1057
1452
|
declare module '#i18n-internal/plural' {
|
|
1058
1453
|
export const plural: PluralFunc
|
|
1059
1454
|
}
|
|
1060
1455
|
|
|
1061
1456
|
declare module '#i18n-internal/payload-source' {
|
|
1457
|
+
export const embeddedPayloads: boolean
|
|
1062
1458
|
export function readPayload(relPath: string): Promise<Record<string, unknown>>
|
|
1063
1459
|
}
|
|
1064
1460
|
`;
|
|
1065
1461
|
}
|
|
1066
1462
|
});
|
|
1463
|
+
let generatedPages = [];
|
|
1464
|
+
let staticPaths = [];
|
|
1465
|
+
const createBuildStrategy = () => createLocalizedPathResolver(generatedPages, {
|
|
1466
|
+
strategy: options.strategy,
|
|
1467
|
+
defaultLocale: buildConfig.defaultLocale,
|
|
1468
|
+
locales: buildConfig.locales,
|
|
1469
|
+
domain: buildConfig.domain,
|
|
1470
|
+
linkLocales: buildConfig.linkLocales,
|
|
1471
|
+
localizedRouteNamePrefix: options.localizedRouteNamePrefix,
|
|
1472
|
+
customRegexMatcher: fullConfig.customRegexMatcher
|
|
1473
|
+
});
|
|
1474
|
+
const unlinked = (name) => !options.routesLocaleLinks?.[name];
|
|
1067
1475
|
const addDataRoutes = (pages = []) => {
|
|
1068
1476
|
if (!translationPayloads.prerenderRoutes) return;
|
|
1069
|
-
const pagesForDataRoutes = pages.filter((p) => p.name !== void 0 && (
|
|
1477
|
+
const pagesForDataRoutes = pages.filter((p) => p.name !== void 0 && unlinked(p.name));
|
|
1070
1478
|
const dataRoutes = routeGenerator.generateDataRoutes(pagesForDataRoutes, apiBaseUrl, !!options.disablePageLocales);
|
|
1071
1479
|
addPrerenderRoutes(dataRoutes);
|
|
1072
1480
|
};
|
|
1073
1481
|
nuxt.hook("pages:resolved", (pages) => {
|
|
1074
|
-
const pagesNames = pages.map((page) => page.name).filter((name) => name !== void 0 && (
|
|
1482
|
+
const pagesNames = pages.map((page) => page.name).filter((name) => name !== void 0 && unlinked(name));
|
|
1075
1483
|
if (!options.disableWatcher) {
|
|
1076
|
-
routeGenerator.ensureTranslationFilesExist(pagesNames,
|
|
1484
|
+
routeGenerator.ensureTranslationFilesExist(pagesNames, translationDirName, nuxt.options.rootDir, options.disablePageLocales);
|
|
1077
1485
|
}
|
|
1078
1486
|
addDataRoutes(pages);
|
|
1487
|
+
routeNames = pageNames(pages).filter(unlinked);
|
|
1488
|
+
staticPaths = staticPagePaths(pages);
|
|
1079
1489
|
routeGenerator.extendPages(pages);
|
|
1490
|
+
generatedPages = pages;
|
|
1080
1491
|
});
|
|
1081
1492
|
if (options.disablePageLocales) {
|
|
1082
1493
|
nuxt.hook("build:before", () => addDataRoutes([]));
|
|
1083
1494
|
}
|
|
1084
|
-
addVitePlugin(
|
|
1085
|
-
createDefineI18nRoutePlugin({
|
|
1086
|
-
buildDir: nuxt.options.buildDir,
|
|
1087
|
-
rootDirs
|
|
1088
|
-
}).vite()
|
|
1089
|
-
);
|
|
1090
1495
|
const { pluralTemplate, strategyTemplate, configTemplate } = templates;
|
|
1091
1496
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
1092
1497
|
nitroConfig.alias = nitroConfig.alias || {};
|
|
@@ -1095,39 +1500,35 @@ declare module '#i18n-internal/payload-source' {
|
|
|
1095
1500
|
nitroConfig.alias["#i18n-internal/config"] = configTemplate.dst;
|
|
1096
1501
|
nitroConfig.alias["#i18n-internal/payload-source"] = resolver.resolve("./runtime/server/payload-source.fs");
|
|
1097
1502
|
nitroConfig.plugins = nitroConfig.plugins || [];
|
|
1503
|
+
nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/failed-read"));
|
|
1504
|
+
if (!nuxt.options.dev) nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/payload-cache"));
|
|
1098
1505
|
if (nuxt.options.dev && (options.hmr ?? true)) {
|
|
1099
1506
|
nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/watcher.dev"));
|
|
1100
1507
|
}
|
|
1101
1508
|
nitroConfig.routeRules = nitroConfig.routeRules || {};
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1509
|
+
const { file: cacheControl } = resolveTranslationPayloadCachePolicy({
|
|
1510
|
+
httpCacheDuration: options.httpCacheDuration,
|
|
1511
|
+
httpCacheControl: options.httpCacheControl,
|
|
1512
|
+
dateBuild: fullConfig.dateBuild,
|
|
1513
|
+
live: isLiveDictionary(options, translationPayloads.mode, rawServerHost)
|
|
1514
|
+
});
|
|
1105
1515
|
nitroConfig.routeRules[`/${apiBaseUrl}/**`] = {
|
|
1106
1516
|
...nitroConfig.routeRules[`/${apiBaseUrl}/**`] || {},
|
|
1107
|
-
...buildTranslationPayloadRouteRule({ dev: nuxt.options.dev,
|
|
1517
|
+
...buildTranslationPayloadRouteRule({ dev: nuxt.options.dev, cacheControl })
|
|
1108
1518
|
};
|
|
1109
|
-
const
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
}
|
|
1123
|
-
const { redirect, ...restRuleValue } = ruleValue;
|
|
1124
|
-
if (!Object.keys(restRuleValue).length) return;
|
|
1125
|
-
nitroConfig.routeRules[localizedPath] = {
|
|
1126
|
-
...nitroConfig.routeRules[localizedPath],
|
|
1127
|
-
...restRuleValue
|
|
1128
|
-
};
|
|
1129
|
-
logger.debug(`Replicated routeRule for ${localizedPath}: ${JSON.stringify(restRuleValue)}`);
|
|
1130
|
-
});
|
|
1519
|
+
const noDocuments = Boolean(buildConfig.hashMode) && options.strategy === "prefix";
|
|
1520
|
+
localizeRouteRules(
|
|
1521
|
+
nuxt.options.routeRules || {},
|
|
1522
|
+
nitroConfig.routeRules,
|
|
1523
|
+
routeGenerator,
|
|
1524
|
+
(path, rule) => {
|
|
1525
|
+
logger.debug(`Replicated routeRule for ${path}: ${JSON.stringify(rule)}`);
|
|
1526
|
+
},
|
|
1527
|
+
noDocuments
|
|
1528
|
+
);
|
|
1529
|
+
if (routeGenerator.locales.some((locale) => locale.baseUrl)) {
|
|
1530
|
+
for (const [path, rule] of Object.entries(nitroConfig.routeRules)) {
|
|
1531
|
+
nitroConfig.routeRules[path] = varyRouteCacheByHost(rule, Boolean(options.trustForwardedHost));
|
|
1131
1532
|
}
|
|
1132
1533
|
}
|
|
1133
1534
|
nitroConfig.handlers = nitroConfig.handlers || [];
|
|
@@ -1137,10 +1538,30 @@ declare module '#i18n-internal/payload-source' {
|
|
|
1137
1538
|
});
|
|
1138
1539
|
});
|
|
1139
1540
|
nuxt.hook("nitro:init", (nitro) => {
|
|
1140
|
-
if (
|
|
1541
|
+
if (buildConfig.domain) {
|
|
1542
|
+
let localize;
|
|
1543
|
+
nitro.hooks.hook("prerender:generate", async (route) => {
|
|
1544
|
+
localize ??= await createBuildStrategy();
|
|
1545
|
+
if (route.route !== "/" && localize.isPage(route.route) && !localize.isServed(route.route)) route.skip = true;
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
isSSG = Boolean(nitro.options.static);
|
|
1549
|
+
if (isSSG) {
|
|
1550
|
+
if (!generateDomain && routeGenerator.locales.some((locale) => locale.baseUrl)) {
|
|
1551
|
+
logger.warn(
|
|
1552
|
+
"locales[].baseUrl in a static build without NUXT_I18N_DOMAIN: the build config applies, and locales with a baseUrl are links. Generate once per domain."
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
const staticWarning = getStaticPayloadWarning({ translationPayloads, apiBaseClientHost });
|
|
1556
|
+
if (staticWarning) logger.warn(staticWarning);
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1141
1559
|
const readsPublicDir = shouldReadPayloadsFromPublicDir(nitro.options);
|
|
1142
1560
|
if (!readsPublicDir) {
|
|
1143
1561
|
nitro.options.alias["#i18n-internal/payload-source"] = resolver.resolve("./runtime/server/payload-source.assets");
|
|
1562
|
+
if (options.cacheStorage && !apiBaseServerHost) {
|
|
1563
|
+
logger.info("cacheStorage: dictionaries are embedded on this preset, so the shared cache only applies with apiBaseServerHost.");
|
|
1564
|
+
}
|
|
1144
1565
|
}
|
|
1145
1566
|
if (shouldRegisterNitroServerAssets(translationPayloads, readsPublicDir)) {
|
|
1146
1567
|
nitro.options.serverAssets.push({
|
|
@@ -1148,11 +1569,6 @@ declare module '#i18n-internal/payload-source' {
|
|
|
1148
1569
|
// Same layout as public (`mode`): premerged matrix or compact source.
|
|
1149
1570
|
dir: publicAssetsDir
|
|
1150
1571
|
});
|
|
1151
|
-
if (translationPayloads.mode === "premerged") {
|
|
1152
|
-
logger.warn(
|
|
1153
|
-
`Preset "${nitro.options.preset}" does not ship public/ with the server, so translationPayloads.mode:"premerged" embeds the full page/locale matrix via Nitro serverAssets (Rollup raw:). Prefer mode:'source' for large catalogs.`
|
|
1154
|
-
);
|
|
1155
|
-
}
|
|
1156
1572
|
}
|
|
1157
1573
|
if (readsPublicDir && translationPayloads.serverAssets && !translationPayloads.publicAssets) {
|
|
1158
1574
|
logger.info("translationPayloads.serverAssets: on Node, payloads are served from public/ (publicAssets copy enabled for SSR).");
|
|
@@ -1162,11 +1578,22 @@ declare module '#i18n-internal/payload-source' {
|
|
|
1162
1578
|
const isProd = nuxt.options.dev === false;
|
|
1163
1579
|
const readsPublicDir = shouldReadPayloadsFromPublicDir(nitro.options);
|
|
1164
1580
|
const copyPublicPayloads = shouldCopyTranslationPayloadsToPublic(translationPayloads, readsPublicDir);
|
|
1581
|
+
if (isProd && !isSSG && shouldRegisterNitroServerAssets(translationPayloads, readsPublicDir)) {
|
|
1582
|
+
const sizeWarning = getEmbeddedPayloadSizeWarning(nitro.options.preset, publicAssetsDir, translationPayloads.mode);
|
|
1583
|
+
if (sizeWarning) logger.warn(sizeWarning);
|
|
1584
|
+
}
|
|
1165
1585
|
if (isProd && copyPublicPayloads) {
|
|
1166
1586
|
const publicDir = resolveTranslationPayloadPublicDir(nitro.options.output.publicDir, options, apiBaseUrl);
|
|
1167
1587
|
try {
|
|
1168
1588
|
if (existsSync(publicAssetsDir)) {
|
|
1169
|
-
|
|
1589
|
+
const filter = domainPayloadFilter({
|
|
1590
|
+
mode: translationPayloads.mode,
|
|
1591
|
+
root: publicAssetsDir,
|
|
1592
|
+
served: buildConfig.locales,
|
|
1593
|
+
linkLocales: buildConfig.linkLocales,
|
|
1594
|
+
fallbackLocale: options.fallbackLocale
|
|
1595
|
+
});
|
|
1596
|
+
fs__default.cpSync(publicAssetsDir, publicDir, { recursive: true, filter });
|
|
1170
1597
|
logger.log(`Translation payloads copied to public directory`);
|
|
1171
1598
|
const compressed = compressTranslationPayloads(publicDir, nitro.options.compressPublicAssets);
|
|
1172
1599
|
if (compressed > 0) {
|
|
@@ -1184,73 +1611,21 @@ declare module '#i18n-internal/payload-source' {
|
|
|
1184
1611
|
if (isNoPrefixStrategy(options.strategy)) {
|
|
1185
1612
|
return;
|
|
1186
1613
|
}
|
|
1187
|
-
const routesSet = prerenderRoutes.routes;
|
|
1188
1614
|
const routeRules = nuxt.options.routeRules || {};
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
if (localizedRoot && localizedRoot !== "/" && routeRules[localizedRoot]?.prerender !== false) {
|
|
1201
|
-
routesSet.add(localizedRoot);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
for (const route of routesSet) {
|
|
1206
|
-
if (isInternalPath(route, options.excludePatterns)) {
|
|
1207
|
-
routesSet.delete(route);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
for (const route of routesSet) {
|
|
1211
|
-
if (/\.[a-z0-9]+$/i.test(route)) {
|
|
1212
|
-
continue;
|
|
1213
|
-
}
|
|
1214
|
-
if (routeRules[route]?.prerender === false) {
|
|
1215
|
-
continue;
|
|
1216
|
-
}
|
|
1217
|
-
const firstSegment = route.replace(/^\//, "").split("/")[0];
|
|
1218
|
-
if (firstSegment && localeCodes.has(firstSegment)) {
|
|
1219
|
-
continue;
|
|
1220
|
-
}
|
|
1221
|
-
for (const locale of routeGenerator.locales) {
|
|
1222
|
-
if (!isLocaleAllowedForUnlocalizedRoute(routeGenerator.routeLocales, routeGenerator.locales, route, locale.code)) {
|
|
1223
|
-
continue;
|
|
1224
|
-
}
|
|
1225
|
-
const localizedRoute = routeGenerator.resolveLocalizedPath(route, locale.code);
|
|
1226
|
-
if (localizedRoute === route) {
|
|
1227
|
-
continue;
|
|
1228
|
-
}
|
|
1229
|
-
if (routeRules[localizedRoute]?.prerender === false) {
|
|
1230
|
-
continue;
|
|
1231
|
-
}
|
|
1232
|
-
additionalRoutes.add(localizedRoute);
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
if (additionalRoutes.size > 0) {
|
|
1236
|
-
logger.debug("[i18n prerender:routes] added localized routes:", [...additionalRoutes].sort().join(", "));
|
|
1237
|
-
}
|
|
1238
|
-
for (const route of additionalRoutes) {
|
|
1239
|
-
routesSet.add(route);
|
|
1615
|
+
const { added, removed } = seedPrerenderRoutes(prerenderRoutes.routes, {
|
|
1616
|
+
// the same router and strategy the app runs: custom paths, `false` pages and routeLocales as generated
|
|
1617
|
+
localize: await createBuildStrategy(),
|
|
1618
|
+
locales: buildConfig.locales,
|
|
1619
|
+
staticPaths: isSSG ? staticPaths : null,
|
|
1620
|
+
strategy: options.strategy,
|
|
1621
|
+
prerendered: (route) => routeRules[route]?.prerender !== false,
|
|
1622
|
+
isInternal: (route) => isInternalPath(route, options.excludePatterns, apiBaseUrl)
|
|
1623
|
+
});
|
|
1624
|
+
if (added.length > 0) {
|
|
1625
|
+
logger.debug("[i18n prerender:routes] added localized routes:", [...added].sort().join(", "));
|
|
1240
1626
|
}
|
|
1241
|
-
if (
|
|
1242
|
-
|
|
1243
|
-
for (const route of routesSet) {
|
|
1244
|
-
if (route === "/" || route === "") continue;
|
|
1245
|
-
const firstSegment = route.replace(/^\//, "").split("/")[0];
|
|
1246
|
-
if (firstSegment && !localeCodes.has(firstSegment)) {
|
|
1247
|
-
routesSet.delete(route);
|
|
1248
|
-
deleted.push(route);
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
if (deleted.length > 0) {
|
|
1252
|
-
logger.info(`[i18n prerender:routes] removed from prerender list (no locale prefix): ${deleted.join(", ")}`);
|
|
1253
|
-
}
|
|
1627
|
+
if (removed.length > 0) {
|
|
1628
|
+
logger.info(`[i18n prerender:routes] removed from prerender list (no locale prefix): ${removed.join(", ")}`);
|
|
1254
1629
|
}
|
|
1255
1630
|
});
|
|
1256
1631
|
const devtoolsOptions = nuxt.options.devtools;
|