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 = /]*>([\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 } } 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 };