|  | 
|  | 1 | +import { fileURLToPath } from "node:url"; | 
|  | 2 | +import path from "node:path"; | 
|  | 3 | +import fs from "node:fs/promises"; | 
|  | 4 | +import type { AstroConfig, AstroIntegration } from "astro"; | 
|  | 5 | +import { getDirectoriesList, getGeneratedFileTemplate, isDirectory, isPathExist } from "./utils"; | 
|  | 6 | + | 
|  | 7 | +interface SupportedLanguage { | 
|  | 8 | +  code: string; | 
|  | 9 | +  label: string; | 
|  | 10 | +  default?: boolean; | 
|  | 11 | +} | 
|  | 12 | + | 
|  | 13 | +/** | 
|  | 14 | + * Manual routes for middleware configuration | 
|  | 15 | + * Add any custom routes here that aren't automatically detected | 
|  | 16 | + * | 
|  | 17 | + * Examples: '/custom-page/:path*', '/api/:path*', | 
|  | 18 | + */ | 
|  | 19 | +type ManualRouteMatcher = string; | 
|  | 20 | + | 
|  | 21 | +interface i18nMatcherGeneratorOptions { | 
|  | 22 | +  supportedLanguages: SupportedLanguage[]; | 
|  | 23 | +  manualMatchers?: ManualRouteMatcher[]; | 
|  | 24 | +} | 
|  | 25 | + | 
|  | 26 | +export default function i18nMatcherGenerator({ | 
|  | 27 | +  supportedLanguages, | 
|  | 28 | +  manualMatchers, | 
|  | 29 | +}: i18nMatcherGeneratorOptions) { | 
|  | 30 | +  let astroConfig: AstroConfig; | 
|  | 31 | + | 
|  | 32 | +  const integration: AstroIntegration = { | 
|  | 33 | +    name: "i18n-matcher-generator", | 
|  | 34 | +    hooks: { | 
|  | 35 | +      "astro:config:setup": ({ config }) => { | 
|  | 36 | +        astroConfig = config; | 
|  | 37 | +      }, | 
|  | 38 | +      "astro:routes:resolved": async ({ logger }) => { | 
|  | 39 | +        const rootDir = fileURLToPath(astroConfig.root); | 
|  | 40 | + | 
|  | 41 | +        // Get non-English locale codes | 
|  | 42 | +        const NON_ENGLISH_LOCALES = supportedLanguages | 
|  | 43 | +          .filter((lang) => lang.code !== "en") | 
|  | 44 | +          .map((lang) => lang.code); | 
|  | 45 | + | 
|  | 46 | +        // Discover content paths from src/content/docs | 
|  | 47 | +        const contentDocsPath = path.join(rootDir, "src/content/docs"); | 
|  | 48 | +        const contentDirs = await getDirectoriesList(contentDocsPath, NON_ENGLISH_LOCALES).catch( | 
|  | 49 | +          (error: unknown) => { | 
|  | 50 | +            console.warn(`Could not read directories from ${contentDocsPath}: ${String(error)}`); | 
|  | 51 | + | 
|  | 52 | +            return []; | 
|  | 53 | +          }, | 
|  | 54 | +        ); | 
|  | 55 | +        const contentPaths = contentDirs.map((dir) => `/${dir}/:path*`); | 
|  | 56 | +        // Discover paths from src/pages/[...lang] | 
|  | 57 | +        const langPagesPath = path.join(rootDir, "src/pages/[...lang]"); | 
|  | 58 | +        let pagePaths: string[] = []; | 
|  | 59 | + | 
|  | 60 | +        if (await isDirectory(langPagesPath)) { | 
|  | 61 | +          try { | 
|  | 62 | +            const pageFiles = await fs.readdir(langPagesPath, { withFileTypes: true }); | 
|  | 63 | + | 
|  | 64 | +            // Get .astro files | 
|  | 65 | +            const astroFiles = pageFiles | 
|  | 66 | +              .filter((file) => file.isFile() && file.name.endsWith(".astro")) | 
|  | 67 | +              .map((file) => `/${file.name.replace(".astro", "")}`); | 
|  | 68 | + | 
|  | 69 | +            // Get directories | 
|  | 70 | +            const pageDirectories = pageFiles | 
|  | 71 | +              .filter((dirent) => dirent.isDirectory()) | 
|  | 72 | +              .map((dirent) => `/${dirent.name}/:path*`); | 
|  | 73 | + | 
|  | 74 | +            pagePaths = [...astroFiles, ...pageDirectories]; | 
|  | 75 | +          } catch (error) { | 
|  | 76 | +            logger.warn(`Could not read files from ${langPagesPath}: ${String(error)}`); | 
|  | 77 | +          } | 
|  | 78 | +        } | 
|  | 79 | + | 
|  | 80 | +        // Check if API reference is enabled from environment variables | 
|  | 81 | +        const apiReferencePaths = []; | 
|  | 82 | +        try { | 
|  | 83 | +          const envPath = path.join(rootDir, ".env"); | 
|  | 84 | +          if (await isPathExist(envPath)) { | 
|  | 85 | +            const envContent = await fs.readFile(envPath, "utf8"); | 
|  | 86 | +            if (envContent.includes("ENABLE_API_REFERENCE=true")) { | 
|  | 87 | +              apiReferencePaths.push("/api-reference/:path*"); | 
|  | 88 | +            } | 
|  | 89 | +          } | 
|  | 90 | +        } catch (error) { | 
|  | 91 | +          logger.warn(`Could not check for API reference configuration: ${String(error)}`); | 
|  | 92 | +        } | 
|  | 93 | + | 
|  | 94 | +        // Combine all content paths | 
|  | 95 | +        const ALL_CONTENT_PATHS = [ | 
|  | 96 | +          "/", | 
|  | 97 | +          ...contentPaths, | 
|  | 98 | +          ...pagePaths, | 
|  | 99 | +          ...apiReferencePaths, | 
|  | 100 | +          ...(manualMatchers ?? []), | 
|  | 101 | +        ]; | 
|  | 102 | + | 
|  | 103 | +        // Generate language-specific paths | 
|  | 104 | +        const LANGUAGE_PATHS: string[] = []; | 
|  | 105 | +        NON_ENGLISH_LOCALES.forEach((code) => { | 
|  | 106 | +          // Add the base language path with exact matching to avoid matching _astro paths | 
|  | 107 | +          LANGUAGE_PATHS.push(`/${code}$`); | 
|  | 108 | + | 
|  | 109 | +          // Add localized versions of all content paths (except the root path) | 
|  | 110 | +          ALL_CONTENT_PATHS.forEach((contentPath) => { | 
|  | 111 | +            // Skip the root path as we already have /{code} | 
|  | 112 | +            if (contentPath !== "/") { | 
|  | 113 | +              LANGUAGE_PATHS.push(`/${code}${contentPath}`); | 
|  | 114 | +            } | 
|  | 115 | +          }); | 
|  | 116 | +        }); | 
|  | 117 | + | 
|  | 118 | +        // Combine all paths | 
|  | 119 | +        const ALL_PATHS = [...ALL_CONTENT_PATHS, ...LANGUAGE_PATHS]; | 
|  | 120 | + | 
|  | 121 | +        // Write the file | 
|  | 122 | +        const outputPath = path.join(rootDir, "src/middlewares/matcher-routes-dynamic.js"); | 
|  | 123 | +        await fs.writeFile(outputPath, getGeneratedFileTemplate(ALL_PATHS)); | 
|  | 124 | +        logger.info("Middleware matcher file generated successfully!"); | 
|  | 125 | +      }, | 
|  | 126 | +    }, | 
|  | 127 | +  }; | 
|  | 128 | + | 
|  | 129 | +  return integration; | 
|  | 130 | +} | 
0 commit comments