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.
Files changed (3) hide show
  1. package/README.md +40 -1
  2. package/package.json +1 -1
  3. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18n-smell-detector",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Find copied locale values, placeholder mismatches, and hardcoded user-visible strings.",
5
5
  "type": "module",
6
6
  "bin": {
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)