2025-09-05 14:59:21 +08:00

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 };