docs-i18n 0.11.1 → 0.12.0

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.
package/dist/index.js CHANGED
@@ -617,18 +617,241 @@ function getPackageVersion() {
617
617
  }
618
618
  return "unknown";
619
619
  }
620
+
621
+ // src/plugins/runner.ts
622
+ import { existsSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
623
+ import { join, resolve as resolve2 } from "path";
624
+ import matter from "gray-matter";
625
+ function walkFiles(dir, extensions) {
626
+ const results = [];
627
+ if (!existsSync(dir)) return results;
628
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
629
+ const fullPath = join(dir, entry.name);
630
+ if (entry.isDirectory()) {
631
+ results.push(...walkFiles(fullPath, extensions));
632
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
633
+ results.push(fullPath);
634
+ }
635
+ }
636
+ return results;
637
+ }
638
+ function runBeforeScan(plugins, config, opts) {
639
+ const beforeScanPlugins = plugins.filter((p) => p.beforeScan);
640
+ if (beforeScanPlugins.length === 0) return 0;
641
+ const sources = flattenSources(config);
642
+ const extensions = (config.include ?? ["**/*.mdx", "**/*.md"]).map(
643
+ (p) => p.replace("**/*", "")
644
+ );
645
+ let modifiedCount = 0;
646
+ for (const source of sources) {
647
+ if (opts?.project && source.project !== opts.project) continue;
648
+ if (opts?.version && source.version !== opts.version) continue;
649
+ const enDir = resolve2(source.sourcePath);
650
+ if (!existsSync(enDir)) {
651
+ console.log(` [SKIP] ${source.versionKey}: source dir not found`);
652
+ continue;
653
+ }
654
+ const files = walkFiles(enDir, extensions);
655
+ for (const filePath of files) {
656
+ const content = readFileSync2(filePath, "utf-8");
657
+ let parsed;
658
+ try {
659
+ parsed = matter(content);
660
+ } catch {
661
+ continue;
662
+ }
663
+ const readFile = (refPath) => {
664
+ const fromDir = filePath.substring(0, filePath.lastIndexOf("/"));
665
+ const candidates = [
666
+ resolve2(fromDir, refPath),
667
+ resolve2(enDir, refPath),
668
+ resolve2(process.cwd(), refPath)
669
+ ];
670
+ for (const candidate of candidates) {
671
+ if (existsSync(candidate)) {
672
+ return readFileSync2(candidate, "utf-8");
673
+ }
674
+ }
675
+ return null;
676
+ };
677
+ let modified = content;
678
+ let wasModified = false;
679
+ for (const plugin of beforeScanPlugins) {
680
+ const result = plugin.beforeScan({
681
+ filePath,
682
+ content: modified,
683
+ frontmatter: parsed.data,
684
+ readFile,
685
+ project: source.project,
686
+ version: source.version
687
+ });
688
+ if (typeof result === "string") {
689
+ modified = result;
690
+ wasModified = true;
691
+ try {
692
+ parsed = matter(modified);
693
+ } catch {
694
+ }
695
+ }
696
+ }
697
+ if (wasModified) {
698
+ const relPath = filePath.slice(enDir.length + 1);
699
+ if (opts?.dryRun) {
700
+ console.log(` [DRY-RUN] ${source.versionKey}/${relPath}`);
701
+ } else {
702
+ writeFileSync(filePath, modified, "utf-8");
703
+ console.log(` [RESOLVED] ${source.versionKey}/${relPath}`);
704
+ }
705
+ modifiedCount++;
706
+ }
707
+ }
708
+ }
709
+ return modifiedCount;
710
+ }
711
+ function createShouldScanBody(plugins) {
712
+ const hooks = plugins.filter((p) => p.shouldScanBody);
713
+ if (hooks.length === 0) return void 0;
714
+ return (ctx) => {
715
+ for (const plugin of hooks) {
716
+ if (plugin.shouldScanBody(ctx) === false) {
717
+ return false;
718
+ }
719
+ }
720
+ return true;
721
+ };
722
+ }
723
+
724
+ // src/plugins/presets/tanstack-ref.ts
725
+ import matter2 from "gray-matter";
726
+ var MAX_REF_DEPTH = 4;
727
+ function replaceContent(text, originFrontmatter) {
728
+ let result = text;
729
+ const replace = originFrontmatter.data.replace;
730
+ if (replace) {
731
+ for (const [key, value] of Object.entries(replace)) {
732
+ result = result.replace(new RegExp(key, "g"), value);
733
+ }
734
+ }
735
+ return result;
736
+ }
737
+ function replaceSections(text, originFrontmatter) {
738
+ let result = text;
739
+ const sectionMarkerRegex = /\[\/\/\]: # '([a-zA-Z\d]*)'/g;
740
+ const sectionRegex = /\[\/\/\]: # '([a-zA-Z\d]*)'[\S\s]*?\[\/\/\]: # '([a-zA-Z\d]*)'/g;
741
+ const substitutes = /* @__PURE__ */ new Map();
742
+ for (const match of originFrontmatter.content.matchAll(sectionRegex)) {
743
+ if (match[1] !== match[2]) {
744
+ console.error(
745
+ `Origin section '${match[1]}' does not have matching closing token (found '${match[2]}'). Please make sure that each section has corresponding closing token and that sections are not nested.`
746
+ );
747
+ }
748
+ substitutes.set(match[1], match);
749
+ }
750
+ const sections = /* @__PURE__ */ new Map();
751
+ for (const match of result.matchAll(sectionRegex)) {
752
+ if (match[1] !== match[2]) {
753
+ console.error(
754
+ `Target section '${match[1]}' does not have matching closing token (found '${match[2]}'). Please make sure that each section has corresponding closing token and that sections are not nested.`
755
+ );
756
+ }
757
+ sections.set(match[1], match);
758
+ }
759
+ Array.from(substitutes.entries()).reverse().forEach(([key, value]) => {
760
+ const sectionMatch = sections.get(key);
761
+ if (sectionMatch) {
762
+ result = result.slice(0, sectionMatch.index) + value[0] + result.slice(sectionMatch.index + sectionMatch[0].length, result.length);
763
+ }
764
+ });
765
+ result = result.replaceAll(sectionMarkerRegex, "");
766
+ return result;
767
+ }
768
+ function normalizeRefPath(ref) {
769
+ if (ref.startsWith("docs/")) {
770
+ return ref.slice(5);
771
+ }
772
+ return ref;
773
+ }
774
+ function tryReadRef(refPath, readFile) {
775
+ let content = readFile(refPath);
776
+ if (content) return content;
777
+ const normalized = normalizeRefPath(refPath);
778
+ if (normalized !== refPath) {
779
+ content = readFile(normalized);
780
+ if (content) return content;
781
+ }
782
+ return null;
783
+ }
784
+ function tanstackRefPlugin() {
785
+ return {
786
+ name: "tanstack-ref",
787
+ beforeScan(ctx) {
788
+ const { filePath, content, frontmatter, readFile } = ctx;
789
+ if (!frontmatter.ref) {
790
+ return void 0;
791
+ }
792
+ const originParsed = matter2(content);
793
+ let refPath = frontmatter.ref;
794
+ let currentDepth = 1;
795
+ while (currentDepth <= MAX_REF_DEPTH) {
796
+ const refContent = tryReadRef(refPath, readFile);
797
+ if (!refContent) {
798
+ console.warn(` [tanstack-ref] ${filePath}: ref target not found: ${refPath}`);
799
+ return void 0;
800
+ }
801
+ const refParsed = matter2(refContent);
802
+ if (!refParsed.data.ref) {
803
+ return resolveWithContent(refContent, originParsed);
804
+ }
805
+ refPath = refParsed.data.ref;
806
+ currentDepth++;
807
+ }
808
+ console.warn(` [tanstack-ref] ${filePath}: ref chain exceeded max depth of ${MAX_REF_DEPTH}`);
809
+ return void 0;
810
+ }
811
+ };
812
+ }
813
+ function resolveWithContent(targetRaw, originParsed) {
814
+ let content = targetRaw;
815
+ content = replaceContent(content, originParsed);
816
+ content = replaceSections(content, originParsed);
817
+ const newData = { ...originParsed.data };
818
+ delete newData.ref;
819
+ delete newData.replace;
820
+ const targetParsed = matter2(content);
821
+ const mergedData = { ...targetParsed.data, ...newData };
822
+ delete mergedData.ref;
823
+ delete mergedData.replace;
824
+ return matter2.stringify(targetParsed.content, mergedData);
825
+ }
826
+
827
+ // src/plugins/presets/nextjs-source.ts
828
+ function nextjsSourcePlugin() {
829
+ return {
830
+ name: "nextjs-source",
831
+ shouldScanBody(ctx) {
832
+ if (ctx.frontmatter.source) {
833
+ return false;
834
+ }
835
+ return true;
836
+ }
837
+ };
838
+ }
620
839
  export {
621
840
  TranslationCache,
622
841
  assemble,
842
+ createShouldScanBody,
623
843
  defineConfig,
624
844
  extractHeadings,
625
845
  extractTranslatableFields,
626
846
  flattenSources,
627
847
  getPackageVersion,
628
848
  loadConfig,
849
+ nextjsSourcePlugin,
629
850
  normalize,
630
851
  parseMdx,
631
852
  processor,
632
853
  reconstructFrontmatter,
633
- renderMarkdown
854
+ renderMarkdown,
855
+ runBeforeScan,
856
+ tanstackRefPlugin
634
857
  };
@@ -0,0 +1,104 @@
1
+ import {
2
+ createShouldScanBody,
3
+ runBeforeScan
4
+ } from "./chunk-BHYFOPS6.js";
5
+ import "./chunk-TRURQFP4.js";
6
+ import "./chunk-PNKVD2UK.js";
7
+
8
+ // src/commands/pipeline.ts
9
+ import { execSync } from "child_process";
10
+ async function pipeline(config, opts) {
11
+ const plugins = config.plugins ?? [];
12
+ const syncScript = config.syncScript;
13
+ const d1 = config.d1;
14
+ const t0 = Date.now();
15
+ if (syncScript && !opts.skipSync) {
16
+ console.log("\n=== Step 1: Sync ===");
17
+ if (opts.dryRun) {
18
+ console.log(` [DRY-RUN] Would run: ${syncScript}`);
19
+ } else {
20
+ console.log(` Running: ${syncScript}`);
21
+ execSync(syncScript, { stdio: "inherit", shell: "/bin/bash" });
22
+ }
23
+ } else {
24
+ console.log("\n=== Step 1: Sync (skipped) ===");
25
+ }
26
+ console.log("\n=== Step 2: Resolve ===");
27
+ if (plugins.length > 0) {
28
+ const modified = runBeforeScan(plugins, config, {
29
+ project: opts.project,
30
+ version: opts.version,
31
+ dryRun: opts.dryRun
32
+ });
33
+ console.log(` ${modified} file(s) resolved`);
34
+ } else {
35
+ console.log(" No plugins configured, skipping");
36
+ }
37
+ console.log("\n=== Step 3: Rescan ===");
38
+ const { rescan } = await import("./rescan-7XDHUNH5.js");
39
+ const shouldScanBody = createShouldScanBody(plugins);
40
+ await rescan(config, {
41
+ project: opts.project,
42
+ version: opts.version,
43
+ shouldScanBody
44
+ });
45
+ if (!opts.skipTranslate) {
46
+ console.log("\n=== Step 4: Translate ===");
47
+ const { translate } = await import("./translate-AGUNMZMQ.js");
48
+ const langs = opts.lang ? [opts.lang] : config.languages;
49
+ for (const lang of langs) {
50
+ await translate(config, {
51
+ lang,
52
+ project: opts.project,
53
+ version: opts.version,
54
+ max: opts.max,
55
+ concurrency: opts.concurrency,
56
+ dryRun: opts.dryRun
57
+ });
58
+ }
59
+ } else {
60
+ console.log("\n=== Step 4: Translate (skipped) ===");
61
+ }
62
+ if (!opts.skipUpload && d1) {
63
+ console.log("\n=== Step 5: Upload ===");
64
+ if (opts.dryRun) {
65
+ console.log(" [DRY-RUN] Would upload to D1");
66
+ } else {
67
+ console.log(" Uploading to D1...");
68
+ const { collectContentFiles, generateContentSql, collectTranslations, generateTranslationSql } = await import("./upload-KYKJVERO.js");
69
+ const { writeFileSync } = await import("fs");
70
+ const { resolve } = await import("path");
71
+ const { tmpdir } = await import("os");
72
+ const projectRoot = process.cwd();
73
+ const contentRows = collectContentFiles(projectRoot);
74
+ const contentSql = generateContentSql(contentRows);
75
+ const translationData = collectTranslations(projectRoot);
76
+ const translationSql = generateTranslationSql(translationData);
77
+ const allSql = [...contentSql, ...translationSql];
78
+ const sqlFile = resolve(tmpdir(), "docs-i18n-pipeline-upload.sql");
79
+ writeFileSync(sqlFile, allSql.join("\n"));
80
+ const dbName = d1.database;
81
+ const wranglerArgs = ["d1", "execute", dbName, "--file", sqlFile, "--remote"];
82
+ console.log(` wrangler ${wranglerArgs.join(" ")}`);
83
+ const { spawnSync } = await import("child_process");
84
+ const result = spawnSync("npx", ["wrangler", ...wranglerArgs], {
85
+ stdio: "inherit",
86
+ shell: true
87
+ });
88
+ if (result.status !== 0) {
89
+ console.error(" Upload failed");
90
+ }
91
+ }
92
+ } else {
93
+ console.log("\n=== Step 5: Upload (skipped) ===");
94
+ }
95
+ console.log("\n=== Step 6: Status ===");
96
+ const { status } = await import("./status-NX47XZIU.js");
97
+ await status(config, { lang: opts.lang });
98
+ const elapsed = ((Date.now() - t0) / 1e3).toFixed(1);
99
+ console.log(`
100
+ Pipeline complete in ${elapsed}s`);
101
+ }
102
+ export {
103
+ pipeline
104
+ };
@@ -5,10 +5,10 @@ import {
5
5
  import {
6
6
  TranslationCache
7
7
  } from "./chunk-QKIR7RKQ.js";
8
+ import "./chunk-L64GJ4OB.js";
8
9
  import {
9
10
  flattenSources
10
11
  } from "./chunk-TRURQFP4.js";
11
- import "./chunk-L64GJ4OB.js";
12
12
  import "./chunk-PNKVD2UK.js";
13
13
 
14
14
  // src/commands/rescan.ts
@@ -28,6 +28,23 @@ function walkFiles(dir, patterns) {
28
28
  }
29
29
  return results;
30
30
  }
31
+ function parseFrontmatter(content) {
32
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
33
+ if (!match) return {};
34
+ try {
35
+ const lines = match[1].split("\n");
36
+ const data = {};
37
+ for (const line of lines) {
38
+ const kv = line.match(/^(\w[\w.-]*)\s*:\s*(.+)/);
39
+ if (kv) {
40
+ data[kv[1]] = kv[2].trim().replace(/^['"]|['"]$/g, "");
41
+ }
42
+ }
43
+ return data;
44
+ } catch {
45
+ return {};
46
+ }
47
+ }
31
48
  async function rescan(config, opts) {
32
49
  const cacheDir = resolve(config.cacheDir ?? ".cache");
33
50
  const cache = new TranslationCache(cacheDir);
@@ -47,12 +64,22 @@ async function rescan(config, opts) {
47
64
  const files = walkFiles(enDir, extensions);
48
65
  cache.clearSources("", source.versionKey);
49
66
  let nodeCount = 0;
67
+ let skippedBodyCount = 0;
50
68
  for (const file of files) {
51
69
  const relPath = file.slice(enDir.length + 1);
52
70
  const content = readFileSync(file, "utf8");
53
71
  const nodes = parseMdx(content);
72
+ let scanBody = true;
73
+ if (opts.shouldScanBody) {
74
+ const frontmatter = parseFrontmatter(content);
75
+ scanBody = opts.shouldScanBody({ filePath: file, frontmatter });
76
+ if (!scanBody) skippedBodyCount++;
77
+ }
54
78
  for (const node of nodes) {
55
79
  if (node.needsTranslation && node.md5) {
80
+ if (!scanBody && node.type !== "frontmatter") {
81
+ continue;
82
+ }
56
83
  const line = content.substring(0, node.startOffset).split("\n").length;
57
84
  cache.setSource(node.md5, node.rawText, node.type);
58
85
  cache.updateSource("", node.md5, relPath, line, source.versionKey);
@@ -60,7 +87,11 @@ async function rescan(config, opts) {
60
87
  }
61
88
  }
62
89
  }
63
- console.log(`\u2705 ${source.versionKey}: ${files.length} files, ${nodeCount} nodes`);
90
+ let msg = `\u2705 ${source.versionKey}: ${files.length} files, ${nodeCount} nodes`;
91
+ if (skippedBodyCount > 0) {
92
+ msg += ` (${skippedBodyCount} body-skipped)`;
93
+ }
94
+ console.log(msg);
64
95
  }
65
96
  const orphanT = cache.db.prepare("DELETE FROM translations WHERE key NOT IN (SELECT DISTINCT key FROM source_files)").run();
66
97
  if (orphanT.changes > 0) console.log(`\u{1F5D1}\uFE0F Deleted ${orphanT.changes} orphan translations`);
@@ -0,0 +1,10 @@
1
+ import {
2
+ createShouldScanBody,
3
+ runBeforeScan
4
+ } from "./chunk-BHYFOPS6.js";
5
+ import "./chunk-TRURQFP4.js";
6
+ import "./chunk-PNKVD2UK.js";
7
+ export {
8
+ createShouldScanBody,
9
+ runBeforeScan
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docs-i18n",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Universal documentation translation engine — parse, translate, cache, assemble, manage.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,6 +30,7 @@
30
30
  "dependencies": {
31
31
  "better-sqlite3": "^12.8.0",
32
32
  "glob": "^11.0.2",
33
+ "gray-matter": "^4.0.3",
33
34
  "hono": "^4.12.0",
34
35
  "jiti": "^2.4.0",
35
36
  "openai": "^5.1.1",
@@ -17912,7 +17912,8 @@ const FastURL = /* @__PURE__ */ (() => {
17912
17912
  #searchParams;
17913
17913
  #pos;
17914
17914
  constructor(url) {
17915
- if (typeof url === "string") this.#href = url;
17915
+ if (typeof url === "string") if (url[0] === "/") this.#href = url;
17916
+ else this.#url = new NativeURL(url);
17916
17917
  else if (_needsNormRE.test(url.pathname)) this.#url = new NativeURL(`${url.protocol || "http:"}//${url.host || "localhost"}${url.pathname}${url.search || ""}`);
17917
17918
  else {
17918
17919
  this.#protocol = url.protocol;
@@ -18447,21 +18448,18 @@ async function getStartManifest(matchedRoutes) {
18447
18448
  injectedHeadScripts
18448
18449
  };
18449
18450
  }
18450
- const manifest = { "2da8ac67817387d067b3bdceb46771231f5e146e3854d166a502e2e544b99e75": {
18451
- functionName: "fetchDocsConfig_createServerFn_handler",
18452
- importer: () => import("./assets/_lang.docs-DSB3iI6a.js")
18453
- }, "98301a535a39285242f45a98dbf7a395ebc47fd9497246cb5e514e5900cf025d": {
18451
+ const manifest = { "98301a535a39285242f45a98dbf7a395ebc47fd9497246cb5e514e5900cf025d": {
18454
18452
  functionName: "fetchDoc_createServerFn_handler",
18455
18453
  importer: () => import("./assets/_lang.docs._-S-y4-3Qn.js")
18456
18454
  }, "fe3a75503837cf2fc9240e65f0849090d48fa1c44543665b480ec53331c230ed": {
18457
18455
  functionName: "fetchBlogPosts_createServerFn_handler",
18458
18456
  importer: () => import("./assets/_lang.blog.index-Dk5fYp4Q.js")
18459
- }, "3d681c18f721dc4f29239d355a6b01ee50157021f1206a70284a9dfeb3375f2b": {
18460
- functionName: "fetchBlogPost_createServerFn_handler",
18461
- importer: () => import("./assets/_lang.blog._-CjgFipz7.js")
18462
18457
  }, "d70cb4120f5e21fc51a269146a3e4e34bf32fcdfc44d688d7a5f077efca69cf4": {
18463
18458
  functionName: "fetchDocsConfig_createServerFn_handler",
18464
18459
  importer: () => import("./assets/_lang._project.docs-BsnkRf-R.js")
18460
+ }, "3d681c18f721dc4f29239d355a6b01ee50157021f1206a70284a9dfeb3375f2b": {
18461
+ functionName: "fetchBlogPost_createServerFn_handler",
18462
+ importer: () => import("./assets/_lang.blog._-CjgFipz7.js")
18465
18463
  }, "094bd97706d5673a22d38d99e5497787a36ac27d573b5d695feb60c7dacf19e0": {
18466
18464
  functionName: "fetchDoc_createServerFn_handler",
18467
18465
  importer: () => import("./assets/_lang._project.docs._-axRfm8vS.js")
@@ -18474,6 +18472,9 @@ const manifest = { "2da8ac67817387d067b3bdceb46771231f5e146e3854d166a502e2e544b9
18474
18472
  }, "ba14c96811d1fd21f8c969f3eed0af525dfa9df823e738b56e90f3b53073f89f": {
18475
18473
  functionName: "fetchDoc_createServerFn_handler",
18476
18474
  importer: () => import("./assets/_lang._project._version.docs._-DSg82-Ug.js")
18475
+ }, "2da8ac67817387d067b3bdceb46771231f5e146e3854d166a502e2e544b99e75": {
18476
+ functionName: "fetchDocsConfig_createServerFn_handler",
18477
+ importer: () => import("./assets/_lang.docs-DSB3iI6a.js")
18477
18478
  }, "94a3ef92793a719752941579c1203c60a93e325afd12353810d1fd6ab6efa524": {
18478
18479
  functionName: "fetchFrameworkDoc_createServerFn_handler",
18479
18480
  importer: () => import("./assets/_lang._project._version.docs.framework._framework._-DbeN6toN.js")
@@ -5,10 +5,10 @@ import "./chunk-JHBSHTXC.js";
5
5
  import {
6
6
  TranslationCache
7
7
  } from "./chunk-QKIR7RKQ.js";
8
+ import "./chunk-L64GJ4OB.js";
8
9
  import {
9
10
  flattenSources
10
11
  } from "./chunk-TRURQFP4.js";
11
- import "./chunk-L64GJ4OB.js";
12
12
  import "./chunk-PNKVD2UK.js";
13
13
 
14
14
  // src/commands/assemble.ts
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  TranslationCache
3
3
  } from "./chunk-QKIR7RKQ.js";
4
+ import "./chunk-L64GJ4OB.js";
4
5
  import {
5
6
  flattenSources
6
7
  } from "./chunk-TRURQFP4.js";
7
- import "./chunk-L64GJ4OB.js";
8
8
  import "./chunk-PNKVD2UK.js";
9
9
 
10
10
  // src/commands/status.ts
@@ -6,10 +6,10 @@ import {
6
6
  import {
7
7
  TranslationCache
8
8
  } from "./chunk-QKIR7RKQ.js";
9
+ import "./chunk-L64GJ4OB.js";
9
10
  import {
10
11
  flattenSources
11
12
  } from "./chunk-TRURQFP4.js";
12
- import "./chunk-L64GJ4OB.js";
13
13
  import {
14
14
  __esm,
15
15
  __export,