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