i18n-smell-detector 0.6.1 → 0.7.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 (112) hide show
  1. package/README.md +58 -515
  2. package/bin/i18n-smell-detector.js +6 -1
  3. package/dist/baseline.d.ts +4 -0
  4. package/dist/baseline.js +68 -0
  5. package/dist/baseline.js.map +1 -0
  6. package/dist/check-hardcoded.d.ts +1 -0
  7. package/dist/check-hardcoded.js +39 -0
  8. package/dist/check-hardcoded.js.map +1 -0
  9. package/dist/check-identical.d.ts +11 -0
  10. package/dist/check-identical.js +49 -0
  11. package/dist/check-identical.js.map +1 -0
  12. package/dist/check-placeholders.d.ts +13 -0
  13. package/dist/check-placeholders.js +52 -0
  14. package/dist/check-placeholders.js.map +1 -0
  15. package/dist/cli.d.ts +1 -0
  16. package/dist/cli.js +294 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config.d.ts +2 -0
  19. package/dist/config.js +237 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/errors.d.ts +5 -0
  22. package/dist/errors.js +12 -0
  23. package/dist/errors.js.map +1 -0
  24. package/dist/index.d.ts +17 -0
  25. package/dist/index.js +17 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/locale/flatten-locale.d.ts +6 -0
  28. package/dist/locale/flatten-locale.js +26 -0
  29. package/dist/locale/flatten-locale.js.map +1 -0
  30. package/dist/locale/load-locales.d.ts +6 -0
  31. package/dist/locale/load-locales.js +44 -0
  32. package/dist/locale/load-locales.js.map +1 -0
  33. package/dist/reporters/combined.d.ts +5 -0
  34. package/dist/reporters/combined.js +69 -0
  35. package/dist/reporters/combined.js.map +1 -0
  36. package/dist/reporters/console.d.ts +1 -0
  37. package/dist/reporters/console.js +55 -0
  38. package/dist/reporters/console.js.map +1 -0
  39. package/dist/reporters/html.d.ts +1 -0
  40. package/dist/reporters/html.js +84 -0
  41. package/dist/reporters/html.js.map +1 -0
  42. package/dist/reporters/index.d.ts +1 -0
  43. package/dist/reporters/index.js +32 -0
  44. package/dist/reporters/index.js.map +1 -0
  45. package/dist/reporters/json.d.ts +1 -0
  46. package/dist/reporters/json.js +6 -0
  47. package/dist/reporters/json.js.map +1 -0
  48. package/dist/reporters/markdown.d.ts +1 -0
  49. package/dist/reporters/markdown.js +54 -0
  50. package/dist/reporters/markdown.js.map +1 -0
  51. package/dist/reporters/sarif.d.ts +1 -0
  52. package/dist/reporters/sarif.js +107 -0
  53. package/dist/reporters/sarif.js.map +1 -0
  54. package/dist/reporters/summary.d.ts +1 -0
  55. package/dist/reporters/summary.js +7 -0
  56. package/dist/reporters/summary.js.map +1 -0
  57. package/dist/rules/classify-hardcoded.d.ts +7 -0
  58. package/dist/rules/classify-hardcoded.js +49 -0
  59. package/dist/rules/classify-hardcoded.js.map +1 -0
  60. package/dist/rules/classify-identical.d.ts +24 -0
  61. package/dist/rules/classify-identical.js +74 -0
  62. package/dist/rules/classify-identical.js.map +1 -0
  63. package/dist/rules/match-pattern.d.ts +1 -0
  64. package/{src → dist}/rules/match-pattern.js +2 -2
  65. package/dist/rules/match-pattern.js.map +1 -0
  66. package/dist/rules/match-rule.d.ts +6 -0
  67. package/dist/rules/match-rule.js +21 -0
  68. package/dist/rules/match-rule.js.map +1 -0
  69. package/dist/run-checks.d.ts +2 -0
  70. package/dist/run-checks.js +43 -0
  71. package/dist/run-checks.js.map +1 -0
  72. package/dist/severity.d.ts +7 -0
  73. package/dist/severity.js +10 -0
  74. package/dist/severity.js.map +1 -0
  75. package/dist/source/scan-js-source.d.ts +6 -0
  76. package/dist/source/scan-js-source.js +166 -0
  77. package/dist/source/scan-js-source.js.map +1 -0
  78. package/dist/source/scan-vue-sfc.d.ts +1 -0
  79. package/dist/source/scan-vue-sfc.js +35 -0
  80. package/dist/source/scan-vue-sfc.js.map +1 -0
  81. package/dist/source/scan-vue-template.d.ts +5 -0
  82. package/dist/source/scan-vue-template.js +101 -0
  83. package/dist/source/scan-vue-template.js.map +1 -0
  84. package/dist/types.d.ts +90 -0
  85. package/dist/types.js +2 -0
  86. package/dist/types.js.map +1 -0
  87. package/package.json +26 -9
  88. package/src/baseline.js +0 -73
  89. package/src/check-hardcoded.js +0 -45
  90. package/src/check-identical.js +0 -53
  91. package/src/check-placeholders.js +0 -60
  92. package/src/cli.js +0 -321
  93. package/src/config.js +0 -262
  94. package/src/errors.js +0 -11
  95. package/src/locale/flatten-locale.js +0 -28
  96. package/src/locale/load-locales.js +0 -47
  97. package/src/reporters/combined.js +0 -68
  98. package/src/reporters/console.js +0 -56
  99. package/src/reporters/index.js +0 -19
  100. package/src/reporters/json.js +0 -6
  101. package/src/reporters/markdown.js +0 -61
  102. package/src/reporters/sarif.js +0 -105
  103. package/src/reporters/summary.js +0 -9
  104. package/src/rules/classify-hardcoded.js +0 -54
  105. package/src/rules/classify-identical.js +0 -85
  106. package/src/rules/match-rule.js +0 -21
  107. package/src/run-checks.js +0 -45
  108. package/src/severity.js +0 -10
  109. package/src/source/scan-js-source.js +0 -163
  110. package/src/source/scan-vue-sfc.js +0 -40
  111. package/src/source/scan-vue-template.js +0 -105
  112. package/src/types.js +0 -52
package/README.md CHANGED
@@ -7,19 +7,18 @@
7
7
 
8
8
  Find localization issues that key coverage checks miss.
9
9
 
10
- `i18n-smell-detector` checks localization files for copied base-locale values and placeholder mismatches. It can also scan Vue, JavaScript, TypeScript, JSX, and TSX source for hardcoded user-visible strings.
10
+ `i18n-smell-detector` checks locale files for copied base-locale values and placeholder mismatches. It can also scan Vue, JavaScript, TypeScript, JSX, and TSX source for hardcoded user-visible strings.
11
11
 
12
- Current scope: text classification is rule-based and conservative, so projects with many proper nouns, product codes, or non-English copy should start with `--fail-on none` and tune allowlists before enforcing CI.
12
+ The classifier is deterministic and conservative. Start with `--fail-on none`, review the report, then tune allowlists before enforcing CI.
13
13
 
14
14
  ## Features
15
15
 
16
- - Detect values that are identical to the base locale
17
- - Detect missing or extra placeholders across locale values
18
- - Detect hardcoded strings in Vue templates, JSX/TSX text and attributes, and configured JS/TS function calls
19
- - Ignore expected matches by key or value
20
- - Classify findings as `high`, `medium`, `low`, or `ignored`
21
- - Output results as console text, JSON, Markdown, or SARIF
22
- - Fail CI based on a configurable severity level
16
+ - Detect copied base-locale values.
17
+ - Detect missing or extra placeholders.
18
+ - Detect hardcoded user-visible strings in Vue, JS, TS, JSX, and TSX.
19
+ - Output console, JSON, Markdown, SARIF, or HTML reports.
20
+ - Support baselines for gradual adoption.
21
+ - Publish TypeScript types and a small public API for integrations.
23
22
 
24
23
  ## Install
25
24
 
@@ -29,13 +28,14 @@ npm install -D i18n-smell-detector
29
28
 
30
29
  ## Quick Start
31
30
 
32
- Installing the package only adds the CLI. Generate a starter config in your project root:
33
-
34
31
  ```bash
35
32
  npx i18n-smell-detector init
33
+ npx i18n-smell-detector check --config i18n-smell.config.mjs --fail-on none
36
34
  ```
37
35
 
38
- This creates `i18n-smell.config.mjs`. A typical config looks like this:
36
+ `init` creates `i18n-smell.config.mjs`. When it finds locale JSON files in `src/locales`, `src/i18n`, `locales`, or `i18n`, it writes them into the config automatically.
37
+
38
+ A typical config:
39
39
 
40
40
  ```js
41
41
  export default {
@@ -43,551 +43,94 @@ export default {
43
43
  locales: {
44
44
  en: './src/locales/en.json',
45
45
  zh: './src/locales/zh.json',
46
- 'zh-Hans': './src/locales/zh-Hans.json'
46
+ 'zh-Hans': './src/locales/zh-Hans.json',
47
47
  },
48
- source: [
49
- 'src/**/*.{vue,js,jsx,ts,tsx}'
50
- ],
51
- failOn: 'none'
48
+ source: ['src/**/*.{vue,js,jsx,ts,tsx}'],
49
+ failOn: 'none',
52
50
  };
53
51
  ```
54
52
 
55
- When possible, `init` discovers locale JSON files in common project directories and writes them into `locales` automatically. It checks the first matching directory from this list:
56
-
57
- - `src/locales`
58
- - `src/i18n`
59
- - `locales`
60
- - `i18n`
61
-
62
- For example, if `src/locales/en.json` and `src/locales/ja.json` exist, `init` writes both entries and uses `en` as `baseLocale`. If no locale JSON files are found in those directories, it falls back to a starter `en` and `zh` config. Locale keys that contain `-`, such as `'zh-Hans'`, must be quoted as shown above.
63
-
64
- Adjust `locales` and `source` if needed, then run:
65
-
66
- ```bash
67
- npx i18n-smell-detector check --config i18n-smell.config.mjs --fail-on none
68
- ```
69
-
70
- If your config file lives somewhere else, pass that path:
71
-
72
- ```bash
73
- npx i18n-smell-detector init --config ./config/i18n-smell.config.mjs
74
- npx i18n-smell-detector check --config ./config/i18n-smell.config.mjs
75
- ```
76
-
77
- `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. Locale discovery is intentionally conservative: it only checks direct `.json` files in the project directories listed above, and it does not scan unrelated JSON files elsewhere.
53
+ Locale keys that contain `-`, such as `'zh-Hans'`, must be quoted.
78
54
 
79
- ## Usage
80
-
81
- Run the check:
55
+ ## Commands
82
56
 
83
57
  ```bash
84
- npx i18n-smell-detector init
85
58
  npx i18n-smell-detector check --config i18n-smell.config.mjs
86
59
  npx i18n-smell-detector check-identical --config i18n-smell.config.mjs
87
60
  npx i18n-smell-detector check-hardcoded --config i18n-smell.config.mjs
88
61
  npx i18n-smell-detector check-placeholders --config i18n-smell.config.mjs
89
62
  ```
90
63
 
91
- Output formats:
92
-
93
- ```bash
94
- npx i18n-smell-detector check-identical --format console
95
- npx i18n-smell-detector check-identical --format json
96
- npx i18n-smell-detector check-identical --format markdown
97
- npx i18n-smell-detector check-hardcoded --format json
98
- npx i18n-smell-detector check --format markdown --output i18n-report.md
99
- npx i18n-smell-detector check --format sarif --output i18n-smell.sarif
100
- ```
101
-
102
- CLI options:
103
-
104
- | Option | Description |
105
- |---|---|
106
- | `-c, --config <path>` | Use a specific config file. |
107
- | `--format <format>` | Print `console`, `json`, `markdown`, or `sarif` output. |
108
- | `--fail-on <level>` | Exit with code `1` for findings at `high`, `medium`, or `low`. Use `none` while tuning. |
109
- | `--include-ignored` | Include ignored findings in the report. |
110
- | `--output <path>` | Write the full report to a file and print a short summary. |
111
- | `--baseline <path>` | Suppress findings stored in a baseline file. |
112
- | `--update-baseline` | Write the current findings to the baseline file. |
113
- | `-h, --help` | Show CLI help. |
114
-
115
- ## CI usage
116
-
117
- Use `--fail-on` to choose the lowest severity that should make the command exit with code `1`.
118
-
119
- ```bash
120
- npx i18n-smell-detector check --config i18n-smell.config.mjs --fail-on high
121
- ```
122
-
123
- For a new project, it is often easier to start in reporting mode while tuning allowlists:
124
-
125
- ```bash
126
- npx i18n-smell-detector check --config i18n-smell.config.mjs --fail-on none
127
- ```
128
-
129
- ### CI report artifact
130
-
131
- Add a workflow such as `.github/workflows/i18n-smell.yml`:
132
-
133
- ```yaml
134
- name: i18n smell check
135
-
136
- on:
137
- pull_request:
138
- push:
139
- branches: [main]
140
-
141
- jobs:
142
- i18n-smell:
143
- runs-on: ubuntu-latest
144
-
145
- steps:
146
- - uses: actions/checkout@v6
147
-
148
- - uses: actions/setup-node@v6
149
- with:
150
- node-version: 22
151
-
152
- - run: npm ci
153
-
154
- - name: Run i18n checks
155
- run: npx i18n-smell-detector check --config i18n-smell.config.mjs --fail-on high
156
-
157
- - name: Write Markdown report
158
- if: always()
159
- run: npx i18n-smell-detector check --config i18n-smell.config.mjs --format markdown --output reports/i18n-smell.md --fail-on none
160
-
161
- - uses: actions/upload-artifact@v4
162
- if: always()
163
- with:
164
- name: i18n-smell-report
165
- path: reports/i18n-smell.md
166
- ```
167
-
168
- ## Configuration
169
-
170
- Create `i18n-smell.config.mjs` and adjust `locales` and `source` to match your project:
171
-
172
- ```js
173
- export default {
174
- baseLocale: 'en',
175
- checks: {
176
- identical: true,
177
- hardcoded: true,
178
- placeholders: true
179
- },
180
- source: [
181
- 'src/**/*.{vue,js,jsx,ts,tsx}'
182
- ],
183
-
184
- locales: {
185
- en: './src/locales/en.json',
186
- zh: './src/locales/zh.json',
187
- 'zh-Hans': './src/locales/zh-Hans.json',
188
- ja: './src/locales/ja.json'
189
- },
190
-
191
- allowIdenticalKeys: [
192
- 'brand.*',
193
- /^protocol\./,
194
- 'common.ok',
195
- 'common.id'
196
- ],
197
-
198
- allowIdenticalValues: [
199
- 'OK',
200
- 'ID',
201
- /^HTTP\/\d(\.\d)?$/,
202
- 'API',
203
- 'GET',
204
- 'POST'
205
- ],
206
-
207
- placeholderPatterns: [
208
- '\\{\\{[^}]+\\}\\}',
209
- '(?<!\\{)\\{[^{}]+\\}(?!\\})',
210
- '%[sdif]',
211
- '%\\([^)]+\\)[sdif]',
212
- '\\$\\d+'
213
- ],
214
-
215
- ignoreCodeLike: true,
216
- hardcoded: {
217
- vueAttributes: [
218
- 'placeholder',
219
- 'title',
220
- 'alt',
221
- 'aria-label',
222
- 'aria-description'
223
- ],
224
- jsxAttributes: [
225
- 'placeholder',
226
- 'title',
227
- 'alt',
228
- 'aria-label',
229
- 'aria-description'
230
- ],
231
- functions: [
232
- 'toast.success',
233
- 'toast.error',
234
- 'alert',
235
- 'confirm'
236
- ],
237
- ignoreFiles: [
238
- 'src/generated/**'
239
- ],
240
- ignoreValues: [
241
- 'OK',
242
- 'ID'
243
- ],
244
- ignorePatterns: [
245
- '^v\\d+$'
246
- ]
247
- },
248
- ignoreSameLanguageFamily: true,
249
- trimWhitespace: true,
250
- ignoreCase: false,
251
- includeIgnored: false,
252
- format: 'console',
253
- baseline: '.i18n-smell-baseline.json',
254
- failOn: 'high'
255
- };
256
- ```
257
-
258
- ### Configuration reference
259
-
260
- | Option | Type | Default | Description |
261
- |---|---|---:|---|
262
- | `baseLocale` | `string` | `'en'` | Locale used as the comparison source. It must also be listed in `locales`. |
263
- | `checks` | `{ identical?: boolean, hardcoded?: boolean, placeholders?: boolean }` | all enabled | Checks run by the combined `check` command. Existing single-check commands ignore this setting. |
264
- | `source` | `string[]` | `['src/**/*.{vue,js,jsx,ts,tsx}']` | Source globs used by `check-hardcoded`. `node_modules` and `hardcoded.ignoreFiles` are ignored. |
265
- | `locales` | `Record<string, string>` | `{}` | Map of locale codes to JSON locale file paths. Relative paths are resolved from the config file directory. |
266
- | `allowIdenticalKeys` | `(string \| RegExp)[]` | `[]` | Key rules that are allowed to match the base locale. String rules support `*` wildcards, for example `brand.*`. `RegExp` rules are tested against the flattened key. |
267
- | `allowIdenticalValues` | `(string \| RegExp)[]` | `[]` | Value rules that are allowed to match the base locale. String rules are exact matches, for example `OK`, `API`, or `GET`. `RegExp` rules are tested against the full value. |
268
- | `placeholderPatterns` | `(string \| RegExp)[]` | common brace, printf, named printf, and `$1` patterns | Placeholder patterns used by `check-identical` placeholder-only ignores and `check-placeholders` mismatch detection. String entries must be valid regular expression sources. |
269
- | `ignoreCodeLike` | `boolean` | `true` | Ignore built-in code-like values such as paths, colors, acronyms, and identifiers. Set to `false` to report them. |
270
- | `hardcoded.vueAttributes` | `string[]` | `placeholder`, `title`, `alt`, `aria-label`, `aria-description` | Static Vue attributes checked by `check-hardcoded`. Dynamic bindings such as `:placeholder` are ignored. |
271
- | `hardcoded.jsxAttributes` | `string[]` | `placeholder`, `title`, `alt`, `aria-label`, `aria-description` | Static JSX/TSX attributes checked by `check-hardcoded`. Expression attributes are ignored unless they contain a static string literal. |
272
- | `hardcoded.functions` | `string[]` | `alert`, `confirm`, `toast.success`, `toast.error` | JS/TS call expressions whose static string arguments are checked by `check-hardcoded`. |
273
- | `hardcoded.ignoreFiles` | `string[]` | `[]` | Glob patterns excluded from `check-hardcoded`, useful for generated files or vendored code. |
274
- | `hardcoded.ignoreValues` | `(string \| RegExp)[]` | `[]` | Exact values or regular expressions ignored by `check-hardcoded`. |
275
- | `hardcoded.ignorePatterns` | `(string \| RegExp)[]` | `[]` | Regular expression patterns ignored by `check-hardcoded`. String entries are compiled as regular expressions. |
276
- | `failOn` | `'high' \| 'medium' \| 'low' \| 'none'` | `'high'` | Lowest severity that makes the CLI exit with code `1`. Use `none` to report without failing. |
277
- | `trimWhitespace` | `boolean` | `true` | Trim leading and trailing whitespace before comparing values. |
278
- | `ignoreCase` | `boolean` | `false` | Compare values case-insensitively when set to `true`. |
279
- | `ignoreSameLanguageFamily` | `boolean` | `true` | Ignore regional variants that share the same language family, such as `en` and `en-GB`. Set to `false` to check regional variants too. |
280
- | `includeIgnored` | `boolean` | `false` | Include ignored findings in reports. Can also be enabled with `--include-ignored`. |
281
- | `format` | `'console' \| 'json' \| 'markdown' \| 'sarif'` | `'console'` | Default report format. CLI `--format` takes precedence. |
282
- | `output` | `string` | unset | Report file path. CLI `--output` takes precedence. |
283
- | `baseline` | `string` | unset | Baseline file path used by `--baseline` or `--update-baseline`. |
284
-
285
- ## Example output
286
-
287
- ```txt
288
- i18n-smell-detector: identical translations
289
- high=2 medium=1 low=0 ignored=16
290
-
291
- HIGH zh.home.welcome
292
- value: "Welcome back"
293
- reason: copied English phrase
294
-
295
- HIGH zh.settings.profile
296
- value: "Profile settings"
297
- reason: copied English phrase
298
-
299
- MEDIUM ja.form.search
300
- value: "Search"
301
- reason: copied English word
302
- ```
303
-
304
- ## JSON output
305
-
306
- `--format json` prints an object with a `summary` and an `issues` array:
307
-
308
- ```json
309
- {
310
- "summary": {
311
- "high": 1,
312
- "medium": 0,
313
- "low": 0,
314
- "ignored": 16
315
- },
316
- "issues": [
317
- {
318
- "key": "home.welcome",
319
- "baseLocale": "en",
320
- "targetLocale": "zh",
321
- "value": "Welcome back",
322
- "severity": "high",
323
- "reason": "copied English phrase"
324
- }
325
- ]
326
- }
327
- ```
328
-
329
- Fields:
330
-
331
- Summary counts include all findings, including ignored findings that may be omitted from the visible `issues` array.
332
-
333
- | Field | Type | Description |
334
- |---|---|---|
335
- | `key` | `string` | Flattened locale key, such as `settings.profile.title`. |
336
- | `baseLocale` | `string` | Locale being compared from. |
337
- | `targetLocale` | `string` | Locale being checked. |
338
- | `value` | `string` | Identical target value. |
339
- | `severity` | `'high' \| 'medium' \| 'low' \| 'ignored'` | Classification assigned by the detection rules. |
340
- | `reason` | `string` | Short explanation for the classification. |
341
- | `id` | `string` | Stable issue identifier used by JSON, SARIF, and baselines. |
342
-
343
- Ignored issues are omitted by default. Pass `--include-ignored` to include them in JSON, console, or Markdown output.
344
-
345
- ## Placeholder output
346
-
347
- `check-placeholders` reports target locale values whose placeholder set differs from the base locale:
348
-
349
- ```txt
350
- i18n-smell-detector: placeholder mismatches
351
- high=1 medium=0 low=0 ignored=0
352
-
353
- HIGH zh.user.greeting
354
- value: "你好"
355
- missing: {name}
356
- reason: missing placeholder
357
- ```
358
-
359
- JSON output includes `missing` and `extra` arrays:
360
-
361
- ```json
362
- {
363
- "summary": {
364
- "high": 1,
365
- "medium": 0,
366
- "low": 0,
367
- "ignored": 0
368
- },
369
- "issues": [
370
- {
371
- "key": "user.greeting",
372
- "baseLocale": "en",
373
- "targetLocale": "zh",
374
- "value": "你好",
375
- "missing": ["{name}"],
376
- "extra": [],
377
- "severity": "high",
378
- "reason": "missing placeholder",
379
- "id": "placeholders:zh:user.greeting"
380
- }
381
- ]
382
- }
383
- ```
384
-
385
- ## Hardcoded string output
386
-
387
- `check-hardcoded` reports Vue template text, static Vue attributes, JSX/TSX text and attributes, and configured JS/TS function call strings:
388
-
389
- ```txt
390
- i18n-smell-detector: hardcoded strings
391
- high=1 medium=1 low=0 ignored=4
392
-
393
- HIGH src/components/UserPanel.vue:12:15
394
- value: "Profile settings"
395
- reason: static template text
396
-
397
- MEDIUM src/components/SearchInput.vue:8:23
398
- value: "Search"
399
- reason: static placeholder attribute
400
- ```
401
-
402
- JSON output includes source locations:
403
-
404
- ```json
405
- {
406
- "summary": {
407
- "high": 1,
408
- "medium": 1,
409
- "low": 0,
410
- "ignored": 4
411
- },
412
- "issues": [
413
- {
414
- "file": "src/components/UserPanel.vue",
415
- "line": 12,
416
- "column": 15,
417
- "value": "Profile settings",
418
- "severity": "high",
419
- "reason": "static template text",
420
- "kind": "vue-text",
421
- "id": "hardcoded:src/components/UserPanel.vue:12:15:Profile settings"
422
- }
423
- ]
424
- }
425
- ```
64
+ Common options:
426
65
 
427
- ## Running all checks
66
+ | Option | Description |
67
+ | --------------------- | -------------------------------------------------- |
68
+ | `-c, --config <path>` | Use a specific config file. |
69
+ | `--format <format>` | `console`, `json`, `markdown`, `sarif`, or `html`. |
70
+ | `--output <path>` | Write the full report to a file. |
71
+ | `--fail-on <level>` | `high`, `medium`, `low`, or `none`. |
72
+ | `--baseline <path>` | Suppress findings stored in a baseline file. |
73
+ | `--update-baseline` | Write current findings to the baseline file. |
74
+ | `--include-ignored` | Include ignored findings in the report. |
75
+ | `--debug` | Print stack traces for troubleshooting. |
428
76
 
429
- Use `check` to run every enabled check in one command:
77
+ ## Reports
430
78
 
431
79
  ```bash
432
- npx i18n-smell-detector check --config i18n-smell.config.mjs
433
- ```
434
-
435
- Disable checks during gradual rollout:
436
-
437
- ```js
438
- export default {
439
- checks: {
440
- identical: true,
441
- hardcoded: false,
442
- placeholders: true
443
- }
444
- };
80
+ npx i18n-smell-detector check --format markdown --output reports/i18n-smell.md
81
+ npx i18n-smell-detector check --format html --output reports/i18n-smell.html
82
+ npx i18n-smell-detector check --format sarif --output reports/i18n-smell.sarif
445
83
  ```
446
84
 
447
- Combined JSON output groups counts by check:
448
-
449
- ```json
450
- {
451
- "summary": {
452
- "identical": {
453
- "high": 2,
454
- "medium": 1,
455
- "low": 0,
456
- "ignored": 16
457
- },
458
- "hardcoded": {
459
- "high": 3,
460
- "medium": 2,
461
- "low": 0,
462
- "ignored": 3
463
- },
464
- "placeholders": {
465
- "high": 1,
466
- "medium": 0,
467
- "low": 0,
468
- "ignored": 0
469
- }
470
- },
471
- "issues": []
472
- }
473
- ```
85
+ Use `--format json` for automation and `--format sarif` for code-scanning tools.
474
86
 
475
- ## Writing reports
87
+ ## CI
476
88
 
477
- Use `--output` to write the full report to a file. The CLI prints a short summary to stdout after writing.
89
+ Start in reporting mode:
478
90
 
479
91
  ```bash
480
- npx i18n-smell-detector check --format markdown --output reports/i18n-report.md
481
- npx i18n-smell-detector check --format json --output reports/i18n-report.json
482
- npx i18n-smell-detector check --format sarif --output reports/i18n-smell.sarif
92
+ npx i18n-smell-detector check --config i18n-smell.config.mjs --fail-on none
483
93
  ```
484
94
 
485
- Markdown output for `check` includes a summary table and one section per check, which makes it suitable for CI artifacts.
486
-
487
- SARIF output uses stable issue ids as `partialFingerprints.stableId`, so it can be uploaded to code scanning tools that understand SARIF 2.1.0.
488
-
489
- ## Gradual adoption
490
-
491
- Use a baseline to ignore existing findings while still failing CI for new ones:
95
+ After tuning allowlists and baselines, enforce a severity threshold:
492
96
 
493
97
  ```bash
494
- npx i18n-smell-detector check --baseline .i18n-smell-baseline.json --update-baseline
495
- npx i18n-smell-detector check --baseline .i18n-smell-baseline.json
98
+ npx i18n-smell-detector check --config i18n-smell.config.mjs --fail-on high
496
99
  ```
497
100
 
498
- The baseline stores stable issue identifiers. Updating it removes findings that no longer exist and records the current visible findings.
499
-
500
- ## Exit codes
501
-
502
- | Code | Meaning |
503
- |---:|---|
504
- | `0` | Completed successfully and no visible issue reached the configured `failOn` threshold. |
505
- | `1` | Completed successfully, but at least one visible issue reached the configured `failOn` threshold. |
506
- | `2` | Runtime or configuration error, such as a missing config file, missing locale file, invalid JSON, or unsupported CLI option. |
101
+ ## TypeScript API
507
102
 
508
- ## Detection rules
103
+ The package exposes types and a small public API from the package root:
509
104
 
510
- | Case | Level |
511
- |---|---|
512
- | Key matches `allowIdenticalKeys` | ignored |
513
- | Value matches `allowIdenticalValues` | ignored |
514
- | Same language family, such as `en` and `en-GB` | ignored |
515
- | Placeholder-only value, such as `{count}` | ignored |
516
- | URL, path, color, or code-like value | ignored by default |
517
- | Short common label | low |
518
- | Single English word | medium |
519
- | English phrase | high |
520
-
521
- For `check-hardcoded`, sentence-like template text and static attributes are `high`, single words are `medium`, short common labels are `low`, and configured or non-user-facing values are `ignored`.
522
-
523
- ## False positive tuning
524
-
525
- Identical text is sometimes intentional. Start by running the detector in reporting mode, then allow known names, protocol terms, short labels, and other strings that should stay the same across languages.
526
-
527
- ```js
528
- export default {
529
- baseLocale: 'en',
530
- locales: {
531
- en: './src/locales/en.json',
532
- zh: './src/locales/zh.json',
533
- 'zh-Hans': './src/locales/zh-Hans.json'
534
- },
535
- allowIdenticalKeys: [
536
- 'brand.*',
537
- 'legal.name',
538
- /^protocol\./
539
- ],
540
- allowIdenticalValues: [
541
- 'OK',
542
- 'ID',
543
- 'API',
544
- /^HTTP\/\d(\.\d)?$/
545
- ],
546
- placeholderPatterns: [
547
- '\\{\\{[^}]+\\}\\}',
548
- '(?<!\\{)\\{[^{}]+\\}(?!\\})',
549
- '%[sdif]',
550
- '%\\([^)]+\\)[sdif]',
551
- '\\$\\d+'
552
- ],
553
- failOn: 'high'
554
- };
105
+ ```ts
106
+ import { checkIdenticalTranslations, severityRank } from 'i18n-smell-detector';
107
+ import type { DetectorConfigInput, DetectorIssue, CheckResult } from 'i18n-smell-detector';
555
108
  ```
556
109
 
557
- Useful tuning patterns:
558
-
559
- - Use `allowIdenticalKeys` for groups of expected identical values, such as names, legal labels, and protocol keys.
560
- - Use string `allowIdenticalKeys` entries for exact or wildcard key matches, and `RegExp` entries for broader key families.
561
- - Use string `allowIdenticalValues` entries for exact shared labels, and `RegExp` entries for structured values such as `HTTP/2`.
562
- - Add `placeholderPatterns` for framework-specific placeholders such as `%s`, `%(name)s`, or `$1`. Mixed content such as `Total: {count}` is still reported.
563
- - Set `ignoreCodeLike: false` if paths, colors, acronyms, or identifiers should be shown as findings.
564
- - Set `ignoreSameLanguageFamily: false` if you want to check regional variants such as `en-GB`, `en-AU`, or `pt-BR`.
565
- - Set `failOn: 'none'` while tuning a new project, then raise it to `high` or `medium` once the allowlist is stable.
566
- - Pass `--include-ignored` when reviewing why a value was ignored.
567
- - Use `source` to choose Vue, JS, TS, JSX, and TSX files for `check-hardcoded`.
568
- - Tune `hardcoded.vueAttributes` if your project uses additional static attributes for user-visible text.
569
- - Tune `hardcoded.jsxAttributes` if your React components use additional static attributes for user-visible text.
570
- - Tune `hardcoded.functions` to check project-specific notification, modal, or validation helpers.
571
- - Use `hardcoded.ignoreFiles` for generated source files.
572
- - Use `hardcoded.ignoreValues` and `hardcoded.ignorePatterns` for intentional literals such as `OK`, version labels, or product codes.
573
-
574
- ## Limitations
575
-
576
- `check-hardcoded` scans configured static strings in Vue, JS, TS, JSX, and TSX. It does not evaluate runtime expressions or follow variables. Classification uses heuristic rules rather than full language detection.
110
+ ## Documentation
577
111
 
578
- ## Notes
112
+ Full documentation lives in the GitHub Wiki:
579
113
 
580
- - This is not a coverage checker. It only reports keys that exist in both files and have identical values.
581
- - The rules are conservative. Tune `allowIdenticalKeys` and `allowIdenticalValues` before making this a required CI step.
582
- - Only string values are checked. Non-string locale values are ignored.
114
+ - [Home](https://github.com/xue-moe/i18n-smell-detector/wiki)
115
+ - [Getting started](https://github.com/xue-moe/i18n-smell-detector/wiki/Getting-Started)
116
+ - [Commands](https://github.com/xue-moe/i18n-smell-detector/wiki/Commands)
117
+ - [Configuration](https://github.com/xue-moe/i18n-smell-detector/wiki/Configuration)
118
+ - [Checks](https://github.com/xue-moe/i18n-smell-detector/wiki/Checks)
119
+ - [Reports and CI](https://github.com/xue-moe/i18n-smell-detector/wiki/Reports-and-CI)
120
+ - [Baseline](https://github.com/xue-moe/i18n-smell-detector/wiki/Baseline)
121
+ - [TypeScript API](https://github.com/xue-moe/i18n-smell-detector/wiki/TypeScript-API)
122
+ - [Integrations](https://github.com/xue-moe/i18n-smell-detector/wiki/Integrations)
123
+ - [Performance](https://github.com/xue-moe/i18n-smell-detector/wiki/Performance)
124
+ - [Troubleshooting](https://github.com/xue-moe/i18n-smell-detector/wiki/Troubleshooting)
583
125
 
584
126
  ## Development
585
127
 
586
128
  ```bash
587
129
  npm install
130
+ npm run typecheck
588
131
  npm test
589
132
  npm run check
590
- npm run example:basic
133
+ npm run benchmark
591
134
  ```
592
135
 
593
136
  ## License
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from '../src/cli.js';
2
+ import { runCli } from '../dist/cli.js';
3
3
 
4
4
  runCli(process.argv).catch((error) => {
5
5
  const message = error instanceof Error ? error.message : String(error);
6
+ const debug = process.argv.includes('--debug') || process.env.I18N_SMELL_DEBUG === '1';
6
7
  console.error(`i18n-smell-detector: ${message}`);
8
+ if (debug && error instanceof Error) {
9
+ if ('code' in error) console.error(`code: ${error.code}`);
10
+ if (error.stack) console.error(error.stack);
11
+ }
7
12
  process.exit(2);
8
13
  });
@@ -0,0 +1,4 @@
1
+ export declare function issueId(check: any, issue: any): string;
2
+ export declare function loadBaseline(filePath: any): Promise<Set<unknown>>;
3
+ export declare function applyBaseline(results: any, ids: any): any;
4
+ export declare function writeBaseline(filePath: any, results: any): Promise<number>;