@farming-labs/docs 0.0.30 → 0.0.32

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 (2) hide show
  1. package/dist/cli/index.mjs +251 -17
  2. package/package.json +1 -1
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/docs",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI",
5
5
  "keywords": [
6
6
  "docs",