specra 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/config/specra.config.schema.json +42 -2
  2. package/dist/{chunk-4WCWKNQV.mjs → chunk-6S3EJVEO.mjs} +1 -1
  3. package/dist/chunk-6S3EJVEO.mjs.map +1 -0
  4. package/dist/{chunk-5CAUTWFL.mjs → chunk-CWHRZHZO.mjs} +21 -5
  5. package/dist/chunk-CWHRZHZO.mjs.map +1 -0
  6. package/dist/{chunk-QXGWZII6.mjs → chunk-WSQZILUA.mjs} +98 -36
  7. package/dist/chunk-WSQZILUA.mjs.map +1 -0
  8. package/dist/components/index.d.mts +20 -2
  9. package/dist/components/index.d.ts +20 -2
  10. package/dist/components/index.js +98 -42
  11. package/dist/components/index.js.map +1 -1
  12. package/dist/components/index.mjs +98 -42
  13. package/dist/components/index.mjs.map +1 -1
  14. package/dist/{config.types-DaHC_z_S.d.ts → config.types-Ccp3jN5U.d.mts} +23 -5
  15. package/dist/{config.types-DaHC_z_S.d.mts → config.types-Ccp3jN5U.d.ts} +23 -5
  16. package/dist/index.d.mts +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +257 -175
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +13 -4
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/layouts/index.d.mts +1 -1
  23. package/dist/layouts/index.d.ts +1 -1
  24. package/dist/layouts/index.js +103 -82
  25. package/dist/layouts/index.js.map +1 -1
  26. package/dist/layouts/index.mjs +2 -2
  27. package/dist/lib/index.d.mts +2 -2
  28. package/dist/lib/index.d.ts +2 -2
  29. package/dist/lib/index.js +231 -170
  30. package/dist/lib/index.js.map +1 -1
  31. package/dist/lib/index.mjs +2 -2
  32. package/package.json +1 -1
  33. package/dist/chunk-4WCWKNQV.mjs.map +0 -1
  34. package/dist/chunk-5CAUTWFL.mjs.map +0 -1
  35. package/dist/chunk-QXGWZII6.mjs.map +0 -1
@@ -138,8 +138,15 @@
138
138
  ]
139
139
  },
140
140
  "i18n": {
141
- "description": "Enable i18n (internationalization)",
142
- "type": "boolean"
141
+ "anyOf": [
142
+ {
143
+ "type": "boolean"
144
+ },
145
+ {
146
+ "$ref": "#/definitions/I18nConfig"
147
+ }
148
+ ],
149
+ "description": "Internationalization (i18n) configuration Set to true to enable with defaults, or provide configuration object"
143
150
  },
144
151
  "showAuthors": {
145
152
  "description": "Show author information",
@@ -262,6 +269,39 @@
262
269
  },
263
270
  "type": "object"
264
271
  },
272
+ "I18nConfig": {
273
+ "additionalProperties": false,
274
+ "description": "Internationalization configuration",
275
+ "properties": {
276
+ "defaultLocale": {
277
+ "description": "The default locale (e.g. 'en') - used when no locale is present in URL",
278
+ "type": "string"
279
+ },
280
+ "localeNames": {
281
+ "additionalProperties": {
282
+ "type": "string"
283
+ },
284
+ "description": "Map of locale codes to display names (e.g. { en: \"English\", fr: \"Français\" })",
285
+ "type": "object"
286
+ },
287
+ "locales": {
288
+ "description": "List of supported locales (e.g. ['en', 'fr', 'es'])",
289
+ "items": {
290
+ "type": "string"
291
+ },
292
+ "type": "array"
293
+ },
294
+ "prefixDefault": {
295
+ "description": "Whether to use locale subpaths for default locale (default: false)",
296
+ "type": "boolean"
297
+ }
298
+ },
299
+ "required": [
300
+ "defaultLocale",
301
+ "locales"
302
+ ],
303
+ "type": "object"
304
+ },
265
305
  "NavigationConfig": {
266
306
  "additionalProperties": false,
267
307
  "description": "Navigation and sidebar configuration",
@@ -256,4 +256,4 @@ export {
256
256
  getConfig,
257
257
  reloadConfig
258
258
  };
259
- //# sourceMappingURL=chunk-4WCWKNQV.mjs.map
259
+ //# sourceMappingURL=chunk-6S3EJVEO.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/sidebar-utils.ts","../src/lib/config.types.ts","../src/lib/config.server.ts"],"sourcesContent":["/**\n * Unified sidebar sorting and structure building utilities\n * This module provides consistent sidebar logic across the application\n * to ensure ordering is handled the same way everywhere.\n */\n\nexport interface SidebarGroup {\n label: string\n path: string\n icon?: string\n items: any[]\n position: number\n collapsible: boolean\n defaultCollapsed: boolean\n children: Record<string, SidebarGroup>\n}\n\n/**\n * Sort sidebar items by their position.\n * Items with explicit sidebar_position come first (sorted numerically),\n * followed by items without position (sorted by their original order).\n *\n * @param items - Array of items with optional sidebar_position\n * @returns Sorted array of items\n */\nexport function sortSidebarItems<T extends { sidebar_position?: number; meta?: any }>(items: T[]): T[] {\n return [...items].sort((a, b) => {\n const posA = a.sidebar_position ?? a.meta?.sidebar_position ?? a.meta?.order ?? 999\n const posB = b.sidebar_position ?? b.meta?.sidebar_position ?? b.meta?.order ?? 999\n return posA - posB\n })\n}\n\n/**\n * Sort sidebar groups by their position.\n * Groups with explicit position come first (sorted numerically),\n * followed by groups without position at the end (sorted by their original order).\n *\n * @param groups - Record of group key to group object with position\n * @returns Sorted array of [key, group] tuples\n */\nexport function sortSidebarGroups<T extends { position: number }>(\n groups: Record<string, T>\n): [string, T][] {\n return Object.entries(groups).sort(([, a], [, b]) => {\n const posA = a.position ?? 999\n const posB = b.position ?? 999\n return posA - posB\n })\n}\n\n/**\n * Build hierarchical sidebar structure from flat list of documents\n * This is the single source of truth for sidebar structure used by both\n * the sidebar component and navigation (prev/next) links.\n *\n * @param docs - Array of documents with metadata\n * @returns Object containing root groups and standalone items\n */\nexport function buildSidebarStructure<T extends {\n filePath: string\n slug: string\n categoryLabel?: string\n categoryPosition?: number\n categoryIcon?: string\n categoryCollapsible?: boolean\n categoryCollapsed?: boolean\n meta: any\n}>(docs: T[]): {\n rootGroups: Record<string, SidebarGroup>\n standalone: T[]\n} {\n const rootGroups: Record<string, SidebarGroup> = {}\n const standalone: T[] = []\n\n // First pass: collect category metadata from all docs to build complete folder structure\n const categoryMetadata = new Map<string, {\n label?: string\n position?: number\n icon?: string\n collapsible?: boolean\n collapsed?: boolean\n }>()\n\n docs.forEach((doc) => {\n const pathParts = doc.filePath.split(\"/\")\n const folderPath = pathParts.length > 1 ? pathParts.slice(0, -1).join(\"/\") : \"\"\n\n if (folderPath && doc.categoryLabel) {\n categoryMetadata.set(folderPath, {\n label: doc.categoryLabel,\n position: doc.categoryPosition,\n icon: doc.categoryIcon,\n collapsible: doc.categoryCollapsible,\n collapsed: doc.categoryCollapsed\n })\n }\n })\n\n docs.forEach((doc) => {\n const pathParts = doc.filePath.split(\"/\")\n const isIndexFile = doc.filePath.endsWith(\"/index\") ||\n doc.filePath === \"index\" ||\n (pathParts.length > 1 && doc.slug === pathParts.slice(0, -1).join(\"/\"))\n\n const customGroup = doc.meta.sidebar || doc.meta.group\n\n if (customGroup) {\n const groupName = customGroup.charAt(0).toUpperCase() + customGroup.slice(1)\n if (!rootGroups[groupName]) {\n rootGroups[groupName] = {\n label: groupName,\n path: customGroup,\n items: [],\n position: 999,\n collapsible: doc.categoryCollapsible ?? true,\n defaultCollapsed: doc.categoryCollapsed ?? false,\n children: {}\n }\n }\n if (isIndexFile) {\n // Use categoryPosition if available (from _category_.json), otherwise sidebar_position from frontmatter\n rootGroups[groupName].position = doc.categoryPosition ?? doc.meta.sidebar_position ?? 999\n rootGroups[groupName].icon = doc.categoryIcon\n } else {\n rootGroups[groupName].items.push(doc)\n }\n return\n }\n\n if (pathParts.length > 1) {\n const folderParts = pathParts.slice(0, -1)\n let currentLevel = rootGroups\n let currentPath = \"\"\n\n for (let i = 0; i < folderParts.length; i++) {\n const folder = folderParts[i]\n currentPath = currentPath ? `${currentPath}/${folder}` : folder\n const folderLabel = folder.split(\"-\").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(\" \")\n\n // Get metadata for this specific folder path\n const metadata = categoryMetadata.get(currentPath)\n\n if (!currentLevel[folder]) {\n currentLevel[folder] = {\n label: metadata?.label ?? folderLabel,\n path: currentPath,\n icon: metadata?.icon,\n items: [],\n position: metadata?.position ?? 999,\n collapsible: metadata?.collapsible ?? true,\n defaultCollapsed: metadata?.collapsed ?? false,\n children: {}\n }\n }\n\n if (i === folderParts.length - 1) {\n if (isIndexFile) {\n // Update position, label, and icon if this is the index file for this folder\n currentLevel[folder].position = doc.categoryPosition ?? doc.meta.sidebar_position ?? currentLevel[folder].position\n if (doc.categoryLabel) {\n currentLevel[folder].label = doc.categoryLabel\n }\n if (doc.categoryIcon) {\n currentLevel[folder].icon = doc.categoryIcon\n }\n if (doc.categoryCollapsible !== undefined) {\n currentLevel[folder].collapsible = doc.categoryCollapsible\n }\n if (doc.categoryCollapsed !== undefined) {\n currentLevel[folder].defaultCollapsed = doc.categoryCollapsed\n }\n } else {\n currentLevel[folder].items.push(doc)\n }\n }\n\n currentLevel = currentLevel[folder].children\n }\n } else {\n if (!isIndexFile) {\n standalone.push(doc)\n }\n }\n })\n\n return { rootGroups, standalone }\n}\n","/**\n * Configuration schema for Specra documentation system\n */\n\n/**\n * Site metadata and branding\n */\nexport interface SiteConfig {\n /** The title of the documentation site */\n title: string\n /** Short description of the documentation */\n description?: string\n /** URL where the documentation is hosted */\n url?: string\n /** Base URL path for the documentation (e.g., '/docs') */\n baseUrl?: string\n /** Path to the site logo - can be a string or an object with light/dark variants */\n logo?: string | { light: string; dark: string }\n /** Path to the favicon */\n favicon?: string\n /** Default language for the documentation */\n language?: string\n /** Organization or author name */\n organizationName?: string\n /** Project name */\n projectName?: string\n /** Active/default version for the documentation */\n activeVersion?: string\n /** Whether to hide the site title in the header */\n hideTitle?: boolean\n /** Whether to hide the site logo in the header */\n hideLogo?: boolean\n}\n\n/**\n * Theme and appearance settings\n */\nexport interface ThemeConfig {\n /** Primary color for the theme */\n primaryColor?: string\n /** Default theme mode */\n defaultMode?: \"light\" | \"dark\" | \"system\"\n /** Whether to respect system preferences */\n respectPrefersColorScheme?: boolean\n /** Custom CSS file path */\n customCss?: string\n}\n\n/**\n * Tab group for organizing documentation\n */\nexport interface TabGroup {\n /** Unique identifier for the tab group */\n id: string\n /** Display label for the tab */\n label: string\n /** Optional icon name (lucide-react icon) */\n icon?: string\n}\n\n/**\n * Navigation and sidebar configuration\n */\nexport interface NavigationConfig {\n /** Whether to show the sidebar by default */\n showSidebar?: boolean\n /** Whether the sidebar is collapsible */\n collapsibleSidebar?: boolean\n /** Whether to show breadcrumbs */\n showBreadcrumbs?: boolean\n /** Whether to show table of contents */\n showTableOfContents?: boolean\n /** Position of table of contents */\n tocPosition?: \"left\" | \"right\"\n /** Maximum depth for table of contents */\n tocMaxDepth?: number\n /** Tab groups for organizing documentation sections */\n tabGroups?: TabGroup[]\n}\n\n/**\n * Social and external links\n */\nexport interface SocialLinks {\n /** GitHub repository URL */\n github?: string\n /** Twitter/X handle or URL */\n twitter?: string\n /** Discord invite URL */\n discord?: string\n /** LinkedIn profile or company page */\n linkedin?: string\n /** YouTube channel URL */\n youtube?: string\n /** Custom social links */\n custom?: Array<{\n label: string\n url: string\n icon?: string\n }>\n}\n\n/**\n * Search configuration\n */\nexport interface SearchConfig {\n /** Enable/disable search functionality */\n enabled?: boolean\n /** Placeholder text for search input */\n placeholder?: string\n /** Search provider type */\n provider?: \"meilisearch\" | \"algolia\" | \"local\"\n /** Meilisearch configuration */\n meilisearch?: {\n /** Meilisearch server URL */\n host: string\n /** API key for Meilisearch */\n apiKey?: string\n /** Index name */\n indexName: string\n }\n}\n\n/**\n * Analytics configuration\n */\nexport interface AnalyticsConfig {\n /** Google Analytics tracking ID */\n googleAnalytics?: string\n /** Google Tag Manager ID */\n googleTagManager?: string\n /** Plausible Analytics domain */\n plausible?: string\n /** Custom analytics scripts */\n custom?: Array<{\n src: string\n async?: boolean\n defer?: boolean\n }>\n}\n\n/**\n * Footer branding configuration\n */\nexport interface FooterBranding {\n /** Whether to show the branding */\n showBranding?: boolean\n /** Logo for the branding - can be a string or an object with light/dark variants */\n logo?: string | { light: string; dark: string }\n /** Title text for the branding */\n title?: string\n /** URL to link to when branding is clicked */\n url?: string\n}\n\n/**\n * Footer configuration\n */\nexport interface FooterConfig {\n /** Copyright text */\n copyright?: string\n /** Footer links organized by columns */\n links?: Array<{\n title: string\n items: Array<{\n label: string\n href: string\n }>\n }>\n /** Custom footer content */\n customContent?: string\n /** Branding configuration for \"Powered by\" section */\n branding?: FooterBranding\n}\n\n/**\n * Documentation features\n */\nexport interface FeaturesConfig {\n /**\n * Enable/disable edit this page links.\n * Provide a URL template (e.g., \"https://github.com/user/repo/edit/main/docs\") or false to disable\n */\n editUrl?: string | boolean\n /** Show last updated timestamp */\n showLastUpdated?: boolean\n /** Show reading time estimate */\n showReadingTime?: boolean\n /** Show author information */\n showAuthors?: boolean\n /** Show tags */\n showTags?: boolean\n /** Enable version dropdown */\n versioning?: boolean\n /** \n * Internationalization (i18n) configuration\n * Set to true to enable with defaults, or provide configuration object\n */\n i18n?: boolean | I18nConfig\n}\n\n/**\n * Internationalization configuration\n */\nexport interface I18nConfig {\n /** The default locale (e.g. 'en') - used when no locale is present in URL */\n defaultLocale: string\n /** List of supported locales (e.g. ['en', 'fr', 'es']) */\n locales: string[]\n /** Map of locale codes to display names (e.g. { en: \"English\", fr: \"Français\" }) */\n localeNames?: Record<string, string>\n /** Whether to use locale subpaths for default locale (default: false) */\n prefixDefault?: boolean\n}\n\n/**\n * Site-wide banner configuration\n */\nexport interface BannerConfig {\n /** Whether the banner is enabled */\n enabled?: boolean\n /** Banner message */\n message?: string\n /** Banner type */\n type?: \"info\" | \"warning\" | \"success\" | \"error\"\n /** Whether the banner can be dismissed */\n dismissible?: boolean\n}\n\n/**\n * Environment variables that can be used in documentation\n * These will be replaced at build time or runtime\n */\nexport interface EnvironmentVariables {\n /** API base URL */\n API_BASE_URL?: string\n /** API version */\n API_VERSION?: string\n /** CDN URL */\n CDN_URL?: string\n /** Custom environment variables */\n [key: string]: string | undefined\n}\n\n/**\n * Deployment configuration for different hosting scenarios\n */\nexport interface DeploymentConfig {\n /**\n * Deployment target\n * - 'vercel': For Vercel or similar Node.js hosting (uses 'standalone' output)\n * - 'github-pages': For GitHub Pages static hosting (uses 'export' output)\n * - 'static': For any static hosting like Netlify, Cloudflare Pages, etc.\n * - 'custom-domain-static': For static hosting with custom domain (no basePath needed)\n */\n target?: \"vercel\" | \"github-pages\" | \"static\" | \"custom-domain-static\"\n\n /**\n * Base path for assets when deploying to GitHub Pages without custom domain\n * This should be your repository name (e.g., 'my-repo')\n * Only used when target is 'github-pages' and no custom domain is configured\n */\n basePath?: string\n\n /**\n * Whether a custom domain is configured\n * When true, basePath will be ignored even for GitHub Pages\n */\n customDomain?: boolean\n}\n\n/**\n * Main configuration interface\n */\nexport interface SpecraConfig {\n /** JSON Schema reference (for IDE support) */\n $schema?: string\n /** Site metadata and branding */\n site: SiteConfig\n /** Theme and appearance settings */\n theme?: ThemeConfig\n /** Navigation and sidebar configuration */\n navigation?: NavigationConfig\n /** Social and external links */\n social?: SocialLinks\n /** Search configuration */\n search?: SearchConfig\n /** Analytics configuration */\n analytics?: AnalyticsConfig\n /** Footer configuration */\n footer?: FooterConfig\n /** Site-wide banner */\n banner?: BannerConfig\n /** Documentation features */\n features?: FeaturesConfig\n /** Environment variables for use in docs */\n env?: EnvironmentVariables\n /** Deployment configuration */\n deployment?: DeploymentConfig\n}\n\n/**\n * Default configuration values\n */\nexport const defaultConfig: SpecraConfig = {\n site: {\n title: \"Documentation\",\n description: \"Project documentation\",\n baseUrl: \"/\",\n language: \"en\",\n },\n theme: {\n defaultMode: \"system\",\n respectPrefersColorScheme: true,\n },\n navigation: {\n showSidebar: true,\n collapsibleSidebar: true,\n showBreadcrumbs: true,\n showTableOfContents: true,\n tocPosition: \"right\",\n tocMaxDepth: 3,\n },\n search: {\n enabled: true,\n provider: \"local\",\n placeholder: \"Search documentation...\",\n },\n features: {\n showLastUpdated: true,\n showReadingTime: true,\n showAuthors: false,\n showTags: true,\n versioning: true,\n i18n: false,\n },\n}\n","import { SpecraConfig, defaultConfig } from \"./config.types\"\n\n/**\n * Deep merge two objects\n */\nfunction deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {\n const result = { ...target }\n\n for (const key in source) {\n const sourceValue = source[key]\n const targetValue = result[key]\n\n if (sourceValue && typeof sourceValue === \"object\" && !Array.isArray(sourceValue)) {\n result[key] = deepMerge(\n targetValue && typeof targetValue === \"object\" ? targetValue : {},\n sourceValue,\n ) as T[Extract<keyof T, string>]\n } else if (sourceValue !== undefined) {\n result[key] = sourceValue as T[Extract<keyof T, string>]\n }\n }\n\n return result\n}\n\n/**\n * Load and parse the Specra configuration file\n * Falls back to default configuration if file doesn't exist or is invalid\n */\nexport function loadConfig(userConfig: Partial<SpecraConfig>): SpecraConfig {\n try {\n // const userConfig = specraConfigJson as unknown as Partial<SpecraConfig>\n\n // Merge user config with defaults \n const config = deepMerge(defaultConfig, userConfig)\n\n return config\n } catch (error) {\n console.error(`❌ Error loading configuration:`, error)\n console.warn(\"Using default configuration.\")\n return defaultConfig\n }\n}\n\n/**\n * Get a specific configuration value by path (SERVER ONLY)\n * Example: getConfigValue('site.title') or getConfigValue('theme.defaultMode')\n */\nexport function getConfigValue<T = any>(path: string, config?: SpecraConfig): T | undefined {\n const cfg = config || loadConfig({})\n const keys = path.split(\".\")\n let value: any = cfg\n\n for (const key of keys) {\n if (value && typeof value === \"object\" && key in value) {\n value = value[key]\n } else {\n return undefined\n }\n }\n\n return value as T\n}\n\n/**\n * Replace environment variables in a string (SERVER ONLY)\n * Supports ${ENV_VAR} and {{ENV_VAR}} syntax\n */\nexport function replaceEnvVariables(text: string, config?: SpecraConfig): string {\n const cfg = config || loadConfig({})\n const envVars = cfg.env || {}\n\n let result = text\n\n // Replace ${VAR} syntax\n result = result.replace(/\\$\\{([^}]+)\\}/g, (match, varName) => {\n return envVars[varName] || match\n })\n\n // Replace {{VAR}} syntax\n result = result.replace(/\\{\\{([^}]+)\\}\\}/g, (match, varName) => {\n return envVars[varName] || match\n })\n\n return result\n}\n\n/**\n * Process content and replace all environment variables (SERVER ONLY)\n */\nexport function processContentWithEnv(content: string, config?: SpecraConfig): string {\n return replaceEnvVariables(content, config)\n}\n\n/**\n * Validate configuration (basic validation) (SERVER ONLY)\n */\nexport function validateConfig(config: SpecraConfig): { valid: boolean; errors: string[] } {\n const errors: string[] = []\n\n // Required fields\n if (!config.site?.title) {\n errors.push(\"site.title is required\")\n }\n\n // URL validation\n if (config.site?.url) {\n try {\n new URL(config.site.url)\n } catch {\n errors.push(\"site.url must be a valid URL\")\n }\n }\n\n // Social links validation\n if (config.social) {\n const socialKeys = [\"github\", \"twitter\", \"discord\", \"linkedin\", \"youtube\"] as const\n for (const key of socialKeys) {\n const url = config.social[key]\n if (url) {\n try {\n new URL(url)\n } catch {\n errors.push(`social.${key} must be a valid URL`)\n }\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n }\n}\n\n// Singleton instance\nlet configInstance: SpecraConfig | null = null\n\n/**\n * Initialize the Specra configuration\n * Can be called multiple times - subsequent calls will update the config\n * @param userConfig - Partial configuration to merge with defaults\n * @returns The initialized configuration\n */\nexport function initConfig(userConfig: Partial<SpecraConfig>): SpecraConfig {\n configInstance = loadConfig(userConfig)\n return configInstance\n}\n\n/**\n * Get the configuration instance (cached) (SERVER ONLY)\n * If not initialized, returns default config\n */\nexport function getConfig(): SpecraConfig {\n if (!configInstance) {\n // Auto-initialize with defaults if not already initialized\n configInstance = loadConfig({})\n }\n return configInstance\n}\n\n/**\n * Reload the configuration (useful for development) (SERVER ONLY)\n */\nexport function reloadConfig(userConfig: Partial<SpecraConfig>): SpecraConfig {\n configInstance = loadConfig(userConfig)\n return configInstance\n}\n\n/**\n * Export the loaded config as default (SERVER ONLY)\n */\n// export default getConfig()\n"],"mappings":"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","names":[]}
@@ -1,17 +1,33 @@
1
+ import {
2
+ getConfig
3
+ } from "./chunk-6S3EJVEO.mjs";
4
+
1
5
  // src/components/docs/breadcrumb.tsx
2
6
  import Link from "next/link";
3
7
  import { ChevronRight } from "lucide-react";
4
8
  import { jsx, jsxs } from "react/jsx-runtime";
5
9
  function Breadcrumb({ version, slug, title }) {
10
+ const config = getConfig();
11
+ const i18n = config.features?.i18n;
12
+ const locales = typeof i18n === "object" ? i18n.locales : i18n ? ["en"] : [];
13
+ const defaultLocale = typeof i18n === "object" ? i18n.defaultLocale : "en";
6
14
  const parts = slug.split("/");
15
+ const potentialLocale = parts[0];
16
+ const isLc = locales.includes(potentialLocale);
17
+ const isDefaultLc = potentialLocale === defaultLocale;
18
+ const homeHref = isLc ? `/docs/${version}/${potentialLocale}` : `/docs/${version}`;
7
19
  const breadcrumbs = [
8
- { label: "Docs", href: `/docs/${version}` }
20
+ { label: "Docs", href: homeHref }
9
21
  ];
10
22
  let currentPath = "";
11
23
  for (let i = 0; i < parts.length - 1; i++) {
12
- currentPath += (currentPath ? "/" : "") + parts[i];
24
+ const part = parts[i];
25
+ currentPath += (currentPath ? "/" : "") + part;
26
+ if (i === 0 && isLc) {
27
+ continue;
28
+ }
13
29
  breadcrumbs.push({
14
- label: parts[i].replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
30
+ label: part.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
15
31
  href: `/docs/${version}/${currentPath}`
16
32
  });
17
33
  }
@@ -55,7 +71,7 @@ function DocMetadata({ meta, config }) {
55
71
  /* @__PURE__ */ jsx2(Calendar, { className: "h-4 w-4" }),
56
72
  /* @__PURE__ */ jsxs2("span", { children: [
57
73
  "Updated ",
58
- new Date(meta.last_updated).toLocaleDateString()
74
+ new Date(meta.last_updated).toLocaleDateString(meta.locale || "en")
59
75
  ] })
60
76
  ] }),
61
77
  showAuthors && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5", children: [
@@ -149,4 +165,4 @@ export {
149
165
  DocTags,
150
166
  DraftBadge
151
167
  };
152
- //# sourceMappingURL=chunk-5CAUTWFL.mjs.map
168
+ //# sourceMappingURL=chunk-CWHRZHZO.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/docs/breadcrumb.tsx","../src/components/docs/doc-metadata.tsx","../src/components/docs/doc-navigation.tsx","../src/components/docs/doc-tags.tsx","../src/components/docs/draft-badge.tsx"],"sourcesContent":["import Link from \"next/link\"\nimport { ChevronRight } from \"lucide-react\"\n\nimport { getConfig } from \"@/lib/config\"\n\ninterface BreadcrumbProps {\n version: string\n slug: string\n title: string\n}\n\nexport function Breadcrumb({ version, slug, title }: BreadcrumbProps) {\n const config = getConfig()\n const i18n = config.features?.i18n\n const locales = typeof i18n === 'object' ? i18n.locales : (i18n ? ['en'] : [])\n const defaultLocale = typeof i18n === 'object' ? i18n.defaultLocale : 'en'\n\n const parts = slug.split(\"/\")\n\n // Check if first part is a locale\n const potentialLocale = parts[0]\n const isLc = locales.includes(potentialLocale)\n const isDefaultLc = potentialLocale === defaultLocale\n\n // If slug starts with locale, we might want to skip showing it as a breadcrumb item\n // But we want the \"Docs\" home link to point to it (if it's not default provided we prefix default?)\n\n const homeHref = isLc\n ? `/docs/${version}/${potentialLocale}`\n : `/docs/${version}`\n\n const breadcrumbs = [\n { label: \"Docs\", href: homeHref },\n ]\n\n // Build breadcrumb path\n let currentPath = \"\"\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]\n currentPath += (currentPath ? \"/\" : \"\") + part\n\n // Skip the locale part in the breadcrumb visual trail if it's the first part\n if (i === 0 && isLc) {\n continue\n }\n\n breadcrumbs.push({\n label: part.replace(/-/g, \" \").replace(/\\b\\w/g, (l) => l.toUpperCase()),\n href: `/docs/${version}/${currentPath}`,\n })\n }\n\n // Add current page\n breadcrumbs.push({\n label: title,\n href: `/docs/${version}/${slug}`,\n })\n\n return (\n <nav className=\"flex items-center gap-2 text-sm text-muted-foreground mb-4\" aria-label=\"Breadcrumb\">\n {breadcrumbs.map((crumb, index) => (\n <div key={crumb.href} className=\"flex items-center gap-2\">\n {index > 0 && <ChevronRight className=\"h-4 w-4\" />}\n {index === breadcrumbs.length - 1 ? (\n <span className=\"text-foreground font-medium\">{crumb.label}</span>\n ) : (\n <Link\n href={crumb.href}\n className=\"hover:text-foreground transition-colors\"\n >\n {crumb.label}\n </Link>\n )}\n </div>\n ))}\n </nav>\n )\n}\n","import { Clock, Calendar, User } from \"lucide-react\"\nimport type { DocMeta } from \"@/lib/mdx\"\nimport { getConfig, SpecraConfig } from \"@/lib/config\"\n\ninterface DocMetadataProps {\n meta: DocMeta\n config: SpecraConfig\n}\n\nexport function DocMetadata({ meta, config }: DocMetadataProps) {\n // Server component - can use getConfig directly\n // const config = getConfig()\n\n const showReadingTime = config.features?.showReadingTime && meta.reading_time\n const showLastUpdated = config.features?.showLastUpdated && meta.last_updated\n const showAuthors = config.features?.showAuthors && meta.authors?.length\n\n const hasMetadata = showReadingTime || showLastUpdated || showAuthors\n\n if (!hasMetadata) {\n return null\n }\n\n return (\n <div className=\"flex flex-wrap items-center gap-4 text-sm text-muted-foreground border-b border-border pb-4 mb-6\">\n {showReadingTime && (\n <div className=\"flex items-center gap-1.5\">\n <Clock className=\"h-4 w-4\" />\n <span>{meta.reading_time} min read</span>\n </div>\n )}\n\n {showLastUpdated && meta.last_updated && (\n <div className=\"flex items-center gap-1.5\">\n <Calendar className=\"h-4 w-4\" />\n <span>Updated {new Date(meta.last_updated).toLocaleDateString(meta.locale || 'en')}</span>\n </div>\n )}\n\n {showAuthors && (\n <div className=\"flex items-center gap-1.5\">\n <User className=\"h-4 w-4\" />\n <span>\n {meta.authors!.map((author, idx) => (\n <span key={author.id}>\n {author.name || author.id}\n {idx < meta.authors!.length - 1 && \", \"}\n </span>\n ))}\n </span>\n </div>\n )}\n </div>\n )\n}\n","import Link from \"next/link\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\n\ninterface DocNavigationProps {\n previousDoc?: {\n title: string\n slug: string\n }\n nextDoc?: {\n title: string\n slug: string\n }\n version: string\n}\n\nexport function DocNavigation({ previousDoc, nextDoc, version }: DocNavigationProps) {\n if (!previousDoc && !nextDoc) return null\n\n return (\n <div className=\"mt-12 pt-8 border-t border-border grid grid-cols-2 gap-4\">\n {previousDoc ? (\n <Link\n href={`/docs/${version}/${previousDoc.slug}`}\n className=\"group flex flex-col gap-2 p-4 rounded-xl border border-border hover:border-primary/50 hover:bg-muted/50 transition-all\"\n style={{\n textDecoration: \"none !important\"\n }}\n >\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <ChevronLeft className=\"h-4 w-4\" />\n <span>Previous</span>\n </div>\n <div className=\"text-base font-medium text-foreground group-hover:text-primary transition-colors\">\n {previousDoc.title}\n </div>\n </Link>\n ) : (\n <div />\n )}\n\n {nextDoc ? (\n <Link\n href={`/docs/${version}/${nextDoc.slug}`}\n className=\"group flex flex-col gap-2 p-4 rounded-xl border border-border hover:border-primary/50 hover:bg-muted/50 transition-all text-right\"\n style={{\n textDecoration: \"none !important\"\n }}\n >\n <div className=\"flex items-center justify-end gap-2 text-sm text-muted-foreground\">\n <span>Next</span>\n <ChevronRight className=\"h-4 w-4\" />\n </div>\n <div className=\"text-base font-medium text-foreground group-hover:text-primary transition-colors\">\n {nextDoc.title}\n </div>\n </Link>\n ) : (\n <div />\n )}\n </div>\n )\n}\n","import { Tag } from \"lucide-react\"\n\ninterface DocTagsProps {\n tags: string[]\n}\n\nexport function DocTags({ tags }: DocTagsProps) {\n if (!tags || tags.length === 0) {\n return null\n }\n\n return (\n <div className=\"flex flex-wrap items-center gap-2 mt-6 pt-6 border-t border-border\">\n <Tag className=\"h-4 w-4 text-muted-foreground\" />\n {tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-primary/10 text-primary border border-primary/20\"\n >\n {tag}\n </span>\n ))}\n </div>\n )\n}\n","import { FileWarning } from \"lucide-react\"\n\nexport function DraftBadge() {\n return (\n <div className=\"inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-yellow-500/10 border border-yellow-500/20 text-yellow-600 dark:text-yellow-400 text-sm font-medium mb-4\">\n <FileWarning className=\"h-4 w-4\" />\n <span>Draft - Not visible in production</span>\n </div>\n )\n}\n"],"mappings":"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","names":["jsx","jsxs","Link","ChevronRight","jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
@@ -5,9 +5,10 @@ import {
5
5
  } from "./chunk-BE7EROIW.mjs";
6
6
  import {
7
7
  buildSidebarStructure,
8
+ getConfig,
8
9
  sortSidebarGroups,
9
10
  sortSidebarItems
10
- } from "./chunk-4WCWKNQV.mjs";
11
+ } from "./chunk-6S3EJVEO.mjs";
11
12
 
12
13
  // src/lib/mdx.ts
13
14
  import fs2 from "fs";
@@ -141,24 +142,55 @@ function readDocFromFile(filePath, originalSlug) {
141
142
  return null;
142
143
  }
143
144
  }
144
- async function getDocBySlug(slug, version = "v1.0.0") {
145
+ function getI18nConfig() {
146
+ const config = getConfig();
147
+ const i18n = config.features?.i18n;
148
+ if (!i18n) return null;
149
+ if (typeof i18n === "boolean") {
150
+ return i18n ? {
151
+ defaultLocale: "en",
152
+ locales: ["en"],
153
+ localeNames: { en: "English" }
154
+ } : null;
155
+ }
156
+ return i18n;
157
+ }
158
+ async function getDocBySlug(slug, version = "v1.0.0", locale) {
145
159
  try {
146
- const sanitizedSlug = sanitizePath(slug);
147
160
  const sanitizedVersion = sanitizePath(version);
148
- let filePath = path2.join(DOCS_DIR2, sanitizedVersion, `${sanitizedSlug}.mdx`);
149
- let doc = readDocFromFile(filePath, sanitizedSlug);
150
- if (doc) return doc;
151
- const versionDir = path2.join(DOCS_DIR2, sanitizedVersion);
152
- if (!fs2.existsSync(versionDir)) {
153
- return null;
161
+ let sanitizedSlug = sanitizePath(slug);
162
+ const i18nConfig = getI18nConfig();
163
+ let detectedLocale = locale || i18nConfig?.defaultLocale;
164
+ if (i18nConfig) {
165
+ const parts = sanitizedSlug.split("/");
166
+ if (parts.length > 0 && i18nConfig.locales.includes(parts[0])) {
167
+ detectedLocale = parts[0];
168
+ sanitizedSlug = parts.slice(1).join("/");
169
+ if (sanitizedSlug === "") sanitizedSlug = "index";
170
+ }
154
171
  }
155
- const mdxFiles = findMdxFiles(versionDir);
156
- for (const file of mdxFiles) {
157
- const fileSlug = file.replace(/\.mdx$/, "");
158
- const testPath = path2.join(versionDir, `${fileSlug}.mdx`);
159
- const testDoc = readDocFromFile(testPath, fileSlug);
160
- if (testDoc && testDoc.slug === sanitizedSlug) {
161
- return testDoc;
172
+ const targetLocale = detectedLocale;
173
+ const isDefaultLocale = targetLocale === i18nConfig?.defaultLocale;
174
+ const basePath = path2.join(DOCS_DIR2, sanitizedVersion);
175
+ if (targetLocale) {
176
+ const localizedPath = path2.join(basePath, `${sanitizedSlug}.${targetLocale}.mdx`);
177
+ const doc2 = readDocFromFile(localizedPath, sanitizedSlug);
178
+ if (doc2) {
179
+ doc2.slug = i18nConfig ? `${targetLocale}/${sanitizedSlug}` : sanitizedSlug;
180
+ doc2.meta.locale = targetLocale;
181
+ return doc2;
182
+ }
183
+ }
184
+ const defaultPath = path2.join(basePath, `${sanitizedSlug}.mdx`);
185
+ const doc = readDocFromFile(defaultPath, sanitizedSlug);
186
+ if (doc) {
187
+ if (isDefaultLocale || !i18nConfig) {
188
+ const usePrefix = i18nConfig && (i18nConfig.prefixDefault || targetLocale !== i18nConfig.defaultLocale);
189
+ if (usePrefix && targetLocale) {
190
+ doc.slug = `${targetLocale}/${doc.slug}`;
191
+ }
192
+ doc.meta.locale = targetLocale || "en";
193
+ return doc;
162
194
  }
163
195
  }
164
196
  return null;
@@ -167,32 +199,43 @@ async function getDocBySlug(slug, version = "v1.0.0") {
167
199
  return null;
168
200
  }
169
201
  }
170
- async function getAllDocs(version = "v1.0.0") {
202
+ async function getAllDocs(version = "v1.0.0", locale) {
171
203
  try {
172
204
  const versionDir = path2.join(DOCS_DIR2, version);
173
205
  if (!fs2.existsSync(versionDir)) {
174
206
  return [];
175
207
  }
208
+ const i18nConfig = getI18nConfig();
209
+ const targetLocale = locale || i18nConfig?.defaultLocale || "en";
176
210
  const mdxFiles = findMdxFiles(versionDir);
177
211
  const categoryConfigs = getAllCategoryConfigs(version);
178
212
  const docs = await Promise.all(
179
213
  mdxFiles.map(async (file) => {
180
- const originalFilePath = file.replace(/\.mdx$/, "");
181
- const slug = originalFilePath;
182
- const doc = await getDocBySlug(slug, version);
183
- if (doc) {
184
- doc.filePath = originalFilePath;
185
- const folderPath = path2.dirname(originalFilePath).replace(/\\/g, "/");
186
- if (folderPath !== ".") {
187
- const categoryConfig = categoryConfigs.get(folderPath);
188
- if (categoryConfig) {
189
- doc.categoryLabel = categoryConfig.label;
190
- doc.categoryPosition = categoryConfig.position ?? categoryConfig.sidebar_position;
191
- doc.categoryCollapsible = categoryConfig.collapsible;
192
- doc.categoryCollapsed = categoryConfig.collapsed;
193
- doc.categoryIcon = categoryConfig.icon;
194
- doc.categoryTabGroup = categoryConfig.tab_group;
195
- }
214
+ let originalFilePath = file.replace(/\.mdx$/, "");
215
+ let isLocalized = false;
216
+ let fileLocale = i18nConfig?.defaultLocale || "en";
217
+ if (i18nConfig) {
218
+ const parts = originalFilePath.split(".");
219
+ const lastPart = parts[parts.length - 1];
220
+ if (i18nConfig.locales.includes(lastPart)) {
221
+ fileLocale = lastPart;
222
+ isLocalized = true;
223
+ originalFilePath = parts.slice(0, -1).join(".");
224
+ }
225
+ }
226
+ const doc = await getDocBySlug(isLocalized ? `${originalFilePath}.${fileLocale}` : originalFilePath, version, isLocalized ? fileLocale : void 0);
227
+ if (!doc) return null;
228
+ doc.filePath = originalFilePath;
229
+ const folderPath = path2.dirname(originalFilePath).replace(/\\/g, "/");
230
+ if (folderPath !== ".") {
231
+ const categoryConfig = categoryConfigs.get(folderPath);
232
+ if (categoryConfig) {
233
+ doc.categoryLabel = categoryConfig.label;
234
+ doc.categoryPosition = categoryConfig.position ?? categoryConfig.sidebar_position;
235
+ doc.categoryCollapsible = categoryConfig.collapsible;
236
+ doc.categoryCollapsed = categoryConfig.collapsed;
237
+ doc.categoryIcon = categoryConfig.icon;
238
+ doc.categoryTabGroup = categoryConfig.tab_group;
196
239
  }
197
240
  }
198
241
  return doc;
@@ -200,8 +243,27 @@ async function getAllDocs(version = "v1.0.0") {
200
243
  );
201
244
  const isDevelopment3 = process.env.NODE_ENV === "development";
202
245
  const uniqueDocs = /* @__PURE__ */ new Map();
203
- docs.filter((doc) => doc !== null).filter((doc) => isDevelopment3 || !doc.meta.draft).forEach((doc) => {
204
- uniqueDocs.set(doc.slug, doc);
246
+ const validDocs = docs.filter((doc) => doc !== null && (isDevelopment3 || !doc.meta.draft));
247
+ validDocs.forEach((doc) => {
248
+ let logicalSlug = doc.slug;
249
+ if (i18nConfig) {
250
+ const parts = logicalSlug.split("/");
251
+ if (i18nConfig.locales.includes(parts[0])) {
252
+ logicalSlug = parts.slice(1).join("/");
253
+ }
254
+ }
255
+ const existing = uniqueDocs.get(logicalSlug);
256
+ if (!existing) {
257
+ if (doc.meta.locale === targetLocale) {
258
+ uniqueDocs.set(logicalSlug, doc);
259
+ } else if (doc.meta.locale === i18nConfig?.defaultLocale) {
260
+ uniqueDocs.set(logicalSlug, doc);
261
+ }
262
+ } else {
263
+ if (doc.meta.locale === targetLocale && existing.meta.locale !== targetLocale) {
264
+ uniqueDocs.set(logicalSlug, doc);
265
+ }
266
+ }
205
267
  });
206
268
  return Array.from(uniqueDocs.values()).sort((a, b) => {
207
269
  const orderA = a.meta.sidebar_position ?? a.meta.order ?? 999;
@@ -514,4 +576,4 @@ export {
514
576
  buildRedirectMappings,
515
577
  findRedirect
516
578
  };
517
- //# sourceMappingURL=chunk-QXGWZII6.mjs.map
579
+ //# sourceMappingURL=chunk-WSQZILUA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/mdx.ts","../src/lib/category.ts","../src/lib/mdx-cache.ts","../src/lib/dev-utils.ts","../src/lib/toc.ts","../src/lib/redirects.ts"],"sourcesContent":["import fs from \"fs\"\nimport path from \"path\"\nimport matter from \"gray-matter\"\nimport { getAllCategoryConfigs } from \"./category\"\nimport { sortSidebarItems, sortSidebarGroups, buildSidebarStructure, type SidebarGroup } from \"./sidebar-utils\"\nimport { sanitizePath, validatePathWithinDirectory, validateMDXSecurity } from \"./mdx-security\"\nimport { getConfig } from \"./config\"\nimport { I18nConfig } from \"./config.types\"\n\nconst DOCS_DIR = path.join(process.cwd(), \"docs\")\n\n/**\n * Calculate reading time based on word count\n * Average reading speed: 200 words per minute\n */\nfunction calculateReadingTime(content: string): { minutes: number; words: number } {\n const words = content.trim().split(/\\s+/).length\n const minutes = Math.ceil(words / 200)\n return { minutes, words }\n}\n\nexport interface DocMeta {\n title: string\n description?: string\n slug?: string\n section?: string\n group?: string\n sidebar?: string\n order?: number\n sidebar_position?: number\n content?: string\n last_updated?: string\n draft?: boolean\n authors?: Array<{ id: string; name?: string }>\n tags?: string[]\n redirect_from?: string[]\n reading_time?: number\n word_count?: number\n icon?: string // Icon name for sidebar display (Lucide icon name)\n tab_group?: string // Tab group ID for organizing docs into tabs\n locale?: string // Locale of the document\n}\n\nexport interface Doc {\n slug: string\n filePath: string // Original file path for sidebar grouping\n title: string\n meta: DocMeta\n content: string\n categoryLabel?: string // Label from _category_.json\n categoryPosition?: number // Position from _category_.json\n categoryCollapsible?: boolean // Collapsible from _category_.json\n categoryCollapsed?: boolean // Default collapsed state from _category_.json\n categoryIcon?: string // Icon from _category_.json\n categoryTabGroup?: string // Tab group from _category_.json\n locale?: string // Locale of the document\n}\n\nexport interface TocItem {\n id: string\n title: string\n level: number\n}\n\nexport function getVersions(): string[] {\n try {\n const versions = fs.readdirSync(DOCS_DIR)\n return versions.filter((v) => fs.statSync(path.join(DOCS_DIR, v)).isDirectory())\n } catch (error) {\n return [\"v1.0.0\"]\n }\n}\n\n/**\n * Recursively find all MDX files in a directory\n */\nfunction findMdxFiles(dir: string, baseDir: string = dir): string[] {\n const files: string[] = []\n\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true })\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name)\n\n if (entry.isDirectory()) {\n files.push(...findMdxFiles(fullPath, baseDir))\n } else if (entry.isFile() && entry.name.endsWith(\".mdx\")) {\n // Get relative path from base directory and normalize to forward slashes\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, '/')\n files.push(relativePath)\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error)\n }\n\n return files\n}\n\n/**\n * Internal function to read a doc from file path\n */\nfunction readDocFromFile(filePath: string, originalSlug: string): Doc | null {\n try {\n if (!fs.existsSync(filePath)) {\n return null\n }\n\n // Validate path is within allowed directory\n if (!validatePathWithinDirectory(filePath, DOCS_DIR)) {\n console.error(`[Security] Path traversal attempt blocked: ${filePath}`)\n return null\n }\n\n const fileContents = fs.readFileSync(filePath, \"utf8\")\n const { data, content } = matter(fileContents)\n\n // Security: Validate MDX content for dangerous patterns\n const securityCheck = validateMDXSecurity(content, {\n strictMode: process.env.NODE_ENV === 'production',\n blockDangerousPatterns: true,\n })\n\n if (!securityCheck.valid) {\n console.error(`[Security] MDX validation failed for ${filePath}:`, securityCheck.issues)\n if (process.env.NODE_ENV === 'production') {\n return null\n }\n // In development, log warnings but continue\n console.warn('[Security] Continuing in development mode with sanitized content')\n }\n\n // Use sanitized content if available\n const safeContent = securityCheck.sanitized || content\n\n // Calculate reading time\n const { minutes, words } = calculateReadingTime(safeContent)\n\n // If custom slug provided, replace only the filename part, keep the folder structure\n let finalSlug = originalSlug\n if (data.slug) {\n const customSlug = data.slug.replace(/^\\//, '')\n const parts = originalSlug.split(\"/\")\n\n if (parts.length > 1) {\n // Keep folder structure, replace only filename\n parts[parts.length - 1] = customSlug\n finalSlug = parts.join(\"/\")\n } else {\n // Root level file, use custom slug as-is\n finalSlug = customSlug\n }\n }\n\n return {\n slug: finalSlug,\n filePath: originalSlug, // Keep original file path for sidebar\n title: data.title || originalSlug,\n meta: {\n ...data,\n content: safeContent,\n reading_time: minutes,\n word_count: words,\n } as DocMeta,\n content: safeContent,\n }\n } catch (error) {\n console.error(`Error reading file ${filePath}:`, error)\n return null\n }\n}\n\nfunction getI18nConfig(): I18nConfig | null {\n const config = getConfig()\n const i18n = config.features?.i18n\n\n if (!i18n) return null\n\n if (typeof i18n === 'boolean') {\n return i18n ? {\n defaultLocale: 'en',\n locales: ['en'],\n localeNames: { en: 'English' }\n } : null\n }\n\n return i18n\n}\n\nexport async function getDocBySlug(slug: string, version = \"v1.0.0\", locale?: string): Promise<Doc | null> {\n try {\n // Security: Sanitize and validate slug\n const sanitizedVersion = sanitizePath(version)\n let sanitizedSlug = sanitizePath(slug)\n\n // Get i18n config\n const i18nConfig = getI18nConfig()\n\n // Determine locale from slug if not provided\n let detectedLocale = locale || i18nConfig?.defaultLocale\n\n if (i18nConfig) {\n const parts = sanitizedSlug.split('/')\n if (parts.length > 0 && i18nConfig.locales.includes(parts[0])) {\n detectedLocale = parts[0]\n sanitizedSlug = parts.slice(1).join('/')\n if (sanitizedSlug === \"\") sanitizedSlug = \"index\"\n }\n }\n\n const targetLocale = detectedLocale\n const isDefaultLocale = targetLocale === i18nConfig?.defaultLocale\n\n // Try finding the file in this order:\n // 1. Localized extension: slug.locale.mdx (e.g. guide.fr.mdx)\n // 2. Default file: slug.mdx (only if using default locale and configured to fallback or strictly default)\n\n // Construct potential paths\n const basePath = path.join(DOCS_DIR, sanitizedVersion)\n\n // 1. Try localized file extension\n if (targetLocale) {\n const localizedPath = path.join(basePath, `${sanitizedSlug}.${targetLocale}.mdx`)\n const doc = readDocFromFile(localizedPath, sanitizedSlug) // Keep parsed slug\n if (doc) {\n doc.slug = i18nConfig ? `${targetLocale}/${sanitizedSlug}` : sanitizedSlug\n doc.meta.locale = targetLocale\n return doc\n }\n }\n\n // 2. Try default file\n const defaultPath = path.join(basePath, `${sanitizedSlug}.mdx`)\n const doc = readDocFromFile(defaultPath, sanitizedSlug)\n\n if (doc) {\n // If we found a default file but requested a specific locale using prefix, \n // we might want to return it but with the prefix in slug if we are doing fallback.\n // For now, strict mode: if I request /fr/guide and guide.fr.mdx is missing, \n // should I return guide.mdx? \n // Let's assume explicitly: Yes, but keep the URL /fr/guide ?? \n // No, typically you want 404 if translation missing, OR fallback.\n // Let's stick to: if explicit locale requested and file not found, we fall through to null eventually.\n // BUT if it matches default locale, we return it.\n\n if (isDefaultLocale || !i18nConfig) {\n // For default locale, we might want to prefix if prompt said \"add language to url\"\n // If i18n enabled and prefixDefault is true, ensure slug has prefix.\n // But existing behavior for default locale usually omits prefix.\n // If plan said: /en/docs/... then we probably want prefix even for default?\n // The config I added has `prefixDefault`. Checks that.\n\n const usePrefix = i18nConfig && (i18nConfig.prefixDefault || targetLocale !== i18nConfig.defaultLocale)\n\n if (usePrefix && targetLocale) {\n doc.slug = `${targetLocale}/${doc.slug}`\n }\n doc.meta.locale = targetLocale || 'en'\n return doc\n }\n }\n\n // If still not found, search all docs for a matching custom slug\n // This part is expensive and might need update for i18n, disabling for now or leaving as is for default locale\n // Ideally custom slugs should also be localized? \n // Let's rely on standard file resolution for now for i18n to ensure stability.\n\n return null\n } catch (error) {\n console.error(`Error reading doc ${slug}:`, error)\n return null\n }\n}\n\nexport async function getAllDocs(version = \"v1.0.0\", locale?: string): Promise<Doc[]> {\n try {\n const versionDir = path.join(DOCS_DIR, version)\n\n if (!fs.existsSync(versionDir)) {\n return []\n }\n\n // Get i18n config\n const i18nConfig = getI18nConfig()\n const targetLocale = locale || i18nConfig?.defaultLocale || 'en'\n\n const mdxFiles = findMdxFiles(versionDir)\n const categoryConfigs = getAllCategoryConfigs(version)\n\n const docs = await Promise.all(\n mdxFiles.map(async (file) => {\n // file contains path relative to version dir, e.g. \"getting-started/intro.mdx\" or \"intro.fr.mdx\"\n\n let originalFilePath = file.replace(/\\.mdx$/, \"\")\n\n // Handle localized files\n let isLocalized = false\n let fileLocale = i18nConfig?.defaultLocale || 'en'\n\n if (i18nConfig) {\n // Check for .<locale> suffix\n const parts = originalFilePath.split('.')\n const lastPart = parts[parts.length - 1]\n if (i18nConfig.locales.includes(lastPart)) {\n fileLocale = lastPart\n isLocalized = true\n originalFilePath = parts.slice(0, -1).join('.')\n }\n }\n\n // If we requested a specific locale, filter out others\n // If target is 'fr', we want intro.fr.mdx (if exists) OR intro.mdx (fallback? no, getAllDocs is usually for list)\n // Actually, for sidebar we want the \"best\" version of each doc for the current locale.\n\n // Strategy: Map all files to their logical slug, then group by slug and pick best locale.\n // But getAllDocs is async and parallel.\n\n // Simplified: Just process all files, returning the doc with its true locale.\n // Then filter/merge later? \n // No, current logic returns flat array.\n\n // Let's try to load the doc.\n const doc = await getDocBySlug(isLocalized ? `${originalFilePath}.${fileLocale}` : originalFilePath, version, isLocalized ? fileLocale : undefined)\n\n if (!doc) return null\n\n // Override filePath properties for sidebar grouping \n // (we want grouped by logical path, not physically localized path if possible)\n doc.filePath = originalFilePath // Use logical path (without .fr) for grouping\n\n const folderPath = path.dirname(originalFilePath).replace(/\\\\/g, '/')\n if (folderPath !== \".\") {\n const categoryConfig = categoryConfigs.get(folderPath)\n if (categoryConfig) {\n doc.categoryLabel = categoryConfig.label\n doc.categoryPosition = categoryConfig.position ?? categoryConfig.sidebar_position\n doc.categoryCollapsible = categoryConfig.collapsible\n doc.categoryCollapsed = categoryConfig.collapsed\n doc.categoryIcon = categoryConfig.icon\n doc.categoryTabGroup = categoryConfig.tab_group\n }\n }\n\n return doc\n }),\n )\n\n const isDevelopment = process.env.NODE_ENV === \"development\"\n\n // Create a map to track unique slugs and avoid duplicates, prioritizing target locale\n const uniqueDocs = new Map<string, Doc>()\n\n // Sort docs such that target locale comes first? No, we need to filter/merge.\n const validDocs = docs.filter((doc): doc is Doc => doc !== null && (isDevelopment || !doc.meta.draft))\n\n // Group by logical slug (we stored logical path in filePath, maybe use that?)\n // Actually doc.slug might differ if custom slug used.\n\n // If we have intro.mdx (en) and intro.fr.mdx (fr)\n // And targetLocale is 'fr'\n // We want the 'fr' one.\n\n validDocs.forEach(doc => {\n // Identify logical slug. \n // If doc.slug already has prefix (e.g. fr/intro), stripped slug is 'intro'.\n let logicalSlug = doc.slug\n if (i18nConfig) {\n const parts = logicalSlug.split('/')\n if (i18nConfig.locales.includes(parts[0])) {\n logicalSlug = parts.slice(1).join('/')\n }\n }\n\n const existing = uniqueDocs.get(logicalSlug)\n\n if (!existing) {\n // If doc matches target locale or is default (and we allow default fallback), take it.\n // For now, take everything, filter later?\n // Better: Only add if it matches target locale OR is default and we don't have target yet.\n if (doc.meta.locale === targetLocale) {\n uniqueDocs.set(logicalSlug, doc)\n } else if (doc.meta.locale === i18nConfig?.defaultLocale) {\n uniqueDocs.set(logicalSlug, doc)\n }\n } else {\n // We have an existing entry. prefer targetLocale\n if (doc.meta.locale === targetLocale && existing.meta.locale !== targetLocale) {\n uniqueDocs.set(logicalSlug, doc)\n }\n }\n })\n\n return Array.from(uniqueDocs.values()).sort((a, b) => {\n const orderA = a.meta.sidebar_position ?? a.meta.order ?? 999\n const orderB = b.meta.sidebar_position ?? b.meta.order ?? 999\n return orderA - orderB\n })\n } catch (error) {\n console.error(`Error getting all docs for version ${version}:`, error)\n return []\n }\n}\n\n// export function getAdjacentDocs(currentSlug: string, allDocs: Doc[]): { previous?: Doc; next?: Doc } {\n// const currentIndex = allDocs.findIndex((doc) => doc.slug === currentSlug)\n\n// if (currentIndex === -1) {\n// return {}\n// }\n\n// return {\n// previous: currentIndex > 0 ? allDocs[currentIndex - 1] : undefined,\n// next: currentIndex < allDocs.length - 1 ? allDocs[currentIndex + 1] : undefined,\n// }\n// }\n\n// Flatten the sidebar structure into a linear order\nfunction flattenSidebarOrder(\n rootGroups: Record<string, SidebarGroup>,\n standalone: Doc[]\n): Doc[] {\n const flatDocs: Doc[] = []\n\n // Recursively flatten groups - intermix folders and files by position\n const flattenGroup = (group: SidebarGroup) => {\n const sortedChildren = sortSidebarGroups(group.children)\n const sortedItems = sortSidebarItems(group.items)\n\n // Merge child groups and items, then sort by position\n const merged: Array<{ type: 'group', group: SidebarGroup, position: number } | { type: 'item', doc: Doc, position: number }> = [\n ...sortedChildren.map(([, childGroup]) => ({\n type: 'group' as const,\n group: childGroup,\n position: childGroup.position\n })),\n ...sortedItems.map((doc) => ({\n type: 'item' as const,\n doc,\n position: doc.meta.sidebar_position ?? doc.meta.order ?? 999\n }))\n ]\n\n // Sort by position\n merged.sort((a, b) => a.position - b.position)\n\n // Process in sorted order\n merged.forEach((item) => {\n if (item.type === 'group') {\n flattenGroup(item.group)\n } else {\n flatDocs.push(item.doc)\n }\n })\n }\n\n // Add standalone items first\n sortSidebarItems(standalone).forEach((doc) => {\n flatDocs.push(doc)\n })\n\n // Then add all grouped items\n const sortedRootGroups = sortSidebarGroups(rootGroups)\n sortedRootGroups.forEach(([, group]) => {\n flattenGroup(group)\n })\n\n return flatDocs\n}\n\nexport function getAdjacentDocs(currentSlug: string, allDocs: Doc[]): { previous?: Doc; next?: Doc } {\n // Build the same sidebar structure\n const { rootGroups, standalone } = buildSidebarStructure(allDocs)\n\n // Flatten into the same order as shown in the sidebar\n const orderedDocs = flattenSidebarOrder(rootGroups, standalone)\n\n // Find current doc in the ordered list\n const currentIndex = orderedDocs.findIndex((doc) => doc.slug === currentSlug)\n\n if (currentIndex === -1) {\n return {}\n }\n\n const currentDoc = orderedDocs[currentIndex]\n\n // Get current doc's tab group (from meta or category)\n const currentTabGroup = currentDoc.meta?.tab_group || currentDoc.categoryTabGroup\n\n // Filter docs to match the current doc's tab group status\n // If current has a tab group, only show docs in the same tab group\n // If current has NO tab group, only show docs with NO tab group\n const filteredDocs = orderedDocs.filter((doc) => {\n const docTabGroup = doc.meta?.tab_group || doc.categoryTabGroup\n\n // If current doc has a tab group, only include docs with the same tab group\n if (currentTabGroup) {\n return docTabGroup === currentTabGroup\n }\n\n // If current doc has no tab group, only include docs with no tab group\n return !docTabGroup\n })\n\n // Find current doc's index within the filtered list\n const filteredIndex = filteredDocs.findIndex((doc) => doc.slug === currentSlug)\n\n if (filteredIndex === -1) {\n return {}\n }\n\n return {\n previous: filteredIndex > 0 ? filteredDocs[filteredIndex - 1] : undefined,\n next: filteredIndex < filteredDocs.length - 1 ? filteredDocs[filteredIndex + 1] : undefined,\n }\n}\n\nexport function extractTableOfContents(content: string): TocItem[] {\n const headingRegex = /^(#{2,3})\\s+(.+)$/gm\n const toc: TocItem[] = []\n let match\n\n while ((match = headingRegex.exec(content)) !== null) {\n const level = match[1].length\n const text = match[2]\n // Generate ID the same way rehype-slug does\n const id = text\n .toLowerCase()\n .replace(/\\s+/g, \"-\") // Replace spaces with hyphens first\n .replace(/[^a-z0-9-]/g, \"\") // Remove special chars (dots, slashes, etc)\n .replace(/^-|-$/g, \"\") // Remove leading/trailing hyphens\n\n toc.push({ id, title: text, level })\n }\n\n return toc\n}\n\n/**\n * Check if a slug represents a category (has child documents)\n */\nexport function isCategoryPage(slug: string, allDocs: Doc[]): boolean {\n return allDocs.some((doc) => {\n const parts = doc.slug.split(\"/\")\n const docParent = parts.slice(0, -1).join(\"/\")\n return docParent === slug && doc.slug !== slug\n })\n}\n\n\n","// Note: This file uses server-only APIs (fs, path) and should only be imported in Server Components\nimport fs from \"fs\"\nimport path from \"path\"\n\nexport interface CategoryConfig {\n label?: string\n position?: number\n sidebar_position?: number // Alternative naming for position\n link?: {\n type: \"generated-index\" | \"doc\"\n slug?: string\n }\n collapsed?: boolean\n collapsible?: boolean\n icon?: string // Icon name for sidebar display (Lucide icon name)\n tab_group?: string // Tab group ID for organizing categories into tabs\n}\n\nconst DOCS_DIR = path.join(process.cwd(), \"docs\")\n\n/**\n * Read category.json from a folder\n */\nexport function getCategoryConfig(folderPath: string): CategoryConfig | null {\n try {\n const categoryPath = path.join(folderPath, \"_category_.json\")\n\n if (!fs.existsSync(categoryPath)) {\n return null\n }\n\n const content = fs.readFileSync(categoryPath, \"utf8\")\n return JSON.parse(content) as CategoryConfig\n } catch (error) {\n console.error(`Error reading category config from ${folderPath}:`, error)\n return null\n }\n}\n\n/**\n * Get all category configs for a version\n */\nexport function getAllCategoryConfigs(version: string): Map<string, CategoryConfig> {\n const configs = new Map<string, CategoryConfig>()\n const versionDir = path.join(DOCS_DIR, version)\n\n if (!fs.existsSync(versionDir)) {\n return configs\n }\n\n function scanDirectory(dir: string, relativePath: string = \"\") {\n const entries = fs.readdirSync(dir, { withFileTypes: true })\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const fullPath = path.join(dir, entry.name)\n const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name\n\n const config = getCategoryConfig(fullPath)\n if (config) {\n configs.set(relPath, config)\n }\n\n // Recursively scan subdirectories\n scanDirectory(fullPath, relPath)\n }\n }\n }\n\n scanDirectory(versionDir)\n return configs\n}\n","/**\n * Caching layer for MDX operations to improve development performance\n *\n * This module provides in-memory caching for expensive file system operations\n * that occur during static generation. In development mode, caches are\n * invalidated automatically when files change.\n */\n\n// Note: This file uses server-only APIs (fs, path) and should only be imported in Server Components\nimport { Doc, getVersions, getAllDocs, getDocBySlug } from './mdx'\nimport { watch } from 'fs'\nimport { join } from 'path'\nimport { PerfTimer, logCacheOperation } from './dev-utils'\n\nconst isDevelopment = process.env.NODE_ENV === 'development'\n\n// Cache stores\nconst versionsCache = {\n data: null as string[] | null,\n timestamp: 0,\n}\n\nconst allDocsCache = new Map<string, {\n data: Doc[]\n timestamp: number\n}>()\n\nconst docBySlugCache = new Map<string, {\n data: Doc | null\n timestamp: number\n}>()\n\n// Cache TTL (time to live) in milliseconds\nconst CACHE_TTL = isDevelopment ? 5000 : 60000 // 5s in dev, 60s in prod\n\n// Track if we've set up file watchers\nlet watchersInitialized = false\n\n/**\n * Initialize file watchers to invalidate cache on changes\n * Only runs in development mode\n */\nfunction initializeWatchers() {\n if (!isDevelopment || watchersInitialized) return\n\n watchersInitialized = true\n const docsPath = join(process.cwd(), 'docs')\n\n try {\n watch(docsPath, { recursive: true }, (eventType, filename) => {\n if (!filename) return\n\n // Invalidate relevant caches when MDX or JSON files change\n if (filename.endsWith('.mdx') || filename.endsWith('.json')) {\n // Extract version from path\n const parts = filename.split(/[/\\\\]/)\n const version = parts[0]\n\n // Clear all docs cache for this version\n allDocsCache.delete(version)\n\n // Clear individual doc caches for this version\n const cacheKeysToDelete: string[] = []\n docBySlugCache.forEach((_, key) => {\n if (key.startsWith(`${version}:`)) {\n cacheKeysToDelete.push(key)\n }\n })\n cacheKeysToDelete.forEach(key => docBySlugCache.delete(key))\n\n // Clear versions cache if directory structure changed\n if (eventType === 'rename') {\n versionsCache.data = null\n }\n\n console.log(`[MDX Cache] Invalidated cache for: ${filename}`)\n }\n })\n\n console.log('[MDX Cache] File watchers initialized')\n } catch (error) {\n console.error('[MDX Cache] Failed to initialize watchers:', error)\n }\n}\n\n/**\n * Check if a cache entry is still valid\n */\nfunction isCacheValid(timestamp: number): boolean {\n return Date.now() - timestamp < CACHE_TTL\n}\n\n/**\n * Cached version of getVersions()\n */\nexport function getCachedVersions(): string[] {\n // Initialize watchers on first use\n initializeWatchers()\n\n if (versionsCache.data && isCacheValid(versionsCache.timestamp)) {\n logCacheOperation('hit', 'versions')\n return versionsCache.data\n }\n\n logCacheOperation('miss', 'versions')\n const timer = new PerfTimer('getVersions')\n const versions = getVersions()\n timer.end()\n\n versionsCache.data = versions\n versionsCache.timestamp = Date.now()\n\n return versions\n}\n\n/**\n * Cached version of getAllDocs()\n */\nexport async function getCachedAllDocs(version = 'v1.0.0'): Promise<Doc[]> {\n // Initialize watchers on first use\n initializeWatchers()\n\n const cached = allDocsCache.get(version)\n if (cached && isCacheValid(cached.timestamp)) {\n logCacheOperation('hit', `getAllDocs:${version}`)\n return cached.data\n }\n\n logCacheOperation('miss', `getAllDocs:${version}`)\n const timer = new PerfTimer(`getAllDocs(${version})`)\n const docs = await getAllDocs(version)\n timer.end()\n\n allDocsCache.set(version, {\n data: docs,\n timestamp: Date.now(),\n })\n\n return docs\n}\n\n/**\n * Cached version of getDocBySlug()\n */\nexport async function getCachedDocBySlug(\n slug: string,\n version = 'v1.0.0'\n): Promise<Doc | null> {\n // Initialize watchers on first use\n initializeWatchers()\n\n const cacheKey = `${version}:${slug}`\n const cached = docBySlugCache.get(cacheKey)\n\n if (cached && isCacheValid(cached.timestamp)) {\n logCacheOperation('hit', `getDocBySlug:${cacheKey}`)\n return cached.data\n }\n\n logCacheOperation('miss', `getDocBySlug:${cacheKey}`)\n const timer = new PerfTimer(`getDocBySlug(${slug})`)\n const doc = await getDocBySlug(slug, version)\n timer.end()\n\n docBySlugCache.set(cacheKey, {\n data: doc,\n timestamp: Date.now(),\n })\n\n return doc\n}\n\n/**\n * Manually clear all caches\n * Useful for testing or when you want to force a refresh\n */\nexport function clearAllCaches() {\n versionsCache.data = null\n allDocsCache.clear()\n docBySlugCache.clear()\n console.log('[MDX Cache] All caches cleared')\n}\n\n/**\n * Get cache statistics for debugging\n */\nexport function getCacheStats() {\n return {\n versions: {\n cached: versionsCache.data !== null,\n age: versionsCache.timestamp ? Date.now() - versionsCache.timestamp : 0,\n },\n allDocs: {\n entries: allDocsCache.size,\n versions: Array.from(allDocsCache.keys()),\n },\n docBySlug: {\n entries: docBySlugCache.size,\n },\n }\n}\n","/**\n * Development utilities for debugging and performance monitoring\n * Only active in development mode\n */\n\nconst isDevelopment = process.env.NODE_ENV === 'development'\n\n/**\n * Performance timer for measuring operation duration\n */\nexport class PerfTimer {\n private startTime: number\n private label: string\n\n constructor(label: string) {\n this.label = label\n this.startTime = isDevelopment ? performance.now() : 0\n }\n\n end() {\n if (!isDevelopment) return\n\n const duration = performance.now() - this.startTime\n const color = duration > 1000 ? '\\x1b[31m' : duration > 500 ? '\\x1b[33m' : '\\x1b[32m'\n const reset = '\\x1b[0m'\n\n console.log(`${color}⏱️ ${this.label}: ${duration.toFixed(2)}ms${reset}`)\n }\n}\n\n/**\n * Log file system operations\n */\nexport function logFsOperation(operation: string, path: string, details?: any) {\n if (!isDevelopment) return\n\n console.log(`📁 [FS] ${operation}: ${path}`, details || '')\n}\n\n/**\n * Log cache operations\n */\nexport function logCacheOperation(operation: 'hit' | 'miss' | 'invalidate', key: string) {\n if (!isDevelopment) return\n\n const emoji = operation === 'hit' ? '✅' : operation === 'miss' ? '❌' : '🔄'\n console.log(`${emoji} [Cache] ${operation}: ${key}`)\n}\n\n/**\n * Memory usage reporter\n */\nexport function logMemoryUsage(label?: string) {\n if (!isDevelopment) return\n\n const used = process.memoryUsage()\n const prefix = label ? `[${label}] ` : ''\n\n console.log(`💾 ${prefix}Memory Usage:`, {\n rss: `${Math.round(used.rss / 1024 / 1024)}MB`,\n heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,\n heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,\n })\n}\n\n/**\n * Pretty print object for debugging\n */\nexport function debugLog(label: string, data: any) {\n if (!isDevelopment) return\n\n console.log(`\\n🔍 ${label}:`)\n console.dir(data, { depth: null, colors: true })\n console.log('')\n}\n","export interface TOCItem {\n id: string\n title: string\n level: number\n}\n\n/**\n * Extract headings from HTML string for table of contents\n */\nexport function extractHeadings(html: string): TOCItem[] {\n const headingRegex = /<h([2-3])[^>]*id=\"([^\"]*)\"[^>]*>(.*?)<\\/h\\1>/g\n const toc: TOCItem[] = []\n let match\n\n while ((match = headingRegex.exec(html)) !== null) {\n const level = Number.parseInt(match[1])\n const id = match[2]\n const title = match[3].replace(/<[^>]*>/g, \"\") // Strip HTML tags\n\n toc.push({ id, title, level })\n }\n\n return toc\n}\n","import { getAllDocs, getVersions } from \"./mdx\"\n\nexport interface RedirectMapping {\n from: string\n to: string\n}\n\n/**\n * Build redirect mappings from all docs' redirect_from frontmatter\n */\nexport async function buildRedirectMappings(): Promise<RedirectMapping[]> {\n const versions = getVersions()\n const redirects: RedirectMapping[] = []\n\n for (const version of versions) {\n const docs = await getAllDocs(version)\n \n for (const doc of docs) {\n if (doc.meta.redirect_from && Array.isArray(doc.meta.redirect_from)) {\n for (const oldPath of doc.meta.redirect_from) {\n redirects.push({\n from: oldPath,\n to: `/docs/${version}/${doc.slug}`,\n })\n }\n }\n }\n }\n\n return redirects\n}\n\n/**\n * Find redirect destination for a given path\n */\nexport async function findRedirect(path: string): Promise<string | null> {\n const redirects = await buildRedirectMappings()\n const redirect = redirects.find((r) => r.from === path)\n return redirect ? redirect.to : null\n}\n"],"mappings":"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","names":["fs","path","DOCS_DIR","path","fs","doc","isDevelopment","path","isDevelopment","path"]}
@@ -274,8 +274,24 @@ interface FeaturesConfig {
274
274
  showTags?: boolean;
275
275
  /** Enable version dropdown */
276
276
  versioning?: boolean;
277
- /** Enable i18n (internationalization) */
278
- i18n?: boolean;
277
+ /**
278
+ * Internationalization (i18n) configuration
279
+ * Set to true to enable with defaults, or provide configuration object
280
+ */
281
+ i18n?: boolean | I18nConfig;
282
+ }
283
+ /**
284
+ * Internationalization configuration
285
+ */
286
+ interface I18nConfig {
287
+ /** The default locale (e.g. 'en') - used when no locale is present in URL */
288
+ defaultLocale: string;
289
+ /** List of supported locales (e.g. ['en', 'fr', 'es']) */
290
+ locales: string[];
291
+ /** Map of locale codes to display names (e.g. { en: "English", fr: "Français" }) */
292
+ localeNames?: Record<string, string>;
293
+ /** Whether to use locale subpaths for default locale (default: false) */
294
+ prefixDefault?: boolean;
279
295
  }
280
296
  /**
281
297
  * Site-wide banner configuration
@@ -380,6 +396,7 @@ interface DocMeta {
380
396
  word_count?: number;
381
397
  icon?: string;
382
398
  tab_group?: string;
399
+ locale?: string;
383
400
  }
384
401
  interface Doc {
385
402
  slug: string;
@@ -393,6 +410,7 @@ interface Doc {
393
410
  categoryCollapsed?: boolean;
394
411
  categoryIcon?: string;
395
412
  categoryTabGroup?: string;
413
+ locale?: string;
396
414
  }
397
415
 
398
416
  interface DocLayoutWrapperProps {
@@ -274,8 +274,24 @@ interface FeaturesConfig {
274
274
  showTags?: boolean;
275
275
  /** Enable version dropdown */
276
276
  versioning?: boolean;
277
- /** Enable i18n (internationalization) */
278
- i18n?: boolean;
277
+ /**
278
+ * Internationalization (i18n) configuration
279
+ * Set to true to enable with defaults, or provide configuration object
280
+ */
281
+ i18n?: boolean | I18nConfig;
282
+ }
283
+ /**
284
+ * Internationalization configuration
285
+ */
286
+ interface I18nConfig {
287
+ /** The default locale (e.g. 'en') - used when no locale is present in URL */
288
+ defaultLocale: string;
289
+ /** List of supported locales (e.g. ['en', 'fr', 'es']) */
290
+ locales: string[];
291
+ /** Map of locale codes to display names (e.g. { en: "English", fr: "Français" }) */
292
+ localeNames?: Record<string, string>;
293
+ /** Whether to use locale subpaths for default locale (default: false) */
294
+ prefixDefault?: boolean;
279
295
  }
280
296
  /**
281
297
  * Site-wide banner configuration
@@ -380,6 +396,7 @@ interface DocMeta {
380
396
  word_count?: number;
381
397
  icon?: string;
382
398
  tab_group?: string;
399
+ locale?: string;
383
400
  }
384
401
  interface Doc {
385
402
  slug: string;
@@ -393,6 +410,7 @@ interface Doc {
393
410
  categoryCollapsed?: boolean;
394
411
  categoryIcon?: string;
395
412
  categoryTabGroup?: string;
413
+ locale?: string;
396
414
  }
397
415
 
398
416
  interface DocLayoutWrapperProps {