import { createResolver, defineNuxtModule, logger } from '@nuxt/kit' import { defu } from 'defu' import { existsSync } from 'node:fs' import { join } from 'node:path' import { inferSiteURL, getPackageJsonMetadata } from '../utils/meta' import { getGitBranch, getGitEnv, getLocalGitInfo } from '../utils/git' const log = logger.withTag('docus') type I18nLocale = string | { code: string, name?: string } type DocusI18nOptions = { locales?: I18nLocale[], strategy?: string } type DocusMcpOptions = { route?: string, enabled?: boolean } type RegisterModuleOptions = { langDir: string locales: Array<{ code: string, name: string, file: string }> } export default defineNuxtModule({ meta: { name: 'config', }, async setup(_options, nuxt) { const dir = nuxt.options.rootDir const url = inferSiteURL() const meta = await getPackageJsonMetadata(dir) const gitInfo = await getLocalGitInfo(dir) || getGitEnv() const siteName = (typeof nuxt.options.site === 'object' && nuxt.options.site?.name) || meta.name || gitInfo?.name || '' nuxt.options.llms = defu(nuxt.options.llms, { domain: url, title: siteName, description: meta.description || '', full: { title: siteName, description: meta.description || '', }, }) nuxt.options.site = defu(nuxt.options.site, { url, name: siteName, debug: false, }) as typeof nuxt.options.site nuxt.options.appConfig.header = defu(nuxt.options.appConfig.header, { title: siteName, }) nuxt.options.appConfig.seo = defu(nuxt.options.appConfig.seo, { titleTemplate: `%s - ${siteName}`, title: siteName, description: meta.description || '', }) nuxt.options.appConfig.github = defu(nuxt.options.appConfig.github, { owner: gitInfo?.owner, name: gitInfo?.name, url: gitInfo?.url, branch: getGitBranch(), }) /* ** MCP route (expose to client so the page header dropdown stays in sync ** with the user-configured `mcp.route` from @nuxtjs/mcp-toolkit) */ const mcpOptions = (nuxt.options as typeof nuxt.options & { mcp?: DocusMcpOptions }).mcp nuxt.options.runtimeConfig.public.mcp = defu( nuxt.options.runtimeConfig.public.mcp as DocusMcpOptions | undefined, { route: mcpOptions?.route || '/mcp' }, ) const forcedColorMode = (nuxt.options.appConfig.docus as Record)?.colorMode as string | undefined if (forcedColorMode === 'light' || forcedColorMode === 'dark') { nuxt.options.colorMode = defu({ preference: forcedColorMode, fallback: forcedColorMode }, nuxt.options.colorMode || {}) as typeof nuxt.options.colorMode } /* ** I18N */ const typedNuxtOptions = nuxt.options as typeof nuxt.options & { i18n?: false | DocusI18nOptions } const i18nOptions = typedNuxtOptions.i18n if (i18nOptions && typeof i18nOptions === 'object' && i18nOptions.locales) { const { resolve } = createResolver(import.meta.url) // Filter locales to only include existing ones const filteredLocales = i18nOptions.locales.filter((locale: I18nLocale) => { const localeCode = typeof locale === 'string' ? locale : locale.code // Check for JSON locale file const localeFilePath = resolve('../i18n/locales', `${localeCode}.json`) const hasLocaleFile = existsSync(localeFilePath) // Check for content folder const contentPath = join(nuxt.options.rootDir, 'content', localeCode) const hasContentFolder = existsSync(contentPath) if (!hasLocaleFile) { log.warn(`Locale file not found: ${localeCode}.json - skipping locale "${localeCode}"`) } if (!hasContentFolder) { log.warn(`Content folder not found: content/${localeCode}/ - skipping locale "${localeCode}"`) } return hasLocaleFile && hasContentFolder }) // Override strategy to prefix typedNuxtOptions.i18n = { ...i18nOptions, strategy: 'prefix', } // Expose filtered locales nuxt.options.runtimeConfig.public.docus = { filteredLocales, } const registerI18nModule = nuxt.hook as unknown as (name: string, callback: (register: (options: RegisterModuleOptions) => void) => void) => void registerI18nModule('i18n:registerModule', (register) => { const langDir = resolve('../i18n/locales') const locales = filteredLocales.map((locale: I18nLocale) => { return typeof locale === 'string' ? { code: locale, name: locale, file: `${locale}.json`, } : { code: locale.code, name: locale.name || locale.code, file: `${locale.code}.json`, } }) register({ langDir, locales, }) }) } }, })