ai-localize-reporting 2.0.4 → 2.0.6

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,35 @@
1
1
  # ai-localize-reporting
2
2
 
3
+ ## 2.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Add per-package README.md files so each package displays documentation on npmjs.com
8
+ - Update README version badge to 2.0.6
9
+
10
+ ## 2.0.5
11
+
12
+ ### Patch Changes
13
+
14
+ - **Bug fix — `report` crashed with `RangeError: Invalid count value`**: The CLI reporter
15
+ computed translation coverage as `(totalUniqueKeys - missingKeys.length) / totalUniqueKeys * 100`.
16
+ Because `missingKeys` are recorded per-language (e.g. 5 unique keys × 2 target languages = 10
17
+ entries), coverage went negative when all keys were untranslated, causing `String.prototype.repeat()`
18
+ to throw. Three fixes applied to `printCliSummary()`:
19
+ - Coverage now counts **distinct** missing key names (`new Set(missingKeys.map(mk => mk.key)).size`)
20
+ instead of raw array length, giving the correct per-key missing count.
21
+ - `coveragePct` is clamped to `[0, 100]` with `Math.max(0, Math.min(100, …))`.
22
+ - `progressBar()`, `miniBar()`, and the `renderTable()` center-align branch all guard
23
+ `filled`, `empty`, and padding values with `Math.max(0, …)` to prevent negative repeats.
24
+ (`packages/reporting/src/cli-reporter.ts`)
25
+
26
+ ## 2.0.4
27
+
28
+ ### Patch Changes
29
+
30
+ - Updated dependencies
31
+ - ai-localize-shared@2.0.4
32
+
3
33
  ## 2.0.3
4
34
 
5
35
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # ai-localize-reporting
2
+
3
+ > HTML analytics dashboard + rich CLI terminal reporter for the [ai-localize-core](https://github.com/ai-localize/ai-localize-core) platform.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/ai-localize-reporting.svg)](https://www.npmjs.com/package/ai-localize-reporting)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ ---
9
+
10
+ ## What it does
11
+
12
+ - **`buildReport()`** — assembles a `Report` object from scan + validation results
13
+ - **`generateHtmlReport()`** — writes a self-contained, interactive HTML analytics dashboard
14
+ - **`printCliSummary()`** — prints a rich structured terminal summary with ANSI colour, bar charts, and tables
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install ai-localize-reporting
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ```ts
25
+ import { buildReport, generateHtmlReport, printCliSummary } from 'ai-localize-reporting';
26
+
27
+ const report = buildReport({
28
+ scanResult,
29
+ validationResult,
30
+ uploadedAssets, // optional
31
+ replacedUrls, // optional
32
+ });
33
+
34
+ // Write HTML dashboard
35
+ generateHtmlReport(report, './.reports/report.html');
36
+
37
+ // Print terminal summary
38
+ printCliSummary(report);
39
+ ```
40
+
41
+ ## HTML report features
42
+
43
+ - **Sticky sidebar navigation** with active section highlighting and alert indicators
44
+ - **Light / dark theme** — system preference, `localStorage`, `Cmd+D` / `Ctrl+D` shortcut
45
+ - **9 stat cards** — top-border colour coding (ok / warn / error / info)
46
+ - **SVG donut chart** — translation coverage % (no external dependencies)
47
+ - **Bar charts** — keys by namespace, texts by AST context
48
+ - **Interactive tables** — search, sort, CSV/JSON export, pagination (50 rows/page)
49
+ - **AI Insights** (100% deterministic) — duplicate text detection, translation inconsistency, unused keys, namespace cleanup hints
50
+ - **Export panel** — full report JSON, summary CSV, print/PDF
51
+ - **Responsive + accessible** — ARIA roles, keyboard navigation, mobile sidebar
52
+
53
+ ## CLI terminal summary features
54
+
55
+ - **Zero new dependencies** — all ANSI codes inlined; `NO_COLOR` env var respected
56
+ - **Full-width banner** with centred title
57
+ - **9 stat lines** with status dots (green/yellow/red) and contextual hints
58
+ - **Coverage progress bar** (24-block, colour-coded)
59
+ - **Top-files bar chart**, missing-translations ranking, namespace distribution, AST context distribution
60
+ - **AI Insights block** and recommended next steps
61
+ - **Responsive width** — reads `process.stdout.columns`, caps at 120 chars
62
+
63
+ ## Coverage calculation (v2.0.5+)
64
+
65
+ Coverage % is computed using **distinct missing key names** (not raw per-language count), so it correctly reflects the fraction of unique keys that have been translated:
66
+
67
+ ```
68
+ coverage = (totalUniqueKeys - distinctMissingKeys) / totalUniqueKeys × 100
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Part of ai-localize-core
74
+
75
+ Install the CLI for the complete toolset: `npm install -g ai-localize-cli`
76
+
77
+ MIT © ai-localize-core contributors
package/dist/index.js CHANGED
@@ -1697,17 +1697,18 @@ function badge2(text, ok) {
1697
1697
  return ok ? c("bgGreen", c("black", ` ${text} `)) : c("bgRed", c("white", ` ${text} `));
1698
1698
  }
1699
1699
  function miniBar(value, max, width = 20, color = "cyan") {
1700
- const filled = max > 0 ? Math.round(value / max * width) : 0;
1700
+ const filled = max > 0 ? Math.max(0, Math.min(width, Math.round(value / max * width))) : 0;
1701
1701
  const empty = width - filled;
1702
1702
  const bar = c(color, "\u2588".repeat(filled)) + dim("\u2591".repeat(empty));
1703
1703
  return "[" + bar + "]";
1704
1704
  }
1705
1705
  function progressBar(pct, width = 24) {
1706
- const filled = Math.round(pct / 100 * width);
1706
+ const safePct = Math.max(0, Math.min(100, pct));
1707
+ const filled = Math.max(0, Math.min(width, Math.round(safePct / 100 * width)));
1707
1708
  const empty = width - filled;
1708
1709
  let color = "brightGreen";
1709
- if (pct < 50) color = "brightRed";
1710
- else if (pct < 80) color = "brightYellow";
1710
+ if (safePct < 50) color = "brightRed";
1711
+ else if (safePct < 80) color = "brightYellow";
1711
1712
  const bar = c(color, "\u2588".repeat(filled)) + dim("\u2591".repeat(empty));
1712
1713
  return "[" + bar + "]";
1713
1714
  }
@@ -1738,8 +1739,10 @@ function renderTable(columns, rows) {
1738
1739
  if (isHeader) s = bold(strip(s));
1739
1740
  if (col?.align === "right") s = padStart(s, w);
1740
1741
  else if (col?.align === "center") {
1741
- const p = Math.floor((w - visLen(s)) / 2);
1742
- s = " ".repeat(p) + s + " ".repeat(w - visLen(s) - p);
1742
+ const visibleLen = visLen(s);
1743
+ const p = Math.max(0, Math.floor((w - visibleLen) / 2));
1744
+ const q = Math.max(0, w - visibleLen - p);
1745
+ s = " ".repeat(p) + s + " ".repeat(q);
1743
1746
  } else {
1744
1747
  s = padEnd(s, w);
1745
1748
  }
@@ -1760,7 +1763,8 @@ function renderTable(columns, rows) {
1760
1763
  function computeCliInsights(report) {
1761
1764
  const { details } = report;
1762
1765
  const totalUniqueKeys = new Set(details.detectedTexts.map((d) => d.suggestedKey)).size;
1763
- const coveragePct = totalUniqueKeys > 0 ? Math.round((totalUniqueKeys - details.missingKeys.length) / totalUniqueKeys * 100) : 100;
1766
+ const missingUniqueKeys = new Set(details.missingKeys.map((mk) => mk.key)).size;
1767
+ const coveragePct = totalUniqueKeys > 0 ? Math.max(0, Math.min(100, Math.round((totalUniqueKeys - missingUniqueKeys) / totalUniqueKeys * 100))) : 100;
1764
1768
  const textMap = /* @__PURE__ */ new Map();
1765
1769
  for (const dt of details.detectedTexts) {
1766
1770
  const t = dt.text.trim();
package/dist/index.mjs CHANGED
@@ -1659,17 +1659,18 @@ function badge2(text, ok) {
1659
1659
  return ok ? c("bgGreen", c("black", ` ${text} `)) : c("bgRed", c("white", ` ${text} `));
1660
1660
  }
1661
1661
  function miniBar(value, max, width = 20, color = "cyan") {
1662
- const filled = max > 0 ? Math.round(value / max * width) : 0;
1662
+ const filled = max > 0 ? Math.max(0, Math.min(width, Math.round(value / max * width))) : 0;
1663
1663
  const empty = width - filled;
1664
1664
  const bar = c(color, "\u2588".repeat(filled)) + dim("\u2591".repeat(empty));
1665
1665
  return "[" + bar + "]";
1666
1666
  }
1667
1667
  function progressBar(pct, width = 24) {
1668
- const filled = Math.round(pct / 100 * width);
1668
+ const safePct = Math.max(0, Math.min(100, pct));
1669
+ const filled = Math.max(0, Math.min(width, Math.round(safePct / 100 * width)));
1669
1670
  const empty = width - filled;
1670
1671
  let color = "brightGreen";
1671
- if (pct < 50) color = "brightRed";
1672
- else if (pct < 80) color = "brightYellow";
1672
+ if (safePct < 50) color = "brightRed";
1673
+ else if (safePct < 80) color = "brightYellow";
1673
1674
  const bar = c(color, "\u2588".repeat(filled)) + dim("\u2591".repeat(empty));
1674
1675
  return "[" + bar + "]";
1675
1676
  }
@@ -1700,8 +1701,10 @@ function renderTable(columns, rows) {
1700
1701
  if (isHeader) s = bold(strip(s));
1701
1702
  if (col?.align === "right") s = padStart(s, w);
1702
1703
  else if (col?.align === "center") {
1703
- const p = Math.floor((w - visLen(s)) / 2);
1704
- s = " ".repeat(p) + s + " ".repeat(w - visLen(s) - p);
1704
+ const visibleLen = visLen(s);
1705
+ const p = Math.max(0, Math.floor((w - visibleLen) / 2));
1706
+ const q = Math.max(0, w - visibleLen - p);
1707
+ s = " ".repeat(p) + s + " ".repeat(q);
1705
1708
  } else {
1706
1709
  s = padEnd(s, w);
1707
1710
  }
@@ -1722,7 +1725,8 @@ function renderTable(columns, rows) {
1722
1725
  function computeCliInsights(report) {
1723
1726
  const { details } = report;
1724
1727
  const totalUniqueKeys = new Set(details.detectedTexts.map((d) => d.suggestedKey)).size;
1725
- const coveragePct = totalUniqueKeys > 0 ? Math.round((totalUniqueKeys - details.missingKeys.length) / totalUniqueKeys * 100) : 100;
1728
+ const missingUniqueKeys = new Set(details.missingKeys.map((mk) => mk.key)).size;
1729
+ const coveragePct = totalUniqueKeys > 0 ? Math.max(0, Math.min(100, Math.round((totalUniqueKeys - missingUniqueKeys) / totalUniqueKeys * 100))) : 100;
1726
1730
  const textMap = /* @__PURE__ */ new Map();
1727
1731
  for (const dt of details.detectedTexts) {
1728
1732
  const t = dt.text.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-localize-reporting",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "Localization scan reporting: JSON, HTML analytics dashboard and CLI summary",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -33,7 +33,7 @@
33
33
  "node": ">=18.0.0"
34
34
  },
35
35
  "dependencies": {
36
- "ai-localize-shared": "2.0.4"
36
+ "ai-localize-shared": "2.0.6"
37
37
  },
38
38
  "devDependencies": {
39
39
  "tsup": "^8.0.1",