@nuxtjs/i18n
Version:
Internationalization for Nuxt
1,398 lines (1,381 loc) • 77.2 kB
JavaScript
import { directoryToURL, resolveModule, useNuxt, resolvePath, useLogger, addTemplate, tryUseNuxt, addBuildPlugin, addWebpackPlugin, addRspackPlugin, extendViteConfig, addServerTemplate, addServerImports, addServerPlugin, createResolver, addPlugin, addVitePlugin, useNitro, addTypeTemplate, addComponent, addImports, defineNuxtModule } from '@nuxt/kit';
import createDebug from 'debug';
import { readFileSync, existsSync } from 'node:fs';
import { assign, isArray, isString, isObject } from '@intlify/shared';
import { parse as parse$1, compileScript } from '@vue/compiler-sfc';
import { walk } from 'estree-walker';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';
import { resolve, relative, join as join$1, isAbsolute, parse, dirname, basename } from 'pathe';
import defu$1, { defu } from 'defu';
import { encodePath, parseURL, parseQuery } from 'ufo';
import { createRoutesContext } from 'unplugin-vue-router';
import { resolveOptions } from 'unplugin-vue-router/options';
import { findStaticImports, resolveModuleExportNames } from 'mlly';
import yamlPlugin from '@rollup/plugin-yaml';
import json5Plugin from '@miyaneee/rollup-plugin-json5';
import VueI18nPlugin from '@intlify/unplugin-vue-i18n';
import MagicString from 'magic-string';
import { createUnplugin } from 'unplugin';
import { pathToFileURL, fileURLToPath } from 'node:url';
import { transform as transform$1 } from 'esbuild';
import { genArrayFromRaw, genObjectFromRaw, genObjectFromValues, genString, genSafeVariableName, genImport, 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 DEFAULT_DYNAMIC_PARAMS_KEY = "nuxtI18nInternal";
const DEFAULT_COOKIE_KEY = "i18n_redirected";
const SWITCH_LOCALE_PATH_LINK_IDENTIFIER = "nuxt-i18n-slp";
const DEFAULT_OPTIONS = {
restructureDir: "i18n",
experimental: {
localeDetector: "",
switchLocalePathLinkSSR: false,
autoImportTranslationFunctions: false,
typedPages: true,
typedOptionsAndMessages: false,
generatedLocaleFilePathFormat: "absolute",
alternateLinkCanonicalQueries: false,
hmr: true
},
bundle: {
compositionOnly: true,
runtimeOnly: false,
fullInstall: true,
dropMessageCompiler: false,
optimizeTranslationDirective: true
},
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,
lazy: false,
langDir: "locales",
rootRedirect: void 0,
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
};
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 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$/;
const debug$a = createDebug("@nuxtjs/i18n:alias");
function setupAlias({ userOptions: options, isDev, isPrepare }, nuxt) {
const modules = {
[VUE_I18N_PKG]: `${VUE_I18N_PKG}/dist/vue-i18n${!isDev && !isPrepare && 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 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);
debug$a(`${moduleName} alias`, nuxt.options.alias[moduleName]);
}
}
let parseSync;
async function initParser() {
try {
parseSync = await import('oxc-parser').then((r) => r.parseSync);
} catch (_) {
console.warn("[nuxt-i18n]: Unable to import `oxc-parser`, falling back to `@oxc-parser/wasm`.");
const { parseSync: parse } = await import('@oxc-parser/wasm');
parseSync = (filename, sourceText, options) => (
// @ts-expect-error sourceType property conflict
parse(sourceText, {
...options || {},
sourceFilename: filename.replace(/\?.*$/, "") + `.${options?.lang || "ts"}`,
sourceType: "module"
})
);
}
}
function formatMessage(message) {
return `[${NUXT_I18N_MODULE_ID}]: ${message}`;
}
function filterLocales(options, nuxt) {
const project = getLayerI18n(nuxt.options._layers[0]);
const includingLocales = toArray(project?.bundle?.onlyLocales ?? []).filter(isString);
if (!includingLocales.length) {
return;
}
options.locales = options.locales.filter(
(locale) => includingLocales.includes(isString(locale) ? locale : locale.code)
);
}
function getNormalizedLocales(locales = []) {
const normalized = [];
for (const locale of locales) {
normalized.push(isString(locale) ? { code: locale, language: locale } : locale);
}
return normalized;
}
function resolveLocales(srcDir, locales, buildDir) {
const localesResolved = [];
for (const locale of locales) {
const resolved = assign({ meta: [] }, locale);
delete resolved.file;
delete resolved.files;
const files = getLocaleFiles(locale);
for (const f of files) {
const path = resolve(srcDir, f.path);
const type = getLocaleType(path);
resolved.meta.push({
type,
path,
hash: getHash(path),
loadPath: relative(buildDir, path),
file: {
path: f.path,
cache: f.cache ?? type !== "dynamic"
}
});
}
localesResolved.push(resolved);
}
return localesResolved;
}
function getLocaleType(path) {
if (!EXECUTABLE_EXT_RE.test(path)) {
return "static";
}
const parsed = parseSync(path, readFileSync(path, "utf-8"));
const analyzed = scanProgram(parsed.program);
return analyzed === "object" ? "static" : analyzed === "function" ? "dynamic" : "unknown";
}
function scanProgram(program) {
let varDeclarationName;
const varDeclarations = [];
for (const node of program.body) {
switch (node.type) {
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;
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", buildDir = useNuxt().options.buildDir) {
const absolutePath = await resolvePath(configPath, { cwd: rootDir, extensions: EXECUTABLE_EXTENSIONS });
if (!existsSync(absolutePath))
return void 0;
const relativeBase = relative(buildDir, rootDir);
return {
rootDir,
meta: {
loadPath: join$1(relativeBase, relative(rootDir, absolutePath)),
// relative
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) {
const rootDir = useNuxt().options.rootDir;
return getLocaleFiles(locale).map((file) => ({
path: resolve(rootDir, resolve(config.langDir ?? "", file.path)),
cache: file.cache
}));
}
const mergeConfigLocales = (configs, baseLocales = []) => {
const mergedLocales = /* @__PURE__ */ new Map();
for (const locale of baseLocales) {
mergedLocales.set(locale.code, locale);
}
for (const config of configs) {
for (const locale of config.locales ?? []) {
if (isString(locale)) {
mergedLocales.set(locale, mergedLocales.get(locale) ?? { language: locale, code: locale });
continue;
}
const files = resolveRelativeLocales(locale, config);
delete locale.file;
const merged = mergedLocales.get(locale.code);
if (merged != null) {
merged.files ??= [];
merged.files.unshift(...files);
mergedLocales.set(locale.code, assign({}, locale, merged));
continue;
}
mergedLocales.set(locale.code, assign({}, locale, { files }));
}
}
return Array.from(mergedLocales.values());
};
const mergeI18nModules = async (options, nuxt) => {
const i18nModules = [];
await nuxt.callHook(
"i18n:registerModule",
(config) => config.langDir && config.locales && i18nModules.push({ langDir: config.langDir, locales: config.locales })
);
if (i18nModules.length > 0) {
const baseLocales = [];
for (const locale of options.locales) {
if (!isObject(locale))
continue;
baseLocales.push(assign({}, locale, { file: void 0, files: getLocaleFiles(locale) }));
}
options.locales = mergeConfigLocales(i18nModules, baseLocales);
}
options.i18nModules = i18nModules;
};
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;
}
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);
assign(options, defu(overrides, mergedOptions));
}
};
function toArray(value) {
return Array.isArray(value) ? value : [value];
}
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 shouldPrefix(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 (isString(locale))
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 localizeSingleRoute(route, options, { locales = [], parent, parentLocalized, extra = false, defaultLocales }) {
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 { strategy, trailingSlash, multiDomainLocales, routesNameSeparator, defaultLocaleRouteNameSuffix } = options;
const resultRoutes = [];
for (const locale of componentOptions.locales) {
const localized = { ...route, locale, parent };
const isDefaultLocale = defaultLocales.includes(locale);
const addDefaultTree = isDefaultLocale && strategy === "prefix_and_default" && parent == null && !extra;
if (addDefaultTree && parent == null && !extra) {
const extraRoutes = localizeSingleRoute(route, options, { locales: [locale], extra: true, defaultLocales });
resultRoutes.push(...extraRoutes);
}
if (localized.name) {
const nameSegments = [localized.name, routesNameSeparator, locale];
if (extra) {
nameSegments.push(routesNameSeparator, defaultLocaleRouteNameSuffix);
}
localized.name = join(...nameSegments);
}
localized.path = componentOptions.paths?.[locale] ?? localized.path;
const defaultLocale = isDefaultLocale ? locale : options.defaultLocale;
if (shouldPrefix({ defaultLocale, ...localized }, options, extra)) {
if (multiDomainLocales && (strategy === "prefix_except_default" || strategy === "prefix_and_default")) {
resultRoutes.push({
...localized,
name: join(localized.name, routesNameSeparator, defaultLocaleRouteNameSuffix)
});
}
localized.path = join("/", locale, localized.path);
if (isDefaultLocale && strategy === "prefix" && options.includeUnprefixedFallback) {
resultRoutes.push({ ...route, locale, parent });
}
}
if (localized.alias) {
const newAliases = [];
for (const alias of toArray(localized.alias)) {
let localizedAlias = alias;
if (shouldPrefix({ defaultLocale, ...localized, path: alias }, options, extra)) {
localizedAlias = join("/", locale, localizedAlias);
}
localizedAlias &&= adjustRoutePathForTrailingSlash({ ...localized, path: localizedAlias }, trailingSlash);
newAliases.push(localizedAlias);
}
localized.alias = newAliases;
}
localized.path &&= adjustRoutePathForTrailingSlash(localized, trailingSlash);
if (parentLocalized != null) {
localized.path = localized.path.replace(parentLocalized.path + "/", "");
if (localized.path === parentLocalized.path) {
localized.path = "";
}
}
if (localized.children) {
let children = [];
for (const child of localized.children) {
children = children.concat(
localizeSingleRoute(child, options, {
locales: [locale],
parent: route,
parentLocalized: localized,
extra,
defaultLocales
})
);
}
localized.children = children;
}
resultRoutes.push(localized);
}
for (const x of resultRoutes) {
delete x.parent;
delete x.locale;
}
return resultRoutes;
}
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);
}
let processed = [];
for (const route of routes) {
processed = processed.concat(localizeSingleRoute(route, options, { locales: options.localeCodes, defaultLocales }));
}
return processed;
}
const debug$9 = createDebug("@nuxtjs/i18n:layers");
function 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" : "extended";
const layerHint = `In ${configLocation} layer (\`${resolve(project.config.rootDir, layer.configFile)}\`) -`;
try {
if (layerI18n.langDir) {
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 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(formatMessage(`${layerHint} ${err.message}`));
}
}
}
function 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$9("mergeLayerPages: path ->", lPath);
analyzer(lPath);
}
}
function resolveI18nDir(layer, i18n, fromRootDir = false) {
if (i18n.restructureDir !== false) {
return resolve(layer.config.rootDir, i18n.restructureDir ?? "i18n");
}
return resolve(layer.config.rootDir, fromRootDir ? "" : layer.config.srcDir);
}
function resolveLayerLangDir(layer, i18n) {
i18n.restructureDir ??= "i18n";
i18n.langDir ??= i18n.restructureDir !== false ? "locales" : "";
return resolve(resolveI18nDir(layer, i18n), i18n.langDir);
}
function applyLayerOptions(options, nuxt) {
options.locales ??= [];
const configs = [];
for (const layer of nuxt.options._layers) {
const i18n = getLayerI18n(layer);
if (i18n?.locales == null)
continue;
configs.push(assign({}, i18n, { langDir: resolveLayerLangDir(layer, i18n), locales: i18n.locales }));
}
const installModuleConfigMap = /* @__PURE__ */ new Map();
outer:
for (const locale of options.locales) {
if (isString(locale))
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(...installModuleConfigMap.values());
debug$9("merged locales", configs);
options.locales = mergeConfigLocales(configs);
}
async function resolveLayerVueI18nConfigInfo(options) {
const logger = useLogger(NUXT_I18N_MODULE_ID);
const nuxt = useNuxt();
const resolved = await Promise.all(
nuxt.options._layers.map(async (layer) => {
const i18n = getLayerI18n(layer);
const i18nDirPath = resolveI18nDir(layer, i18n || {}, true);
const res = await resolveVueI18nConfigInfo(i18nDirPath, i18n?.vueI18n);
if (res == null && i18n?.vueI18n != null) {
logger.warn(`Vue I18n configuration file \`${i18n.vueI18n}\` not found in \`${i18nDirPath}\`. Skipping...`);
return void 0;
}
return res;
})
);
if (options.vueI18n && isAbsolute(options.vueI18n)) {
resolved.unshift(await resolveVueI18nConfigInfo(parse(options.vueI18n).dir, options.vueI18n));
}
return resolved.filter((x) => x != null);
}
const debug$8 = createDebug("@nuxtjs/i18n:pages");
async function setupPages({ localeCodes, options, isSSR }, nuxt) {
if (!localeCodes.length)
return;
let includeUnprefixedFallback = !isSSR;
nuxt.hook("nitro:init", () => {
debug$8("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$8(`pagesDir: ${pagesDir}, srcDir: ${srcDir}, trailingSlash: ${options.trailingSlash}`);
const typedRouter = await setupExperimentalTypedRoutes(options, nuxt);
const pagesHook = nuxt.options.experimental.scanPageMeta === "after-resolve" ? "pages:resolved" : "pages:extend";
nuxt.hook(pagesHook, async (pages) => {
debug$8("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);
if (typedRouter) {
await typedRouter.createContext(pages).scanPages(false);
}
const localizedPages = localizeRoutes(pages, {
...options,
localeCodes,
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.length = 0;
pages.unshift(...localizedPages);
}
debug$8("... made pages", pages);
});
}
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, 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),
// if route has an index child the parent will not have a name
name: page.name ?? page.children?.find((x) => x.path.endsWith("/index"))?.name,
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$8("getRouteOptionsResolver useConfig", useConfig);
const getter = useConfig ? getRouteOptionsFromPages : getRouteOptionsFromComponent;
return (route, localeCodes) => {
const ret = getter(route, localeCodes, ctx, pages, defaultLocale);
debug$8("getRouteOptionsResolver resolved", route.path, route.name, ret);
return ret;
};
}
function resolveRoutePath(path) {
const normalizePath = path.slice(1, path.length);
const tokens = parseSegment(normalizePath);
return getRoutePath(tokens);
}
function getRouteOptionsFromPages(route, localeCodes, ctx, 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 valueByName = pageMeta.name ? pages[pageMeta.name] : void 0;
const pageOptions = valueByName ?? pages[pageMeta.path];
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) {
if (isString(pageOptions[locale])) {
options.paths[locale] = resolveRoutePath(pageOptions[locale]);
continue;
}
if (isString(pageOptions[defaultLocale])) {
options.paths[locale] = resolveRoutePath(pageOptions[defaultLocale]);
}
}
return options;
}
function getRouteOptionsFromComponent(route, localeCodes) {
debug$8("getRouteOptionsFromComponent", route);
if (route.file == null) {
return void 0;
}
const options = {
locales: localeCodes,
paths: {}
};
const componentOptions = readComponent(route.file);
if (componentOptions == null) {
return options;
}
if (componentOptions === false) {
return void 0;
}
options.locales = componentOptions.locales || localeCodes;
for (const locale in componentOptions.paths) {
if (isString(componentOptions.paths[locale])) {
options.paths[locale] = resolveRoutePath(componentOptions.paths[locale]);
}
}
return options;
}
function readComponent(target) {
try {
const content = readFileSync(target, "utf-8");
const { descriptor } = parse$1(content);
if (!content.includes(NUXT_I18N_COMPOSABLE_DEFINE_ROUTE)) {
return void 0;
}
const desc = compileScript(descriptor, { id: target });
let extract = "";
const genericSetupAst = desc.scriptSetupAst || desc.scriptAst || [];
for (const ast of genericSetupAst) {
walk(ast, {
enter(node) {
if (node.type !== "CallExpression")
return;
if (node.callee.type === "Identifier" && node.callee.name === NUXT_I18N_COMPOSABLE_DEFINE_ROUTE) {
const arg = node.arguments[0];
if (arg.type === "BooleanLiteral" || arg.type === "ObjectExpression" && verifyObjectValue(arg.properties)) {
extract = desc.loc.source.slice(arg.start, arg.end);
}
}
}
});
}
if (extract) {
return evalValue(extract);
}
} catch (e) {
console.warn(formatMessage(`Couldn't read component data at ${target}: (${e.message})`));
}
return void 0;
}
function nodeNameOrValue(val, name) {
return val.type === "Identifier" && val.name === name || val.type === "StringLiteral" && val.value === name;
}
function verifyObjectValue(properties) {
for (const prop of properties) {
if (prop.type !== "ObjectProperty") {
console.warn(formatMessage(`'defineI18nRoute' requires an object as argument`));
return false;
}
if (nodeNameOrValue(prop.key, "locales")) {
if (prop.value.type !== "ArrayExpression" || !verifyLocalesArrayExpression(prop.value.elements)) {
console.warn(formatMessage(`expected 'locale' to be an array`));
return false;
}
}
if (nodeNameOrValue(prop.key, "paths")) {
if (prop.value.type !== "ObjectExpression" || !verifyPathsObjectExpress(prop.value.properties)) {
console.warn(formatMessage(`expected 'paths' to be an object`));
return false;
}
}
}
return true;
}
function verifyPathsObjectExpress(properties) {
for (const prop of properties) {
if (prop.type !== "ObjectProperty") {
console.warn(formatMessage(`'paths' is required object`));
return false;
}
if (prop.key.type === "Identifier" && prop.value.type !== "StringLiteral") {
console.warn(formatMessage(`expected 'paths.${prop.key.name}' to be a string literal`));
return false;
}
if (prop.key.type === "StringLiteral" && prop.value.type !== "StringLiteral") {
console.warn(formatMessage(`expected 'paths.${prop.key.value}' to be a string literal`));
return false;
}
}
return true;
}
function verifyLocalesArrayExpression(elements) {
for (const element of elements) {
if (element?.type !== "StringLiteral") {
console.warn(formatMessage(`required 'locales' value string literal`));
return false;
}
}
return true;
}
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 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;
}
const debug$7 = createDebug("@nuxtjs/i18n:transform:macros");
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(code, id) {
debug$7("transform", id);
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(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$7("transformed: id -> ", id);
debug$7("transformed: code -> ", s.toString());
return {
code: s.toString(),
map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
};
}
}
};
});
async function transform(input, options) {
return await transform$1(input, { ...tryUseNuxt()?.options.esbuild?.options, ...options });
}
const debug$6 = createDebug("@nuxtjs/i18n:transform:resource");
const ResourcePlugin = (options, ctx) => createUnplugin(() => {
debug$6("options", options);
const pattern = [NUXT_I18N_COMPOSABLE_DEFINE_LOCALE, NUXT_I18N_COMPOSABLE_DEFINE_CONFIG].join("|");
const DEFINE_I18N_FN_RE = new RegExp(`\\b(${pattern})\\s*\\((.+)\\s*\\)`, "gms");
const i18nFileMetas = [...ctx.localeInfo.flatMap((x) => x.meta), ...ctx.vueI18nConfigPaths.map((x) => x.meta)];
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)) {
debug$6("transformInclude", id);
return /\.([c|m]?[j|t]s)$/.test(id);
}
},
/**
* Match and replace `defineI18nX(<content>)` with its `<content>`
*/
async transform(_code, id) {
debug$6("transform", id);
let code = _code;
const staticImports = findStaticImports(_code);
for (const x of staticImports) {
i18nPathSet.add(await resolvePath(resolve(dirname(id), x.specifier)));
}
if (/(c|m)?ts$/.test(id)) {
code = (await transform(_code, { loader: "ts" })).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 && !/\.([c|m]?ts)$/.test(id) ? s.generateMap({ hires: true }) : null
};
}
}
};
});
const debug$5 = 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 = (options) => createUnplugin(() => {
return {
name: "nuxtjs:i18n-function-injection",
enforce: "pre",
transformInclude(id) {
return isVue(id, { type: ["script"] });
},
transform(code, id) {
debug$5("transform", id);
const script = extractScriptContent(code);
if (!script || !TRANSLATION_FUNCTIONS_RE.test(script)) {
return;
}
const scriptSetup = parse$1(code, { sourceMap: false }).descriptor.scriptSetup;
if (!scriptSetup) {
return;
}
const ast = parseSync(id, script, { lang: "tsx" });
let scopeTracker = new ScopeTracker();
const varCollector = new ScopedVarsCollector();
walk(ast.program, {
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.program, {
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$5(`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$5("transformed: id -> ", id);
debug$5("transformed: code -> ", s.toString());
return {
code: s.toString(),
map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
};
}
}
};
});
const SFC_SCRIPT_RE = /<script\s*[^>]*>([\s\S]*?)<\/script\s*[^>]*>/i;
function extractScriptContent(html) {
const match = html.match(SFC_SCRIPT_RE);
if (match && match[1]) {
return match[1].trim();
}
return null;
}
class ScopeTracker {
// the top of the stack is not a part of current key, it is used for next level
scopeIndexStack;
curScopeKey;
constructor() {
this.scopeIndexStack = [0];
this.curScopeKey = "";
}
getKey() {
return this.scopeIndexStack.slice(0, -1).join("-");
}
enterScope() {
this.scopeIndexStack.push(0);
this.curScopeKey = this.getKey();
}
leaveScope() {
this.scopeIndexStack.pop();
this.curScopeKey = this.getKey();
this.scopeIndexStack[this.scopeIndexStack.length - 1]++;
}
}
class ScopedVarsCollector {
curScopeKey;
all;
constructor() {
this.all = /* @__PURE__ */ new Map();
this.curScopeKey = "";
}
refresh(scopeKey) {
this.curScopeKey = scopeKey;
}
addVar(name) {
let vars = this.all.get(this.curScopeKey);
if (!vars) {
vars = /* @__PURE__ */ new Set();
this.all.set(this.curScopeKey, vars);
}
vars.add(name);
}
hasVar(scopeKey, name) {
const indices = scopeKey.split("-").map(Number);
for (let i = indices.length; i >= 0; i--) {
if (this.all.get(indices.slice(0, i).join("-"))?.has(name)) {
return true;
}
}
return false;
}
collect(n) {
const t = n.type;
if (t === "Identifier") {
this.addVar(n.name);
} else if (t === "RestElement") {
this.collect(n.argument);
} else if (t === "AssignmentPattern") {
this.collect(n.left);
} else if (t === "ArrayPattern") {
n.elements.forEach((e) => e && this.collect(e));
} else if (t === "ObjectPattern") {
n.properties.forEach((p) => {
if (p.type === "RestElement") {
this.collect(p);
} else {
this.collect(p.value);
}
});
}
}
}
const version = "9.5.5";
const debug$4 = createDebug("@nuxtjs/i18n:bundler");
async function extendBundler(ctx, nuxt) {
addTemplate({
write: true,
filename: "nuxt-i18n-logger.mjs",
getContents() {
if (!ctx.options.debug && !nuxt.options._i18nTest) {
return `export function createLogger() {}`;
}
return `
import { createConsola } from 'consola'
const debugLogger = createConsola({ level: ${ctx.options.debug === "verbose" ? 999 : 4} }).withTag('i18n')
export function createLogger(label) {
return debugLogger.withTag(label)
}`;
}
});
nuxt.options.alias[asI18nVirtual("logger")] = ctx.resolver.resolve(nuxt.options.buildDir, "./nuxt-i18n-logger.mjs");
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(resourcePlugin.rollup());
});
const { options } = ctx;
const localePaths = [.../* @__PURE__ */ new Set([...ctx.localeInfo.flatMap((x) => x.meta.map((m) => m.path))])];
const vueI18nPluginOptions = {
allowDynamic: true,
include: localePaths.length ? localePaths : void 0,
runtimeOnly: options.bundle.runtimeOnly,
fullInstall: options.bundle.fullInstall,
onlyLocales: options.bundle.onlyLocales,
escapeHtml: options.compilation.escapeHtml,
compositionOnly: options.bundle.compositionOnly,
strictMessage: options.compilation.strictMessage,
defaultSFCLang: options.customBlocks.defaultSFCLang,
globalSFCScope: options.customBlocks.globalSFCScope,
dropMessageCompiler: options.bundle.dropMessageCompiler,
optimizeTranslationDirective: options.bundle.optimizeTranslationDirective
};
addBuildPlugin({
vite: () => VueI18nPlugin.vite(vueI18nPluginOptions),
webpack: () => VueI18nPlugin.webpack(vueI18nPluginOptions)
});
addBuildPlugin(TransformMacroPlugin(pluginOptions));
if (options.experimental.autoImportTranslationFunctions) {
addBuildPlugin(TransformI18nFunctionPlugin(pluginOptions));
}
const defineConfig = {
...getFeatureFlags(options.bundle),
__DEBUG__: String(!!options.debug),
__TEST__: String(!!options.debug || nuxt.options._i18nTest),
__NUXT_I18N_VERSION__: JSON.stringify(version)
};
if (nuxt.options.builder === "@nuxt/webpack-builder") {
try {
const webpack = await import('webpack').then((m) => m.default || m);
addWebpackPlugin(new webpack.DefinePlugin(defineConfig));
} catch (e) {
debug$4(e.message);
}
}
if (nuxt.options.builder === "@nuxt/rspack-builder") {
try {
const { rspack } = await import('@rspack/core');
addRspackPlugin(new rspack.DefinePlugin(defineConfig));
} catch (e) {
debug$4(e.message);
}
}
extendViteConfig((config) => {
config.define ??= {};
config.define["__DEBUG__"] = defineConfig["__DEBUG__"];
config.define["__TEST__"] = defineConfig["__TEST__"];
config.define["__NUXT_I18N_VERSION__"] = JSON.stringify(version);
debug$4("vite.config.define", config.define);
});
}
function getFeatureFlags({ compositionOnly = true, fullInstall = true, dropMessageCompiler = false }) {
return {
__VUE_I18N_FULL_INSTALL__: String(fullInstall),
__VUE_I18N_LEGACY_API__: String(!compositionOnly),
__INTLIFY_PROD_DEVTOOLS__: "false",
__INTLIFY_DROP_MESSAGE_COMPILER__: String(dropMessageCompiler)
};
}
const debug$3 = createDebug("@nuxtjs/i18n:nitro");
async function setupNitro(ctx, nuxt) {
const [enableServerIntegration, localeDetectionPath] = await resolveLocaleDetectorPath(nuxt);
const setupServer = enableServerIntegration || ctx.options.experimental.typedOptionsAndMessages && ctx.isDev;
if (setupServer) {
addServerTemplate({
filename: "#internal/i18n/options.mjs",
getContents: () => nuxt.vfs["#build/i18n.options.mjs"]?.replace(/\/\*\* client \*\*\/[\s\S]*\/\*\* client-end \*\*\//, "")
});