nuxt-i18n-micro 3.30.3 → 4.0.1
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/356b87e3-45dd-4f80-a40a-b649665c1844.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 +857 -476
- 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/60974e48-d00e-4f9b-9467-9d4b322519da.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,
|
|
4
|
-
import { fileURLToPath
|
|
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';
|
|
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
|
+
import { fileURLToPath } from 'node:url';
|
|
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
|
|
958
|
+
return 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,72 @@ 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
|
-
|
|
602
|
-
localizedRouteNamePrefix: options.localizedRouteNamePrefix ?? "localized-",
|
|
1003
|
+
prefixCaseRedirect: options.prefixCaseRedirect !== false,
|
|
1004
|
+
localizedRouteNamePrefix: options.localizedRouteNamePrefix,
|
|
603
1005
|
routesLocaleLinks: options.routesLocaleLinks ?? {},
|
|
604
|
-
|
|
605
|
-
debug: options.debug ?? false,
|
|
1006
|
+
debug: options.debug,
|
|
606
1007
|
customRegexMatcher: options.customRegexMatcher instanceof RegExp ? options.customRegexMatcher.source : options.customRegexMatcher,
|
|
607
1008
|
cacheMaxSize: options.cacheMaxSize ?? 0,
|
|
608
1009
|
cacheTtl: options.cacheTtl ?? 0,
|
|
609
1010
|
httpCacheDuration: options.httpCacheDuration,
|
|
1011
|
+
httpCacheControl: options.httpCacheControl,
|
|
610
1012
|
numberFormats: options.numberFormats ?? {},
|
|
611
1013
|
datetimeFormats: options.datetimeFormats ?? {},
|
|
612
|
-
|
|
1014
|
+
timeZone: options.timeZone,
|
|
1015
|
+
translationPayloadMode,
|
|
1016
|
+
publicPayloads,
|
|
1017
|
+
rootDelta,
|
|
1018
|
+
noScripts: noScripts === "all" || !!noScripts && !nuxt.options.dev || void 0
|
|
613
1019
|
};
|
|
614
1020
|
}
|
|
615
|
-
function buildPrivateConfig(options, nuxt, apiConfig, locales, payloadFsDir, payloadPublicRel) {
|
|
1021
|
+
function buildPrivateConfig(options, nuxt, rootDirs, apiConfig, locales, payloadFsDir, payloadPublicRel) {
|
|
616
1022
|
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(),
|
|
1023
|
+
rootDirs,
|
|
620
1024
|
debug: options.debug ?? false,
|
|
621
1025
|
locales: locales ?? [],
|
|
622
|
-
fallbackLocale: options.fallbackLocale ?? void 0,
|
|
623
1026
|
translationDir: options.translationDir ?? "locales",
|
|
624
1027
|
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
1028
|
apiBaseServerHost: apiConfig.apiBaseServerHost,
|
|
630
1029
|
serverTranslationPreload: options.serverTranslationPreload ?? false,
|
|
1030
|
+
cacheStorage: options.cacheStorage,
|
|
1031
|
+
cacheStorageBuild: nuxt.options.buildId,
|
|
631
1032
|
payloadFsDir,
|
|
632
1033
|
payloadPublicRel
|
|
633
1034
|
};
|
|
634
1035
|
}
|
|
635
|
-
function registerI18nTemplates(options, resolvedStrategyPath, resolvedHelpersPath, fullConfig, privateConfig) {
|
|
636
|
-
let runtimeFullConfig = fullConfig;
|
|
1036
|
+
function registerI18nTemplates(options, resolvedStrategyPath, resolvedHelpersPath, fullConfig, privateConfig, disabledLocales, dev, serverPathFallback) {
|
|
637
1037
|
const pluralTemplate = addTemplate({
|
|
638
1038
|
filename: "i18n.plural.mjs",
|
|
639
1039
|
write: true,
|
|
@@ -643,42 +1043,42 @@ function registerI18nTemplates(options, resolvedStrategyPath, resolvedHelpersPat
|
|
|
643
1043
|
filename: "i18n.strategy.mjs",
|
|
644
1044
|
write: true,
|
|
645
1045
|
getContents: () => {
|
|
646
|
-
const
|
|
647
|
-
return `import { Strategy } from '${resolvedStrategyPath}'
|
|
1046
|
+
const domains = fullConfig.locales.some((locale) => locale.baseUrl);
|
|
1047
|
+
return `import { ${domains ? "DomainPathStrategy" : "I18nPathStrategy"} as Strategy } from '${resolvedStrategyPath}'
|
|
648
1048
|
|
|
649
|
-
const __fullConfig = ${
|
|
1049
|
+
const __fullConfig = ${buildFullConfigSource(fullConfig)}
|
|
1050
|
+
// disabled at build: no routes and not in the config, but routing must answer for their prefix
|
|
1051
|
+
const __disabledLocales = ${JSON.stringify(disabledLocales)}
|
|
650
1052
|
|
|
651
1053
|
export function getI18nConfig() { return __fullConfig }
|
|
652
1054
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
params: r.params || {},
|
|
663
|
-
query: r.query || {},
|
|
664
|
-
hash: r.hash || '',
|
|
665
|
-
}
|
|
666
|
-
},
|
|
667
|
-
}
|
|
1055
|
+
// a constant, so a build without locale domains drops their code
|
|
1056
|
+
export const domains = ${domains}
|
|
1057
|
+
|
|
1058
|
+
// a constant, so a build that keeps the locale in no cookie drops useCookie (no runtime override reaches these two)
|
|
1059
|
+
export const cookies = ${fullConfig.localeCookie !== null || fullConfig.hashMode}
|
|
1060
|
+
|
|
1061
|
+
// constants too, so a build with these options off drops their code from the loader plugin
|
|
1062
|
+
export const hooks = ${options.hooks !== false}
|
|
1063
|
+
export const defineRoute = ${options.define !== false}
|
|
668
1064
|
|
|
1065
|
+
// Nuxt 4.6+: a client navigation to a URL with no record becomes a document load (experimental.serverPathFallback)
|
|
1066
|
+
export const serverPathFallback = ${serverPathFallback}
|
|
1067
|
+
|
|
1068
|
+
// vue-router's Router is the adapter as is: no wrapper copying every resolve result.
|
|
1069
|
+
// config: the config with runtime overrides (defaultLocale, disabledLocales) \u2014 routing follows them.
|
|
1070
|
+
export function createI18nStrategy(router, config = __fullConfig) {
|
|
669
1071
|
return new Strategy({
|
|
670
|
-
strategy:
|
|
671
|
-
defaultLocale:
|
|
672
|
-
locales:
|
|
673
|
-
localizedRouteNamePrefix:
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
hashMode: __fullConfig.hashMode,
|
|
681
|
-
disablePageLocales: __fullConfig.disablePageLocales,
|
|
1072
|
+
strategy: config.strategy,
|
|
1073
|
+
defaultLocale: config.defaultLocale,
|
|
1074
|
+
locales: [...config.locales, ...__disabledLocales],
|
|
1075
|
+
localizedRouteNamePrefix: config.localizedRouteNamePrefix,
|
|
1076
|
+
router,
|
|
1077
|
+
hashMode: config.hashMode,
|
|
1078
|
+
disablePageLocales: config.disablePageLocales,
|
|
1079
|
+
customRegexMatcher: config.customRegexMatcher,
|
|
1080
|
+
// a prefix in another case is a 404 when nothing may redirect it
|
|
1081
|
+
prefixCaseRedirect: config.redirects !== false && config.prefixCaseRedirect !== false,${domains ? "\n domain: config.domain,\n linkLocales: config.linkLocales," : ""}
|
|
682
1082
|
})
|
|
683
1083
|
}
|
|
684
1084
|
`;
|
|
@@ -687,23 +1087,20 @@ export function createI18nStrategy(router) {
|
|
|
687
1087
|
const configTemplate = addTemplate({
|
|
688
1088
|
filename: "i18n.config.mjs",
|
|
689
1089
|
write: true,
|
|
690
|
-
getContents: () =>
|
|
691
|
-
export function getI18nPrivateConfig() { return __privateConfig }
|
|
692
|
-
`
|
|
1090
|
+
getContents: () => buildPrivateConfigSource(privateConfig, dev)
|
|
693
1091
|
});
|
|
694
1092
|
return {
|
|
695
1093
|
pluralTemplate,
|
|
696
1094
|
strategyTemplate,
|
|
697
|
-
configTemplate
|
|
698
|
-
setFullConfig(next) {
|
|
699
|
-
runtimeFullConfig = next;
|
|
700
|
-
}
|
|
1095
|
+
configTemplate
|
|
701
1096
|
};
|
|
702
1097
|
}
|
|
703
1098
|
const module$1 = defineNuxtModule({
|
|
704
1099
|
meta: {
|
|
705
1100
|
name: "nuxt-i18n-micro",
|
|
706
|
-
configKey: "i18n"
|
|
1101
|
+
configKey: "i18n",
|
|
1102
|
+
// Vite environment hotUpdate (HMR) and NuxtPage's ~transitionPromise
|
|
1103
|
+
compatibility: { nuxt: ">=3.18.0" }
|
|
707
1104
|
},
|
|
708
1105
|
defaults: {
|
|
709
1106
|
locales: [],
|
|
@@ -711,6 +1108,7 @@ const module$1 = defineNuxtModule({
|
|
|
711
1108
|
debug: false,
|
|
712
1109
|
define: true,
|
|
713
1110
|
redirects: true,
|
|
1111
|
+
prefixCaseRedirect: true,
|
|
714
1112
|
plugin: true,
|
|
715
1113
|
hooks: true,
|
|
716
1114
|
components: true,
|
|
@@ -724,11 +1122,8 @@ const module$1 = defineNuxtModule({
|
|
|
724
1122
|
disablePageLocales: false,
|
|
725
1123
|
disableWatcher: false,
|
|
726
1124
|
noPrefixRedirect: false,
|
|
727
|
-
fallbackLocale: void 0,
|
|
728
1125
|
localeCookie: null,
|
|
729
1126
|
apiBaseUrl: "_locales",
|
|
730
|
-
apiBaseClientHost: void 0,
|
|
731
|
-
apiBaseServerHost: void 0,
|
|
732
1127
|
translationPayloads: {
|
|
733
1128
|
mode: "premerged",
|
|
734
1129
|
serverAssets: true,
|
|
@@ -740,20 +1135,18 @@ const module$1 = defineNuxtModule({
|
|
|
740
1135
|
},
|
|
741
1136
|
routesLocaleLinks: {},
|
|
742
1137
|
globalLocaleRoutes: {},
|
|
743
|
-
canonicalQueryWhitelist: void 0,
|
|
744
1138
|
plural: defaultPlural,
|
|
745
|
-
customRegexMatcher: void 0,
|
|
746
|
-
excludePatterns: void 0,
|
|
747
1139
|
localizedRouteNamePrefix: "localized-",
|
|
748
1140
|
missingWarn: true,
|
|
749
1141
|
metaTrustForwardedHost: true,
|
|
750
1142
|
metaTrustForwardedProto: true,
|
|
1143
|
+
trustForwardedHost: false,
|
|
751
1144
|
hreflangBaseLanguage: false,
|
|
752
1145
|
httpCacheDuration: 31536e3
|
|
753
1146
|
},
|
|
754
1147
|
async setup(options, nuxt) {
|
|
755
|
-
const defaultLocale = process.env.DEFAULT_LOCALE
|
|
756
|
-
|
|
1148
|
+
const defaultLocale = process.env.DEFAULT_LOCALE || options.defaultLocale || "en";
|
|
1149
|
+
let isSSG = false;
|
|
757
1150
|
const logger = useLogger("nuxt-i18n-micro");
|
|
758
1151
|
nuxt.options.typescript.hoist ||= [];
|
|
759
1152
|
for (const pkg of ["@i18n-micro/types", "@i18n-micro/path-strategy"]) {
|
|
@@ -761,12 +1154,13 @@ const module$1 = defineNuxtModule({
|
|
|
761
1154
|
nuxt.options.typescript.hoist.push(pkg);
|
|
762
1155
|
}
|
|
763
1156
|
}
|
|
764
|
-
|
|
1157
|
+
const cookieOptOut = nuxt.options.i18n?.localeCookie === null;
|
|
1158
|
+
if (options.strategy === "no_prefix" && !options.localeCookie && !cookieOptOut) {
|
|
765
1159
|
options.localeCookie = "user-locale";
|
|
766
1160
|
logger.info("Strategy 'no_prefix': localeCookie automatically set to 'user-locale' for locale persistence.");
|
|
767
1161
|
}
|
|
768
1162
|
if (options.strategy !== "no_prefix" && options.redirects !== false && !options.localeCookie) {
|
|
769
|
-
logger.
|
|
1163
|
+
logger.info(
|
|
770
1164
|
"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
1165
|
);
|
|
772
1166
|
}
|
|
@@ -775,7 +1169,12 @@ const module$1 = defineNuxtModule({
|
|
|
775
1169
|
const mergedLocalesDir = resolve(nuxt.options.buildDir, "i18n-merged");
|
|
776
1170
|
const sourceLocalesDir = resolve(nuxt.options.buildDir, "i18n-source");
|
|
777
1171
|
const translationDirName = options.translationDir || "locales";
|
|
778
|
-
const translationsHash = options.dateBuild === void 0 ? hashTranslationSources(
|
|
1172
|
+
const translationsHash = options.dateBuild === void 0 ? hashTranslationSources(
|
|
1173
|
+
rootDirs,
|
|
1174
|
+
translationDirName,
|
|
1175
|
+
options.additionalTranslationDirs,
|
|
1176
|
+
JSON.stringify([options.fallbackLocale, options.disablePageLocales, (options.locales ?? []).map((l) => [l.code, l.fallbackLocale])])
|
|
1177
|
+
) : null;
|
|
779
1178
|
const translationPayloads = resolveTranslationPayloadOptions(options);
|
|
780
1179
|
const publicAssetsDir = translationPayloads.mode === "source" ? sourceLocalesDir : mergedLocalesDir;
|
|
781
1180
|
for (const warning of getTranslationPayloadMisconfigurationWarnings({
|
|
@@ -783,9 +1182,10 @@ const module$1 = defineNuxtModule({
|
|
|
783
1182
|
apiBaseClientHost: options.apiBaseClientHost,
|
|
784
1183
|
apiBaseServerHost: options.apiBaseServerHost
|
|
785
1184
|
})) {
|
|
786
|
-
logger.warn(warning
|
|
1185
|
+
logger.warn(warning);
|
|
787
1186
|
}
|
|
788
|
-
|
|
1187
|
+
const rawServerHost = process.env.NUXT_I18N_APP_BASE_SERVER_HOST ?? options.apiBaseServerHost ?? void 0;
|
|
1188
|
+
if (!translationPayloads.serverAssets && !translationPayloads.publicAssets && !rawServerHost) {
|
|
789
1189
|
throw new Error(
|
|
790
1190
|
"nuxt-i18n-micro: local SSR payloads are disabled (serverAssets/publicAssets false) and apiBaseServerHost is not set. SSR would load empty translations."
|
|
791
1191
|
);
|
|
@@ -799,79 +1199,53 @@ const module$1 = defineNuxtModule({
|
|
|
799
1199
|
const prev = localeInfosByCode.get(locale.code);
|
|
800
1200
|
localeInfosByCode.set(locale.code, {
|
|
801
1201
|
code: locale.code,
|
|
802
|
-
fallbackLocale:
|
|
803
|
-
disabled:
|
|
1202
|
+
fallbackLocale: prev?.fallbackLocale ?? locale.fallbackLocale,
|
|
1203
|
+
disabled: prev?.disabled ?? locale.disabled
|
|
804
1204
|
});
|
|
805
1205
|
}
|
|
806
1206
|
const localeInfos = [...localeInfosByCode.values()].filter((locale) => !locale.disabled).map(({ code, fallbackLocale }) => ({ code, fallbackLocale }));
|
|
1207
|
+
let routeNames = [];
|
|
807
1208
|
nuxt.hook("build:before", async () => {
|
|
808
1209
|
if (translationPayloads.mode === "source") {
|
|
809
1210
|
await buildTranslationSourceLayers(rootDirs, translationDirName, sourceLocalesDir, options.additionalTranslationDirs);
|
|
810
1211
|
logger.info(`Built compact translation source from ${rootDirs.length} layer(s) into ${sourceLocalesDir}`);
|
|
811
1212
|
} else {
|
|
812
|
-
await preMergeLocales(
|
|
1213
|
+
await preMergeLocales({
|
|
813
1214
|
rootDirs,
|
|
814
1215
|
translationDirName,
|
|
815
|
-
mergedLocalesDir,
|
|
816
|
-
localeInfos,
|
|
817
|
-
options.fallbackLocale,
|
|
818
|
-
options.disablePageLocales,
|
|
819
|
-
options.additionalTranslationDirs
|
|
820
|
-
|
|
1216
|
+
outputDir: mergedLocalesDir,
|
|
1217
|
+
locales: localeInfos,
|
|
1218
|
+
globalFallbackLocale: options.fallbackLocale,
|
|
1219
|
+
disablePageLocales: options.disablePageLocales,
|
|
1220
|
+
additionalTranslationDirs: options.additionalTranslationDirs,
|
|
1221
|
+
routeNames,
|
|
1222
|
+
// dev reads live files, and a prerender merges what it reads like any server
|
|
1223
|
+
rootDelta: !nuxt.options.dev && translationPayloads.rootDelta
|
|
1224
|
+
});
|
|
821
1225
|
logger.info(`Pre-merged translations from ${rootDirs.length} layer(s) into ${mergedLocalesDir}`);
|
|
822
1226
|
}
|
|
823
1227
|
const payloadStats = scanTranslationPayloadDirectory(publicAssetsDir);
|
|
824
|
-
const sizeWarning = getTranslationPayloadSizeWarning(payloadStats,
|
|
825
|
-
if (sizeWarning)
|
|
826
|
-
logger.warn(sizeWarning.replace("[nuxt-i18n-micro] ", ""));
|
|
827
|
-
}
|
|
1228
|
+
const sizeWarning = getTranslationPayloadSizeWarning(payloadStats, options.translationPayloads);
|
|
1229
|
+
if (sizeWarning) logger.warn(sizeWarning);
|
|
828
1230
|
});
|
|
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();
|
|
1231
|
+
const pageFiles = (await Promise.all(rootDirs.map((cwd) => globby(["pages/**/*.vue", "app/pages/**/*.vue"], { cwd, absolute: true })))).flat();
|
|
1232
|
+
const {
|
|
1233
|
+
routeLocales,
|
|
1234
|
+
routeDisableMeta,
|
|
1235
|
+
globalLocaleRoutes: mergedGlobalLocaleRoutes
|
|
1236
|
+
} = mergeDefineI18nRouteMeta(collectDefineI18nRouteMetaFromFiles(pageFiles, rootDirs), options);
|
|
1237
|
+
const resolvedStrategyPath = await resolveEntry("@i18n-micro/path-strategy");
|
|
1238
|
+
const resolvedHelpersPath = await resolveEntry("@i18n-micro/core/helpers");
|
|
862
1239
|
let apiBaseClientHost = process.env.NUXT_I18N_APP_BASE_CLIENT_HOST ?? options.apiBaseClientHost ?? void 0;
|
|
863
1240
|
if (apiBaseClientHost?.endsWith("/")) {
|
|
864
1241
|
apiBaseClientHost = apiBaseClientHost.slice(0, -1);
|
|
865
1242
|
}
|
|
866
|
-
|
|
867
|
-
if (apiBaseServerHost?.endsWith("/")) {
|
|
868
|
-
apiBaseServerHost = apiBaseServerHost.slice(0, -1);
|
|
869
|
-
}
|
|
1243
|
+
const apiBaseServerHost = rawServerHost?.endsWith("/") ? rawServerHost.slice(0, -1) : rawServerHost;
|
|
870
1244
|
const rawUrl = process.env.NUXT_I18N_APP_BASE_URL ?? options.apiBaseUrl ?? "_locales";
|
|
871
1245
|
if (rawUrl.startsWith("http://") || rawUrl.startsWith("https://")) {
|
|
872
1246
|
throw new Error("Nuxt-i18n-micro: Please use NUXT_I18N_APP_BASE_CLIENT_HOST or NUXT_I18N_APP_BASE_SERVER_HOST instead.");
|
|
873
1247
|
}
|
|
874
|
-
const apiBaseUrl = rawUrl
|
|
1248
|
+
const apiBaseUrl = pathSegments(rawUrl);
|
|
875
1249
|
const apiConfig = { apiBaseUrl, apiBaseClientHost, apiBaseServerHost };
|
|
876
1250
|
const payloadPublicRel = resolveTranslationPayloadPublicRel(options, apiBaseUrl);
|
|
877
1251
|
const routeGenerator = new RouteGenerator({
|
|
@@ -879,31 +1253,50 @@ const module$1 = defineNuxtModule({
|
|
|
879
1253
|
defaultLocaleCode: defaultLocale,
|
|
880
1254
|
strategy: options.strategy,
|
|
881
1255
|
globalLocaleRoutes: mergedGlobalLocaleRoutes,
|
|
882
|
-
filesLocaleRoutes: globalLocaleRoutes,
|
|
883
1256
|
routeLocales,
|
|
1257
|
+
routeDisableMeta,
|
|
884
1258
|
noPrefixRedirect: options.noPrefixRedirect,
|
|
1259
|
+
redirects: options.redirects,
|
|
885
1260
|
excludePatterns: options.excludePatterns,
|
|
1261
|
+
apiBaseUrl,
|
|
886
1262
|
localizedRouteNamePrefix: options.localizedRouteNamePrefix,
|
|
887
|
-
customRegexMatcher: options.customRegexMatcher
|
|
1263
|
+
customRegexMatcher: options.customRegexMatcher,
|
|
1264
|
+
parentPaths: parentPagePaths(pageFiles, rootDirs)
|
|
888
1265
|
});
|
|
889
1266
|
const fullConfig = buildFullConfig({
|
|
890
1267
|
options,
|
|
891
1268
|
nuxt,
|
|
892
1269
|
defaultLocale,
|
|
893
|
-
isSSG,
|
|
894
1270
|
apiConfig,
|
|
895
1271
|
translationPayloadMode: translationPayloads.mode,
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
mergedGlobalLocaleRoutes,
|
|
1272
|
+
publicPayloads: translationPayloads.mode === "premerged" && translationPayloads.publicAssets,
|
|
1273
|
+
rootDelta: !nuxt.options.dev && translationPayloads.mode === "premerged" && translationPayloads.rootDelta && !options.disablePageLocales && (translationPayloads.publicAssets || Boolean(options.apiBaseClientHost)),
|
|
899
1274
|
locales: routeGenerator.locales ?? [],
|
|
900
1275
|
translationsHash
|
|
901
1276
|
});
|
|
902
|
-
const
|
|
903
|
-
const
|
|
1277
|
+
const generateDomain = process.env.NUXT_I18N_DOMAIN;
|
|
1278
|
+
const buildConfig = generateDomain ? configForHost(fullConfig, generateDomain) : fullConfig;
|
|
1279
|
+
if (generateDomain && !buildConfig.domain)
|
|
1280
|
+
logger.warn(`NUXT_I18N_DOMAIN "${generateDomain}" is no host of locales[].baseUrl: the build config applies.`);
|
|
1281
|
+
const siteUrl = nuxt.options.site?.url || process.env.NUXT_PUBLIC_SITE_URL || process.env.NUXT_SITE_URL;
|
|
1282
|
+
const originWarning = buildConfig.domain ? null : unfixedOriginWarning(routeGenerator.locales, defaultLocale, options.metaBaseUrl, siteUrl);
|
|
1283
|
+
if (originWarning) logger.warn(originWarning);
|
|
1284
|
+
const privateConfig = buildPrivateConfig(options, nuxt, rootDirs, apiConfig, routeGenerator.locales ?? [], publicAssetsDir, payloadPublicRel);
|
|
1285
|
+
const templates = registerI18nTemplates(
|
|
1286
|
+
options,
|
|
1287
|
+
resolvedStrategyPath,
|
|
1288
|
+
resolvedHelpersPath,
|
|
1289
|
+
fullConfig,
|
|
1290
|
+
privateConfig,
|
|
1291
|
+
routeGenerator.disabledLocales,
|
|
1292
|
+
nuxt.options.dev,
|
|
1293
|
+
nuxt.options.experimental.serverPathFallback === true && !nuxt.options.router?.options?.hashMode
|
|
1294
|
+
);
|
|
904
1295
|
if (typeof options.customRegexMatcher !== "undefined") {
|
|
905
|
-
const
|
|
906
|
-
|
|
1296
|
+
const failedCodes = localesOutsideMatcher(
|
|
1297
|
+
routeGenerator.locales.map((l) => l.code),
|
|
1298
|
+
options.customRegexMatcher
|
|
1299
|
+
);
|
|
907
1300
|
if (failedCodes.length > 0) {
|
|
908
1301
|
throw new Error(
|
|
909
1302
|
"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 +1310,48 @@ const module$1 = defineNuxtModule({
|
|
|
917
1310
|
name: "i18n-plugin-loader",
|
|
918
1311
|
order: -5
|
|
919
1312
|
});
|
|
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) {
|
|
1313
|
+
if (options.meta) {
|
|
1314
|
+
addPlugin({
|
|
1315
|
+
src: resolver.resolve("./runtime/plugins/02.meta"),
|
|
1316
|
+
name: "i18n-plugin-meta",
|
|
1317
|
+
order: 2
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
if (nuxt.options.dev && options.vueDevtools !== false) {
|
|
1321
|
+
addPlugin({
|
|
1322
|
+
src: resolver.resolve("./runtime/plugins/04.vue-devtools"),
|
|
1323
|
+
name: "i18n-vue-devtools",
|
|
1324
|
+
mode: "client",
|
|
1325
|
+
order: 5
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
944
1328
|
addPlugin({
|
|
945
|
-
src: resolver.resolve("./runtime/plugins/
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
order:
|
|
1329
|
+
src: resolver.resolve("./runtime/plugins/06.redirect"),
|
|
1330
|
+
mode: "server",
|
|
1331
|
+
name: "i18n-plugin-redirect",
|
|
1332
|
+
order: 10
|
|
949
1333
|
});
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
if (options.
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1334
|
+
if (options.redirects !== false && options.strategy !== "no_prefix") {
|
|
1335
|
+
addRouteMiddleware({
|
|
1336
|
+
name: "i18n-redirect",
|
|
1337
|
+
path: resolver.resolve("./runtime/middleware/i18n-redirect.global"),
|
|
1338
|
+
global: true
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
} else if (options.define) {
|
|
1342
|
+
addPluginTemplate({
|
|
1343
|
+
filename: "i18n.define.mjs",
|
|
1344
|
+
getContents: () => `import { defineNuxtPlugin } from '#imports'
|
|
1345
|
+
export default defineNuxtPlugin(() => ({ provide: { defineI18nRoute: async () => {} } }))
|
|
1346
|
+
`
|
|
962
1347
|
});
|
|
963
1348
|
}
|
|
964
|
-
|
|
1349
|
+
addServerImports([
|
|
1350
|
+
{ name: "useLocaleServerMiddleware", from: resolver.resolve("./runtime/server/utils/locale-server-middleware") },
|
|
1351
|
+
{ name: "useTranslationServerMiddleware", from: resolver.resolve("./runtime/server/utils/translation-server-middleware") },
|
|
1352
|
+
{ name: "loadTranslationsFromServer", from: resolver.resolve("./runtime/server/utils/server-loader") },
|
|
1353
|
+
{ name: "detectCurrentLocale", from: resolver.resolve("./runtime/server/utils/locale-detector") }
|
|
1354
|
+
]);
|
|
965
1355
|
if (translationPayloads.serverHandler) {
|
|
966
1356
|
addServerHandler({
|
|
967
1357
|
route: `/${apiBaseUrl}/:page/:locale/data.json`,
|
|
@@ -976,30 +1366,26 @@ const module$1 = defineNuxtModule({
|
|
|
976
1366
|
});
|
|
977
1367
|
}
|
|
978
1368
|
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();
|
|
1369
|
+
const translationDirs = resolveLiveTranslationDirs({
|
|
1370
|
+
rootDirs,
|
|
1371
|
+
translationDir: translationDirName,
|
|
1372
|
+
additionalTranslationDirs: options.additionalTranslationDirs
|
|
1373
|
+
});
|
|
1374
|
+
addVitePlugin(
|
|
1375
|
+
{
|
|
1376
|
+
name: "i18n-micro:translations-hmr",
|
|
1377
|
+
hotUpdate({ file }) {
|
|
1378
|
+
if (this.environment.name !== "client" || !translationRelativePath(file, translationDirs)) return;
|
|
1379
|
+
this.environment.hot.send({ type: "custom", event: TRANSLATIONS_HMR_EVENT });
|
|
1380
|
+
return [];
|
|
1381
|
+
}
|
|
1382
|
+
},
|
|
1383
|
+
{ server: false }
|
|
1384
|
+
);
|
|
996
1385
|
const tpl = addTemplate({
|
|
997
1386
|
filename: "i18n.hmr.mjs",
|
|
998
1387
|
write: true,
|
|
999
|
-
getContents: () => generateHmrPlugin(
|
|
1000
|
-
files.map((f) => f.replace(/\\/g, "/")),
|
|
1001
|
-
{ additionalRootFiles }
|
|
1002
|
-
)
|
|
1388
|
+
getContents: () => generateHmrPlugin()
|
|
1003
1389
|
});
|
|
1004
1390
|
addPlugin({
|
|
1005
1391
|
src: tpl.dst,
|
|
@@ -1015,13 +1401,19 @@ const module$1 = defineNuxtModule({
|
|
|
1015
1401
|
});
|
|
1016
1402
|
addTypeTemplate({
|
|
1017
1403
|
filename: "types/h3.d.ts",
|
|
1018
|
-
getContents: () => `import type { Translations } from '@i18n-micro/types'
|
|
1404
|
+
getContents: () => `import type { ResolvedI18nConfig, Translations } from '@i18n-micro/types'
|
|
1019
1405
|
|
|
1020
1406
|
declare module 'h3' {
|
|
1021
1407
|
interface H3EventContext {
|
|
1022
1408
|
i18n?: {
|
|
1023
1409
|
locale: string
|
|
1024
1410
|
translations: Translations
|
|
1411
|
+
/** 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. */
|
|
1412
|
+
load?: (locale: string, routeName: string) => Promise<Translations | null>
|
|
1413
|
+
/** The module config with runtime overrides, resolved once for this request. */
|
|
1414
|
+
config?: ResolvedI18nConfig
|
|
1415
|
+
/** A dictionary of this page render could not be read: no cache may keep the page. */
|
|
1416
|
+
failedRead?: boolean
|
|
1025
1417
|
}
|
|
1026
1418
|
}
|
|
1027
1419
|
}
|
|
@@ -1033,101 +1425,116 @@ export {}
|
|
|
1033
1425
|
addTypeTemplate({
|
|
1034
1426
|
filename: "types/i18n-internal.d.ts",
|
|
1035
1427
|
getContents: () => {
|
|
1036
|
-
|
|
1428
|
+
const strategyDeclaration = `
|
|
1429
|
+
export const domains: boolean
|
|
1430
|
+
export const cookies: boolean
|
|
1431
|
+
export const hooks: boolean
|
|
1432
|
+
export const defineRoute: boolean
|
|
1433
|
+
export const serverPathFallback: boolean
|
|
1434
|
+
export function getI18nConfig(): ResolvedI18nConfig
|
|
1435
|
+
export function createI18nStrategy(router: I18nStrategyRouter, config?: ModuleOptionsExtend): PathStrategy
|
|
1436
|
+
`;
|
|
1437
|
+
return `import type { ModuleOptionsExtend, ModulePrivateOptionsExtend, Params, Getter, PluralFunc, ResolvedI18nConfig } from '@i18n-micro/types'
|
|
1037
1438
|
import type { PathStrategy } from '@i18n-micro/path-strategy'
|
|
1439
|
+
import type { Router } from 'vue-router'
|
|
1440
|
+
|
|
1441
|
+
/** The app's vue-router: createI18nStrategy reads hasRoute, resolve and getRoutes from it. */
|
|
1442
|
+
type I18nStrategyRouter = Pick<Router, 'hasRoute' | 'resolve' | 'getRoutes'>
|
|
1038
1443
|
|
|
1039
1444
|
declare module '#build/i18n.plural.mjs' {
|
|
1040
1445
|
export function plural(key: string, count: number, params: Params, locale: string, getter: Getter): string | null
|
|
1041
1446
|
}
|
|
1042
1447
|
|
|
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
|
-
}
|
|
1448
|
+
declare module '#build/i18n.strategy.mjs' {${strategyDeclaration}}
|
|
1047
1449
|
|
|
1048
1450
|
declare module '#i18n-internal/config' {
|
|
1049
1451
|
export function getI18nPrivateConfig(): ModulePrivateOptionsExtend
|
|
1050
1452
|
}
|
|
1051
1453
|
|
|
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
|
-
}
|
|
1454
|
+
declare module '#i18n-internal/strategy' {${strategyDeclaration}}
|
|
1056
1455
|
|
|
1057
1456
|
declare module '#i18n-internal/plural' {
|
|
1058
1457
|
export const plural: PluralFunc
|
|
1059
1458
|
}
|
|
1060
1459
|
|
|
1061
1460
|
declare module '#i18n-internal/payload-source' {
|
|
1461
|
+
export const embeddedPayloads: boolean
|
|
1062
1462
|
export function readPayload(relPath: string): Promise<Record<string, unknown>>
|
|
1063
1463
|
}
|
|
1064
1464
|
`;
|
|
1065
1465
|
}
|
|
1066
1466
|
});
|
|
1467
|
+
let generatedPages = [];
|
|
1468
|
+
let staticPaths = [];
|
|
1469
|
+
const createBuildStrategy = () => createLocalizedPathResolver(generatedPages, {
|
|
1470
|
+
strategy: options.strategy,
|
|
1471
|
+
defaultLocale: buildConfig.defaultLocale,
|
|
1472
|
+
locales: buildConfig.locales,
|
|
1473
|
+
domain: buildConfig.domain,
|
|
1474
|
+
linkLocales: buildConfig.linkLocales,
|
|
1475
|
+
localizedRouteNamePrefix: options.localizedRouteNamePrefix,
|
|
1476
|
+
customRegexMatcher: fullConfig.customRegexMatcher
|
|
1477
|
+
});
|
|
1478
|
+
const unlinked = (name) => !options.routesLocaleLinks?.[name];
|
|
1067
1479
|
const addDataRoutes = (pages = []) => {
|
|
1068
1480
|
if (!translationPayloads.prerenderRoutes) return;
|
|
1069
|
-
const pagesForDataRoutes = pages.filter((p) => p.name !== void 0 && (
|
|
1481
|
+
const pagesForDataRoutes = pages.filter((p) => p.name !== void 0 && unlinked(p.name));
|
|
1070
1482
|
const dataRoutes = routeGenerator.generateDataRoutes(pagesForDataRoutes, apiBaseUrl, !!options.disablePageLocales);
|
|
1071
1483
|
addPrerenderRoutes(dataRoutes);
|
|
1072
1484
|
};
|
|
1073
1485
|
nuxt.hook("pages:resolved", (pages) => {
|
|
1074
|
-
const pagesNames = pages.map((page) => page.name).filter((name) => name !== void 0 && (
|
|
1486
|
+
const pagesNames = pages.map((page) => page.name).filter((name) => name !== void 0 && unlinked(name));
|
|
1075
1487
|
if (!options.disableWatcher) {
|
|
1076
|
-
routeGenerator.ensureTranslationFilesExist(pagesNames,
|
|
1488
|
+
routeGenerator.ensureTranslationFilesExist(pagesNames, translationDirName, nuxt.options.rootDir, options.disablePageLocales);
|
|
1077
1489
|
}
|
|
1078
1490
|
addDataRoutes(pages);
|
|
1491
|
+
routeNames = pageNames(pages).filter(unlinked);
|
|
1492
|
+
staticPaths = staticPagePaths(pages);
|
|
1079
1493
|
routeGenerator.extendPages(pages);
|
|
1494
|
+
generatedPages = pages;
|
|
1080
1495
|
});
|
|
1081
1496
|
if (options.disablePageLocales) {
|
|
1082
1497
|
nuxt.hook("build:before", () => addDataRoutes([]));
|
|
1083
1498
|
}
|
|
1084
|
-
addVitePlugin(
|
|
1085
|
-
createDefineI18nRoutePlugin({
|
|
1086
|
-
buildDir: nuxt.options.buildDir,
|
|
1087
|
-
rootDirs
|
|
1088
|
-
}).vite()
|
|
1089
|
-
);
|
|
1090
1499
|
const { pluralTemplate, strategyTemplate, configTemplate } = templates;
|
|
1091
1500
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
1092
1501
|
nitroConfig.alias = nitroConfig.alias || {};
|
|
1093
1502
|
nitroConfig.alias["#i18n-internal/plural"] = pluralTemplate.dst;
|
|
1094
1503
|
nitroConfig.alias["#i18n-internal/strategy"] = strategyTemplate.dst;
|
|
1095
1504
|
nitroConfig.alias["#i18n-internal/config"] = configTemplate.dst;
|
|
1505
|
+
nitroConfig.externals = nitroConfig.externals || {};
|
|
1506
|
+
nitroConfig.externals.inline = [...nitroConfig.externals.inline || [], pluralTemplate.dst, strategyTemplate.dst];
|
|
1096
1507
|
nitroConfig.alias["#i18n-internal/payload-source"] = resolver.resolve("./runtime/server/payload-source.fs");
|
|
1097
1508
|
nitroConfig.plugins = nitroConfig.plugins || [];
|
|
1509
|
+
nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/failed-read"));
|
|
1510
|
+
if (!nuxt.options.dev) nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/payload-cache"));
|
|
1098
1511
|
if (nuxt.options.dev && (options.hmr ?? true)) {
|
|
1099
1512
|
nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/watcher.dev"));
|
|
1100
1513
|
}
|
|
1101
1514
|
nitroConfig.routeRules = nitroConfig.routeRules || {};
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1515
|
+
const { file: cacheControl } = resolveTranslationPayloadCachePolicy({
|
|
1516
|
+
httpCacheDuration: options.httpCacheDuration,
|
|
1517
|
+
httpCacheControl: options.httpCacheControl,
|
|
1518
|
+
dateBuild: fullConfig.dateBuild,
|
|
1519
|
+
live: isLiveDictionary(options, translationPayloads.mode, rawServerHost)
|
|
1520
|
+
});
|
|
1105
1521
|
nitroConfig.routeRules[`/${apiBaseUrl}/**`] = {
|
|
1106
1522
|
...nitroConfig.routeRules[`/${apiBaseUrl}/**`] || {},
|
|
1107
|
-
...buildTranslationPayloadRouteRule({ dev: nuxt.options.dev,
|
|
1523
|
+
...buildTranslationPayloadRouteRule({ dev: nuxt.options.dev, cacheControl })
|
|
1108
1524
|
};
|
|
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
|
-
});
|
|
1525
|
+
const noDocuments = Boolean(buildConfig.hashMode) && options.strategy === "prefix";
|
|
1526
|
+
localizeRouteRules(
|
|
1527
|
+
nuxt.options.routeRules || {},
|
|
1528
|
+
nitroConfig.routeRules,
|
|
1529
|
+
routeGenerator,
|
|
1530
|
+
(path, rule) => {
|
|
1531
|
+
logger.debug(`Replicated routeRule for ${path}: ${JSON.stringify(rule)}`);
|
|
1532
|
+
},
|
|
1533
|
+
noDocuments
|
|
1534
|
+
);
|
|
1535
|
+
if (routeGenerator.locales.some((locale) => locale.baseUrl)) {
|
|
1536
|
+
for (const [path, rule] of Object.entries(nitroConfig.routeRules)) {
|
|
1537
|
+
nitroConfig.routeRules[path] = varyRouteCacheByHost(rule, Boolean(options.trustForwardedHost));
|
|
1131
1538
|
}
|
|
1132
1539
|
}
|
|
1133
1540
|
nitroConfig.handlers = nitroConfig.handlers || [];
|
|
@@ -1137,10 +1544,30 @@ declare module '#i18n-internal/payload-source' {
|
|
|
1137
1544
|
});
|
|
1138
1545
|
});
|
|
1139
1546
|
nuxt.hook("nitro:init", (nitro) => {
|
|
1140
|
-
if (
|
|
1547
|
+
if (buildConfig.domain) {
|
|
1548
|
+
let localize;
|
|
1549
|
+
nitro.hooks.hook("prerender:generate", async (route) => {
|
|
1550
|
+
localize ??= await createBuildStrategy();
|
|
1551
|
+
if (route.route !== "/" && localize.isPage(route.route) && !localize.isServed(route.route)) route.skip = true;
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
isSSG = Boolean(nitro.options.static);
|
|
1555
|
+
if (isSSG) {
|
|
1556
|
+
if (!generateDomain && routeGenerator.locales.some((locale) => locale.baseUrl)) {
|
|
1557
|
+
logger.warn(
|
|
1558
|
+
"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."
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
const staticWarning = getStaticPayloadWarning({ translationPayloads, apiBaseClientHost });
|
|
1562
|
+
if (staticWarning) logger.warn(staticWarning);
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1141
1565
|
const readsPublicDir = shouldReadPayloadsFromPublicDir(nitro.options);
|
|
1142
1566
|
if (!readsPublicDir) {
|
|
1143
1567
|
nitro.options.alias["#i18n-internal/payload-source"] = resolver.resolve("./runtime/server/payload-source.assets");
|
|
1568
|
+
if (options.cacheStorage && !apiBaseServerHost) {
|
|
1569
|
+
logger.info("cacheStorage: dictionaries are embedded on this preset, so the shared cache only applies with apiBaseServerHost.");
|
|
1570
|
+
}
|
|
1144
1571
|
}
|
|
1145
1572
|
if (shouldRegisterNitroServerAssets(translationPayloads, readsPublicDir)) {
|
|
1146
1573
|
nitro.options.serverAssets.push({
|
|
@@ -1148,11 +1575,6 @@ declare module '#i18n-internal/payload-source' {
|
|
|
1148
1575
|
// Same layout as public (`mode`): premerged matrix or compact source.
|
|
1149
1576
|
dir: publicAssetsDir
|
|
1150
1577
|
});
|
|
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
1578
|
}
|
|
1157
1579
|
if (readsPublicDir && translationPayloads.serverAssets && !translationPayloads.publicAssets) {
|
|
1158
1580
|
logger.info("translationPayloads.serverAssets: on Node, payloads are served from public/ (publicAssets copy enabled for SSR).");
|
|
@@ -1162,11 +1584,22 @@ declare module '#i18n-internal/payload-source' {
|
|
|
1162
1584
|
const isProd = nuxt.options.dev === false;
|
|
1163
1585
|
const readsPublicDir = shouldReadPayloadsFromPublicDir(nitro.options);
|
|
1164
1586
|
const copyPublicPayloads = shouldCopyTranslationPayloadsToPublic(translationPayloads, readsPublicDir);
|
|
1587
|
+
if (isProd && !isSSG && shouldRegisterNitroServerAssets(translationPayloads, readsPublicDir)) {
|
|
1588
|
+
const sizeWarning = getEmbeddedPayloadSizeWarning(nitro.options.preset, publicAssetsDir, translationPayloads.mode);
|
|
1589
|
+
if (sizeWarning) logger.warn(sizeWarning);
|
|
1590
|
+
}
|
|
1165
1591
|
if (isProd && copyPublicPayloads) {
|
|
1166
1592
|
const publicDir = resolveTranslationPayloadPublicDir(nitro.options.output.publicDir, options, apiBaseUrl);
|
|
1167
1593
|
try {
|
|
1168
1594
|
if (existsSync(publicAssetsDir)) {
|
|
1169
|
-
|
|
1595
|
+
const filter = domainPayloadFilter({
|
|
1596
|
+
mode: translationPayloads.mode,
|
|
1597
|
+
root: publicAssetsDir,
|
|
1598
|
+
served: buildConfig.locales,
|
|
1599
|
+
linkLocales: buildConfig.linkLocales,
|
|
1600
|
+
fallbackLocale: options.fallbackLocale
|
|
1601
|
+
});
|
|
1602
|
+
fs__default.cpSync(publicAssetsDir, publicDir, { recursive: true, filter });
|
|
1170
1603
|
logger.log(`Translation payloads copied to public directory`);
|
|
1171
1604
|
const compressed = compressTranslationPayloads(publicDir, nitro.options.compressPublicAssets);
|
|
1172
1605
|
if (compressed > 0) {
|
|
@@ -1184,73 +1617,21 @@ declare module '#i18n-internal/payload-source' {
|
|
|
1184
1617
|
if (isNoPrefixStrategy(options.strategy)) {
|
|
1185
1618
|
return;
|
|
1186
1619
|
}
|
|
1187
|
-
const routesSet = prerenderRoutes.routes;
|
|
1188
1620
|
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);
|
|
1621
|
+
const { added, removed } = seedPrerenderRoutes(prerenderRoutes.routes, {
|
|
1622
|
+
// the same router and strategy the app runs: custom paths, `false` pages and routeLocales as generated
|
|
1623
|
+
localize: await createBuildStrategy(),
|
|
1624
|
+
locales: buildConfig.locales,
|
|
1625
|
+
staticPaths: isSSG ? staticPaths : null,
|
|
1626
|
+
strategy: options.strategy,
|
|
1627
|
+
prerendered: (route) => routeRules[route]?.prerender !== false,
|
|
1628
|
+
isInternal: (route) => isInternalPath(route, options.excludePatterns, apiBaseUrl)
|
|
1629
|
+
});
|
|
1630
|
+
if (added.length > 0) {
|
|
1631
|
+
logger.debug("[i18n prerender:routes] added localized routes:", [...added].sort().join(", "));
|
|
1240
1632
|
}
|
|
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
|
-
}
|
|
1633
|
+
if (removed.length > 0) {
|
|
1634
|
+
logger.info(`[i18n prerender:routes] removed from prerender list (no locale prefix): ${removed.join(", ")}`);
|
|
1254
1635
|
}
|
|
1255
1636
|
});
|
|
1256
1637
|
const devtoolsOptions = nuxt.options.devtools;
|