@farming-labs/docs 0.0.29 → 0.0.31

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.
@@ -226,6 +226,14 @@ const THEME_INFO = {
226
226
  function getThemeInfo(theme) {
227
227
  return THEME_INFO[theme] ?? THEME_INFO.fumadocs;
228
228
  }
229
+ function renderI18nConfig(cfg, indent = " ") {
230
+ const i18n = cfg.i18n;
231
+ if (!i18n || i18n.locales.length === 0) return "";
232
+ return `${indent}i18n: {\n${indent} locales: [${i18n.locales.map((locale) => `"${locale}"`).join(", ")}],\n${indent} defaultLocale: "${i18n.defaultLocale}",\n${indent}},\n`;
233
+ }
234
+ function toLocaleImportName(locale) {
235
+ return `LocalePage_${locale.replace(/[^a-zA-Z0-9_$]/g, "_")}`;
236
+ }
229
237
  function getThemeExportName(themeName) {
230
238
  const base = themeName.replace(/\.ts$/i, "").trim();
231
239
  if (!base) return "customTheme";
@@ -315,7 +323,7 @@ import { ${exportName} } from "${cfg.useAlias ? "@/themes/" + cfg.customThemeNam
315
323
 
316
324
  export default defineDocs({
317
325
  entry: "${cfg.entry}",
318
- theme: ${exportName}({
326
+ ${renderI18nConfig(cfg)} theme: ${exportName}({
319
327
  ui: {
320
328
  colors: { primary: "#6366f1" },
321
329
  },
@@ -335,7 +343,7 @@ import { ${t.factory} } from "${t.nextImport}";
335
343
 
336
344
  export default defineDocs({
337
345
  entry: "${cfg.entry}",
338
- theme: ${t.factory}({
346
+ ${renderI18nConfig(cfg)} theme: ${t.factory}({
339
347
  ui: {
340
348
  colors: { primary: "#6366f1" },
341
349
  },
@@ -473,6 +481,48 @@ export default function Layout({ children }: { children: React.ReactNode }) {
473
481
  }
474
482
  `;
475
483
  }
484
+ function nextLocaleDocPageTemplate(defaultLocale) {
485
+ return `\
486
+ import type { ComponentType } from "react";
487
+
488
+ type SearchParams = Promise<{ lang?: string | string[] | undefined }> | undefined;
489
+
490
+ function normalizeLang(value: string | string[] | undefined) {
491
+ return Array.isArray(value) ? value[0] : value;
492
+ }
493
+
494
+ export async function resolveLocaleDocPage<T extends ComponentType>(
495
+ searchParams: SearchParams,
496
+ pages: Record<string, T>,
497
+ fallbackLocale = "${defaultLocale}",
498
+ ) {
499
+ const params = (await searchParams) ?? {};
500
+ const locale = normalizeLang(params.lang) ?? fallbackLocale;
501
+
502
+ return pages[locale] ?? pages[fallbackLocale];
503
+ }
504
+ `;
505
+ }
506
+ function nextLocalizedPageTemplate(options) {
507
+ const importLines = options.pageImports.map(({ locale, importPath }) => `import ${toLocaleImportName(locale)} from "${importPath}";`).join("\n");
508
+ const pageMap = options.pageImports.map(({ locale }) => ` ${JSON.stringify(locale)}: ${toLocaleImportName(locale)},`).join("\n");
509
+ return `\
510
+ ${importLines}
511
+ import { resolveLocaleDocPage } from "${options.helperImport}";
512
+
513
+ type PageProps = {
514
+ searchParams?: Promise<{ lang?: string | string[] | undefined }>;
515
+ };
516
+
517
+ export default async function ${options.componentName}({ searchParams }: PageProps) {
518
+ const Page = await resolveLocaleDocPage(searchParams, {
519
+ ${pageMap}
520
+ }, "${options.defaultLocale}");
521
+
522
+ return <Page />;
523
+ }
524
+ `;
525
+ }
476
526
  function postcssConfigTemplate() {
477
527
  return `\
478
528
  const config = {
@@ -696,7 +746,8 @@ import { ${exportName} } from "${"../../themes/" + cfg.customThemeName.replace(/
696
746
 
697
747
  export default defineDocs({
698
748
  entry: "${cfg.entry}",
699
- theme: ${exportName}({
749
+ contentDir: "${cfg.entry}",
750
+ ${renderI18nConfig(cfg)} theme: ${exportName}({
700
751
  ui: {
701
752
  colors: { primary: "#6366f1" },
702
753
  },
@@ -723,7 +774,8 @@ import { ${t.factory} } from "${t.svelteImport}";
723
774
 
724
775
  export default defineDocs({
725
776
  entry: "${cfg.entry}",
726
- theme: ${t.factory}({
777
+ contentDir: "${cfg.entry}",
778
+ ${renderI18nConfig(cfg)} theme: ${t.factory}({
727
779
  ui: {
728
780
  colors: { primary: "#6366f1" },
729
781
  },
@@ -1005,7 +1057,7 @@ import { ${exportName} } from "${"../../themes/" + cfg.customThemeName.replace(/
1005
1057
  export default defineDocs({
1006
1058
  entry: "${cfg.entry}",
1007
1059
  contentDir: "${cfg.entry}",
1008
- theme: ${exportName}({
1060
+ ${renderI18nConfig(cfg)} theme: ${exportName}({
1009
1061
  ui: {
1010
1062
  colors: { primary: "#6366f1" },
1011
1063
  },
@@ -1033,7 +1085,7 @@ import { ${t.factory} } from "${t.astroImport}";
1033
1085
  export default defineDocs({
1034
1086
  entry: "${cfg.entry}",
1035
1087
  contentDir: "${cfg.entry}",
1036
- theme: ${t.factory}({
1088
+ ${renderI18nConfig(cfg)} theme: ${t.factory}({
1037
1089
  ui: {
1038
1090
  colors: { primary: "#6366f1" },
1039
1091
  },
@@ -1361,7 +1413,7 @@ import { ${exportName} } from "${cfg.useAlias ? "~/themes/" + cfg.customThemeNam
1361
1413
  export default defineDocs({
1362
1414
  entry: "${cfg.entry}",
1363
1415
  contentDir: "${cfg.entry}",
1364
- theme: ${exportName}({
1416
+ ${renderI18nConfig(cfg)} theme: ${exportName}({
1365
1417
  ui: {
1366
1418
  colors: { primary: "#6366f1" },
1367
1419
  },
@@ -1389,7 +1441,7 @@ import { ${t.factory} } from "${t.nuxtImport}";
1389
1441
  export default defineDocs({
1390
1442
  entry: "${cfg.entry}",
1391
1443
  contentDir: "${cfg.entry}",
1392
- theme: ${t.factory}({
1444
+ ${renderI18nConfig(cfg)} theme: ${t.factory}({
1393
1445
  ui: {
1394
1446
  colors: { primary: "#6366f1" },
1395
1447
  },
@@ -1713,6 +1765,79 @@ const VALID_TEMPLATES = [
1713
1765
  "sveltekit",
1714
1766
  "astro"
1715
1767
  ];
1768
+ const COMMON_LOCALE_OPTIONS = [
1769
+ {
1770
+ value: "en",
1771
+ label: "English",
1772
+ hint: "en"
1773
+ },
1774
+ {
1775
+ value: "fr",
1776
+ label: "French",
1777
+ hint: "fr"
1778
+ },
1779
+ {
1780
+ value: "es",
1781
+ label: "Spanish",
1782
+ hint: "es"
1783
+ },
1784
+ {
1785
+ value: "de",
1786
+ label: "German",
1787
+ hint: "de"
1788
+ },
1789
+ {
1790
+ value: "pt",
1791
+ label: "Portuguese",
1792
+ hint: "pt"
1793
+ },
1794
+ {
1795
+ value: "it",
1796
+ label: "Italian",
1797
+ hint: "it"
1798
+ },
1799
+ {
1800
+ value: "ja",
1801
+ label: "Japanese",
1802
+ hint: "ja"
1803
+ },
1804
+ {
1805
+ value: "ko",
1806
+ label: "Korean",
1807
+ hint: "ko"
1808
+ },
1809
+ {
1810
+ value: "zh",
1811
+ label: "Chinese",
1812
+ hint: "zh"
1813
+ },
1814
+ {
1815
+ value: "ar",
1816
+ label: "Arabic",
1817
+ hint: "ar"
1818
+ },
1819
+ {
1820
+ value: "hi",
1821
+ label: "Hindi",
1822
+ hint: "hi"
1823
+ },
1824
+ {
1825
+ value: "ru",
1826
+ label: "Russian",
1827
+ hint: "ru"
1828
+ }
1829
+ ];
1830
+ function normalizeLocaleCode(value) {
1831
+ const trimmed = value.trim();
1832
+ if (!trimmed) return "";
1833
+ const [language, ...rest] = trimmed.split("-");
1834
+ const normalizedLanguage = language.toLowerCase();
1835
+ if (rest.length === 0) return normalizedLanguage;
1836
+ return `${normalizedLanguage}-${rest.join("-").toUpperCase()}`;
1837
+ }
1838
+ function parseLocaleInput(input) {
1839
+ return Array.from(new Set(input.split(",").map((value) => normalizeLocaleCode(value)).filter(Boolean)));
1840
+ }
1716
1841
  async function init(options = {}) {
1717
1842
  const cwd = process.cwd();
1718
1843
  p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
@@ -2032,6 +2157,64 @@ async function init(options = {}) {
2032
2157
  process.exit(0);
2033
2158
  }
2034
2159
  const entryPath = entry.trim() || defaultEntry;
2160
+ const enableI18n = await p.confirm({
2161
+ message: "Do you want to scaffold internationalized docs with locale folders?",
2162
+ initialValue: false
2163
+ });
2164
+ if (p.isCancel(enableI18n)) {
2165
+ p.outro(pc.red("Init cancelled."));
2166
+ process.exit(0);
2167
+ }
2168
+ let docsI18n;
2169
+ if (enableI18n) {
2170
+ const selectedLocales = await p.multiselect({
2171
+ message: "Which languages should we scaffold?",
2172
+ options: COMMON_LOCALE_OPTIONS.map((option) => ({
2173
+ value: option.value,
2174
+ label: option.label,
2175
+ hint: option.hint
2176
+ }))
2177
+ });
2178
+ if (p.isCancel(selectedLocales)) {
2179
+ p.outro(pc.red("Init cancelled."));
2180
+ process.exit(0);
2181
+ }
2182
+ const extraLocalesAnswer = await p.text({
2183
+ message: "Any additional locale codes? (comma-separated, optional)",
2184
+ placeholder: "nl, sv, pt-BR",
2185
+ defaultValue: "",
2186
+ validate: (value) => {
2187
+ return parseLocaleInput(value ?? "").every((locale) => /^[a-z]{2,3}(?:-[A-Z]{2})?$/.test(locale)) ? void 0 : "Use locale codes like en, fr, zh, or pt-BR";
2188
+ }
2189
+ });
2190
+ if (p.isCancel(extraLocalesAnswer)) {
2191
+ p.outro(pc.red("Init cancelled."));
2192
+ process.exit(0);
2193
+ }
2194
+ const locales = Array.from(new Set([...(selectedLocales ?? []).map((locale) => normalizeLocaleCode(locale)), ...parseLocaleInput(extraLocalesAnswer ?? "")])).filter(Boolean);
2195
+ if (locales.length === 0) {
2196
+ p.log.error("Pick at least one locale to scaffold i18n support.");
2197
+ p.outro(pc.red("Init cancelled."));
2198
+ process.exit(1);
2199
+ }
2200
+ const defaultLocaleAnswer = await p.select({
2201
+ message: "Which locale should be the default?",
2202
+ options: locales.map((locale) => ({
2203
+ value: locale,
2204
+ label: locale,
2205
+ hint: locale === "en" ? "Recommended default" : void 0
2206
+ })),
2207
+ initialValue: locales[0]
2208
+ });
2209
+ if (p.isCancel(defaultLocaleAnswer)) {
2210
+ p.outro(pc.red("Init cancelled."));
2211
+ process.exit(0);
2212
+ }
2213
+ docsI18n = {
2214
+ locales,
2215
+ defaultLocale: defaultLocaleAnswer
2216
+ };
2217
+ }
2035
2218
  const detectedCssFiles = detectGlobalCssFiles(cwd);
2036
2219
  let globalCssRelPath;
2037
2220
  const defaultCssPath = framework === "sveltekit" ? "src/app.css" : framework === "astro" ? "src/styles/global.css" : framework === "nuxt" ? "assets/css/main.css" : framework === "nextjs" ? `${nextAppDir}/globals.css` : "app/globals.css";
@@ -2078,6 +2261,7 @@ async function init(options = {}) {
2078
2261
  framework,
2079
2262
  useAlias,
2080
2263
  astroAdapter,
2264
+ i18n: docsI18n,
2081
2265
  ...framework === "nextjs" && { nextAppDir }
2082
2266
  };
2083
2267
  const s = p.spinner();
@@ -2219,6 +2403,9 @@ async function init(options = {}) {
2219
2403
  process.exit(1);
2220
2404
  }
2221
2405
  }
2406
+ function getScaffoldContentRoots(cfg) {
2407
+ return cfg.i18n?.locales?.length ? cfg.i18n.locales.map((locale) => `${cfg.entry}/${locale}`) : [cfg.entry];
2408
+ }
2222
2409
  function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
2223
2410
  const appDir = cfg.nextAppDir ?? "app";
2224
2411
  if (cfg.theme === "custom" && cfg.customThemeName) {
@@ -2258,6 +2445,46 @@ function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
2258
2445
  write(`${appDir}/${cfg.entry}/layout.tsx`, docsLayoutTemplate(cfg));
2259
2446
  write("postcss.config.mjs", postcssConfigTemplate());
2260
2447
  if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate(cfg.useAlias));
2448
+ if (cfg.i18n?.locales.length) {
2449
+ write(`${appDir}/components/locale-doc-page.tsx`, nextLocaleDocPageTemplate(cfg.i18n.defaultLocale));
2450
+ write(`${appDir}/${cfg.entry}/page.tsx`, nextLocalizedPageTemplate({
2451
+ locales: cfg.i18n.locales,
2452
+ defaultLocale: cfg.i18n.defaultLocale,
2453
+ componentName: "DocsIndexPage",
2454
+ helperImport: "../components/locale-doc-page",
2455
+ pageImports: cfg.i18n.locales.map((locale) => ({
2456
+ locale,
2457
+ importPath: `./${locale}/page.mdx`
2458
+ }))
2459
+ }));
2460
+ write(`${appDir}/${cfg.entry}/installation/page.tsx`, nextLocalizedPageTemplate({
2461
+ locales: cfg.i18n.locales,
2462
+ defaultLocale: cfg.i18n.defaultLocale,
2463
+ componentName: "InstallationPage",
2464
+ helperImport: "../../components/locale-doc-page",
2465
+ pageImports: cfg.i18n.locales.map((locale) => ({
2466
+ locale,
2467
+ importPath: `../${locale}/installation/page.mdx`
2468
+ }))
2469
+ }));
2470
+ write(`${appDir}/${cfg.entry}/quickstart/page.tsx`, nextLocalizedPageTemplate({
2471
+ locales: cfg.i18n.locales,
2472
+ defaultLocale: cfg.i18n.defaultLocale,
2473
+ componentName: "QuickstartPage",
2474
+ helperImport: "../../components/locale-doc-page",
2475
+ pageImports: cfg.i18n.locales.map((locale) => ({
2476
+ locale,
2477
+ importPath: `../${locale}/quickstart/page.mdx`
2478
+ }))
2479
+ }));
2480
+ for (const locale of cfg.i18n.locales) {
2481
+ const base = `${appDir}/${cfg.entry}/${locale}`;
2482
+ write(`${base}/page.mdx`, welcomePageTemplate(cfg));
2483
+ write(`${base}/installation/page.mdx`, installationPageTemplate(cfg));
2484
+ write(`${base}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
2485
+ }
2486
+ return;
2487
+ }
2261
2488
  write(`${appDir}/${cfg.entry}/page.mdx`, welcomePageTemplate(cfg));
2262
2489
  write(`${appDir}/${cfg.entry}/installation/page.mdx`, installationPageTemplate(cfg));
2263
2490
  write(`${appDir}/${cfg.entry}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
@@ -2273,6 +2500,7 @@ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written)
2273
2500
  write(`src/routes/${cfg.entry}/+layout.svelte`, svelteDocsLayoutTemplate(cfg));
2274
2501
  write(`src/routes/${cfg.entry}/+layout.server.js`, svelteDocsLayoutServerTemplate(cfg));
2275
2502
  write(`src/routes/${cfg.entry}/[...slug]/+page.svelte`, svelteDocsPageTemplate(cfg));
2503
+ if (cfg.i18n?.locales.length) write(`src/routes/${cfg.entry}/+page.svelte`, svelteDocsPageTemplate(cfg));
2276
2504
  if (!readFileSafe(path.join(cwd, "src/routes/+layout.svelte"))) write("src/routes/+layout.svelte", svelteRootLayoutTemplate(globalCssRelPath));
2277
2505
  const globalCssAbsPath = path.join(cwd, globalCssRelPath);
2278
2506
  const existingGlobalCss = readFileSafe(globalCssAbsPath);
@@ -2293,9 +2521,11 @@ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written)
2293
2521
  written.push(globalCssRelPath + " (updated)");
2294
2522
  } else skipped.push(globalCssRelPath + " (already configured)");
2295
2523
  } else write(globalCssRelPath, cfg.theme === "custom" && cfg.customThemeName ? svelteGlobalCssTemplate("custom", cfg.customThemeName, globalCssRelPath) : svelteGlobalCssTemplate(cssTheme));
2296
- write(`${cfg.entry}/page.md`, svelteWelcomePageTemplate(cfg));
2297
- write(`${cfg.entry}/installation/page.md`, svelteInstallationPageTemplate(cfg));
2298
- write(`${cfg.entry}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
2524
+ for (const base of getScaffoldContentRoots(cfg)) {
2525
+ write(`${base}/page.md`, svelteWelcomePageTemplate(cfg));
2526
+ write(`${base}/installation/page.md`, svelteInstallationPageTemplate(cfg));
2527
+ write(`${base}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
2528
+ }
2299
2529
  }
2300
2530
  function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
2301
2531
  if (cfg.theme === "custom" && cfg.customThemeName) {
@@ -2328,9 +2558,11 @@ function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
2328
2558
  written.push(globalCssRelPath + " (updated)");
2329
2559
  } else skipped.push(globalCssRelPath + " (already configured)");
2330
2560
  } else write(globalCssRelPath, cfg.theme === "custom" && cfg.customThemeName ? astroGlobalCssTemplate("custom", cfg.customThemeName, globalCssRelPath) : astroGlobalCssTemplate(cssTheme));
2331
- write(`${cfg.entry}/page.md`, astroWelcomePageTemplate(cfg));
2332
- write(`${cfg.entry}/installation/page.md`, astroInstallationPageTemplate(cfg));
2333
- write(`${cfg.entry}/quickstart/page.md`, astroQuickstartPageTemplate(cfg));
2561
+ for (const base of getScaffoldContentRoots(cfg)) {
2562
+ write(`${base}/page.md`, astroWelcomePageTemplate(cfg));
2563
+ write(`${base}/installation/page.md`, astroInstallationPageTemplate(cfg));
2564
+ write(`${base}/quickstart/page.md`, astroQuickstartPageTemplate(cfg));
2565
+ }
2334
2566
  }
2335
2567
  function scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written) {
2336
2568
  if (cfg.theme === "custom" && cfg.customThemeName) {
@@ -2365,9 +2597,11 @@ function scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written) {
2365
2597
  written.push(globalCssRelPath + " (updated)");
2366
2598
  } else skipped.push(globalCssRelPath + " (already configured)");
2367
2599
  } else write(globalCssRelPath, cfg.theme === "custom" && cfg.customThemeName ? nuxtGlobalCssTemplate("custom", cfg.customThemeName, globalCssRelPath) : nuxtGlobalCssTemplate(cssTheme));
2368
- write(`${cfg.entry}/page.md`, nuxtWelcomePageTemplate(cfg));
2369
- write(`${cfg.entry}/installation/page.md`, nuxtInstallationPageTemplate(cfg));
2370
- write(`${cfg.entry}/quickstart/page.md`, nuxtQuickstartPageTemplate(cfg));
2600
+ for (const base of getScaffoldContentRoots(cfg)) {
2601
+ write(`${base}/page.md`, nuxtWelcomePageTemplate(cfg));
2602
+ write(`${base}/installation/page.md`, nuxtInstallationPageTemplate(cfg));
2603
+ write(`${base}/quickstart/page.md`, nuxtQuickstartPageTemplate(cfg));
2604
+ }
2371
2605
  }
2372
2606
 
2373
2607
  //#endregion
package/dist/index.d.mts CHANGED
@@ -999,11 +999,23 @@ interface CodeBlockCopyData {
999
999
  /** Language / syntax hint (e.g. "tsx", "bash"), if present */
1000
1000
  language?: string;
1001
1001
  }
1002
+ interface DocsI18nConfig {
1003
+ /** Supported locale identifiers (e.g. ["en", "fr"]). */
1004
+ locales: string[];
1005
+ /** Default locale when `?lang=` is missing or invalid. Defaults to first locale. */
1006
+ defaultLocale?: string;
1007
+ }
1002
1008
  interface DocsConfig {
1003
1009
  /** Entry folder for docs (e.g. "docs" → /docs) */
1004
1010
  entry: string;
1005
1011
  /** Path to the content directory. Defaults to `entry` value. */
1006
1012
  contentDir?: string;
1013
+ /**
1014
+ * Internationalization (i18n) configuration.
1015
+ * When set, docs content is expected under `${contentDir}/{locale}` and
1016
+ * the active locale is selected by `?lang=<locale>` in the URL.
1017
+ */
1018
+ i18n?: DocsI18nConfig;
1007
1019
  /**
1008
1020
  * Set to `true` when building for full static export (e.g. Cloudflare Pages).
1009
1021
  * When using `output: 'export'` in Next.js, the `/api/docs` route is not generated.
@@ -1326,6 +1338,21 @@ declare function createTheme(baseTheme: DocsTheme): (overrides?: Partial<DocsThe
1326
1338
  */
1327
1339
  declare function extendTheme(baseTheme: DocsTheme, extensions: Partial<DocsTheme>): DocsTheme;
1328
1340
  //#endregion
1341
+ //#region src/i18n.d.ts
1342
+ interface ResolvedDocsI18n {
1343
+ locales: string[];
1344
+ defaultLocale: string;
1345
+ }
1346
+ interface DocsPathMatch {
1347
+ /** Slug path relative to the docs root (no leading slash). */
1348
+ slug: string;
1349
+ /** Entry path used for URLs (no locale segment). */
1350
+ entryPath: string;
1351
+ }
1352
+ declare function resolveDocsI18n(config?: DocsI18nConfig | null): ResolvedDocsI18n | null;
1353
+ declare function resolveDocsLocale(searchParams: URLSearchParams, i18n?: ResolvedDocsI18n | null): string | undefined;
1354
+ declare function resolveDocsPath(pathname: string, entry: string): DocsPathMatch;
1355
+ //#endregion
1329
1356
  //#region src/metadata.d.ts
1330
1357
  /**
1331
1358
  * Resolve page title using metadata titleTemplate.
@@ -1350,4 +1377,4 @@ declare function buildPageOpenGraph(page: Pick<PageFrontmatter, "title" | "descr
1350
1377
  */
1351
1378
  declare function buildPageTwitter(page: Pick<PageFrontmatter, "title" | "description" | "ogImage" | "openGraph" | "twitter">, ogConfig?: OGConfig, baseUrl?: string): PageTwitter | undefined;
1352
1379
  //#endregion
1353
- export { type AIConfig, type BreadcrumbConfig, type CodeBlockCopyData, type CopyMarkdownConfig, type DocsConfig, type DocsMetadata, type DocsNav, type DocsTheme, type FontStyle, type GithubConfig, type LastUpdatedConfig, type LlmsTxtConfig, type OGConfig, type OpenDocsConfig, type OpenDocsProvider, type OpenGraphImage, type OrderingItem, type PageActionsConfig, type PageFrontmatter, type PageOpenGraph, type PageTwitter, type SidebarComponentProps, type SidebarConfig, type SidebarFolderNode, type SidebarNode, type SidebarPageNode, type SidebarTree, type ThemeToggleConfig, type TypographyConfig, type UIConfig, buildPageOpenGraph, buildPageTwitter, createTheme, deepMerge, defineDocs, extendTheme, resolveOGImage, resolveTitle };
1380
+ export { type AIConfig, type BreadcrumbConfig, type CodeBlockCopyData, type CopyMarkdownConfig, type DocsConfig, type DocsI18nConfig, type DocsMetadata, type DocsNav, type DocsPathMatch, type DocsTheme, type FontStyle, type GithubConfig, type LastUpdatedConfig, type LlmsTxtConfig, type OGConfig, type OpenDocsConfig, type OpenDocsProvider, type OpenGraphImage, type OrderingItem, type PageActionsConfig, type PageFrontmatter, type PageOpenGraph, type PageTwitter, type ResolvedDocsI18n, type SidebarComponentProps, type SidebarConfig, type SidebarFolderNode, type SidebarNode, type SidebarPageNode, type SidebarTree, type ThemeToggleConfig, type TypographyConfig, type UIConfig, buildPageOpenGraph, buildPageTwitter, createTheme, deepMerge, defineDocs, extendTheme, resolveDocsI18n, resolveDocsLocale, resolveDocsPath, resolveOGImage, resolveTitle };
package/dist/index.mjs CHANGED
@@ -6,6 +6,7 @@ function defineDocs(config) {
6
6
  return {
7
7
  entry: config.entry ?? "docs",
8
8
  contentDir: config.contentDir,
9
+ i18n: config.i18n,
9
10
  theme: config.theme,
10
11
  nav: config.nav,
11
12
  github: config.github,
@@ -99,6 +100,45 @@ function extendTheme(baseTheme, extensions) {
99
100
  return deepMerge(baseTheme, extensions);
100
101
  }
101
102
 
103
+ //#endregion
104
+ //#region src/i18n.ts
105
+ function normalizeSegment(value) {
106
+ return value.replace(/^\/+|\/+$/g, "");
107
+ }
108
+ function splitSegments(value) {
109
+ const cleaned = normalizeSegment(value);
110
+ return cleaned ? cleaned.split("/").filter(Boolean) : [];
111
+ }
112
+ function resolveDocsI18n(config) {
113
+ if (!config || !Array.isArray(config.locales)) return null;
114
+ const locales = Array.from(new Set(config.locales.map((l) => l.trim()).filter(Boolean)));
115
+ if (locales.length === 0) return null;
116
+ return {
117
+ locales,
118
+ defaultLocale: config.defaultLocale && locales.includes(config.defaultLocale) ? config.defaultLocale : locales[0]
119
+ };
120
+ }
121
+ function resolveDocsLocale(searchParams, i18n) {
122
+ if (!i18n) return void 0;
123
+ const raw = searchParams.get("lang") ?? searchParams.get("locale");
124
+ if (!raw) return void 0;
125
+ if (i18n.locales.includes(raw)) return raw;
126
+ return i18n.defaultLocale;
127
+ }
128
+ function resolveDocsPath(pathname, entry) {
129
+ const entryBase = normalizeSegment(entry || "docs") || "docs";
130
+ const entryParts = splitSegments(entryBase);
131
+ const pathParts = splitSegments(pathname);
132
+ let rest = pathParts;
133
+ if (entryParts.length > 0) {
134
+ if (pathParts.slice(0, entryParts.length).join("/") === entryParts.join("/")) rest = pathParts.slice(entryParts.length);
135
+ }
136
+ return {
137
+ slug: rest.join("/"),
138
+ entryPath: entryBase
139
+ };
140
+ }
141
+
102
142
  //#endregion
103
143
  //#region src/metadata.ts
104
144
  /**
@@ -180,4 +220,4 @@ function buildPageTwitter(page, ogConfig, baseUrl) {
180
220
  }
181
221
 
182
222
  //#endregion
183
- export { buildPageOpenGraph, buildPageTwitter, createTheme, deepMerge, defineDocs, extendTheme, resolveOGImage, resolveTitle };
223
+ export { buildPageOpenGraph, buildPageTwitter, createTheme, deepMerge, defineDocs, extendTheme, resolveDocsI18n, resolveDocsLocale, resolveDocsPath, resolveOGImage, resolveTitle };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/docs",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI",
5
5
  "keywords": [
6
6
  "docs",