@aigne/doc-smith 0.9.8-beta → 0.9.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.8](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.8-beta.1...v0.9.8) (2025-12-07)
4
+
5
+ ## [0.9.8-beta.1](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.8-beta...v0.9.8-beta.1) (2025-12-06)
6
+
7
+
8
+ ### Features
9
+
10
+ * **translation:** unify expressions and default to English for image info ([#349](https://github.com/AIGNE-io/aigne-doc-smith/issues/349)) ([c134b1a](https://github.com/AIGNE-io/aigne-doc-smith/commit/c134b1a0f38f5b7daf382b24cf5f71ecd2cccae7))
11
+
3
12
  ## [0.9.8-beta](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.7...v0.9.8-beta) (2025-12-05)
4
13
 
5
14
 
@@ -2,7 +2,12 @@ import { copyFile, readFile, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import fs from "fs-extra";
4
4
  import { createHash } from "node:crypto";
5
- import { DIAGRAM_PLACEHOLDER, d2CodeBlockRegex, ensureTmpDir } from "../../utils/d2-utils.mjs";
5
+ import {
6
+ DIAGRAM_PLACEHOLDER,
7
+ d2CodeBlockRegex,
8
+ diagramImageBlockRegex,
9
+ ensureTmpDir,
10
+ } from "../../utils/d2-utils.mjs";
6
11
  import { DOC_SMITH_DIR, TMP_DIR, TMP_ASSETS_DIR } from "../../utils/constants/index.mjs";
7
12
  import { getContentHash, getFileName } from "../../utils/utils.mjs";
8
13
  import { getExtnameFromContentType } from "../../utils/file-utils.mjs";
@@ -418,40 +423,36 @@ function findAllDiagramLocations(content) {
418
423
  // 2. Find DIAGRAM_IMAGE_START markers
419
424
  // Format: <!-- DIAGRAM_IMAGE_START:type:aspectRatio -->...<!-- DIAGRAM_IMAGE_END -->
420
425
  // Note: aspectRatio can contain colon (e.g., "16:9"), so we need to match until -->
421
- const diagramImageRegex = /<!-- DIAGRAM_IMAGE_START:[^>]+ -->[\s\S]*?<!-- DIAGRAM_IMAGE_END -->/g;
422
- let match = diagramImageRegex.exec(content);
423
- while (match !== null) {
426
+ const imageMatches = Array.from(content.matchAll(diagramImageBlockRegex));
427
+ for (const match of imageMatches) {
424
428
  locations.push({
425
429
  type: "image",
426
430
  start: match.index,
427
431
  end: match.index + match[0].length,
428
432
  });
429
- match = diagramImageRegex.exec(content);
430
433
  }
431
434
 
432
435
  // 3. Find D2 code blocks
433
436
  // Note: .* matches title or other text after ```d2 (e.g., ```d2 Vault 驗證流程)
434
- match = d2CodeBlockRegex.exec(content);
435
- while (match !== null) {
437
+ const d2Matches = Array.from(content.matchAll(d2CodeBlockRegex));
438
+ for (const match of d2Matches) {
436
439
  locations.push({
437
440
  type: "d2",
438
441
  start: match.index,
439
442
  end: match.index + match[0].length,
440
443
  });
441
- match = d2CodeBlockRegex.exec(content);
442
444
  }
443
445
 
444
446
  // 4. Find Mermaid code blocks
445
447
  // Note: .* matches title or other text after ```mermaid (e.g., ```mermaid Flow Chart)
446
448
  const mermaidCodeBlockRegex = /```mermaid.*\n([\s\S]*?)```/g;
447
- match = mermaidCodeBlockRegex.exec(content);
448
- while (match !== null) {
449
+ const mermaidMatches = Array.from(content.matchAll(mermaidCodeBlockRegex));
450
+ for (const match of mermaidMatches) {
449
451
  locations.push({
450
452
  type: "mermaid",
451
453
  start: match.index,
452
454
  end: match.index + match[0].length,
453
455
  });
454
- match = mermaidCodeBlockRegex.exec(content);
455
456
  }
456
457
 
457
458
  // Sort by position in document (top to bottom)
@@ -1,4 +1,8 @@
1
1
  import { saveDocTranslation as _saveDocTranslation } from "../../utils/utils.mjs";
2
+ import { readFileContent } from "../../utils/docs-finder-utils.mjs";
3
+ import { getFileName } from "../../utils/utils.mjs";
4
+ import { debug } from "../../utils/debug.mjs";
5
+ import { syncDiagramToTranslations } from "../../utils/sync-diagram-to-translations.mjs";
2
6
 
3
7
  export default async function saveDocTranslation({
4
8
  path,
@@ -7,6 +11,7 @@ export default async function saveDocTranslation({
7
11
  language,
8
12
  labels,
9
13
  isShowMessage = false,
14
+ locale,
10
15
  }) {
11
16
  await _saveDocTranslation({
12
17
  path,
@@ -16,6 +21,35 @@ export default async function saveDocTranslation({
16
21
  labels,
17
22
  });
18
23
 
24
+ // Sync diagram images from main document to translations
25
+ // This ensures all images (including diagrams) in the main document are synced to translation files
26
+ if (path && docsDir && locale) {
27
+ try {
28
+ // Read main document content (it should already be saved)
29
+ const mainFileName = getFileName(path, locale);
30
+ const mainContent = await readFileContent(docsDir, mainFileName);
31
+
32
+ if (mainContent) {
33
+ const syncResult = await syncDiagramToTranslations(
34
+ mainContent,
35
+ path,
36
+ docsDir,
37
+ locale,
38
+ "sync",
39
+ );
40
+
41
+ if (syncResult.updated > 0) {
42
+ debug(
43
+ `✅ Synced diagram images to ${syncResult.updated} translation file(s) after translation`,
44
+ );
45
+ }
46
+ }
47
+ } catch (error) {
48
+ // Don't fail the translation if sync fails
49
+ debug(`⚠️ Failed to sync diagram images after translation: ${error.message}`);
50
+ }
51
+ }
52
+
19
53
  if (isShowMessage) {
20
54
  const message = `✅ Translation completed successfully.`;
21
55
  return { message };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/doc-smith",
3
- "version": "0.9.8-beta",
3
+ "version": "0.9.8",
4
4
  "description": "AI-driven documentation generation tool built on the AIGNE Framework",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -6,7 +6,7 @@ Please follow **all global rules, styles, aspect ratio logic, and diagram-type r
6
6
  - **Diagram Type:** {{ diagramType }}
7
7
  - **Visual Style:** {{ diagramStyle }}
8
8
  - **Aspect Ratio:** {{ aspectRatio }}
9
- - **Language:** {{ locale }}
9
+ - **Language:** English
10
10
 
11
11
  # Your responsibilities:
12
12
  1. Read and analyze the document content.
@@ -1,6 +1,4 @@
1
- import { DIAGRAM_PLACEHOLDER, d2CodeBlockRegex } from "./d2-utils.mjs";
2
-
3
- const diagramImageRegex = /<!--\s*DIAGRAM_IMAGE_START:[^>]+-->/g;
1
+ import { DIAGRAM_PLACEHOLDER, d2CodeBlockRegex, diagramImageStartRegex } from "./d2-utils.mjs";
4
2
 
5
3
  /**
6
4
  * Check if document content contains diagram-related content
@@ -24,7 +22,7 @@ export function hasDiagramContent(content) {
24
22
  }
25
23
 
26
24
  // Check for existing diagram images (DIAGRAM_IMAGE_START markers)
27
- const imageMatches = Array.from(content.matchAll(diagramImageRegex));
25
+ const imageMatches = Array.from(content.matchAll(diagramImageStartRegex));
28
26
  if (imageMatches.length > 0) {
29
27
  return true;
30
28
  }
@@ -51,7 +49,7 @@ export function getDiagramTypeLabels(content) {
51
49
  }
52
50
 
53
51
  // Check for existing diagram images (AI-generated images)
54
- const imageMatches = Array.from(content.matchAll(diagramImageRegex));
52
+ const imageMatches = Array.from(content.matchAll(diagramImageStartRegex));
55
53
  if (imageMatches.length > 0) {
56
54
  labels.push("🍌 Image");
57
55
  }
@@ -88,7 +86,7 @@ export function hasBananaImages(content) {
88
86
  }
89
87
 
90
88
  // Check for existing diagram images (DIAGRAM_IMAGE_START markers)
91
- const imageMatches = Array.from(content.matchAll(diagramImageRegex));
89
+ const imageMatches = Array.from(content.matchAll(diagramImageStartRegex));
92
90
  if (imageMatches.length > 0) {
93
91
  return true;
94
92
  }
@@ -10,6 +10,18 @@ export const d2CodeBlockRegex = /```d2.*\n([\s\S]*?)```/g;
10
10
 
11
11
  export const DIAGRAM_PLACEHOLDER = "DIAGRAM_PLACEHOLDER";
12
12
 
13
+ // Diagram image regex patterns for reuse across the codebase
14
+ // Pattern 1: Match only the start marker (for checking existence)
15
+ export const diagramImageStartRegex = /<!--\s*DIAGRAM_IMAGE_START:[^>]+-->/g;
16
+
17
+ // Pattern 2: Match full diagram image block without capturing image path (for finding/replacing)
18
+ export const diagramImageBlockRegex =
19
+ /<!--\s*DIAGRAM_IMAGE_START:[^>]+-->\s*[\s\S]*?<!--\s*DIAGRAM_IMAGE_END\s*-->/g;
20
+
21
+ // Pattern 3: Match full diagram image block with image path capture (for extracting paths)
22
+ export const diagramImageWithPathRegex =
23
+ /<!--\s*DIAGRAM_IMAGE_START:[^>]+-->\s*!\[[^\]]*\]\(([^)]+)\)\s*<!--\s*DIAGRAM_IMAGE_END\s*-->/g;
24
+
13
25
  export async function ensureTmpDir() {
14
26
  const tmpDir = path.join(DOC_SMITH_DIR, TMP_DIR);
15
27
  if (!(await fs.pathExists(path.join(tmpDir, ".gitignore")))) {
@@ -93,9 +105,8 @@ export function replaceDiagramsWithPlaceholder({ content, diagramIndex }) {
93
105
  return content;
94
106
  }
95
107
 
96
- // Import regex from replace-d2-with-image.mjs to find all diagram locations
108
+ // Use exported regex to find all diagram locations
97
109
  // We'll use a similar approach to findAllDiagramLocations
98
- const diagramImageRegex = /<!-- DIAGRAM_IMAGE_START:[^>]+ -->[\s\S]*?<!-- DIAGRAM_IMAGE_END -->/g;
99
110
  const mermaidCodeBlockRegex = /```mermaid.*\n([\s\S]*?)```/g;
100
111
 
101
112
  // Find all diagram locations
@@ -113,36 +124,33 @@ export function replaceDiagramsWithPlaceholder({ content, diagramIndex }) {
113
124
  }
114
125
 
115
126
  // 2. Find DIAGRAM_IMAGE_START markers (generated images)
116
- let match = diagramImageRegex.exec(content);
117
- while (match !== null) {
127
+ const imageMatches = Array.from(content.matchAll(diagramImageBlockRegex));
128
+ for (const match of imageMatches) {
118
129
  locations.push({
119
130
  type: "image",
120
131
  start: match.index,
121
132
  end: match.index + match[0].length,
122
133
  });
123
- match = diagramImageRegex.exec(content);
124
134
  }
125
135
 
126
136
  // 3. Find D2 code blocks
127
- match = d2CodeBlockRegex.exec(content);
128
- while (match !== null) {
137
+ const d2Matches = Array.from(content.matchAll(d2CodeBlockRegex));
138
+ for (const match of d2Matches) {
129
139
  locations.push({
130
140
  type: "d2",
131
141
  start: match.index,
132
142
  end: match.index + match[0].length,
133
143
  });
134
- match = d2CodeBlockRegex.exec(content);
135
144
  }
136
145
 
137
146
  // 4. Find Mermaid code blocks
138
- match = mermaidCodeBlockRegex.exec(content);
139
- while (match !== null) {
147
+ const mermaidMatches = Array.from(content.matchAll(mermaidCodeBlockRegex));
148
+ for (const match of mermaidMatches) {
140
149
  locations.push({
141
150
  type: "mermaid",
142
151
  start: match.index,
143
152
  end: match.index + match[0].length,
144
153
  });
145
- match = mermaidCodeBlockRegex.exec(content);
146
154
  }
147
155
 
148
156
  // Sort by position (top to bottom)
@@ -19,12 +19,10 @@ export async function extractDiagramImagePaths(content, path, docsDir) {
19
19
  const imagePaths = [];
20
20
 
21
21
  // Pattern to match: <!-- DIAGRAM_IMAGE_START:... -->![alt](path)<!-- DIAGRAM_IMAGE_END -->
22
- const diagramPattern =
23
- /<!--\s*DIAGRAM_IMAGE_START:[^>]+-->\s*!\[[^\]]*\]\(([^)]+)\)\s*<!--\s*DIAGRAM_IMAGE_END\s*-->/g;
22
+ const { diagramImageWithPathRegex } = await import("./d2-utils.mjs");
23
+ const matches = Array.from(content.matchAll(diagramImageWithPathRegex));
24
24
 
25
- diagramPattern.lastIndex = 0; // Reset regex
26
- let match = diagramPattern.exec(content);
27
- while (match !== null) {
25
+ for (const match of matches) {
28
26
  const imagePath = match[1];
29
27
 
30
28
  // Resolve absolute path
@@ -49,8 +47,6 @@ export async function extractDiagramImagePaths(content, path, docsDir) {
49
47
  if (await fs.pathExists(normalizedPath)) {
50
48
  imagePaths.push(normalizedPath);
51
49
  }
52
-
53
- match = diagramPattern.exec(content);
54
50
  }
55
51
 
56
52
  return imagePaths;
@@ -3,9 +3,7 @@ import { readFileContent } from "./docs-finder-utils.mjs";
3
3
  import { debug } from "./debug.mjs";
4
4
  import path from "node:path";
5
5
  import fs from "fs-extra";
6
- import { d2CodeBlockRegex } from "./d2-utils.mjs";
7
- const diagramImageRegex =
8
- /<!--\s*DIAGRAM_IMAGE_START:[^>]+-->\s*!\[[^\]]*\]\(([^)]+)\)\s*<!--\s*DIAGRAM_IMAGE_END\s*-->/g;
6
+ import { d2CodeBlockRegex, diagramImageWithPathRegex } from "./d2-utils.mjs";
9
7
 
10
8
  /**
11
9
  * Find all translation files for a document
@@ -21,15 +19,24 @@ async function findTranslationFiles(docPath, docsDir, locale) {
21
19
  try {
22
20
  const files = readdirSync(docsDir);
23
21
  const translationFiles = [];
22
+ const mainFileName = locale === "en" ? `${flatName}.md` : `${flatName}.${locale}.md`;
24
23
 
25
24
  // Filter files to find translation files matching the pattern
26
25
  for (const file of files) {
27
- if (
28
- file.startsWith(`${flatName}.`) &&
29
- file.endsWith(".md") &&
30
- file !== `${flatName}.md` &&
31
- file.match(/\.\w+(-\w+)?\.md$/)
32
- ) {
26
+ if (!file.endsWith(".md")) continue;
27
+ if (file === mainFileName) continue; // Skip main language file
28
+
29
+ // Case 1: File without language suffix (xxx.md) - this is English translation when main language is not English
30
+ if (file === `${flatName}.md` && locale !== "en") {
31
+ translationFiles.push({
32
+ language: "en",
33
+ fileName: file,
34
+ });
35
+ continue;
36
+ }
37
+
38
+ // Case 2: File with language suffix (xxx.{lang}.md) - all other translations
39
+ if (file.startsWith(`${flatName}.`) && file.match(/\.\w+(-\w+)?\.md$/)) {
33
40
  const langMatch = file.match(/\.(\w+(-\w+)?)\.md$/);
34
41
  if (langMatch && langMatch[1] !== locale) {
35
42
  translationFiles.push({
@@ -54,18 +61,14 @@ async function findTranslationFiles(docPath, docsDir, locale) {
54
61
  */
55
62
  function extractDiagramImagePaths(content) {
56
63
  const images = [];
64
+ const matches = Array.from(content.matchAll(diagramImageWithPathRegex));
57
65
 
58
- // Reset regex lastIndex
59
- diagramImageRegex.lastIndex = 0;
60
-
61
- let match = diagramImageRegex.exec(content);
62
- while (match !== null) {
66
+ for (const match of matches) {
63
67
  images.push({
64
68
  path: match[1],
65
69
  fullMatch: match[0],
66
70
  index: match.index,
67
71
  });
68
- match = diagramImageRegex.exec(content);
69
72
  }
70
73
 
71
74
  return images;
@@ -118,7 +121,8 @@ export async function syncDiagramToTranslations(
118
121
  const translationFilePath = path.join(docsDir, fileName);
119
122
  const translationContent = await readFileContent(docsDir, fileName);
120
123
 
121
- if (!translationContent) {
124
+ // Check for null or undefined (file read failure), but allow empty string (valid content)
125
+ if (translationContent === null || translationContent === undefined) {
122
126
  debug(`⚠️ Could not read translation file: ${fileName}`);
123
127
  result.skipped++;
124
128
  continue;