i18n-smell-detector 0.5.2 → 0.6.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/README.md +40 -1
- package/package.json +1 -1
- package/src/cli.js +51 -4
package/README.md
CHANGED
|
@@ -27,11 +27,50 @@ Current scope: text classification is rule-based and conservative, so projects w
|
|
|
27
27
|
npm install -D i18n-smell-detector
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
Installing the package only adds the CLI. Generate a starter config in your project root:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx i18n-smell-detector init
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This creates `i18n-smell.config.mjs`:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
export default {
|
|
42
|
+
baseLocale: 'en',
|
|
43
|
+
locales: {
|
|
44
|
+
en: './src/locales/en.json',
|
|
45
|
+
zh: './src/locales/zh.json'
|
|
46
|
+
},
|
|
47
|
+
source: [
|
|
48
|
+
'src/**/*.{vue,js,jsx,ts,tsx}'
|
|
49
|
+
]
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Adjust `locales` and `source` to match your project, then run:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx i18n-smell-detector check --config i18n-smell.config.mjs --fail-on none
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If your config file lives somewhere else, pass that path:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx i18n-smell-detector init --config ./config/i18n-smell.config.mjs
|
|
63
|
+
npx i18n-smell-detector check --config ./config/i18n-smell.config.mjs
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`init` refuses to overwrite an existing config. Use `--force` only when you intentionally want to replace it. Locale and source paths inside the config are resolved relative to the config file.
|
|
67
|
+
|
|
30
68
|
## Usage
|
|
31
69
|
|
|
32
70
|
Run the check:
|
|
33
71
|
|
|
34
72
|
```bash
|
|
73
|
+
npx i18n-smell-detector init
|
|
35
74
|
npx i18n-smell-detector check --config i18n-smell.config.mjs
|
|
36
75
|
npx i18n-smell-detector check-identical --config i18n-smell.config.mjs
|
|
37
76
|
npx i18n-smell-detector check-hardcoded --config i18n-smell.config.mjs
|
|
@@ -117,7 +156,7 @@ jobs:
|
|
|
117
156
|
|
|
118
157
|
## Configuration
|
|
119
158
|
|
|
120
|
-
Create `i18n-smell.config.mjs
|
|
159
|
+
Create `i18n-smell.config.mjs` and adjust `locales` and `source` to match your project:
|
|
121
160
|
|
|
122
161
|
```js
|
|
123
162
|
export default {
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { access, mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Command, InvalidArgumentError } from 'commander';
|
|
4
4
|
import { applyBaseline, loadBaseline, writeBaseline } from './baseline.js';
|
|
@@ -17,6 +17,7 @@ Usage:
|
|
|
17
17
|
i18n-smell-detector check-hardcoded [options]
|
|
18
18
|
i18n-smell-detector check-placeholders [options]
|
|
19
19
|
i18n-smell-detector check [options]
|
|
20
|
+
i18n-smell-detector init [options]
|
|
20
21
|
|
|
21
22
|
Options:
|
|
22
23
|
-c, --config <path> Config file path
|
|
@@ -26,12 +27,29 @@ Options:
|
|
|
26
27
|
--baseline <path> Read a baseline file
|
|
27
28
|
--update-baseline Write the current issues to the baseline file
|
|
28
29
|
--include-ignored Print ignored matches as well
|
|
30
|
+
--force Overwrite an existing config file with init
|
|
29
31
|
-h, --help Show help
|
|
30
32
|
|
|
31
33
|
Example:
|
|
32
34
|
i18n-smell-detector check-identical -c i18n-smell.config.mjs --format markdown
|
|
33
35
|
i18n-smell-detector check-hardcoded -c i18n-smell.config.mjs --format json
|
|
34
36
|
i18n-smell-detector check-placeholders -c i18n-smell.config.mjs --format json
|
|
37
|
+
i18n-smell-detector init
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const DEFAULT_CONFIG_PATH = 'i18n-smell.config.mjs';
|
|
41
|
+
|
|
42
|
+
const CONFIG_TEMPLATE = `export default {
|
|
43
|
+
baseLocale: 'en',
|
|
44
|
+
locales: {
|
|
45
|
+
en: './src/locales/en.json',
|
|
46
|
+
zh: './src/locales/zh.json'
|
|
47
|
+
},
|
|
48
|
+
source: [
|
|
49
|
+
'src/**/*.{vue,js,jsx,ts,tsx}'
|
|
50
|
+
],
|
|
51
|
+
failOn: 'none'
|
|
52
|
+
};
|
|
35
53
|
`;
|
|
36
54
|
|
|
37
55
|
function parseArgs(argv) {
|
|
@@ -39,7 +57,7 @@ function parseArgs(argv) {
|
|
|
39
57
|
.name('i18n-smell-detector')
|
|
40
58
|
.usage('[command] [options]')
|
|
41
59
|
.description('Find localization issues that key coverage checks miss.')
|
|
42
|
-
.argument('[command]', 'check-identical | check-hardcoded | check-placeholders | check', 'check-identical')
|
|
60
|
+
.argument('[command]', 'init | check-identical | check-hardcoded | check-placeholders | check', 'check-identical')
|
|
43
61
|
.option('-c, --config <path>', 'Config file path')
|
|
44
62
|
.option('--format <format>', 'console | json | markdown | sarif', readFormat)
|
|
45
63
|
.option('--fail-on <level>', 'high | medium | low | none', readFailOn)
|
|
@@ -47,12 +65,14 @@ function parseArgs(argv) {
|
|
|
47
65
|
.option('--baseline <path>', 'Read a baseline file')
|
|
48
66
|
.option('--update-baseline', 'Write the current issues to the baseline file', false)
|
|
49
67
|
.option('--include-ignored', 'Print ignored matches as well', false)
|
|
68
|
+
.option('--force', 'Overwrite an existing config file with init', false)
|
|
50
69
|
.addHelpText('after', `
|
|
51
70
|
|
|
52
71
|
Example:
|
|
53
72
|
i18n-smell-detector check-identical -c i18n-smell.config.mjs --format markdown
|
|
54
73
|
i18n-smell-detector check-hardcoded -c i18n-smell.config.mjs --format json
|
|
55
|
-
i18n-smell-detector check-placeholders -c i18n-smell.config.mjs --format json
|
|
74
|
+
i18n-smell-detector check-placeholders -c i18n-smell.config.mjs --format json
|
|
75
|
+
i18n-smell-detector init`)
|
|
56
76
|
.exitOverride()
|
|
57
77
|
.configureOutput({
|
|
58
78
|
writeOut: () => {},
|
|
@@ -68,7 +88,7 @@ Example:
|
|
|
68
88
|
}
|
|
69
89
|
|
|
70
90
|
const [command] = program.args;
|
|
71
|
-
if (!['check-identical', 'check-hardcoded', 'check-placeholders', 'check'].includes(command)) {
|
|
91
|
+
if (!['init', 'check-identical', 'check-hardcoded', 'check-placeholders', 'check'].includes(command)) {
|
|
72
92
|
throw appError(`Unknown command: ${command}\n${HELP}`, 'CLI_USAGE');
|
|
73
93
|
}
|
|
74
94
|
|
|
@@ -81,6 +101,7 @@ Example:
|
|
|
81
101
|
baseline: program.opts().baseline,
|
|
82
102
|
updateBaseline: program.opts().updateBaseline,
|
|
83
103
|
includeIgnored: program.opts().includeIgnored,
|
|
104
|
+
force: program.opts().force,
|
|
84
105
|
help: false,
|
|
85
106
|
};
|
|
86
107
|
}
|
|
@@ -116,6 +137,11 @@ export async function runCli(argv) {
|
|
|
116
137
|
return;
|
|
117
138
|
}
|
|
118
139
|
|
|
140
|
+
if (options.command === 'init') {
|
|
141
|
+
await writeDefaultConfig(options.configPath || DEFAULT_CONFIG_PATH, process.cwd(), options.force);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
119
145
|
const configPath = await resolveConfigPath(options.configPath, process.cwd());
|
|
120
146
|
const config = await loadConfig(configPath);
|
|
121
147
|
const effectiveConfig = {
|
|
@@ -201,6 +227,27 @@ async function writeOutput(filePath, report) {
|
|
|
201
227
|
}
|
|
202
228
|
}
|
|
203
229
|
|
|
230
|
+
async function writeDefaultConfig(configPath, cwd, force) {
|
|
231
|
+
const target = path.isAbsolute(configPath) ? configPath : path.resolve(cwd, configPath);
|
|
232
|
+
|
|
233
|
+
if (!force && await exists(target)) {
|
|
234
|
+
throw appError(`Config file already exists: ${target}\nUse --force to overwrite it.`, 'CONFIG_EXISTS');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
238
|
+
await writeFile(target, CONFIG_TEMPLATE);
|
|
239
|
+
console.log(`Created ${path.relative(cwd, target) || target}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function exists(filePath) {
|
|
243
|
+
try {
|
|
244
|
+
await access(filePath);
|
|
245
|
+
return true;
|
|
246
|
+
} catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
204
251
|
function renderWriteSummary(results, output) {
|
|
205
252
|
const summary = summarizeResults(results);
|
|
206
253
|
const counts = Object.entries(summary)
|