@ecodev/natural 69.0.0 → 69.0.2
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/bin/pre-commit-i18n.mjs +102 -0
- package/fesm2022/ecodev-natural.mjs +240 -240
- package/fesm2022/ecodev-natural.mjs.map +1 -1
- package/package.json +4 -1
- package/types/ecodev-natural.d.ts +2 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
3
|
+
|
|
4
|
+
// This script check for errors in HTML files that may break i18n strings
|
|
5
|
+
|
|
6
|
+
import {extname, join, resolve} from 'node:path';
|
|
7
|
+
import {readdirSync, readFileSync, statSync} from 'node:fs';
|
|
8
|
+
import {parse, serializeOuter} from 'parse5';
|
|
9
|
+
|
|
10
|
+
const targets = process.argv.slice(2);
|
|
11
|
+
if (!targets.length) {
|
|
12
|
+
console.error('Error: Missing arguments for file or directory path(s).');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const errorCount = checkAll(targets);
|
|
17
|
+
if (errorCount) {
|
|
18
|
+
console.error(`${errorCount} i18n errors found !`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function checkAll(targets) {
|
|
23
|
+
let errorCount = 0;
|
|
24
|
+
const files = allFiles(targets);
|
|
25
|
+
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
const html = readFileSync(file, 'utf-8');
|
|
28
|
+
const document = parse(html);
|
|
29
|
+
errorCount += walkAST(document, file);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return errorCount;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function allFiles(targets) {
|
|
36
|
+
const files = new Set();
|
|
37
|
+
|
|
38
|
+
for (const target of targets) {
|
|
39
|
+
const path = resolve(target);
|
|
40
|
+
const targetStat = statSync(path);
|
|
41
|
+
|
|
42
|
+
if (targetStat.isDirectory()) {
|
|
43
|
+
const entries = readdirSync(target, {recursive: true, withFileTypes: true});
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (entry.isFile() && extname(entry.name) === '.html') {
|
|
46
|
+
files.add(resolve(join(entry.parentPath, entry.name)));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
files.add(path);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// unique
|
|
55
|
+
return [...files.values()];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function checkError(textContent) {
|
|
59
|
+
if (/^\s|\s$/.test(textContent)) {
|
|
60
|
+
return 'Must not start or finish with whitespace';
|
|
61
|
+
} else if (/^\{\{[^}]*}}$/.test(textContent)) {
|
|
62
|
+
return 'Must not contain only interpolation';
|
|
63
|
+
}
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function walkAST(node, file) {
|
|
68
|
+
let errorCount = 0;
|
|
69
|
+
if ('tagName' in node && node.tagName) {
|
|
70
|
+
const element = node;
|
|
71
|
+
const hasI18n = element.attrs.some(attr => attr.name === 'i18n');
|
|
72
|
+
|
|
73
|
+
if (hasI18n) {
|
|
74
|
+
let textContent = '';
|
|
75
|
+
for (const child of element.childNodes) {
|
|
76
|
+
if (child.nodeName === '#text') {
|
|
77
|
+
textContent += child.value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const errorMessage = checkError(textContent);
|
|
82
|
+
if (errorMessage) {
|
|
83
|
+
console.log(`🛑 ${errorMessage}:`);
|
|
84
|
+
console.log(file);
|
|
85
|
+
console.log(`${serializeOuter(element)}\n`);
|
|
86
|
+
errorCount++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ('childNodes' in node && node.childNodes) {
|
|
92
|
+
for (const child of node.childNodes) {
|
|
93
|
+
errorCount += walkAST(child, file);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (node.nodeName === 'template') {
|
|
98
|
+
errorCount += walkAST(node.content, file);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return errorCount;
|
|
102
|
+
}
|