@nuxtjs/i18n
Version:
Internationalization for Nuxt
1,311 lines (1,293 loc) • 69.8 kB
JavaScript
import { useLogger, resolvePath, useNuxt, directoryToURL, resolveModule, tryUseNuxt, addTemplate, updateTemplates, addBuildPlugin, addServerTemplate, addServerImports, addServerPlugin, addServerHandler, createResolver, addPlugin, addVitePlugin, useNitro, addTypeTemplate, addComponent, addImports, defineNuxtModule } from '@nuxt/kit';
import defu$1, { defu } from 'defu';
import { readFileSync, existsSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { resolve, isAbsolute, parse, relative, dirname, basename, join as join$1 } from 'pathe';
import { isArray, isString, assign } from '@intlify/shared';
import { parseSync } from 'oxc-parser';
import { parse as parse$1 } from '@vue/compiler-sfc';
import { parseAndWalk, ScopeTracker, walk } from 'oxc-walker';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { encodePath, parseURL, parseQuery } from 'ufo';
import { createRoutesContext } from 'unplugin-vue-router';
import { resolveOptions } from 'unplugin-vue-router/options';
import MagicString from 'magic-string';
import { createUnplugin } from 'unplugin';
import { pathToFileURL, fileURLToPath } from 'node:url';
import { findStaticImports, resolveModuleExportNames } from 'mlly';
import { transform as transform$1 } from 'oxc-transform';
import yamlPlugin from '@rollup/plugin-yaml';
import json5Plugin from '@miyaneee/rollup-plugin-json5';
import VueI18nPlugin from '@intlify/unplugin-vue-i18n';
import { addDefinePlugin } from 'nuxt-define';
import { hash } from 'ohash';
import { genArrayFromRaw, genObjectFromRaw, genString, genObjectFromValues, genSafeVariableName, genDynamicImport } from 'knitwork';
import { mkdir as mkdir$1 } from 'fs/promises';
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 STRATEGY_PREFIX_EXCEPT_DEFAULT = "prefix_except_default";
const DYNAMIC_PARAMS_KEY = "nuxtI18nInternal";
const DEFAULT_COOKIE_KEY = "i18n_redirected";
const SWITCH_LOCALE_PATH_LINK_IDENTIFIER = "nuxt-i18n-slp";
const FULL_STATIC_LIFETIME = 60 * 60 * 24;
const DEFAULT_OPTIONS = {
restructureDir: "i18n",
experimental: {
localeDetector: "",
typedPages: true,
typedOptionsAndMessages: false,
alternateLinkCanonicalQueries: true,
devCache: false,
cacheLifetime: void 0,
stripMessagesPayload: false,
preload: false,
strictSeo: false,
nitroContextDetection: true
},
bundle: {
compositionOnly: true,
runtimeOnly: false,
fullInstall: true,
dropMessageCompiler: false
},
compilation: {
strictMessage: true,
escapeHtml: false
},
customBlocks: {
defaultSFCLang: "json",
globalSFCScope: false
},
vueI18n: "",
locales: [],
defaultLocale: "",
defaultDirection: "ltr",
routesNameSeparator: "___",
trailingSlash: false,
defaultLocaleRouteNameSuffix: "default",
strategy: STRATEGY_PREFIX_EXCEPT_DEFAULT,
langDir: "locales",
rootRedirect: void 0,
redirectStatusCode: 302,
detectBrowserLanguage: {
alwaysRedirect: false,
cookieCrossOrigin: false,
cookieDomain: null,
cookieKey: DEFAULT_COOKIE_KEY,
cookieSecure: false,
fallbackLocale: "",
redirectOn: "root",
useCookie: true
},
differentDomains: false,
baseUrl: "",
customRoutes: "page",
pages: {},
skipSettingLocaleOnNavigate: false,
types: "composition",
debug: false,
parallelPlugin: false,
multiDomainLocales: false,
hmr: true,
autoDeclare: true
};
const DEFINE_I18N_ROUTE_FN = "defineI18nRoute";
const DEFINE_I18N_LOCALE_FN = "defineI18nLocale";
const DEFINE_I18N_CONFIG_FN = "defineI18nConfig";
const DEFINE_LOCALE_DETECTOR_FN = "defineI18nLocaleDetector";
const NUXT_I18N_VIRTUAL_PREFIX = "#nuxt-i18n";
const TS_EXTENSIONS = [".ts", ".cts", ".mts"];
const JS_EXTENSIONS = [".js", ".cjs", ".mjs"];
const EXECUTABLE_EXTENSIONS = [...JS_EXTENSIONS, ...TS_EXTENSIONS];
const EXECUTABLE_EXT_RE = /\.[c|m]?[j|t]s$/;
function filterLocales(ctx, nuxt) {
const project = getLayerI18n(nuxt.options._layers[0]);
const include = toArray(project?.bundle?.onlyLocales ?? []).filter(isString);
if (!include.length) {
return ctx.options.locales;
}
return ctx.options.locales.filter((x) => include.includes(isString(x) ? x : x.code));
}
function resolveLocales(srcDir, locales) {
const localesResolved = [];
for (const locale of locales) {
const resolved = assign({ meta: [] }, locale);
delete resolved.file;
delete resolved.files;
for (const f of getLocaleFiles(locale)) {
const path = resolve(srcDir, f.path);
const type = getLocaleType(path);
resolved.meta.push({
type,
path,
hash: getHash(path),
cache: f.cache ?? type !== "dynamic"
});
}
localesResolved.push(resolved);
}
return localesResolved;
}
const analyzedMap = { object: "static", function: "dynamic", unknown: "unknown" };
function getLocaleType(path) {
if (!EXECUTABLE_EXT_RE.test(path)) return "static";
const parsed = parseSync(path, readFileSync(path, "utf-8"));
return analyzedMap[scanProgram(parsed.program) || "unknown"];
}
function scanProgram(program) {
let varDeclarationName;
const varDeclarations = [];
for (const node of program.body) {
switch (node.type) {
// collect variable declarations
case "VariableDeclaration":
for (const decl of node.declarations) {
if (decl.type !== "VariableDeclarator" || decl.init == null) continue;
if ("name" in decl.id === false) continue;
varDeclarations.push(decl);
}
break;
// check default export - store identifier if exporting variable name
case "ExportDefaultDeclaration":
if (node.declaration.type === "Identifier") {
varDeclarationName = node.declaration;
break;
}
if (node.declaration.type === "ObjectExpression") {
return "object";
}
if (node.declaration.type === "CallExpression" && node.declaration.callee.type === "Identifier") {
const [fnNode] = node.declaration.arguments;
if (fnNode?.type === "FunctionExpression" || fnNode?.type === "ArrowFunctionExpression") {
return "function";
}
}
break;
}
}
if (varDeclarationName) {
const n = varDeclarations.find((x) => x.id.type === "Identifier" && x.id.name === varDeclarationName.name);
if (n) {
if (n.init?.type === "ObjectExpression") {
return "object";
}
if (n.init?.type === "CallExpression" && n.init.callee.type === "Identifier") {
const [fnNode] = n.init.arguments;
if (fnNode?.type === "FunctionExpression" || fnNode?.type === "ArrowFunctionExpression") {
return "function";
}
}
}
}
return false;
}
async function resolveVueI18nConfigInfo(rootDir, configPath = "i18n.config") {
const absolutePath = await resolvePath(configPath, { cwd: rootDir, extensions: EXECUTABLE_EXTENSIONS });
if (!existsSync(absolutePath)) return void 0;
return {
path: absolutePath,
// absolute
hash: getHash(absolutePath),
type: getLocaleType(absolutePath)
};
}
const getLocaleFiles = (locale) => {
return toArray(locale.file ?? locale.files).filter((x) => x != null).map((x) => isString(x) ? { path: x, cache: void 0 } : x);
};
function resolveRelativeLocales(locale, config) {
return getLocaleFiles(locale).map((file) => ({
path: resolve(config.langDir, file.path),
cache: file.cache
}));
}
const mergeConfigLocales = (configs) => {
const merged = /* @__PURE__ */ new Map();
for (const config of configs) {
for (const locale of config.locales ?? []) {
const current = isString(locale) ? { code: locale, language: locale } : assign({}, locale);
const files = isString(locale) ? [] : resolveRelativeLocales(current, config);
delete current.file;
delete current.files;
const existing = merged.get(current.code) ?? {
code: current.code,
language: current.language,
files: []
};
existing.files = [...files, ...existing.files];
merged.set(current.code, assign({}, current, existing));
}
}
return Array.from(merged.values());
};
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) && isString(mod[0]) && [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;
}
function toArray(value) {
return Array.isArray(value) ? value : [value];
}
const logger = useLogger("nuxt-i18n");
function checkLayerOptions(_options, nuxt) {
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" : "extended";
const layerHint = `In ${configLocation} layer (\`${resolve(project.config.rootDir, layer.configFile)}\`) -`;
try {
if (!layerI18n.langDir) continue;
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/docs/api/options#langdir for more details.`
);
}
for (const locale of layerI18n.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) continue;
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(`[nuxt-i18n] ${layerHint} ${err.message}`);
}
}
}
function resolveI18nDir(layer, i18n, i18nDir = i18n.restructureDir ?? "i18n") {
return resolve(layer.config.rootDir, i18nDir);
}
async function applyLayerOptions(ctx, nuxt) {
const configs = [];
if (isAbsolute(ctx.options.langDir || "")) {
const config = { langDir: ctx.options.langDir, locales: [] };
for (const locale of ctx.options.locales) {
if (isString(locale) || !getLocaleFiles(locale)?.[0]?.path?.startsWith(config.langDir)) continue;
config.locales.push(locale);
}
configs.push(config);
}
for (const layer of nuxt.options._layers) {
const i18n = getLayerI18n(layer);
if (i18n?.locales == null) continue;
const langDir = resolve(resolveI18nDir(layer, i18n), i18n.langDir ?? "locales");
configs.push(assign({}, i18n, { langDir, locales: i18n.locales }));
}
await nuxt.callHook(
"i18n:registerModule",
({ langDir, locales }) => langDir && locales && configs.push({ langDir, locales })
);
return mergeConfigLocales(configs);
}
async function resolveLayerVueI18nConfigInfo(options, nuxt = useNuxt()) {
const resolvers = [];
if (options.vueI18n && isAbsolute(options.vueI18n)) {
resolvers.push(resolveVueI18nConfigInfo(parse(options.vueI18n).dir, options.vueI18n));
}
for (const layer of nuxt.options._layers) {
resolvers.push(resolveLayerVueI18n(layer));
}
return (await Promise.all(resolvers)).filter((x) => x != null);
}
async function resolveLayerVueI18n(layer) {
const i18n = getLayerI18n(layer);
const i18nDir = resolveI18nDir(layer, i18n || {});
const resolved = await resolveVueI18nConfigInfo(i18nDir, i18n?.vueI18n);
if (import.meta.dev && resolved == null && i18n?.vueI18n) {
logger.warn(`Vue I18n configuration file \`${i18n.vueI18n}\` not found in \`${i18nDir}\`. Skipping...`);
}
return resolved;
}
function setupAlias({ userOptions: options }, nuxt) {
const modules = {
[VUE_I18N_PKG]: `${VUE_I18N_PKG}/dist/vue-i18n${!nuxt.options.dev && !nuxt.options._prepare && options.bundle?.runtimeOnly ? ".runtime" : ""}.mjs`,
[SHARED_PKG]: `${SHARED_PKG}/dist/shared.mjs`,
[MESSAGE_COMPILER_PKG]: `${MESSAGE_COMPILER_PKG}/dist/message-compiler.mjs`,
[CORE_BASE_PKG]: `${CORE_BASE_PKG}/dist/core-base.mjs`,
[CORE_PKG]: `${CORE_PKG}/dist/core.node.mjs`,
[UTILS_H3_PKG]: `${UTILS_PKG}/dist/h3.mjs`,
// for `@intlify/utils/h3`
[UFO_PKG]: UFO_PKG
};
const layerI18nDirs = nuxt.options._layers.map((l) => {
const i18n = getLayerI18n(l);
if (i18n == null) return void 0;
return relative(nuxt.options.buildDir, resolve(resolveI18nDir(l, i18n), "**/*"));
}).filter((x) => !!x);
const moduleIds = Object.keys(modules);
nuxt.options.typescript = defu(nuxt.options.typescript, {
hoist: moduleIds,
tsConfig: {
include: layerI18nDirs
}
});
nuxt.options.vite = defu(nuxt.options.vite, {
optimizeDeps: {
include: moduleIds
}
});
const moduleDirs = [].concat(
nuxt.options.modulesDir,
nuxt.options.modulesDir.map((dir) => `${dir}/${NUXT_I18N_MODULE_ID}/node_modules`)
).map((x) => directoryToURL(x));
for (const [moduleName, moduleFile] of Object.entries(modules)) {
const module = resolveModule(moduleFile, { url: moduleDirs });
if (!module) throw new Error(`Could not resolve module "${moduleFile}"`);
nuxt.options.alias[moduleName] = module;
nuxt.options.build.transpile.push(moduleName);
}
}
const COLON_RE = /:/g;
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}(.*)*` : token.type === 4 /* group */ ? "" : encodePath(token.value).replace(COLON_RE, "\\:"));
}, "/");
}
const PARAM_CHAR_RE = /[\w.]/;
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 */ : state === 4 /* catchall */ ? 3 /* catchall */ : 4 /* group */,
value: buffer
});
buffer = "";
}
while (i < segment.length) {
const c = segment[i];
switch (state) {
case 0 /* initial */:
buffer = "";
if (c === "[") {
state = 2 /* dynamic */;
} else if (c === "(") {
state = 5 /* group */;
} else {
i--;
state = 1 /* static */;
}
break;
case 1 /* static */:
if (c === "[") {
consumeBuffer();
state = 2 /* dynamic */;
} else if (c === "(") {
consumeBuffer();
state = 5 /* group */;
} else {
buffer += c;
}
break;
case 4 /* catchall */:
case 2 /* dynamic */:
case 3 /* optional */:
case 5 /* group */:
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 (c === ")" && state === 5 /* group */) {
if (!buffer) {
throw new Error("Empty group");
} else {
consumeBuffer();
}
state = 0 /* initial */;
} else if (c && PARAM_CHAR_RE.test(c)) {
buffer += c;
} else ;
break;
}
i++;
}
if (state === 2 /* dynamic */) {
throw new Error(`Unfinished param "${buffer}"`);
}
consumeBuffer();
return tokens;
}
const join = (...args) => args.filter(Boolean).join("");
function handlePathNesting(localizedPath, parentLocalizedPath = "") {
if (!parentLocalizedPath) return localizedPath;
if (localizedPath[0] !== "/") {
return localizedPath;
}
const index = localizedPath.indexOf(parentLocalizedPath);
if (index >= 0) {
return localizedPath.slice(localizedPath.indexOf(parentLocalizedPath) + parentLocalizedPath.length + 1);
}
return localizedPath;
}
function createHandleTrailingSlash(ctx) {
return (localizedPath, hasParent) => {
if (!localizedPath) return "";
const isChildWithRelativePath = hasParent && !localizedPath.startsWith("/");
return localizedPath.replace(/\/+$/, "") + (ctx.trailingSlash ? "/" : "") || (isChildWithRelativePath ? "" : "/");
};
}
function createLocalizeAliases(ctx) {
return (route, locale, options) => {
const aliases = toArray(route.alias).filter(Boolean);
return aliases.map((x) => {
const alias = ctx.handleTrailingSlash(x, !!options.parent);
const shouldPrefix = options.shouldPrefix(x, locale, options);
return shouldPrefix ? join("/", locale, alias) : alias;
});
};
}
function createLocalizeChildren(ctx) {
return (route, parentLocalized, locale, opts) => {
const localizeParams = { ...opts, parent: route, locales: [locale], parentLocalized };
return route.children?.flatMap((child) => localizeSingleRoute(child, localizeParams, ctx)) ?? [];
};
}
function getLocalizedRoute(route, locale, localizedPath, options, ctx) {
const path = handlePathNesting(localizedPath, options.parentLocalized?.path);
const localized = { ...route };
localized.path = ctx.handleTrailingSlash(path, !!options.parent);
localized.name &&= ctx.localizeRouteName(localized, locale, options.defaultTree);
localized.alias &&= ctx.localizeAliases(localized, locale, options);
localized.children &&= ctx.localizeChildren(route, localized, locale, options);
return localized;
}
function localizeSingleRoute(route, options, ctx) {
const routeOptions = ctx.optionsResolver(route, options.locales);
if (!routeOptions) {
return [route];
}
const resultRoutes = [];
for (const locale of routeOptions.locales) {
const unprefixed = routeOptions.paths?.[locale] ?? route.path;
const prefixed = join("/", locale, unprefixed);
const usePrefix = options.shouldPrefix(unprefixed, locale, options);
const data = { route, prefixed, unprefixed, locale, usePrefix, ctx, options };
for (const localizer of ctx.localizers) {
if (!localizer.enabled(data)) continue;
resultRoutes.push(...localizer.localizer(data));
}
}
return resultRoutes;
}
function createDefaultOptionsResolver(opts) {
return (route, locales) => {
if (route.redirect && !route.file) return void 0;
if (opts?.optionsResolver == null) return { locales, paths: {} };
return opts.optionsResolver(route, locales);
};
}
function createLocalizeRouteName(opts) {
const separator = opts.routesNameSeparator || "___";
const defaultSuffix = opts.defaultLocaleRouteNameSuffix || "default";
return (route, locale, isDefault) => {
if (route.name == null) return;
return !isDefault ? route.name + separator + locale : route.name + separator + locale + separator + defaultSuffix;
};
}
function createRouteContext(opts) {
const ctx = { localizers: [] };
ctx.trailingSlash = opts.trailingSlash ?? false;
ctx.isDefaultLocale = (locale) => opts.defaultLocales.includes(locale);
ctx.localizeRouteName = createLocalizeRouteName(opts);
ctx.optionsResolver = createDefaultOptionsResolver(opts);
ctx.localizeAliases = createLocalizeAliases(ctx);
ctx.localizeChildren = createLocalizeChildren(ctx);
ctx.handleTrailingSlash = createHandleTrailingSlash(ctx);
ctx.localizers.push({
enabled: () => true,
localizer: ({ prefixed, unprefixed, route, usePrefix, ctx: ctx2, locale, options }) => [
getLocalizedRoute(route, locale, usePrefix ? prefixed : unprefixed, options, ctx2)
]
});
return ctx;
}
function createShouldPrefix(opts, ctx) {
if (opts.strategy === "no_prefix") return () => false;
return (path, locale, options) => {
if (options.defaultTree) return false;
if (options.parent != null && !path.startsWith("/")) return false;
if (ctx.isDefaultLocale(locale) && opts.strategy === "prefix_except_default") return false;
return true;
};
}
function shouldLocalizeRoutes(options) {
if (options.strategy !== "no_prefix") return true;
if (!options.differentDomains) return false;
const domains = /* @__PURE__ */ new Set();
for (const locale of options.locales) {
if (!locale.domain) continue;
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 resolveDefaultLocales(config) {
let defaultLocales = [config.defaultLocale ?? ""];
if (config.differentDomains) {
const domainDefaults = config.locales.filter((locale) => !!locale.domainDefault).map((locale) => locale.code);
defaultLocales = defaultLocales.concat(domainDefaults);
}
return defaultLocales;
}
function localizeRoutes(routes, config) {
if (!shouldLocalizeRoutes(config)) return routes;
const ctx = createRouteContext({
optionsResolver: config.optionsResolver,
trailingSlash: config.trailingSlash ?? false,
defaultLocales: resolveDefaultLocales(config),
routesNameSeparator: config.routesNameSeparator,
defaultLocaleRouteNameSuffix: config.defaultLocaleRouteNameSuffix
});
const strategy = config.strategy ?? "prefix_and_default";
if (strategy === "prefix_and_default") {
ctx.localizers.unshift({
enabled: ({ options, locale }) => ctx.isDefaultLocale(locale) && !options.defaultTree && options.parent == null,
localizer: ({ route, ctx: ctx2, locale, options }) => localizeSingleRoute(route, { ...options, locales: [locale], defaultTree: true }, ctx2)
});
}
const multiDomainLocales = config.multiDomainLocales ?? false;
if (multiDomainLocales && (config.strategy === "prefix_except_default" || config.strategy === "prefix_and_default")) {
ctx.localizers.unshift({
enabled: ({ usePrefix }) => usePrefix,
localizer: ({ unprefixed, route, ctx: ctx2, locale }) => [
{ ...route, name: ctx2.localizeRouteName(route, locale, true), path: unprefixed }
]
});
}
const includeUnprefixedFallback = config.includeUnprefixedFallback ?? false;
if (strategy === "prefix" && includeUnprefixedFallback) {
ctx.localizers.unshift({
enabled: ({ usePrefix, locale }) => usePrefix && ctx.isDefaultLocale(locale),
localizer: ({ route }) => [route]
});
}
const locales = config.locales.map((x) => x.code);
const params = { locales, defaultTree: false, shouldPrefix: createShouldPrefix(config, ctx) };
return routes.flatMap((route) => localizeSingleRoute(route, params, ctx));
}
const VIRTUAL_PREFIX_HEX = "\0";
function asI18nVirtual(val) {
return NUXT_I18N_VIRTUAL_PREFIX + "/" + val;
}
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;
}
function transform(id, input, options) {
const oxcOptions = tryUseNuxt()?.options?.oxc?.transform?.options ?? {};
return transform$1(id, input, { ...oxcOptions, ...options });
}
const pattern = [DEFINE_I18N_LOCALE_FN, DEFINE_I18N_CONFIG_FN].join("|");
const DEFINE_I18N_FN_RE = new RegExp(`\\b(${pattern})\\s*\\((.+)\\s*\\)`, "gms");
const ResourcePlugin = (options, ctx) => createUnplugin(() => {
const i18nFileMetas = [...ctx.localeInfo.flatMap((x) => x.meta), ...ctx.vueI18nConfigPaths];
const i18nPathSet = /* @__PURE__ */ new Set();
const i18nFileHashSet = /* @__PURE__ */ new Map();
for (const meta of i18nFileMetas) {
if (i18nPathSet.has(meta.path)) continue;
i18nPathSet.add(meta.path);
i18nFileHashSet.set(asI18nVirtual(meta.hash), meta.path);
}
return {
name: "nuxtjs:i18n-resource",
enforce: "pre",
// resolve virtual hash to file path
resolveId(id) {
if (!id || id.startsWith(VIRTUAL_PREFIX_HEX) || !id.startsWith(NUXT_I18N_VIRTUAL_PREFIX)) {
return;
}
if (i18nFileHashSet.has(id)) {
return i18nFileHashSet.get(id);
}
},
transformInclude(id) {
if (!id || id.startsWith(VIRTUAL_PREFIX_HEX)) {
return false;
}
if (i18nPathSet.has(id)) {
return /\.[cm]?[jt]s$/.test(id);
}
},
/**
* Match and replace `defineI18nX(<content>)` with its `<content>`
*/
transform: {
filter: {
id: {
include: [...i18nPathSet]
}
},
async handler(_code, id) {
let code = _code;
const staticImports = findStaticImports(_code);
for (const x of staticImports) {
if (x.specifier.startsWith("\0")) continue;
i18nPathSet.add(await resolvePath(resolve(dirname(id), x.specifier)));
}
if (/[cm]?ts$/.test(id)) {
code = transform(id, _code).code;
}
const s = new MagicString(code);
const matches = code.matchAll(DEFINE_I18N_FN_RE);
for (const match of matches) {
s.overwrite(match.index, match.index + match[0].length, match[2]);
}
if (s.hasChanged()) {
return {
code: s.toString(),
map: options.sourcemap && !/\.[cm]?ts$/.test(id) ? s.generateMap({ hires: true }) : null
};
}
}
}
};
});
class NuxtPageAnalyzeContext {
config;
pages = /* @__PURE__ */ new Map();
pathToConfig = {};
fileToPath = {};
constructor(config) {
this.config = config || {};
}
addPage(page, path, name) {
this.pages.set(page.file, { path, name });
const p = path === "index" ? "/" : "/" + path.replace(/\/index$/, "");
this.fileToPath[page.file] = p;
}
}
async function setupPages({ localeCodes, options, normalizedLocales }, nuxt) {
const routeResources = {
i18nPathToPath: {},
pathToI18nConfig: {}
};
addTemplate({
filename: "i18n-route-resources.mjs",
write: true,
getContents: () => {
return `// Generated by @nuxtjs/i18n
export const pathToI18nConfig = ${JSON.stringify(routeResources.pathToI18nConfig, null, 2)};
export const i18nPathToPath = ${JSON.stringify(routeResources.i18nPathToPath, null, 2)};`;
}
});
if (!localeCodes.length) return;
let includeUnprefixedFallback = !nuxt.options.ssr;
nuxt.hook("nitro:init", () => {
includeUnprefixedFallback = options.strategy !== "prefix";
});
const projectLayer = nuxt.options._layers[0];
const typedRouter = await setupExperimentalTypedRoutes(options, nuxt);
nuxt.options.experimental.extraPageMetaExtractionKeys ??= [];
nuxt.options.experimental.extraPageMetaExtractionKeys.push("i18n");
nuxt.hook(
nuxt.options.experimental.scanPageMeta === "after-resolve" ? "pages:resolved" : "pages:extend",
async (pages) => {
const ctx = new NuxtPageAnalyzeContext(options.pages);
for (const layer of nuxt.options._layers) {
const pagesDir = resolve(projectLayer.config.rootDir, layer.config.srcDir, layer.config.dir?.pages ?? "pages");
analyzeNuxtPages(ctx, pagesDir, pages);
}
if (typedRouter) {
await typedRouter.createContext(pages).scanPages(false);
}
const localizedPages = localizeRoutes(pages, {
...options,
includeUnprefixedFallback,
locales: normalizedLocales,
optionsResolver: getRouteOptionsResolver(ctx, options.defaultLocale, options.customRoutes)
});
const indexPage = pages.find((x) => x.path === "/");
if (options.strategy === "prefix" && indexPage != null) {
localizedPages.unshift(indexPage);
}
const invertedMap = {};
const localizedMapInvert = {};
for (const [path, localeConfig] of Object.entries(ctx.pathToConfig)) {
const resPath = resolveRoutePath(path);
invertedMap[resPath] ??= {};
for (const [locale, localePath] of Object.entries(localeConfig)) {
const localized = localePath === true ? path : localePath;
invertedMap[resPath][locale] = localized && resolveRoutePath(localized);
if (invertedMap[resPath][locale]) {
localizedMapInvert[invertedMap[resPath][locale]] = resPath;
}
}
}
routeResources.i18nPathToPath = localizedMapInvert;
routeResources.pathToI18nConfig = invertedMap;
await updateTemplates({
filter: (template) => template.filename === "i18n-route-resources.mjs"
});
if (pages !== localizedPages) {
pages.length = 0;
pages.unshift(...localizedPages);
}
}
);
}
const routeNamedMapTypeRE = /RouteNamedMap\b/;
const declarationFile = "./types/typed-router-i18n.d.ts";
async function setupExperimentalTypedRoutes(userOptions, nuxt) {
if (!nuxt.options.experimental.typedPages || userOptions.experimental?.typedPages === false) {
return void 0;
}
const dtsFile = resolve(nuxt.options.buildDir, declarationFile);
function createContext(pages) {
const typedRouteroptions = {
routesFolder: [],
dts: dtsFile,
logs: !!nuxt.options.debug,
watch: false,
// eslint-disable-next-line @typescript-eslint/require-await
async beforeWriteFiles(rootPage) {
rootPage.children.forEach((child) => child.delete());
function addPage(parent, page) {
const route = parent.insert(page.path, page.file);
if (page.meta) {
route.addToMeta(page.meta);
}
if (page.alias) {
route.addAlias(page.alias);
}
if (page.name) {
route.name = page.name;
}
if (page.children) {
page.children.forEach((child) => addPage(route, child));
}
}
for (const page of pages) {
addPage(rootPage, page);
}
}
};
const context = createRoutesContext(resolveOptions(typedRouteroptions));
const originalScanPages = context.scanPages.bind(context);
context.scanPages = async function(watchers = false) {
await mkdir(dirname(dtsFile), { recursive: true });
await originalScanPages(watchers);
const dtsContent = await readFile(dtsFile, "utf-8");
if (routeNamedMapTypeRE.test(dtsContent)) {
await writeFile(dtsFile, dtsContent.replace(routeNamedMapTypeRE, "RouteNamedMapI18n"));
}
};
return context;
}
addTemplate({
filename: resolve(nuxt.options.buildDir, "./types/i18n-generated-route-types.d.ts"),
getContents: () => {
return `// Generated by @nuxtjs/i18n
declare module 'vue-router' {
import type { RouteNamedMapI18n } from 'vue-router/auto-routes'
export interface TypesConfig {
RouteNamedMapI18n: RouteNamedMapI18n
}
}
export {}`;
}
});
nuxt.hook("prepare:types", ({ references }) => {
references.push({ path: declarationFile });
references.push({ types: "./types/i18n-generated-route-types.d.ts" });
});
await createContext(nuxt.apps.default?.pages ?? []).scanPages(false);
return { createContext };
}
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, pagesDir, pages) {
if (pages == null || pages.length === 0) return;
for (const page of pages) {
if (page.file == null) continue;
const [, filePath] = page.file.split(pagesDir);
if (filePath == null) continue;
ctx.addPage(page, analyzePagePath(filePath), page.name ?? page.children?.find((x) => x.path.endsWith("/index"))?.name);
analyzeNuxtPages(ctx, pagesDir, page.children);
}
}
function getRouteOptionsResolver(ctx, defaultLocale, customRoutes) {
return (route, localeCodes) => {
const res = getRouteOptions(route, localeCodes, ctx, defaultLocale, customRoutes);
if (route.file) {
const localeCfg = res?.srcPaths;
const mappedPath = ctx.fileToPath[route.file];
ctx.pathToConfig[mappedPath] ??= {};
for (const l of localeCodes) {
ctx.pathToConfig[mappedPath][l] ??= localeCfg?.[l] ?? false;
}
for (const l of res?.locales ?? []) {
ctx.pathToConfig[mappedPath][l] ||= true;
}
}
return res;
};
}
function resolveRoutePath(path) {
const tokens = parseSegment(path.slice(1));
return getRoutePath(tokens);
}
function getRouteFromConfig(ctx, route, localeCodes) {
const pageMeta = ctx.pages.get(route.file);
if (pageMeta == null) {
return void 0;
}
const valueByName = pageMeta?.name ? ctx.config?.[pageMeta.name] : void 0;
const valueByPath = pageMeta?.path != null ? ctx.config?.[pageMeta.path] : void 0;
const resolved = valueByName ?? valueByPath;
if (!resolved) return resolved;
return {
paths: resolved ?? {},
locales: localeCodes.filter((locale) => resolved[locale] !== false)
};
}
function getRouteFromResource(localeCodes, resolved) {
if (!resolved) return resolved;
return {
paths: resolved.paths ?? {},
locales: resolved?.locales || localeCodes
};
}
function getRouteOptions(route, localeCodes, ctx, defaultLocale, mode = "config") {
let resolvedOptions;
if (mode === "config") {
resolvedOptions = getRouteFromConfig(ctx, route, localeCodes);
} else {
resolvedOptions = getRouteFromResource(
localeCodes,
mode === "page" ? getI18nRouteConfig(route.file) : route.meta?.i18n
);
}
if (resolvedOptions === false) {
return void 0;
}
const locales = resolvedOptions?.locales || localeCodes;
const paths = {};
if (!resolvedOptions) {
return { locales, paths };
}
for (const locale of resolvedOptions.locales) {
if (isString(resolvedOptions.paths[locale])) {
paths[locale] = resolveRoutePath(resolvedOptions.paths[locale]);
continue;
}
if (isString(resolvedOptions.paths[defaultLocale])) {
paths[locale] = resolveRoutePath(resolvedOptions.paths[defaultLocale]);
}
}
return { locales, paths, srcPaths: resolvedOptions.paths };
}
function getI18nRouteConfig(absolutePath, vfs = {}) {
let extract = void 0;
try {
const content = absolutePath in vfs ? vfs[absolutePath] : readFileSync(absolutePath, "utf-8");
if (!content.includes(DEFINE_I18N_ROUTE_FN)) return void 0;
const { descriptor } = parse$1(content);
const script = descriptor.scriptSetup || descriptor.script;
if (!script) return void 0;
const lang = typeof script.attrs.lang === "string" && /j|tsx/.test(script.attrs.lang) ? "tsx" : "ts";
let code = script.content;
parseAndWalk(script.content, absolutePath.replace(/\.\w+$/, "." + lang), (node) => {
if (extract != null) return;
if (node.type !== "CallExpression" || node.callee.type !== "Identifier" || node.callee.name !== DEFINE_I18N_ROUTE_FN)
return;
let routeArgument = node.arguments[0];
if (routeArgument == null) return;
if (typeof script.attrs.lang === "string" && /tsx?/.test(script.attrs.lang)) {
const transformed = transform("", script.content.slice(node.start, node.end).trim(), { lang });
code = transformed.code;
if (transformed.errors.length) {
for (const error of transformed.errors) {
console.warn(`Error while transforming \`${DEFINE_I18N_ROUTE_FN}()\`` + error.codeframe);
}
return;
}
routeArgument = parseSync("", transformed.code, { lang: "js" }).program.body[0].expression.arguments[0];
}
extract = evalAndValidateValue(code.slice(routeArgument.start, routeArgument.end).trim());
});
} catch (e) {
console.warn(`[nuxt-i18n] Couldn't read component data at ${absolutePath}: (${e.message})`);
}
return extract;
}
function evalValue(value) {
try {
return new Function(`return (${value})`)();
} catch {
console.error(`[nuxt-i18n] Cannot evaluate value: ${value}`);
return;
}
}
function evalAndValidateValue(value) {
const evaluated = evalValue(value);
if (evaluated == null) return;
if (typeof evaluated === "boolean" && evaluated === false) {
return evaluated;
}
if (Object.prototype.toString.call(evaluated) === "[object Object]") {
if (evaluated.locales) {
if (!Array.isArray(evaluated.locales) || evaluated.locales.some((locale) => typeof locale !== "string")) {
console.warn(`[nuxt-i18n] Invalid locale option used with \`defineI18nRoute\`: ${value}`);
return;
}
}
if (evaluated.paths && Object.prototype.toString.call(evaluated.paths) !== "[object Object]") {
console.warn(`[nuxt-i18n] Invalid paths option used with \`defineI18nRoute\`: ${value}`);
return;
}
return evaluated;
}
console.warn(`[nuxt-i18n] Invalid value passed to \`defineI18nRoute\`: ${value}`);
}
const I18N_MACRO_FN_RE = new RegExp(`\\b${DEFINE_I18N_ROUTE_FN}\\s*\\(\\s*`);
const TransformMacroPlugin = (options) => createUnplugin(() => {
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: {
filter: {
code: { include: I18N_MACRO_FN_RE }
},
handler(code) {
const parsed = parse$1(code, { sourceMap: false });
const script = parsed.descriptor.scriptSetup ?? parsed.descriptor.script;
if (!script) {
return;
}
const s = new MagicString(code);
const match = script.content.match(I18N_MACRO_FN_RE);
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()) {
return {
code: s.toString(),
map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
};
}
}
}
};
});
const TRANSLATION_FUNCTIONS = ["$t", "$rt", "$d", "$n", "$tm", "$te"];
const TRANSLATION_FUNCTIONS_RE = /\$([tdn]|rt|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 QUERY_RE = /\?.*$/;
function withoutQuery(id) {
return id.replace(QUERY_RE, "");
}
const TransformI18nFunctionPlugin = (options) => createUnplugin(() => {
return {
name: "nuxtjs:i18n-function-injection",
enforce: "pre",
transformInclude(id) {
return isVue(id, { type: ["script"] });
},
transform: {
filter: {
code: { include: TRANSLATION_FUNCTIONS_RE }
},
handler(code, id) {
const script = extractScriptSetupContent(code);
if (!script) return;
const filepath = withoutQuery(id).replace(/\.\w+$/, "." + script.loader);
const missing = collectMissingI18nFunctions(script.code, filepath);
if (!missing.size) return;
const assignments = [];
for (const entry of missing) {
assignments.push(TRANSLATION_FUNCTIONS_MAP[entry]);
}
const s = new MagicString(code);
s.appendLeft(script.start, `
const { ${assignments.join(", ")} } = useI18n()
`);
return {
code: s.toString(),
map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
};
}
}
};
});
function collectMissingI18nFunctions(script, id) {
const scopeTracker = new ScopeTracker({ preserveExitedScopes: true });
const ast = parseAndWalk(script, id, { scopeTracker });
const missing = /* @__PURE__ */ new Set();
walk(ast.program, {
scopeTracker,
enter(node) {
if (node.type !== "CallExpression" || node.callee.type !== "Identifier") return;
const name = node.callee.name;
if (!name || !TRANSLATION_FUNCTIONS.includes(name) || scopeTracker.isDeclared(name)) return;
missing.add(name);
}
});
return missing;
}
const SFC_SCRIPT_COMPLEX_RE = /<script(?<attrs>[^>]*)>(?<content>[\s\S]*?)<\/script[^>]*>/i;
function extractScriptSetupContent(sfc) {
const match = sfc.match(SFC_SCRIPT_COMPLEX_RE);
if (match?.groups?.content && match.groups.attrs && match.groups.attrs.indexOf("setup") !== -1) {
return {
code: match.groups.content.trim(),
loader: match.groups.attrs && /[tj]sx/.test(match.groups.attrs) ? "tsx" : "ts",
start: sfc.indexOf(match.groups.content)
};
}
}
const HeistPlugin = (options, ctx, nuxt = useNuxt()) => {
const shared = ctx.resolver.resolve(ctx.distDir, "runtime/shared/*");
const replacementName = `__nuxtMock`;
const replacementMock = `const ${replacementName} = { runWithContext: async (fn) => await fn() };`;
const resources = ["i18n-route-resources.mjs", "i18n-options.mjs"];
return createUnplugin(() => ({
name: "nuxtjs:i18n-heist",
enforce: "pre",
transform: {
filter: {
id: [shared, relative(nuxt.options.rootDir, shared)]
},
handler(code) {
const s = new MagicString(code);
if (code.includes("useRuntimeConfig()")) {
s.prepend('import { useRuntimeConfig } from "nitropack/runtime";\n');
}
s.replace(/import.+["']#app["'];?/, replacementMock);
s.replaceAll(/useNuxtApp\(\)/g, replacementName);
for (const resource of resources) {
s.replaceAll(new RegExp(`#build/${resource}`, "g"), `#internal/${resource}`);
}
return {
code: s.toString(),
map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
};
}
}
}));
};
const version = "10.1.0";
async function extendBundler(ctx, nuxt) {
const pluginOptions = {
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client
};
const resourcePlugin = ResourcePlugin(pluginOptions, ctx);
addBuildPlugin(resourcePlugin);
nuxt.hook("nitro:config", async (cfg) => {
cfg.rollupConfig.plugins = await cfg.rollupConfig.plugins || [];
cfg.rollupConfig.plugins = toArray(cfg.rollupConfig.plugins);
cfg.rollupConfig.plugins.push(HeistPlugin(pluginOptions, ctx).rollup());
cfg.rollupConfig.plugins.push(resourcePlugin.rollup());
});
const localePaths = [...new Set(ctx.localeInfo.flatMap((x) => x.meta.map((m) => m.path)))];
ctx.fullStatic = ctx.localeInfo.flatMap((x) => x.meta).every((x) => x.type === "static" || x.cache !== false);
const vueI18nPluginOptions = {
...ctx.options.bundle,
...ctx.options.compilation,
...ctx.options.customBlocks,
allowDynamic: true,
optimizeTranslationDirective: false,
include: localePaths.length ? localePaths : []
};
addBuildPlugin({
vite: () => VueI18nPlugin.vite(vueI18nPluginOptions),
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
webpack: () => VueI18nPlugin.webpack(vueI18nPluginOptions)
});
addBuildPlugin(TransformMacroPlugin(pluginOptions));
if (ctx.options.autoDeclare && nuxt.options.imports.autoImport !== false) {
addBuildPlugin(TransformI18nFunctionPlugin(pluginOptions));
}
const defineConfig = getDefineConfig(ctx);
await addDefinePlugin(defineConfig);
}
function getDefineConfig({ options, fullStatic, deploymentHash }, server = false, nuxt = useNuxt()) {
const cacheLifetime = options.experimental.cacheLifetime ?? (fullStatic ? FULL_STATIC_LIFETIME : -1);
const isCacheEnabled = cacheLifetime >= 0 && (!nuxt.options.dev || !!options.experimental.devCache);
let stripMessagesPayload = !!options.experimental.preload;
if (nuxt.options.i18n?.experimental?.stripMessagesPayload != null) {
stripMessagesPayload = nuxt.options.i18n.experimental.stripMessagesPayload;
}
const common = {
__IS_SSR__: String(nuxt.options.ssr),
__IS_SSG__: String(!!nuxt.options.nitro.static),
__PARALLEL_PLUGIN__: String(options.parallelPlugin),
__DYNAMIC_PARAMS_KEY__: JSON.stringify(DYNAMIC_PARAMS_KEY),
__DEFAULT_COOKIE_KEY__: JSON.stringify(DEFAULT_COOKIE_KEY),
__NUXT_I18N_VERSION__: JSON.stringify(version),
__NUXT_I18N_MODULE_ID__: JSON.stringify(NUXT_I18N_MODULE_ID),
__SWITCH_LOCALE_PATH_LINK_IDENTIFIER__: JSON.stringify(SWITCH_LOCALE_PATH_LINK_IDENTIFIER),
__I18N_STRATEGY__: JSON.stringify(options.strategy),
__DIFFERENT_DOMAINS__: String(options.differentDomains),
__MULTI_DOMAIN_LOCALES__: String(options.multiDomainLocales),
__ROUTE_NAME_SEPARATOR__: JSON.stringify(options.routesNameSeparator),
__ROUTE_NAME_DEFAULT_SUFFIX__: JSON.stringify(options.defaultLocaleRouteNameSuffix),
__TRAILING_SLASH__: String(options.trailingSlash),
__DEFAULT_DIRECTION__: JSON.stringify(options.defaultDirection),
__I18N_CACHE__: String(isCacheEnabled),
__I18N_CACHE_LIFETIME__: JSON.stringify(cacheLifetime),
__I18N_FULL_STATIC__: String(fullStatic),
__I18N_STRIP_UNUSED__: JSON.stringify(stripMessagesPayload),
__I18N_PRELOAD__: JSON.stringify(!!options.experimental.preload),
// eslint-disable-next-line @typescript-eslint/no-base-to-string
__I18N_ROUTING__: JSON.stringify(nuxt.options.pages.toString() && options.strategy !== "no_prefix"),
__I18N_STRICT_SEO__: JSON.stringify(!!options.experimental.strictSeo),
__I18N_SERVER_REDIRECT__: JSON.stringify(!!options.experimental.nitroContextDetection),
__I18N_HASH__: JSON.stringify(deploymentHash)
};
if (nuxt.options.ssr || !server) {
return {
...common,
__VUE_I18N_LEGACY_API__: String(!(options.bundle?.compositionOnly ?? true)),
__VUE_I18N_FULL_INSTALL__: String(options.bundle?.fullInstall ?? true),
__INTLIFY_PROD_DEVTOOLS__: "false",
__INTLIFY_DROP_MESSAGE_COMPILER__: String(options.bundle?.dropMessageCompiler ?? false)
};
}
return common;
}
async function setupNitro(ctx, nuxt) {
const [enableServerIntegration, localeDetectionPath] = await resolveLocaleDetectorPath(nuxt);
addServerTemplate({
filename: "#internal/i18n-options.mjs",
getContents: () => nuxt.vfs["#build/i18n-options.mjs"].replace(/\/\*\* client \*\*\/[\s\S]*\/\*\* client-end \*\*\//, "")
});
addServerTemplate({
filename: "#internal/i18n-route-resources.mjs",
getContents: () => nuxt.vfs["#build/i18n-route-resources.mjs"] || ""
});
addServerTemplate({
filename: "#internal/i18n-locale-detector.mjs",
getContents: () => enableServerIntegration ? `import localeDetector from ${JSON.stringify(localeDetectionPath)}
export { localeDetector }` : `const localeDetector = undefined
export { localeDetector }`
// no-op
});
nuxt.hook("nitro:config", async (nitroConfig) => {
nitroConfig.externals = defu(nitroConfig.externals ?? {}, { inline: [ctx.resolver.resolve("./runtime")] });
nitroConfig.alias["#i18n"] = ctx.resolver.resolve("./runtime/composables/index-server");
nitroConfig.rollupConfig.plugins = await nitroConfig.rollupConfig.plugins || [];
nitroConfig.rollupConfig.plugins = toArray(nitroConfig.rollupConfig.plugins);
const localePathsByType = getResourcePathsGrouped(ctx.localeInfo);
if (localePathsByType.yaml.length > 0) {
nitroConfig.rollupConfig.plugins.push(yamlPlugin({ include: localePathsByType.yaml }));
}
if (localePathsByType.json5.length > 0) {
nitroConfig.rollupConfig.plugins.push(json5Plugin({ include: localePathsByType.json5 }));
}
if (nitroConfig.imports) {
nitroConfig.imports.presets ||= [];
nitroConfig.imports.presets.push({ from: H3_PKG, imports: ["useTranslation"] });
}
nitroConfig.replace = Object.assign({}, nitroConfig.replace, getDefineConfig(ctx, true));
});
addServerImports(
[DEFINE_I18N_LOCALE_FN, DEFINE_I18N_CONFIG_FN].map((key) => ({
name: key,
as: key,
from: ctx.resolver.resolve("runtime/composables/shared")
}))
);
addServerImports([
{
name: DEFINE_LOCALE_DETECTOR_FN,
as: DEFINE_LOCALE_DETECTOR_FN,
from: ctx.resolver.resolve("runtime/composables/server")
}
]);
const h3UtilsExports = await resolveModuleExportNames(UTILS_H3_PKG, { url: import.meta.url });
addServerImports(
h3UtilsExports.map((key) => ({
name: key,
as: key,
from: ctx.resolver.resolve(nuxt.options.alias[UTILS_H3_PKG])
}))
);
addServerPlugin(ctx.resolver.resolve("runtime/server/plugin"));
addServerHandler({
route: `/_i18n/:hash/:locale/messages.json`,
handler: ctx.resolver.resolve("./runtime/server/routes/messages")
});
}
async function resolveLocaleDetectorPath(nuxt) {
const i18nLayer = nuxt.options._layers.find(