1975 lines
70 KiB
JavaScript
1975 lines
70 KiB
JavaScript
import createDebug from 'debug';
|
|
import { tryResolveModule, resolvePath, useNuxt, useLogger, extendPages, addWebpackPlugin, extendWebpackConfig, addVitePlugin, extendViteConfig, createResolver, addServerPlugin, defineNuxtModule, addPlugin, addTemplate, addTypeTemplate, addComponent, addImports } from '@nuxt/kit';
|
|
import { resolve, parse, join as join$1, relative, isAbsolute, dirname } from 'pathe';
|
|
import { defu } from 'defu';
|
|
import { isString, isArray, isRegExp, isFunction, isObject, assign } from '@intlify/shared';
|
|
import { parse as parse$2, compileScript } from '@vue/compiler-sfc';
|
|
import { walk } from 'estree-walker';
|
|
import MagicString from 'magic-string';
|
|
import { readFileSync as readFileSync$1, promises, constants } from 'node:fs';
|
|
import { createHash } from 'node:crypto';
|
|
import { parse as parse$1 } from '@babel/parser';
|
|
import { genSafeVariableName, genDynamicImport, genImport } from 'knitwork';
|
|
import { encodePath, parseURL, parseQuery, withQuery } from 'ufo';
|
|
import { transform } from 'sucrase';
|
|
import { resolveModuleExportNames } from 'mlly';
|
|
import yamlPlugin from '@rollup/plugin-yaml';
|
|
import json5Plugin from '@miyaneee/rollup-plugin-json5';
|
|
import VueI18nWebpackPlugin from '@intlify/unplugin-vue-i18n/webpack';
|
|
import VueI18nVitePlugin from '@intlify/unplugin-vue-i18n/vite';
|
|
import { createUnplugin } from 'unplugin';
|
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
|
|
const NUXT_I18N_MODULE_ID = "@nuxtjs/i18n";
|
|
const VUE_I18N_PKG = "vue-i18n";
|
|
const SHARED_PKG = "@intlify/shared";
|
|
const MESSAGE_COMPILER_PKG = "@intlify/message-compiler";
|
|
const CORE_PKG = "@intlify/core";
|
|
const CORE_BASE_PKG = "@intlify/core-base";
|
|
const H3_PKG = "@intlify/h3";
|
|
const UTILS_PKG = "@intlify/utils";
|
|
const UTILS_H3_PKG = "@intlify/utils/h3";
|
|
const UFO_PKG = "ufo";
|
|
const IS_HTTPS_PKG = "is-https";
|
|
const STRATEGY_PREFIX_EXCEPT_DEFAULT = "prefix_except_default";
|
|
const DEFAULT_DYNAMIC_PARAMS_KEY = "nuxtI18n";
|
|
const DEFAULT_COOKIE_KEY = "i18n_redirected";
|
|
const SWITCH_LOCALE_PATH_LINK_IDENTIFIER = "nuxt-i18n-slp";
|
|
const DEFAULT_OPTIONS = {
|
|
// TODO: set to default 'i18n' before v9 release
|
|
restructureDir: void 0,
|
|
experimental: {
|
|
localeDetector: "",
|
|
switchLocalePathLinkSSR: false,
|
|
autoImportTranslationFunctions: false
|
|
},
|
|
bundle: {
|
|
compositionOnly: true,
|
|
runtimeOnly: false,
|
|
fullInstall: true,
|
|
dropMessageCompiler: false
|
|
},
|
|
compilation: {
|
|
jit: true,
|
|
strictMessage: true,
|
|
escapeHtml: false
|
|
},
|
|
customBlocks: {
|
|
defaultSFCLang: "json",
|
|
globalSFCScope: false
|
|
},
|
|
vueI18n: "",
|
|
locales: [],
|
|
defaultLocale: "",
|
|
defaultDirection: "ltr",
|
|
routesNameSeparator: "___",
|
|
trailingSlash: false,
|
|
defaultLocaleRouteNameSuffix: "default",
|
|
strategy: STRATEGY_PREFIX_EXCEPT_DEFAULT,
|
|
lazy: false,
|
|
// TODO: set to default 'locales' before v9 release
|
|
langDir: null,
|
|
rootRedirect: void 0,
|
|
detectBrowserLanguage: {
|
|
alwaysRedirect: false,
|
|
cookieCrossOrigin: false,
|
|
cookieDomain: null,
|
|
cookieKey: DEFAULT_COOKIE_KEY,
|
|
cookieSecure: false,
|
|
fallbackLocale: "",
|
|
redirectOn: "root",
|
|
useCookie: true
|
|
},
|
|
differentDomains: false,
|
|
baseUrl: "",
|
|
dynamicRouteParams: false,
|
|
customRoutes: "page",
|
|
pages: {},
|
|
skipSettingLocaleOnNavigate: false,
|
|
types: "composition",
|
|
debug: false,
|
|
parallelPlugin: false,
|
|
multiDomainLocales: false
|
|
};
|
|
const NUXT_I18N_TEMPLATE_OPTIONS_KEY = "i18n.options.mjs";
|
|
const NUXT_I18N_COMPOSABLE_DEFINE_ROUTE = "defineI18nRoute";
|
|
const NUXT_I18N_COMPOSABLE_DEFINE_LOCALE = "defineI18nLocale";
|
|
const NUXT_I18N_COMPOSABLE_DEFINE_CONFIG = "defineI18nConfig";
|
|
const NUXT_I18N_COMPOSABLE_DEFINE_LOCALE_DETECTOR = "defineI18nLocaleDetector";
|
|
const TS_EXTENSIONS = [".ts", ".cts", ".mts"];
|
|
const JS_EXTENSIONS = [".js", ".cjs", ".mjs"];
|
|
const EXECUTABLE_EXTENSIONS = [...JS_EXTENSIONS, ...TS_EXTENSIONS];
|
|
const NULL_HASH = "00000000";
|
|
|
|
const debug$b = createDebug("@nuxtjs/i18n:alias");
|
|
async function setupAlias(nuxt, options) {
|
|
const runtimeOnly = options.bundle?.runtimeOnly;
|
|
const modules = {};
|
|
modules[VUE_I18N_PKG] = nuxt.options.dev || nuxt.options._prepare ? `${VUE_I18N_PKG}/dist/vue-i18n.mjs` : `${VUE_I18N_PKG}/dist/vue-i18n${runtimeOnly ? ".runtime" : ""}.mjs`;
|
|
modules[SHARED_PKG] = `${SHARED_PKG}/dist/shared.mjs`;
|
|
modules[MESSAGE_COMPILER_PKG] = `${MESSAGE_COMPILER_PKG}/dist/message-compiler.mjs`;
|
|
modules[CORE_BASE_PKG] = `${CORE_BASE_PKG}/dist/core-base.mjs`;
|
|
modules[CORE_PKG] = `${CORE_PKG}/dist/core.node.mjs`;
|
|
modules[UTILS_H3_PKG] = `${UTILS_PKG}/dist/h3.mjs`;
|
|
modules[UFO_PKG] = UFO_PKG;
|
|
modules[IS_HTTPS_PKG] = IS_HTTPS_PKG;
|
|
const moduleDirs = nuxt.options.modulesDir || [];
|
|
const enhancedModulesDirs = [...moduleDirs, ...moduleDirs.map((dir) => `${dir}/${NUXT_I18N_MODULE_ID}/node_modules`)];
|
|
for (const [moduleName, moduleFile] of Object.entries(modules)) {
|
|
const module = await tryResolveModule(moduleFile, enhancedModulesDirs);
|
|
if (!module)
|
|
throw new Error(`Could not resolve module "${moduleFile}"`);
|
|
nuxt.options.alias[moduleName] = module;
|
|
nuxt.options.build.transpile.push(moduleName);
|
|
debug$b(`${moduleName} alias`, nuxt.options.alias[moduleName]);
|
|
}
|
|
}
|
|
|
|
function formatMessage(message) {
|
|
return `[${NUXT_I18N_MODULE_ID}]: ${message}`;
|
|
}
|
|
function castArray(value) {
|
|
return Array.isArray(value) ? value : [value];
|
|
}
|
|
function normalizeIncludingLocales(locales) {
|
|
return (castArray(locales) ?? []).filter(isString);
|
|
}
|
|
function filterLocales(options, nuxt) {
|
|
const project = getLayerI18n(nuxt.options._layers[0]);
|
|
const includingLocales = normalizeIncludingLocales(project?.bundle?.onlyLocales);
|
|
if (!includingLocales.length) {
|
|
return;
|
|
}
|
|
options.locales = options.locales.filter((locale) => {
|
|
const code = isString(locale) ? locale : locale.code;
|
|
return includingLocales.includes(code);
|
|
});
|
|
}
|
|
function getNormalizedLocales(locales) {
|
|
locales = locales || [];
|
|
const normalized = [];
|
|
for (const locale of locales) {
|
|
if (isString(locale)) {
|
|
normalized.push({ code: locale, language: locale });
|
|
} else {
|
|
normalized.push(locale);
|
|
}
|
|
}
|
|
return normalized;
|
|
}
|
|
const IMPORT_ID_CACHES = /* @__PURE__ */ new Map();
|
|
const normalizeWithUnderScore = (name) => name.replace(/-/g, "_").replace(/\./g, "_").replace(/\//g, "_");
|
|
function convertToImportId(file) {
|
|
if (IMPORT_ID_CACHES.has(file)) {
|
|
return IMPORT_ID_CACHES.get(file);
|
|
}
|
|
const { dir, base } = parse(file);
|
|
const id = normalizeWithUnderScore(`${dir}/${base}`);
|
|
IMPORT_ID_CACHES.set(file, id);
|
|
return id;
|
|
}
|
|
async function resolveLocales(srcDir, locales, buildDir) {
|
|
const files = await Promise.all(locales.flatMap((x) => getLocalePaths(x)).map((x) => resolve(srcDir, x)));
|
|
const find = (f) => files.find((file) => file === resolve(srcDir, f));
|
|
const localesResolved = [];
|
|
for (const { file, ...locale } of locales) {
|
|
const resolved = { ...locale, files: [], meta: [] };
|
|
const files2 = getLocaleFiles(locale);
|
|
for (const f of files2) {
|
|
const filePath = find(f.path) ?? "";
|
|
const localeType = getLocaleType(filePath);
|
|
const isCached = filePath ? localeType !== "dynamic" : true;
|
|
const parsed = parse(filePath);
|
|
const importKey = join$1(parsed.root, parsed.dir, parsed.base);
|
|
const key = genSafeVariableName(`locale_${convertToImportId(importKey)}`);
|
|
const metaFile = {
|
|
path: filePath,
|
|
loadPath: relative(buildDir, filePath),
|
|
type: localeType,
|
|
hash: getHash(filePath),
|
|
parsed,
|
|
key,
|
|
file: {
|
|
path: f.path,
|
|
cache: f.cache ?? isCached
|
|
}
|
|
};
|
|
resolved.meta.push(metaFile);
|
|
resolved.files.push(metaFile.file);
|
|
}
|
|
localesResolved.push(resolved);
|
|
}
|
|
return localesResolved;
|
|
}
|
|
function getLocaleType(path) {
|
|
const ext = parse(path).ext;
|
|
if (EXECUTABLE_EXTENSIONS.includes(ext)) {
|
|
const code = readCode(path, ext);
|
|
const parsed = parseCode(code, path);
|
|
const analyzed = scanProgram(parsed.program);
|
|
if (analyzed === "object") {
|
|
return "static";
|
|
} else if (analyzed === "function" || analyzed === "arrow-function") {
|
|
return "dynamic";
|
|
} else {
|
|
return "unknown";
|
|
}
|
|
} else {
|
|
return "static";
|
|
}
|
|
}
|
|
const PARSE_CODE_CACHES = /* @__PURE__ */ new Map();
|
|
function parseCode(code, path) {
|
|
if (PARSE_CODE_CACHES.has(path)) {
|
|
return PARSE_CODE_CACHES.get(path);
|
|
}
|
|
const parsed = parse$1(code, {
|
|
allowImportExportEverywhere: true,
|
|
sourceType: "module"
|
|
});
|
|
PARSE_CODE_CACHES.set(path, parsed);
|
|
return parsed;
|
|
}
|
|
function scanProgram(program) {
|
|
let ret = false;
|
|
let variableDeclaration;
|
|
for (const node of program.body) {
|
|
if (node.type !== "ExportDefaultDeclaration")
|
|
continue;
|
|
if (node.declaration.type === "ObjectExpression") {
|
|
ret = "object";
|
|
break;
|
|
}
|
|
if (node.declaration.type === "Identifier") {
|
|
variableDeclaration = node.declaration;
|
|
break;
|
|
}
|
|
if (node.declaration.type === "CallExpression" && node.declaration.callee.type === "Identifier") {
|
|
const [fnNode] = node.declaration.arguments;
|
|
if (fnNode.type === "FunctionExpression") {
|
|
ret = "function";
|
|
break;
|
|
}
|
|
if (fnNode.type === "ArrowFunctionExpression") {
|
|
ret = "arrow-function";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (variableDeclaration) {
|
|
for (const node of program.body) {
|
|
if (node.type !== "VariableDeclaration")
|
|
continue;
|
|
for (const decl of node.declarations) {
|
|
if (decl.type !== "VariableDeclarator")
|
|
continue;
|
|
if (decl.init == null)
|
|
continue;
|
|
if ("name" in decl.id === false || decl.id.name !== variableDeclaration.name)
|
|
continue;
|
|
if (decl.init.type === "ObjectExpression") {
|
|
ret = "object";
|
|
break;
|
|
}
|
|
if (decl.init.type === "CallExpression" && decl.init.callee.type === "Identifier") {
|
|
const [fnNode] = decl.init.arguments;
|
|
if (fnNode.type === "FunctionExpression") {
|
|
ret = "function";
|
|
break;
|
|
}
|
|
if (fnNode.type === "ArrowFunctionExpression") {
|
|
ret = "arrow-function";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
function readCode(absolutePath, ext) {
|
|
let code = readFileSync(absolutePath);
|
|
if (TS_EXTENSIONS.includes(ext)) {
|
|
const out = transform(code, {
|
|
transforms: ["typescript", "jsx"],
|
|
keepUnusedImports: true
|
|
});
|
|
code = out.code;
|
|
}
|
|
return code;
|
|
}
|
|
function readFileSync(path) {
|
|
return readFileSync$1(path, { encoding: "utf-8" });
|
|
}
|
|
async function isExists(path) {
|
|
try {
|
|
await promises.access(path, constants.F_OK);
|
|
return true;
|
|
} catch (_e) {
|
|
return false;
|
|
}
|
|
}
|
|
async function resolveVueI18nConfigInfo(rootDir, configPath = "i18n.config", buildDir = useNuxt().options.buildDir) {
|
|
const configPathInfo = {
|
|
relativeBase: relative(buildDir, rootDir),
|
|
relative: configPath,
|
|
absolute: "",
|
|
rootDir,
|
|
hash: NULL_HASH,
|
|
type: "unknown",
|
|
meta: {
|
|
path: "",
|
|
loadPath: "",
|
|
type: "unknown",
|
|
hash: NULL_HASH,
|
|
key: "",
|
|
parsed: { base: "", dir: "", ext: "", name: "", root: "" }
|
|
}
|
|
};
|
|
const absolutePath = await resolvePath(configPathInfo.relative, { cwd: rootDir, extensions: EXECUTABLE_EXTENSIONS });
|
|
if (!await isExists(absolutePath))
|
|
return void 0;
|
|
const parsed = parse(absolutePath);
|
|
const loadPath = join$1(configPathInfo.relativeBase, relative(rootDir, absolutePath));
|
|
configPathInfo.absolute = absolutePath;
|
|
configPathInfo.type = getLocaleType(absolutePath);
|
|
configPathInfo.hash = getHash(loadPath);
|
|
const key = `${normalizeWithUnderScore(configPathInfo.relative)}_${configPathInfo.hash}`;
|
|
configPathInfo.meta = {
|
|
path: absolutePath,
|
|
type: configPathInfo.type,
|
|
hash: configPathInfo.hash,
|
|
loadPath,
|
|
parsed,
|
|
key
|
|
};
|
|
return configPathInfo;
|
|
}
|
|
function toCode(code) {
|
|
if (code === null) {
|
|
return `null`;
|
|
}
|
|
if (code === void 0) {
|
|
return `undefined`;
|
|
}
|
|
if (isString(code)) {
|
|
return JSON.stringify(code);
|
|
}
|
|
if (isRegExp(code) && code.toString) {
|
|
return code.toString();
|
|
}
|
|
if (isFunction(code) && code.toString) {
|
|
return `(${code.toString().replace(new RegExp(`^${code.name}`), "function ")})`;
|
|
}
|
|
if (isArray(code)) {
|
|
return `[${code.map((c) => toCode(c)).join(`,`)}]`;
|
|
}
|
|
if (isObject(code)) {
|
|
return stringifyObj(code);
|
|
}
|
|
return code + ``;
|
|
}
|
|
function stringifyObj(obj) {
|
|
return `Object({${Object.entries(obj).map(([key, value]) => `${JSON.stringify(key)}:${toCode(value)}`).join(`,`)}})`;
|
|
}
|
|
const PARAM_CHAR_RE = /[\w\d_.]/;
|
|
function parseSegment(segment) {
|
|
let state = 0 /* initial */;
|
|
let i = 0;
|
|
let buffer = "";
|
|
const tokens = [];
|
|
function consumeBuffer() {
|
|
if (!buffer) {
|
|
return;
|
|
}
|
|
if (state === 0 /* initial */) {
|
|
throw new Error("wrong state");
|
|
}
|
|
tokens.push({
|
|
type: state === 1 /* static */ ? 0 /* static */ : state === 2 /* dynamic */ ? 1 /* dynamic */ : state === 3 /* optional */ ? 2 /* optional */ : 3 /* catchall */,
|
|
value: buffer
|
|
});
|
|
buffer = "";
|
|
}
|
|
while (i < segment.length) {
|
|
const c = segment[i];
|
|
switch (state) {
|
|
case 0 /* initial */:
|
|
buffer = "";
|
|
if (c === "[") {
|
|
state = 2 /* dynamic */;
|
|
} else {
|
|
i--;
|
|
state = 1 /* static */;
|
|
}
|
|
break;
|
|
case 1 /* static */:
|
|
if (c === "[") {
|
|
consumeBuffer();
|
|
state = 2 /* dynamic */;
|
|
} else {
|
|
buffer += c;
|
|
}
|
|
break;
|
|
case 4 /* catchall */:
|
|
case 2 /* dynamic */:
|
|
case 3 /* optional */:
|
|
if (buffer === "...") {
|
|
buffer = "";
|
|
state = 4 /* catchall */;
|
|
}
|
|
if (c === "[" && state === 2 /* dynamic */) {
|
|
state = 3 /* optional */;
|
|
}
|
|
if (c === "]" && (state !== 3 /* optional */ || segment[i - 1] === "]")) {
|
|
if (!buffer) {
|
|
throw new Error("Empty param");
|
|
} else {
|
|
consumeBuffer();
|
|
}
|
|
state = 0 /* initial */;
|
|
} else if (PARAM_CHAR_RE.test(c)) {
|
|
buffer += c;
|
|
} else ;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
if (state === 2 /* dynamic */) {
|
|
throw new Error(`Unfinished param "${buffer}"`);
|
|
}
|
|
consumeBuffer();
|
|
return tokens;
|
|
}
|
|
const getLocalePaths = (locale) => {
|
|
return getLocaleFiles(locale).map((x) => x.path);
|
|
};
|
|
const getLocaleFiles = (locale) => {
|
|
if (locale.file != null) {
|
|
return [locale.file].map((x) => isString(x) ? { path: x, cache: void 0 } : x);
|
|
}
|
|
if (locale.files != null) {
|
|
return [...locale.files].map((x) => isString(x) ? { path: x, cache: void 0 } : x);
|
|
}
|
|
return [];
|
|
};
|
|
function resolveRelativeLocales(locale, config) {
|
|
const fileEntries = getLocaleFiles(locale);
|
|
return fileEntries.map((file) => ({
|
|
path: resolve(useNuxt().options.rootDir, resolve(config.langDir ?? "", file.path)),
|
|
cache: file.cache
|
|
}));
|
|
}
|
|
const mergeConfigLocales = (configs, baseLocales = []) => {
|
|
const mergedLocales = /* @__PURE__ */ new Map();
|
|
const deprecatedIsolocales = /* @__PURE__ */ new Set();
|
|
for (const locale of baseLocales) {
|
|
mergedLocales.set(locale.code, locale);
|
|
}
|
|
for (const config of configs) {
|
|
if (config.locales == null)
|
|
continue;
|
|
for (const locale of config.locales) {
|
|
const code = isString(locale) ? locale : locale.code;
|
|
const merged = mergedLocales.get(code);
|
|
if (typeof locale === "string") {
|
|
mergedLocales.set(code, merged ?? { language: code, code });
|
|
continue;
|
|
}
|
|
const resolvedFiles = resolveRelativeLocales(locale, config);
|
|
delete locale.file;
|
|
if (locale.iso) {
|
|
deprecatedIsolocales.add(locale.iso);
|
|
locale.language = locale.iso;
|
|
delete locale.iso;
|
|
}
|
|
if (merged != null) {
|
|
merged.files ??= [];
|
|
merged.files.unshift(...resolvedFiles);
|
|
mergedLocales.set(code, {
|
|
...locale,
|
|
...merged
|
|
});
|
|
continue;
|
|
}
|
|
mergedLocales.set(code, { ...locale, files: resolvedFiles });
|
|
}
|
|
}
|
|
if (deprecatedIsolocales.size) {
|
|
console.warn(
|
|
`Locales ${[...deprecatedIsolocales].join(", ")} uses deprecated \`iso\` property, this will be replaced with \`language\` in v9`
|
|
);
|
|
}
|
|
return Array.from(mergedLocales.values());
|
|
};
|
|
const mergeI18nModules = async (options, nuxt) => {
|
|
if (options)
|
|
options.i18nModules = [];
|
|
const registerI18nModule = (config) => {
|
|
if (config.langDir == null)
|
|
return;
|
|
options?.i18nModules?.push(config);
|
|
};
|
|
await nuxt.callHook("i18n:registerModule", registerI18nModule);
|
|
const modules = options?.i18nModules ?? [];
|
|
if (modules.length > 0) {
|
|
const baseLocales = [];
|
|
const layerLocales = options.locales ?? [];
|
|
for (const locale of layerLocales) {
|
|
if (typeof locale !== "object")
|
|
continue;
|
|
baseLocales.push({ ...locale, file: void 0, files: getLocaleFiles(locale) });
|
|
}
|
|
const mergedLocales = mergeConfigLocales(modules, baseLocales);
|
|
options.locales = mergedLocales;
|
|
}
|
|
};
|
|
function getRoutePath(tokens) {
|
|
return tokens.reduce((path, token) => {
|
|
return path + (token.type === 2 /* optional */ ? `:${token.value}?` : token.type === 1 /* dynamic */ ? `:${token.value}()` : token.type === 3 /* catchall */ ? `:${token.value}(.*)*` : encodePath(token.value).replace(/:/g, "\\:"));
|
|
}, "/");
|
|
}
|
|
function getHash(text) {
|
|
return createHash("sha256").update(text).digest("hex").substring(0, 8);
|
|
}
|
|
function getLayerI18n(configLayer) {
|
|
const layerInlineOptions = (configLayer.config.modules || []).find(
|
|
(mod) => isArray(mod) && typeof mod[0] === "string" && [NUXT_I18N_MODULE_ID, `${NUXT_I18N_MODULE_ID}-edge`].includes(mod[0])
|
|
)?.[1];
|
|
if (configLayer.config.i18n) {
|
|
return defu(configLayer.config.i18n, layerInlineOptions);
|
|
}
|
|
return layerInlineOptions;
|
|
}
|
|
const applyOptionOverrides = (options, nuxt) => {
|
|
const project = nuxt.options._layers[0];
|
|
const { overrides, ...mergedOptions } = options;
|
|
if (overrides) {
|
|
delete options.overrides;
|
|
project.config.i18n = defu(overrides, project.config.i18n);
|
|
Object.assign(options, defu(overrides, mergedOptions));
|
|
}
|
|
};
|
|
function toArray(value) {
|
|
return Array.isArray(value) ? value : [value];
|
|
}
|
|
|
|
const join = (...args) => args.filter(Boolean).join("");
|
|
function prefixLocalizedRoute(localizeOptions, options, extra = false) {
|
|
const isDefaultLocale = localizeOptions.locale === (localizeOptions.defaultLocale ?? "");
|
|
const isChildWithRelativePath = localizeOptions.parent != null && !localizeOptions.path.startsWith("/");
|
|
return !extra && !isChildWithRelativePath && options.strategy !== "no_prefix" && // skip default locale if strategy is 'prefix_except_default'
|
|
!(isDefaultLocale && options.strategy === "prefix_except_default");
|
|
}
|
|
function adjustRoutePathForTrailingSlash(localized, trailingSlash) {
|
|
const isChildWithRelativePath = localized.parent != null && !localized.path.startsWith("/");
|
|
return localized.path.replace(/\/+$/, "") + (trailingSlash ? "/" : "") || (isChildWithRelativePath ? "" : "/");
|
|
}
|
|
function shouldLocalizeRoutes(options) {
|
|
if (options.strategy === "no_prefix") {
|
|
if (!options.differentDomains)
|
|
return false;
|
|
const domains = /* @__PURE__ */ new Set();
|
|
for (const locale of options.locales || []) {
|
|
if (typeof locale === "string")
|
|
continue;
|
|
if (locale.domain) {
|
|
if (domains.has(locale.domain)) {
|
|
console.error(
|
|
`Cannot use \`strategy: no_prefix\` when using multiple locales on the same domain - found multiple entries with ${locale.domain}`
|
|
);
|
|
return false;
|
|
}
|
|
domains.add(locale.domain);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function localizeRoutes(routes, options) {
|
|
if (!shouldLocalizeRoutes(options))
|
|
return routes;
|
|
let defaultLocales = [options.defaultLocale ?? ""];
|
|
if (options.differentDomains) {
|
|
const domainDefaults = options.locales.filter((locale) => isObject(locale) ? locale.domainDefault : false).map((locale) => isObject(locale) ? locale.code : locale);
|
|
defaultLocales = defaultLocales.concat(domainDefaults);
|
|
}
|
|
function localizeRoute(route, { locales = [], parent, parentLocalized, extra = false }) {
|
|
if (route.redirect && !route.file) {
|
|
return [route];
|
|
}
|
|
const routeOptions = options.optionsResolver?.(route, locales);
|
|
if (options.optionsResolver != null && routeOptions == null) {
|
|
return [route];
|
|
}
|
|
const componentOptions = {
|
|
// filter locales to prevent child routes from being localized even though they are disabled in the configuration.
|
|
locales: locales.filter((locale) => (routeOptions?.locales ?? locales).includes(locale)),
|
|
paths: {},
|
|
...routeOptions
|
|
};
|
|
const localizedRoutes = [];
|
|
for (const locale of componentOptions.locales) {
|
|
const localized = { ...route, locale, parent };
|
|
const isDefaultLocale = defaultLocales.includes(locale);
|
|
const addDefaultTree = isDefaultLocale && options.strategy === "prefix_and_default" && parent == null && !extra;
|
|
if (addDefaultTree && parent == null && !extra) {
|
|
localizedRoutes.push(...localizeRoute(route, { locales: [locale], extra: true }));
|
|
}
|
|
const nameSegments = [localized.name, options.routesNameSeparator, locale];
|
|
if (extra) {
|
|
nameSegments.push(options.routesNameSeparator, options.defaultLocaleRouteNameSuffix);
|
|
}
|
|
localized.name &&= join(...nameSegments);
|
|
localized.path = componentOptions.paths?.[locale] ?? localized.path;
|
|
const localePrefixable = prefixLocalizedRoute(
|
|
{ defaultLocale: isDefaultLocale ? locale : options.defaultLocale, ...localized },
|
|
options,
|
|
extra
|
|
);
|
|
if (localePrefixable) {
|
|
if (options.multiDomainLocales && (options.strategy === "prefix_except_default" || options.strategy === "prefix_and_default")) {
|
|
localizedRoutes.push({
|
|
...localized,
|
|
name: `${localized.name}___default`
|
|
});
|
|
}
|
|
localized.path = join("/", locale, localized.path);
|
|
if (isDefaultLocale && options.strategy === "prefix" && options.includeUnprefixedFallback) {
|
|
localizedRoutes.push({ ...route, locale, parent });
|
|
}
|
|
}
|
|
if (localized.alias) {
|
|
const aliases = toArray(localized.alias);
|
|
const localizedAliases = [];
|
|
for (const alias of aliases) {
|
|
const aliasPrefixable = prefixLocalizedRoute(
|
|
{ defaultLocale: isDefaultLocale ? locale : options.defaultLocale, ...localized, path: alias },
|
|
options,
|
|
extra
|
|
);
|
|
let localizedAlias = alias;
|
|
if (aliasPrefixable) {
|
|
localizedAlias = join("/", locale, localizedAlias);
|
|
}
|
|
localizedAlias &&= adjustRoutePathForTrailingSlash(
|
|
{ ...localized, path: localizedAlias },
|
|
options.trailingSlash
|
|
);
|
|
localizedAliases.push(localizedAlias);
|
|
}
|
|
localized.alias = localizedAliases;
|
|
}
|
|
localized.path &&= adjustRoutePathForTrailingSlash(localized, options.trailingSlash);
|
|
if (parentLocalized != null) {
|
|
localized.path = localized.path.replace(parentLocalized.path + "/", "");
|
|
}
|
|
localized.children &&= localized.children.flatMap(
|
|
(child) => localizeRoute(child, { locales: [locale], parent: route, parentLocalized: localized, extra })
|
|
);
|
|
localizedRoutes.push(localized);
|
|
}
|
|
return localizedRoutes.flatMap((x) => {
|
|
delete x.parent;
|
|
delete x.locale;
|
|
return x;
|
|
});
|
|
}
|
|
return routes.flatMap(
|
|
(route) => localizeRoute(route, { locales: getNormalizedLocales(options.locales).map((x) => x.code) })
|
|
);
|
|
}
|
|
|
|
const debug$a = createDebug("@nuxtjs/i18n:layers");
|
|
const checkLayerOptions = (_options, nuxt) => {
|
|
const logger = useLogger(NUXT_I18N_MODULE_ID);
|
|
const project = nuxt.options._layers[0];
|
|
const layers = nuxt.options._layers;
|
|
for (const layer of layers) {
|
|
const layerI18n = getLayerI18n(layer);
|
|
if (layerI18n == null)
|
|
continue;
|
|
const configLocation = project.config.rootDir === layer.config.rootDir ? "project layer" : "extended layer";
|
|
const layerHint = `In ${configLocation} (\`${resolve(project.config.rootDir, layer.configFile)}\`) -`;
|
|
try {
|
|
if (layerI18n.langDir) {
|
|
const locales = layerI18n.locales || [];
|
|
if (isString(layerI18n.langDir) && isAbsolute(layerI18n.langDir)) {
|
|
logger.warn(
|
|
`${layerHint} \`langDir\` is set to an absolute path (\`${layerI18n.langDir}\`) but should be set a path relative to \`srcDir\` (\`${layer.config.srcDir}\`). Absolute paths will not work in production, see https://i18n.nuxtjs.org/options/lazy#langdir for more details.`
|
|
);
|
|
}
|
|
for (const locale of locales) {
|
|
if (isString(locale)) {
|
|
throw new Error("When using the `langDir` option the `locales` must be a list of objects.");
|
|
}
|
|
if (!(locale.file || locale.files)) {
|
|
throw new Error(
|
|
`All locales must have the \`file\` or \`files\` property set when using \`langDir\`.
|
|
Found none in:
|
|
${JSON.stringify(locale, null, 2)}.`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (!(err instanceof Error))
|
|
throw err;
|
|
throw new Error(formatMessage(`${layerHint} ${err.message}`));
|
|
}
|
|
}
|
|
};
|
|
const applyLayerOptions = (options, nuxt) => {
|
|
const project = nuxt.options._layers[0];
|
|
const layers = nuxt.options._layers;
|
|
const resolvedLayerPaths = layers.map((l) => resolve(project.config.rootDir, l.config.rootDir));
|
|
debug$a("using layers at paths", resolvedLayerPaths);
|
|
const mergedLocales = mergeLayerLocales(options, nuxt);
|
|
debug$a("merged locales", mergedLocales);
|
|
options.locales = mergedLocales;
|
|
};
|
|
const mergeLayerPages = (analyzer, nuxt) => {
|
|
const project = nuxt.options._layers[0];
|
|
const layers = nuxt.options._layers;
|
|
if (layers.length === 1)
|
|
return;
|
|
for (const l of layers) {
|
|
const lPath = resolve(project.config.rootDir, l.config.srcDir, l.config.dir?.pages ?? "pages");
|
|
debug$a("mergeLayerPages: path ->", lPath);
|
|
analyzer(lPath);
|
|
}
|
|
};
|
|
function resolveI18nDir(layer, i18n, fromRootDir = false) {
|
|
if (i18n.restructureDir) {
|
|
return resolve(layer.config.rootDir, i18n.restructureDir);
|
|
}
|
|
return resolve(layer.config.rootDir, fromRootDir ? "" : layer.config.srcDir);
|
|
}
|
|
function resolveLayerLangDir(layer, i18n) {
|
|
const langDir = i18n.langDir ?? (i18n.restructureDir ? "locales" : "");
|
|
return resolve(resolveI18nDir(layer, i18n), langDir);
|
|
}
|
|
const mergeLayerLocales = (options, nuxt) => {
|
|
debug$a("project layer `lazy` option", options.lazy);
|
|
options.locales ??= [];
|
|
const configs = [];
|
|
for (const layer of nuxt.options._layers) {
|
|
const i18n = getLayerI18n(layer);
|
|
if (i18n?.locales == null)
|
|
continue;
|
|
configs.push({ ...i18n, langDir: resolveLayerLangDir(layer, i18n) });
|
|
}
|
|
const installModuleConfigMap = /* @__PURE__ */ new Map();
|
|
outer:
|
|
for (const locale of options.locales) {
|
|
if (typeof locale === "string")
|
|
continue;
|
|
const files = getLocaleFiles(locale);
|
|
if (files.length === 0)
|
|
continue;
|
|
const langDir = parse(files[0].path).dir;
|
|
const locales = installModuleConfigMap.get(langDir)?.locales ?? [];
|
|
for (const file of files) {
|
|
if (!isAbsolute(file.path))
|
|
continue outer;
|
|
if (configs.find((config) => config.langDir === parse(file.path).dir) != null)
|
|
continue outer;
|
|
}
|
|
locales.push(locale);
|
|
installModuleConfigMap.set(langDir, { langDir, locales });
|
|
}
|
|
configs.unshift(...Array.from(installModuleConfigMap.values()));
|
|
return mergeConfigLocales(configs);
|
|
};
|
|
const getLayerLangPaths = (nuxt) => {
|
|
const langPaths = [];
|
|
for (const layer of nuxt.options._layers) {
|
|
const i18n = getLayerI18n(layer);
|
|
if (!i18n?.restructureDir && i18n?.langDir == null)
|
|
continue;
|
|
langPaths.push(resolveLayerLangDir(layer, i18n));
|
|
}
|
|
return langPaths;
|
|
};
|
|
async function resolveLayerVueI18nConfigInfo(options) {
|
|
const logger = useLogger(NUXT_I18N_MODULE_ID);
|
|
const nuxt = useNuxt();
|
|
const resolveArr = nuxt.options._layers.map(async (layer) => {
|
|
const i18n = getLayerI18n(layer);
|
|
const res = await resolveVueI18nConfigInfo(resolveI18nDir(layer, i18n || {}, true), i18n?.vueI18n);
|
|
if (res == null && i18n?.vueI18n != null) {
|
|
logger.warn(
|
|
`Ignore Vue I18n configuration file does not exist at ${i18n.vueI18n} in ${layer.config.rootDir}. Skipping...`
|
|
);
|
|
return void 0;
|
|
}
|
|
return res;
|
|
});
|
|
const resolved = await Promise.all(resolveArr);
|
|
if (options.vueI18n && isAbsolute(options.vueI18n)) {
|
|
resolved.unshift(await resolveVueI18nConfigInfo(parse(options.vueI18n).dir, options.vueI18n));
|
|
}
|
|
return resolved.filter((x) => x != null);
|
|
}
|
|
|
|
const debug$9 = createDebug("@nuxtjs/i18n:pages");
|
|
function setupPages(options, nuxt) {
|
|
let includeUnprefixedFallback = nuxt.options.ssr === false;
|
|
nuxt.hook("nitro:init", () => {
|
|
debug$9("enable includeUprefixedFallback");
|
|
includeUnprefixedFallback = options.strategy !== "prefix";
|
|
});
|
|
const pagesDir = nuxt.options.dir && nuxt.options.dir.pages ? nuxt.options.dir.pages : "pages";
|
|
const srcDir = nuxt.options.srcDir;
|
|
debug$9(`pagesDir: ${pagesDir}, srcDir: ${srcDir}, trailingSlash: ${options.trailingSlash}`);
|
|
extendPages((pages) => {
|
|
debug$9("pages making ...", pages);
|
|
const ctx = {
|
|
stack: [],
|
|
srcDir,
|
|
pagesDir,
|
|
pages: /* @__PURE__ */ new Map()
|
|
};
|
|
analyzeNuxtPages(ctx, pages);
|
|
const analyzer = (pageDirOverride) => analyzeNuxtPages(ctx, pages, pageDirOverride);
|
|
mergeLayerPages(analyzer, nuxt);
|
|
const localizedPages = localizeRoutes(pages, {
|
|
...options,
|
|
includeUnprefixedFallback,
|
|
optionsResolver: getRouteOptionsResolver(ctx, options)
|
|
});
|
|
const indexPage = pages.find((x) => x.path === "/");
|
|
if (!nuxt.options._generate && options.strategy === "prefix" && indexPage != null) {
|
|
localizedPages.unshift(indexPage);
|
|
}
|
|
if (pages !== localizedPages) {
|
|
pages.splice(0, pages.length);
|
|
pages.unshift(...localizedPages);
|
|
}
|
|
debug$9("... made pages", pages);
|
|
});
|
|
}
|
|
function analyzePagePath(pagePath, parents = 0) {
|
|
const { dir, name } = parse(pagePath);
|
|
if (parents > 0 || dir !== "/") {
|
|
return `${dir.slice(1, dir.length)}/${name}`;
|
|
}
|
|
return name;
|
|
}
|
|
function analyzeNuxtPages(ctx, pages, pageDirOverride) {
|
|
if (pages == null || pages.length === 0)
|
|
return;
|
|
const pagesPath = resolve(ctx.srcDir, pageDirOverride ?? ctx.pagesDir);
|
|
for (const page of pages) {
|
|
if (page.file == null)
|
|
continue;
|
|
const splits = page.file.split(pagesPath);
|
|
const filePath = splits.at(1);
|
|
if (filePath == null)
|
|
continue;
|
|
ctx.pages.set(page, {
|
|
path: analyzePagePath(filePath, ctx.stack.length),
|
|
inRoot: ctx.stack.length === 0
|
|
});
|
|
ctx.stack.push(page.path);
|
|
analyzeNuxtPages(ctx, page.children, pageDirOverride);
|
|
ctx.stack.pop();
|
|
}
|
|
}
|
|
function getRouteOptionsResolver(ctx, options) {
|
|
const { pages, defaultLocale, customRoutes } = options;
|
|
const useConfig = customRoutes === "config";
|
|
debug$9("getRouteOptionsResolver useConfig", useConfig);
|
|
return (route, localeCodes) => {
|
|
const ret = useConfig ? getRouteOptionsFromPages(ctx, route, localeCodes, pages, defaultLocale) : getRouteOptionsFromComponent(route, localeCodes);
|
|
debug$9("getRouteOptionsResolver resolved", route.path, route.name, ret);
|
|
return ret;
|
|
};
|
|
}
|
|
function resolveRoutePath(path) {
|
|
const normalizePath = path.slice(1, path.length);
|
|
const tokens = parseSegment(normalizePath);
|
|
const routePath = getRoutePath(tokens);
|
|
return routePath;
|
|
}
|
|
function getRouteOptionsFromPages(ctx, route, localeCodes, pages, defaultLocale) {
|
|
const options = {
|
|
locales: localeCodes,
|
|
paths: {}
|
|
};
|
|
const pageMeta = ctx.pages.get(route);
|
|
if (pageMeta == null) {
|
|
console.warn(
|
|
formatMessage(`Couldn't find AnalyzedNuxtPageMeta by NuxtPage (${route.path}), so no custom route for it`)
|
|
);
|
|
return options;
|
|
}
|
|
const pageOptions = pageMeta.path ? pages[pageMeta.path] : void 0;
|
|
if (pageOptions === false) {
|
|
return void 0;
|
|
}
|
|
if (!pageOptions) {
|
|
return options;
|
|
}
|
|
options.locales = options.locales.filter((locale) => pageOptions[locale] !== false);
|
|
for (const locale of options.locales) {
|
|
const customLocalePath = pageOptions[locale];
|
|
if (isString(customLocalePath)) {
|
|
options.paths[locale] = resolveRoutePath(customLocalePath);
|
|
continue;
|
|
}
|
|
const customDefaultLocalePath = pageOptions[defaultLocale];
|
|
if (isString(customDefaultLocalePath)) {
|
|
options.paths[locale] = resolveRoutePath(customDefaultLocalePath);
|
|
}
|
|
}
|
|
return options;
|
|
}
|
|
function getRouteOptionsFromComponent(route, localeCodes) {
|
|
debug$9("getRouteOptionsFromComponent", route);
|
|
const file = route.file;
|
|
if (!isString(file)) {
|
|
return void 0;
|
|
}
|
|
const options = {
|
|
locales: localeCodes,
|
|
paths: {}
|
|
};
|
|
const componentOptions = readComponent(file);
|
|
if (componentOptions == null) {
|
|
return options;
|
|
}
|
|
if (componentOptions === false) {
|
|
return void 0;
|
|
}
|
|
options.locales = componentOptions.locales || localeCodes;
|
|
for (const [locale, path] of Object.entries(componentOptions.paths ?? {})) {
|
|
if (isString(path)) {
|
|
options.paths[locale] = resolveRoutePath(path);
|
|
}
|
|
}
|
|
return options;
|
|
}
|
|
function readComponent(target) {
|
|
let options = void 0;
|
|
try {
|
|
const content = readFileSync(target);
|
|
const { descriptor } = parse$2(content);
|
|
if (!content.includes(NUXT_I18N_COMPOSABLE_DEFINE_ROUTE)) {
|
|
return options;
|
|
}
|
|
const desc = compileScript(descriptor, { id: target });
|
|
const { scriptSetupAst, scriptAst } = desc;
|
|
let extract = "";
|
|
const genericSetupAst = scriptSetupAst || scriptAst;
|
|
if (genericSetupAst) {
|
|
const s = new MagicString(desc.loc.source);
|
|
genericSetupAst.forEach((ast) => {
|
|
walk(ast, {
|
|
enter(_node) {
|
|
const node = _node;
|
|
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === NUXT_I18N_COMPOSABLE_DEFINE_ROUTE) {
|
|
const arg = node.arguments[0];
|
|
if (arg.type === "ObjectExpression") {
|
|
if (verifyObjectValue(arg.properties) && arg.start != null && arg.end != null) {
|
|
extract = s.slice(arg.start, arg.end);
|
|
}
|
|
} else if (arg.type === "BooleanLiteral" && arg.start != null && arg.end != null) {
|
|
extract = s.slice(arg.start, arg.end);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
if (extract) {
|
|
options = evalValue(extract);
|
|
}
|
|
} catch (e) {
|
|
console.warn(formatMessage(`Couldn't read component data at ${target}: (${e.message})`));
|
|
}
|
|
return options;
|
|
}
|
|
function verifyObjectValue(properties) {
|
|
let ret = true;
|
|
for (const prop of properties) {
|
|
if (prop.type === "ObjectProperty") {
|
|
if (prop.key.type === "Identifier" && prop.key.name === "locales" || prop.key.type === "StringLiteral" && prop.key.value === "locales") {
|
|
if (prop.value.type === "ArrayExpression") {
|
|
ret = verifyLocalesArrayExpression(prop.value.elements);
|
|
} else {
|
|
console.warn(formatMessage(`'locale' value is required array`));
|
|
ret = false;
|
|
}
|
|
} else if (prop.key.type === "Identifier" && prop.key.name === "paths" || prop.key.type === "StringLiteral" && prop.key.value === "paths") {
|
|
if (prop.value.type === "ObjectExpression") {
|
|
ret = verifyPathsObjectExpress(prop.value.properties);
|
|
} else {
|
|
console.warn(formatMessage(`'paths' value is required object`));
|
|
ret = false;
|
|
}
|
|
}
|
|
} else {
|
|
console.warn(formatMessage(`'defineI18nRoute' is required object`));
|
|
ret = false;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
function verifyPathsObjectExpress(properties) {
|
|
let ret = true;
|
|
for (const prop of properties) {
|
|
if (prop.type === "ObjectProperty") {
|
|
if (prop.key.type === "Identifier" && prop.value.type !== "StringLiteral") {
|
|
console.warn(formatMessage(`'paths.${prop.key.name}' value is required string literal`));
|
|
ret = false;
|
|
} else if (prop.key.type === "StringLiteral" && prop.value.type !== "StringLiteral") {
|
|
console.warn(formatMessage(`'paths.${prop.key.value}' value is required string literal`));
|
|
ret = false;
|
|
}
|
|
} else {
|
|
console.warn(formatMessage(`'paths' is required object`));
|
|
ret = false;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
function verifyLocalesArrayExpression(elements) {
|
|
let ret = true;
|
|
for (const element of elements) {
|
|
if (element?.type !== "StringLiteral") {
|
|
console.warn(formatMessage(`required 'locales' value string literal`));
|
|
ret = false;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
function evalValue(value) {
|
|
try {
|
|
return new Function(`return (${value})`)();
|
|
} catch (_e) {
|
|
console.error(formatMessage(`Cannot evaluate value: ${value}`));
|
|
return;
|
|
}
|
|
}
|
|
|
|
const VIRTUAL_PREFIX_HEX = "\0";
|
|
function isVue(id, opts = {}) {
|
|
const { search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
if (id.endsWith(".vue") && !search) {
|
|
return true;
|
|
}
|
|
if (!search) {
|
|
return false;
|
|
}
|
|
const query = parseQuery(search);
|
|
if (query.nuxt_component) {
|
|
return false;
|
|
}
|
|
if (query.macro && (search === "?macro=true" || !opts.type || opts.type.includes("script"))) {
|
|
return true;
|
|
}
|
|
const type = "setup" in query ? "script" : query.type;
|
|
if (!("vue" in query) || opts.type && !opts.type.includes(type)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const debug$8 = createDebug("@nuxtjs/i18n:transform:macros");
|
|
const TransformMacroPlugin = createUnplugin((options) => {
|
|
return {
|
|
name: "nuxtjs:i18n-macros-transform",
|
|
enforce: "pre",
|
|
transformInclude(id) {
|
|
if (!id || id.startsWith(VIRTUAL_PREFIX_HEX)) {
|
|
return false;
|
|
}
|
|
return isVue(id, { type: ["script"] });
|
|
},
|
|
transform(code, id) {
|
|
debug$8("transform", id);
|
|
const parsed = parse$2(code, { sourceMap: false });
|
|
const script = parsed.descriptor.scriptSetup ?? parsed.descriptor.script;
|
|
if (!script) {
|
|
return;
|
|
}
|
|
const s = new MagicString(code);
|
|
const match = script.content.match(new RegExp(`\\b${NUXT_I18N_COMPOSABLE_DEFINE_ROUTE}\\s*\\(\\s*`));
|
|
if (match?.[0]) {
|
|
const scriptString = new MagicString(script.content);
|
|
scriptString.overwrite(match.index, match.index + match[0].length, `false && /*#__PURE__*/ ${match[0]}`);
|
|
s.overwrite(script.loc.start.offset, script.loc.end.offset, scriptString.toString());
|
|
}
|
|
if (s.hasChanged()) {
|
|
debug$8("transformed: id -> ", id);
|
|
debug$8("transformed: code -> ", s.toString());
|
|
return {
|
|
code: s.toString(),
|
|
map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
|
|
};
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
const debug$7 = createDebug("@nuxtjs/i18n:transform:resource");
|
|
const ResourcePlugin = createUnplugin((options) => {
|
|
debug$7("options", options);
|
|
return {
|
|
name: "nuxtjs:i18n-resource",
|
|
enforce: "post",
|
|
transformInclude(id) {
|
|
debug$7("transformInclude", id);
|
|
if (!id || id.startsWith(VIRTUAL_PREFIX_HEX)) {
|
|
return false;
|
|
}
|
|
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
const query = parseQuery(search);
|
|
return /\.([c|m]?[j|t]s)$/.test(pathname) && (!!query.locale || !!query.config);
|
|
},
|
|
transform(code, id) {
|
|
debug$7("transform", id);
|
|
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
const query = parseQuery(search);
|
|
const s = new MagicString(code);
|
|
function result() {
|
|
if (s.hasChanged()) {
|
|
return {
|
|
code: s.toString(),
|
|
map: options.sourcemap && !/\.([c|m]?ts)$/.test(pathname) ? s.generateMap({ hires: true }) : null
|
|
};
|
|
}
|
|
}
|
|
const pattern = query.locale ? NUXT_I18N_COMPOSABLE_DEFINE_LOCALE : NUXT_I18N_COMPOSABLE_DEFINE_CONFIG;
|
|
const matches = code.matchAll(new RegExp(`\\b${pattern}\\s*`, "g"));
|
|
for (const match of matches) {
|
|
s.remove(match.index, match.index + match[0].length);
|
|
}
|
|
return result();
|
|
}
|
|
};
|
|
});
|
|
|
|
const debug$6 = createDebug("@nuxtjs/i18n:function:injection");
|
|
const TRANSLATION_FUNCTIONS = ["$t", "$rt", "$d", "$n", "$tm", "$te"];
|
|
const TRANSLATION_FUNCTIONS_RE = /\$(t|rt|d|n|tm|te)\s*\(\s*/;
|
|
const TRANSLATION_FUNCTIONS_MAP = {
|
|
$t: "t: $t",
|
|
$rt: "rt: $rt",
|
|
$d: "d: $d",
|
|
$n: "n: $n",
|
|
$tm: "tm: $tm",
|
|
$te: "te: $te"
|
|
};
|
|
const TransformI18nFunctionPlugin = createUnplugin((options) => {
|
|
return {
|
|
name: "nuxtjs:i18n-function-injection",
|
|
enforce: "pre",
|
|
transformInclude(id) {
|
|
return isVue(id, { type: ["script"] });
|
|
},
|
|
transform(code, id) {
|
|
debug$6("transform", id);
|
|
const script = extractScriptContent(code);
|
|
if (!script || !TRANSLATION_FUNCTIONS_RE.test(script)) {
|
|
return;
|
|
}
|
|
const scriptSetup = parse$2(code, { sourceMap: false }).descriptor.scriptSetup;
|
|
if (!scriptSetup) {
|
|
return;
|
|
}
|
|
const scriptTransformed = transform(script, { transforms: ["typescript", "jsx"] }).code;
|
|
const ast = this.parse(scriptTransformed, { sourceType: "module", ecmaVersion: "latest" });
|
|
let scopeTracker = new ScopeTracker();
|
|
const varCollector = new ScopedVarsCollector();
|
|
walk(ast, {
|
|
enter(_node) {
|
|
if (_node.type === "BlockStatement") {
|
|
scopeTracker.enterScope();
|
|
varCollector.refresh(scopeTracker.curScopeKey);
|
|
} else if (_node.type === "FunctionDeclaration" && _node.id) {
|
|
varCollector.addVar(_node.id.name);
|
|
} else if (_node.type === "VariableDeclarator") {
|
|
varCollector.collect(_node.id);
|
|
}
|
|
},
|
|
leave(_node) {
|
|
if (_node.type === "BlockStatement") {
|
|
scopeTracker.leaveScope();
|
|
varCollector.refresh(scopeTracker.curScopeKey);
|
|
}
|
|
}
|
|
});
|
|
const missingFunctionDeclarators = /* @__PURE__ */ new Set();
|
|
scopeTracker = new ScopeTracker();
|
|
walk(ast, {
|
|
enter(_node) {
|
|
if (_node.type === "BlockStatement") {
|
|
scopeTracker.enterScope();
|
|
}
|
|
if (_node.type !== "CallExpression" || _node.callee.type !== "Identifier") {
|
|
return;
|
|
}
|
|
const node = _node;
|
|
const name = "name" in node.callee && node.callee.name;
|
|
if (!name || !TRANSLATION_FUNCTIONS.includes(name)) {
|
|
return;
|
|
}
|
|
if (varCollector.hasVar(scopeTracker.curScopeKey, name)) {
|
|
return;
|
|
}
|
|
missingFunctionDeclarators.add(name);
|
|
},
|
|
leave(_node) {
|
|
if (_node.type === "BlockStatement") {
|
|
scopeTracker.leaveScope();
|
|
}
|
|
}
|
|
});
|
|
const s = new MagicString(code);
|
|
if (missingFunctionDeclarators.size > 0) {
|
|
debug$6(`injecting ${Array.from(missingFunctionDeclarators).join(", ")} declaration to ${id}`);
|
|
const assignments = [];
|
|
for (const missing of missingFunctionDeclarators) {
|
|
assignments.push(TRANSLATION_FUNCTIONS_MAP[missing]);
|
|
}
|
|
s.overwrite(
|
|
scriptSetup.loc.start.offset,
|
|
scriptSetup.loc.end.offset,
|
|
`
|
|
const { ${assignments.join(", ")} } = useI18n()
|
|
` + scriptSetup.content
|
|
);
|
|
}
|
|
if (s.hasChanged()) {
|
|
debug$6("transformed: id -> ", id);
|
|
debug$6("transformed: code -> ", s.toString());
|
|
return {
|
|
code: s.toString(),
|
|
map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
|
|
};
|
|
}
|
|
}
|
|
};
|
|
});
|
|
const SFC_SCRIPT_RE = /<script\s*[^>]*>([\s\S]*?)<\/script\s*[^>]*>/i;
|
|
function extractScriptContent(html) {
|
|
const match = html.match(SFC_SCRIPT_RE);
|
|
if (match && match[1]) {
|
|
return match[1].trim();
|
|
}
|
|
return null;
|
|
}
|
|
class ScopeTracker {
|
|
// the top of the stack is not a part of current key, it is used for next level
|
|
scopeIndexStack;
|
|
curScopeKey;
|
|
constructor() {
|
|
this.scopeIndexStack = [0];
|
|
this.curScopeKey = "";
|
|
}
|
|
getKey() {
|
|
return this.scopeIndexStack.slice(0, -1).join("-");
|
|
}
|
|
enterScope() {
|
|
this.scopeIndexStack.push(0);
|
|
this.curScopeKey = this.getKey();
|
|
}
|
|
leaveScope() {
|
|
this.scopeIndexStack.pop();
|
|
this.curScopeKey = this.getKey();
|
|
this.scopeIndexStack[this.scopeIndexStack.length - 1]++;
|
|
}
|
|
}
|
|
class ScopedVarsCollector {
|
|
curScopeKey;
|
|
all;
|
|
constructor() {
|
|
this.all = /* @__PURE__ */ new Map();
|
|
this.curScopeKey = "";
|
|
}
|
|
refresh(scopeKey) {
|
|
this.curScopeKey = scopeKey;
|
|
}
|
|
addVar(name) {
|
|
let vars = this.all.get(this.curScopeKey);
|
|
if (!vars) {
|
|
vars = /* @__PURE__ */ new Set();
|
|
this.all.set(this.curScopeKey, vars);
|
|
}
|
|
vars.add(name);
|
|
}
|
|
hasVar(scopeKey, name) {
|
|
const indices = scopeKey.split("-").map(Number);
|
|
for (let i = indices.length; i >= 0; i--) {
|
|
if (this.all.get(indices.slice(0, i).join("-"))?.has(name)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
collect(n) {
|
|
const t = n.type;
|
|
if (t === "Identifier") {
|
|
this.addVar(n.name);
|
|
} else if (t === "RestElement") {
|
|
this.collect(n.argument);
|
|
} else if (t === "AssignmentPattern") {
|
|
this.collect(n.left);
|
|
} else if (t === "ArrayPattern") {
|
|
n.elements.forEach((e) => e && this.collect(e));
|
|
} else if (t === "ObjectPattern") {
|
|
n.properties.forEach((p) => {
|
|
if (p.type === "RestElement") {
|
|
this.collect(p);
|
|
} else {
|
|
this.collect(p.value);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
const debug$5 = createDebug("@nuxtjs/i18n:transform:meta-deprecation");
|
|
const MetaDeprecationPlugin = createUnplugin((options) => {
|
|
const logger = useLogger(NUXT_I18N_MODULE_ID);
|
|
debug$5("options", options);
|
|
return {
|
|
name: "nuxtjs:i18n-meta-deprecation",
|
|
enforce: "post",
|
|
transformInclude(id) {
|
|
debug$5("transformInclude", id);
|
|
if (!id || id.startsWith(VIRTUAL_PREFIX_HEX)) {
|
|
return false;
|
|
}
|
|
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
return pathname.endsWith(".vue") || !!parseQuery(search).macro;
|
|
},
|
|
transform(code, id) {
|
|
debug$5("transform", id);
|
|
const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
const s = new MagicString(code);
|
|
function result() {
|
|
if (s.hasChanged()) {
|
|
return {
|
|
code: s.toString(),
|
|
map: options.sourcemap && !/\.([c|m]?ts)$/.test(pathname) ? s.generateMap({ hires: true }) : null
|
|
};
|
|
}
|
|
}
|
|
const match = code.match(new RegExp(`\\bdefinePageMeta\\({[.\\s]+(?=nuxtI18n)`));
|
|
if (match?.[0]) {
|
|
logger.warn(
|
|
`Setting \`nuxtI18n\` on \`definePageMeta\` is deprecated and will be removed in \`v9\`, use the \`useSetI18nParams\` composable instead.
|
|
Usage found in ${id.split("?")[0]}`
|
|
);
|
|
}
|
|
return result();
|
|
}
|
|
};
|
|
});
|
|
|
|
const debug$4 = createDebug("@nuxtjs/i18n:bundler");
|
|
async function extendBundler(nuxt, nuxtOptions) {
|
|
const langPaths = getLayerLangPaths(nuxt);
|
|
debug$4("langPaths -", langPaths);
|
|
const i18nModulePaths = nuxtOptions?.i18nModules?.map((module) => resolve(nuxt.options._layers[0].config.rootDir, module.langDir ?? "")) ?? [];
|
|
debug$4("i18nModulePaths -", i18nModulePaths);
|
|
const localePaths = [...langPaths, ...i18nModulePaths];
|
|
const macroOptions = {
|
|
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client
|
|
};
|
|
const resourceOptions = {
|
|
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client
|
|
};
|
|
const metaDeprecationOptions = {
|
|
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client
|
|
};
|
|
const i18nFunctionOptions = {
|
|
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client
|
|
};
|
|
try {
|
|
const webpack = await import('webpack').then((m) => m.default || m);
|
|
const webpackPluginOptions = {
|
|
allowDynamic: true,
|
|
runtimeOnly: nuxtOptions.bundle.runtimeOnly,
|
|
compositionOnly: nuxtOptions.bundle.compositionOnly,
|
|
jitCompilation: nuxtOptions.compilation.jit,
|
|
onlyLocales: nuxtOptions.bundle.onlyLocales,
|
|
dropMessageCompiler: nuxtOptions.compilation.jit ? nuxtOptions.bundle.dropMessageCompiler : false,
|
|
strictMessage: nuxtOptions.compilation.strictMessage,
|
|
escapeHtml: nuxtOptions.compilation.escapeHtml
|
|
};
|
|
if (localePaths.length > 0) {
|
|
webpackPluginOptions.include = localePaths.map((x) => resolve(x, "./**"));
|
|
}
|
|
addWebpackPlugin(VueI18nWebpackPlugin(webpackPluginOptions));
|
|
addWebpackPlugin(TransformMacroPlugin.webpack(macroOptions));
|
|
addWebpackPlugin(ResourcePlugin.webpack(resourceOptions));
|
|
addWebpackPlugin(MetaDeprecationPlugin.webpack(metaDeprecationOptions));
|
|
if (nuxtOptions.experimental.autoImportTranslationFunctions) {
|
|
addWebpackPlugin(TransformI18nFunctionPlugin.webpack(i18nFunctionOptions));
|
|
}
|
|
extendWebpackConfig((config) => {
|
|
config.plugins.push(
|
|
new webpack.DefinePlugin(
|
|
assign(
|
|
getFeatureFlags({
|
|
jit: nuxtOptions.compilation.jit,
|
|
compositionOnly: nuxtOptions.bundle.compositionOnly,
|
|
fullInstall: nuxtOptions.bundle.fullInstall,
|
|
dropMessageCompiler: nuxtOptions.compilation.jit ? nuxtOptions.bundle.dropMessageCompiler : false
|
|
}),
|
|
{
|
|
__DEBUG__: String(nuxtOptions.debug)
|
|
}
|
|
)
|
|
)
|
|
);
|
|
});
|
|
} catch (e) {
|
|
debug$4(e.message);
|
|
}
|
|
const vitePluginOptions = {
|
|
allowDynamic: true,
|
|
runtimeOnly: nuxtOptions.bundle.runtimeOnly,
|
|
compositionOnly: nuxtOptions.bundle.compositionOnly,
|
|
fullInstall: nuxtOptions.bundle.fullInstall,
|
|
onlyLocales: nuxtOptions.bundle.onlyLocales,
|
|
jitCompilation: nuxtOptions.compilation.jit,
|
|
dropMessageCompiler: nuxtOptions.compilation.jit ? nuxtOptions.bundle.dropMessageCompiler : false,
|
|
strictMessage: nuxtOptions.compilation.strictMessage,
|
|
escapeHtml: nuxtOptions.compilation.escapeHtml,
|
|
defaultSFCLang: nuxtOptions.customBlocks.defaultSFCLang,
|
|
globalSFCScope: nuxtOptions.customBlocks.globalSFCScope
|
|
};
|
|
if (localePaths.length > 0) {
|
|
vitePluginOptions.include = localePaths.map((x) => resolve(x, "./**"));
|
|
}
|
|
addVitePlugin(VueI18nVitePlugin(vitePluginOptions));
|
|
addVitePlugin(TransformMacroPlugin.vite(macroOptions));
|
|
addVitePlugin(ResourcePlugin.vite(resourceOptions));
|
|
addVitePlugin(MetaDeprecationPlugin.vite(metaDeprecationOptions));
|
|
if (nuxtOptions.experimental.autoImportTranslationFunctions) {
|
|
addVitePlugin(TransformI18nFunctionPlugin.vite(i18nFunctionOptions));
|
|
}
|
|
extendViteConfig((config) => {
|
|
if (config.define) {
|
|
config.define["__DEBUG__"] = JSON.stringify(nuxtOptions.debug);
|
|
} else {
|
|
config.define = {
|
|
__DEBUG__: JSON.stringify(nuxtOptions.debug)
|
|
};
|
|
}
|
|
debug$4("vite.config.define", config.define);
|
|
});
|
|
}
|
|
function getFeatureFlags({
|
|
jit = true,
|
|
compositionOnly = true,
|
|
fullInstall = true,
|
|
dropMessageCompiler = false
|
|
}) {
|
|
return {
|
|
__VUE_I18N_FULL_INSTALL__: String(fullInstall),
|
|
__VUE_I18N_LEGACY_API__: String(!compositionOnly),
|
|
__INTLIFY_PROD_DEVTOOLS__: "false",
|
|
__INTLIFY_JIT_COMPILATION__: String(jit),
|
|
__INTLIFY_DROP_MESSAGE_COMPILER__: String(dropMessageCompiler)
|
|
};
|
|
}
|
|
|
|
const debug$3 = createDebug("@nuxtjs/i18n:nitro");
|
|
async function setupNitro(nuxt, nuxtOptions, additionalParams) {
|
|
const resolver = createResolver(import.meta.url);
|
|
const [enableServerIntegration, localeDetectionPath] = await resolveLocaleDetectorPath(nuxt);
|
|
nuxt.hook("nitro:config", async (nitroConfig) => {
|
|
if (enableServerIntegration) {
|
|
nitroConfig.externals = defu(typeof nitroConfig.externals === "object" ? nitroConfig.externals : {}, {
|
|
inline: [resolver.resolve("./runtime")]
|
|
});
|
|
nitroConfig.rollupConfig = nitroConfig.rollupConfig || {};
|
|
nitroConfig.rollupConfig.plugins = await nitroConfig.rollupConfig.plugins || [];
|
|
nitroConfig.rollupConfig.plugins = isArray(nitroConfig.rollupConfig.plugins) ? nitroConfig.rollupConfig.plugins : [nitroConfig.rollupConfig.plugins];
|
|
const yamlPaths = getResourcePaths(additionalParams.localeInfo, /\.ya?ml$/);
|
|
if (yamlPaths.length > 0) {
|
|
nitroConfig.rollupConfig.plugins.push(yamlPlugin({ include: yamlPaths }));
|
|
}
|
|
const json5Paths = getResourcePaths(additionalParams.localeInfo, /\.json5?$/);
|
|
if (json5Paths.length > 0) {
|
|
nitroConfig.rollupConfig.plugins.push(json5Plugin({ include: json5Paths }));
|
|
}
|
|
nitroConfig.virtual = nitroConfig.virtual || {};
|
|
nitroConfig.virtual["#internal/i18n/options.mjs"] = () => additionalParams.optionsCode;
|
|
nitroConfig.virtual["#internal/i18n/locale.detector.mjs"] = () => `
|
|
import localeDetector from ${JSON.stringify(localeDetectionPath)}
|
|
export { localeDetector }
|
|
`;
|
|
if (nitroConfig.imports) {
|
|
nitroConfig.imports.presets = nitroConfig.imports.presets || [];
|
|
nitroConfig.imports.presets.push({
|
|
from: H3_PKG,
|
|
imports: ["useTranslation"]
|
|
});
|
|
const h3UtilsExports = await resolveModuleExportNames(UTILS_H3_PKG, { url: import.meta.url });
|
|
nitroConfig.imports.imports = nitroConfig.imports.imports || [];
|
|
nitroConfig.imports.imports.push(
|
|
...[
|
|
...h3UtilsExports.map((key) => ({
|
|
name: key,
|
|
as: key,
|
|
from: resolver.resolve(nuxt.options.alias[UTILS_H3_PKG])
|
|
})),
|
|
...[NUXT_I18N_COMPOSABLE_DEFINE_LOCALE, NUXT_I18N_COMPOSABLE_DEFINE_CONFIG].map((key) => ({
|
|
name: key,
|
|
as: key,
|
|
from: resolver.resolve("runtime/composables/shared")
|
|
})),
|
|
...[
|
|
{
|
|
name: NUXT_I18N_COMPOSABLE_DEFINE_LOCALE_DETECTOR,
|
|
as: NUXT_I18N_COMPOSABLE_DEFINE_LOCALE_DETECTOR,
|
|
from: resolver.resolve("runtime/composables/server")
|
|
}
|
|
]
|
|
]
|
|
);
|
|
}
|
|
}
|
|
nitroConfig.replace = nitroConfig.replace || {};
|
|
if (nuxt.options.ssr) {
|
|
nitroConfig.replace = assign(
|
|
nitroConfig.replace,
|
|
getFeatureFlags({
|
|
jit: nuxtOptions.compilation.jit,
|
|
compositionOnly: nuxtOptions.bundle.compositionOnly,
|
|
fullInstall: nuxtOptions.bundle.fullInstall,
|
|
dropMessageCompiler: nuxtOptions.compilation.jit ? nuxtOptions.bundle.dropMessageCompiler : false
|
|
})
|
|
);
|
|
}
|
|
nitroConfig.replace["__DEBUG__"] = String(nuxtOptions.debug);
|
|
debug$3("nitro.replace", nitroConfig.replace);
|
|
});
|
|
if (enableServerIntegration) {
|
|
addServerPlugin(resolver.resolve("runtime/server/plugin"));
|
|
}
|
|
}
|
|
async function resolveLocaleDetectorPath(nuxt) {
|
|
const serverI18nLayer = nuxt.options._layers.find((l) => {
|
|
const layerI18n = getLayerI18n(l);
|
|
return layerI18n?.experimental?.localeDetector != null && layerI18n?.experimental?.localeDetector !== "";
|
|
});
|
|
let enableServerIntegration = serverI18nLayer != null;
|
|
if (serverI18nLayer != null) {
|
|
const serverI18nLayerConfig = getLayerI18n(serverI18nLayer);
|
|
const i18nDir = resolveI18nDir(serverI18nLayer, serverI18nLayerConfig, true);
|
|
const pathTo = resolve(i18nDir, serverI18nLayerConfig.experimental.localeDetector);
|
|
const localeDetectorPath = await resolvePath(pathTo, {
|
|
cwd: nuxt.options.rootDir,
|
|
extensions: EXECUTABLE_EXTENSIONS
|
|
});
|
|
const hasLocaleDetector = await isExists(localeDetectorPath);
|
|
if (!hasLocaleDetector) {
|
|
const logger = useLogger(NUXT_I18N_MODULE_ID);
|
|
logger.warn(`localeDetector file '${localeDetectorPath}' does not exist. skip server-side integration ...`);
|
|
enableServerIntegration = false;
|
|
}
|
|
return [enableServerIntegration, localeDetectorPath];
|
|
} else {
|
|
return [enableServerIntegration, ""];
|
|
}
|
|
}
|
|
function getResourcePaths(localeInfo, extPattern) {
|
|
return localeInfo.reduce((acc, locale) => {
|
|
if (locale.meta) {
|
|
const collected = locale.meta.map((meta) => extPattern.test(meta.path) ? meta.path : void 0).filter(Boolean);
|
|
return [...acc, ...collected];
|
|
} else {
|
|
return acc;
|
|
}
|
|
}, []);
|
|
}
|
|
|
|
const debug$2 = createDebug("@nuxtjs/i18n:dirs");
|
|
const distDir = dirname(fileURLToPath(import.meta.url));
|
|
const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url));
|
|
const pkgDir = resolve(distDir, "..");
|
|
debug$2("distDir", distDir);
|
|
debug$2("runtimeDir", runtimeDir);
|
|
debug$2("pkgDir", pkgDir);
|
|
|
|
const debug$1 = createDebug("@nuxtjs/i18n:gen");
|
|
const generateVueI18nConfiguration = (config, isServer = false) => {
|
|
return genDynamicImport(
|
|
genImportSpecifier({ ...config.meta, isServer }, "config"),
|
|
!isServer ? {
|
|
comment: `webpackChunkName: "${config.meta.key}"`
|
|
} : {}
|
|
);
|
|
};
|
|
function simplifyLocaleOptions(nuxt, options) {
|
|
const isLocaleObjectsArray = (locales2) => locales2?.some((x) => typeof x !== "string");
|
|
const hasLocaleObjects = nuxt.options._layers.some((layer) => isLocaleObjectsArray(getLayerI18n(layer)?.locales)) || options?.i18nModules?.some((module) => isLocaleObjectsArray(module?.locales));
|
|
const locales = options.locales ?? [];
|
|
return locales.map(({ meta, ...locale }) => {
|
|
if (!hasLocaleObjects) {
|
|
return locale.code;
|
|
}
|
|
if (locale.file || (locale.files?.length ?? 0) > 0) {
|
|
locale.files = getLocalePaths(locale);
|
|
} else {
|
|
delete locale.files;
|
|
}
|
|
delete locale.file;
|
|
return locale;
|
|
});
|
|
}
|
|
function generateLoaderOptions(nuxt, { nuxtI18nOptions, vueI18nConfigPaths, localeInfo, isServer }) {
|
|
debug$1("generateLoaderOptions: lazy", nuxtI18nOptions.lazy);
|
|
const importMapper = /* @__PURE__ */ new Map();
|
|
const importStrings = [];
|
|
function generateLocaleImports(locale, meta, isServer2 = false) {
|
|
if (importMapper.has(meta.key))
|
|
return;
|
|
const importSpecifier = genImportSpecifier({ ...meta, isServer: isServer2 }, "locale", { locale });
|
|
const importer = { code: locale, key: meta.loadPath, load: "", cache: meta.file.cache ?? true };
|
|
if (nuxtI18nOptions.lazy) {
|
|
importer.load = genDynamicImport(importSpecifier, !isServer2 ? { comment: `webpackChunkName: "${meta.key}"` } : {});
|
|
} else {
|
|
importStrings.push(genImport(importSpecifier, meta.key));
|
|
importer.load = `() => Promise.resolve(${meta.key})`;
|
|
}
|
|
importMapper.set(meta.key, {
|
|
key: toCode(importer?.key),
|
|
load: importer?.load,
|
|
cache: toCode(importer?.cache)
|
|
});
|
|
}
|
|
for (const locale of localeInfo) {
|
|
locale?.meta?.forEach((meta) => generateLocaleImports(locale.code, meta, isServer));
|
|
}
|
|
const vueI18nConfigImports = [...vueI18nConfigPaths].reverse().filter((config) => config.absolute !== "").map((config) => generateVueI18nConfiguration(config, isServer));
|
|
const localeLoaders = localeInfo.map((locale) => [locale.code, locale.meta?.map((meta) => importMapper.get(meta.key))]);
|
|
const generatedNuxtI18nOptions = {
|
|
...nuxtI18nOptions,
|
|
locales: simplifyLocaleOptions(nuxt, nuxtI18nOptions)
|
|
};
|
|
delete nuxtI18nOptions.vueI18n;
|
|
const generated = {
|
|
importStrings,
|
|
localeLoaders,
|
|
nuxtI18nOptions: generatedNuxtI18nOptions,
|
|
vueI18nConfigs: vueI18nConfigImports
|
|
};
|
|
debug$1("generate code", generated);
|
|
return generated;
|
|
}
|
|
function genImportSpecifier({
|
|
loadPath,
|
|
path,
|
|
parsed,
|
|
hash,
|
|
type,
|
|
isServer
|
|
}, resourceType, query = {}) {
|
|
const getLoadPath = () => !isServer ? loadPath : path;
|
|
if (!EXECUTABLE_EXTENSIONS.includes(parsed.ext)) {
|
|
return getLoadPath();
|
|
}
|
|
if (resourceType != null && type === "unknown") {
|
|
throw new Error(`'unknown' type in '${path}'.`);
|
|
}
|
|
if (resourceType === "locale") {
|
|
return !isServer ? withQuery(getLoadPath(), type === "dynamic" ? { hash, ...query } : {}) : getLoadPath();
|
|
}
|
|
if (resourceType === "config") {
|
|
return !isServer ? withQuery(getLoadPath(), { hash, ...query, ...{ config: 1 } }) : getLoadPath();
|
|
}
|
|
return getLoadPath();
|
|
}
|
|
function generateI18nPageTypes() {
|
|
return `// Generated by @nuxtjs/i18n
|
|
declare module 'nuxt/dist/pages/runtime' {
|
|
interface PageMeta {
|
|
nuxtI18n?: Record<string, any>
|
|
}
|
|
}
|
|
|
|
export {}`;
|
|
}
|
|
function generateI18nTypes(nuxt, options) {
|
|
const vueI18nTypes = options.types === "legacy" ? ["VueI18n"] : ["ExportedGlobalComposer", "Composer"];
|
|
const generatedLocales = simplifyLocaleOptions(nuxt, options);
|
|
const resolvedLocaleType = typeof generatedLocales === "string" ? "string[]" : "LocaleObject[]";
|
|
const i18nType = `${vueI18nTypes.join(" & ")} & NuxtI18nRoutingCustomProperties<${resolvedLocaleType}>`;
|
|
const globalTranslationTypes = `
|
|
declare global {
|
|
var $t: (${i18nType})['t']
|
|
var $rt: (${i18nType})['rt']
|
|
var $n: (${i18nType})['n']
|
|
var $d: (${i18nType})['d']
|
|
var $tm: (${i18nType})['tm']
|
|
var $te: (${i18nType})['te']
|
|
}`;
|
|
return `// Generated by @nuxtjs/i18n
|
|
import type { ${vueI18nTypes.join(", ")} } from 'vue-i18n'
|
|
import type { NuxtI18nRoutingCustomProperties, ComposerCustomProperties } from '${relative(
|
|
join$1(nuxt.options.buildDir, "types"),
|
|
resolve(runtimeDir, "types.ts")
|
|
)}'
|
|
import type { Strategies, Directions, LocaleObject } from '${relative(
|
|
join$1(nuxt.options.buildDir, "types"),
|
|
resolve(distDir, "types.d.ts")
|
|
)}'
|
|
|
|
declare module 'vue-i18n' {
|
|
interface ComposerCustom extends ComposerCustomProperties<${resolvedLocaleType}> {}
|
|
interface ExportedGlobalComposer extends NuxtI18nRoutingCustomProperties<${resolvedLocaleType}> {}
|
|
interface VueI18n extends NuxtI18nRoutingCustomProperties<${resolvedLocaleType}> {}
|
|
}
|
|
|
|
declare module '#app' {
|
|
interface NuxtApp {
|
|
$i18n: ${i18nType}
|
|
}
|
|
}
|
|
|
|
${options.experimental?.autoImportTranslationFunctions && globalTranslationTypes || ""}
|
|
|
|
export {}`;
|
|
}
|
|
|
|
function generateTemplateNuxtI18nOptions(options) {
|
|
return `
|
|
// @ts-nocheck
|
|
${options.importStrings.length > 0 ? options.importStrings.join("\n") + "\n" : ""}
|
|
|
|
export const localeCodes = ${JSON.stringify(options.localeCodes, null, 2)}
|
|
|
|
export const localeLoaders = {
|
|
${options.localeLoaders.map(([key, val]) => {
|
|
return ` "${key}": [${val.map(
|
|
(entry) => `{ key: ${entry.key}, load: ${entry.load}, cache: ${entry.cache} }`
|
|
).join(",\n")}]`;
|
|
}).join(",\n")}
|
|
}
|
|
|
|
export const vueI18nConfigs = [
|
|
${options.vueI18nConfigs.length > 0 ? options.vueI18nConfigs.join(",\n ") : ""}
|
|
]
|
|
|
|
export const nuxtI18nOptions = ${JSON.stringify(options.nuxtI18nOptions, null, 2)}
|
|
|
|
export const normalizedLocales = ${JSON.stringify(options.normalizedLocales, null, 2)}
|
|
|
|
export const NUXT_I18N_MODULE_ID = "${NUXT_I18N_MODULE_ID}"
|
|
export const parallelPlugin = ${options.parallelPlugin}
|
|
export const isSSG = ${options.isSSG}
|
|
|
|
export const DEFAULT_DYNAMIC_PARAMS_KEY = ${JSON.stringify(DEFAULT_DYNAMIC_PARAMS_KEY)}
|
|
export const DEFAULT_COOKIE_KEY = ${JSON.stringify(DEFAULT_COOKIE_KEY)}
|
|
export const SWITCH_LOCALE_PATH_LINK_IDENTIFIER = ${JSON.stringify(SWITCH_LOCALE_PATH_LINK_IDENTIFIER)}
|
|
`;
|
|
}
|
|
|
|
const debug = createDebug("@nuxtjs/i18n:module");
|
|
const module = defineNuxtModule({
|
|
meta: {
|
|
name: NUXT_I18N_MODULE_ID,
|
|
configKey: "i18n",
|
|
compatibility: {
|
|
nuxt: ">=3.0.0-rc.11",
|
|
bridge: false
|
|
}
|
|
},
|
|
defaults: DEFAULT_OPTIONS,
|
|
async setup(i18nOptions, nuxt) {
|
|
const logger = useLogger(NUXT_I18N_MODULE_ID);
|
|
const options = i18nOptions;
|
|
applyOptionOverrides(options, nuxt);
|
|
debug("options", options);
|
|
if (!options.compilation.jit) {
|
|
logger.warn(
|
|
`Opt-out JIT compilation. It's necessary to pre-compile locale messages that are not managed by the nuxt i18n module (e.g. in the case of importing from a specific URL, you will need to precompile them yourself.) And also, you need to understand that you cannot support use cases where you dynamically compose locale messages from the back-end via an API.`
|
|
);
|
|
}
|
|
checkLayerOptions(options, nuxt);
|
|
if (options.bundle.compositionOnly && options.types === "legacy") {
|
|
throw new Error(
|
|
formatMessage(
|
|
`\`bundle.compositionOnly\` option and \`types\` option is conflicting: bundle.compositionOnly: ${options.bundle.compositionOnly}, types: ${JSON.stringify(options.types)}`
|
|
)
|
|
);
|
|
}
|
|
if (options.bundle.runtimeOnly && options.compilation.jit) {
|
|
logger.warn(
|
|
`\`bundle.runtimeOnly\` option and \`compilation.jit\` option is conflicting: bundle.runtimeOnly: ${options.bundle.runtimeOnly}, compilation.jit: ${JSON.stringify(
|
|
options.compilation.jit
|
|
)}`
|
|
);
|
|
}
|
|
if (options.dynamicRouteParams) {
|
|
logger.warn(
|
|
"The `dynamicRouteParams` options is deprecated and will be removed in `v9`, use the `useSetI18nParams` composable instead."
|
|
);
|
|
}
|
|
if (options.experimental.autoImportTranslationFunctions && nuxt.options.imports.autoImport === false) {
|
|
logger.warn(
|
|
"Disabling `autoImports` in Nuxt is not compatible with `experimental.autoImportTranslationFunctions`, either enable `autoImports` or disable `experimental.autoImportTranslationFunctions`."
|
|
);
|
|
}
|
|
if (nuxt.options.experimental.scanPageMeta === false) {
|
|
logger.warn(
|
|
"Route localization features (e.g. custom name, prefixed aliases) require Nuxt's `experimental.scanPageMeta` to be enabled.\nThis feature will be enabled in future Nuxt versions (https://github.com/nuxt/nuxt/pull/27134), check out the docs for more details: https://nuxt.com/docs/guide/going-further/experimental-features#scanpagemeta"
|
|
);
|
|
}
|
|
applyLayerOptions(options, nuxt);
|
|
await mergeI18nModules(options, nuxt);
|
|
filterLocales(options, nuxt);
|
|
nuxt.options.runtimeConfig.public.i18n = defu(nuxt.options.runtimeConfig.public.i18n, {
|
|
baseUrl: options.baseUrl,
|
|
defaultLocale: options.defaultLocale,
|
|
defaultDirection: options.defaultDirection,
|
|
strategy: options.strategy,
|
|
lazy: options.lazy,
|
|
rootRedirect: options.rootRedirect,
|
|
routesNameSeparator: options.routesNameSeparator,
|
|
defaultLocaleRouteNameSuffix: options.defaultLocaleRouteNameSuffix,
|
|
skipSettingLocaleOnNavigate: options.skipSettingLocaleOnNavigate,
|
|
differentDomains: options.differentDomains,
|
|
trailingSlash: options.trailingSlash,
|
|
configLocales: options.locales,
|
|
locales: options.locales.reduce(
|
|
(obj, locale) => {
|
|
if (typeof locale === "string") {
|
|
obj[locale] = { domain: void 0 };
|
|
} else {
|
|
obj[locale.code] = { domain: locale.domain };
|
|
}
|
|
return obj;
|
|
},
|
|
{}
|
|
),
|
|
detectBrowserLanguage: options.detectBrowserLanguage ?? DEFAULT_OPTIONS.detectBrowserLanguage,
|
|
experimental: options.experimental,
|
|
multiDomainLocales: options.multiDomainLocales
|
|
// TODO: we should support more i18n module options. welcome PRs :-)
|
|
});
|
|
const normalizedLocales = getNormalizedLocales(options.locales);
|
|
const localeCodes = normalizedLocales.map((locale) => locale.code);
|
|
const localeInfo = await resolveLocales(nuxt.options.srcDir, normalizedLocales, nuxt.options.buildDir);
|
|
debug("localeInfo", localeInfo);
|
|
const vueI18nConfigPaths = await resolveLayerVueI18nConfigInfo(options);
|
|
debug("VueI18nConfigPaths", vueI18nConfigPaths);
|
|
if (localeCodes.length) {
|
|
setupPages(options, nuxt);
|
|
}
|
|
if (options.strategy === "prefix" && nuxt.options._generate) {
|
|
const localizedEntryPages = normalizedLocales.map((x) => ["/", x.code].join(""));
|
|
nuxt.hook("nitro:config", (config) => {
|
|
config.prerender ??= {};
|
|
config.prerender.ignore ??= [];
|
|
config.prerender.ignore.push(/^\/$/);
|
|
config.prerender.routes ??= [];
|
|
config.prerender.routes.push(...localizedEntryPages);
|
|
});
|
|
}
|
|
await setupAlias(nuxt, options);
|
|
addPlugin(resolve(runtimeDir, "plugins/i18n"));
|
|
addPlugin(resolve(runtimeDir, "plugins/switch-locale-path-ssr"));
|
|
nuxt.options.alias["#i18n"] = resolve(distDir, "runtime/composables/index.js");
|
|
nuxt.options.build.transpile.push("#i18n");
|
|
const genTemplate = (isServer, lazy) => {
|
|
const nuxtI18nOptions = defu({}, options);
|
|
if (lazy != null) {
|
|
nuxtI18nOptions.lazy = lazy;
|
|
}
|
|
return generateTemplateNuxtI18nOptions({
|
|
...generateLoaderOptions(nuxt, {
|
|
vueI18nConfigPaths,
|
|
localeInfo,
|
|
nuxtI18nOptions,
|
|
isServer
|
|
}),
|
|
localeCodes,
|
|
normalizedLocales,
|
|
dev: nuxt.options.dev,
|
|
isSSG: nuxt.options._generate,
|
|
parallelPlugin: options.parallelPlugin
|
|
});
|
|
};
|
|
nuxt.options.runtimeConfig.public.i18n.configLocales = simplifyLocaleOptions(nuxt, defu({}, options));
|
|
addTemplate({
|
|
filename: NUXT_I18N_TEMPLATE_OPTIONS_KEY,
|
|
write: true,
|
|
getContents: () => genTemplate(false)
|
|
});
|
|
if (options.dynamicRouteParams) {
|
|
addTypeTemplate({
|
|
filename: "types/i18n-page-meta.d.ts",
|
|
getContents: () => generateI18nPageTypes()
|
|
});
|
|
}
|
|
addTypeTemplate({
|
|
filename: "types/i18n-plugin.d.ts",
|
|
getContents: () => generateI18nTypes(nuxt, i18nOptions)
|
|
});
|
|
nuxt.hook("build:manifest", (manifest) => {
|
|
if (options.lazy) {
|
|
const langFiles = localeInfo.flatMap((locale) => getLocaleFiles(locale)).map((x) => relative(nuxt.options.srcDir, x.path));
|
|
const langPaths = [...new Set(langFiles)];
|
|
for (const key in manifest) {
|
|
if (langPaths.some((x) => key.startsWith(x))) {
|
|
manifest[key].prefetch = false;
|
|
manifest[key].preload = false;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
await extendBundler(nuxt, options);
|
|
await setupNitro(nuxt, options, {
|
|
optionsCode: genTemplate(true, true),
|
|
localeInfo
|
|
});
|
|
const vueI18nPath = nuxt.options.alias[VUE_I18N_PKG];
|
|
debug("vueI18nPath for auto-import", vueI18nPath);
|
|
await addComponent({
|
|
name: "NuxtLinkLocale",
|
|
filePath: resolve(runtimeDir, "components/NuxtLinkLocale")
|
|
});
|
|
await addComponent({
|
|
name: "SwitchLocalePathLink",
|
|
filePath: resolve(runtimeDir, "components/SwitchLocalePathLink")
|
|
});
|
|
addImports([
|
|
{ name: "useI18n", from: vueI18nPath },
|
|
...[
|
|
"useRouteBaseName",
|
|
"useLocalePath",
|
|
"useLocaleRoute",
|
|
"useSwitchLocalePath",
|
|
"useLocaleHead",
|
|
"useBrowserLocale",
|
|
"useCookieLocale",
|
|
"useSetI18nParams",
|
|
NUXT_I18N_COMPOSABLE_DEFINE_ROUTE,
|
|
NUXT_I18N_COMPOSABLE_DEFINE_LOCALE,
|
|
NUXT_I18N_COMPOSABLE_DEFINE_CONFIG
|
|
].map((key) => ({
|
|
name: key,
|
|
as: key,
|
|
from: resolve(runtimeDir, "composables/index")
|
|
}))
|
|
]);
|
|
nuxt.options.build.transpile.push("@nuxtjs/i18n");
|
|
nuxt.options.build.transpile.push("@nuxtjs/i18n-edge");
|
|
nuxt.options.vite.optimizeDeps = nuxt.options.vite.optimizeDeps || {};
|
|
nuxt.options.vite.optimizeDeps.exclude = nuxt.options.vite.optimizeDeps.exclude || [];
|
|
nuxt.options.vite.optimizeDeps.exclude.push("vue-i18n");
|
|
}
|
|
});
|
|
|
|
export { module as default };
|