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.
- package/README.md +59 -503
- package/bin/i18n-smell-detector.js +6 -1
- package/dist/baseline.d.ts +4 -0
- package/dist/baseline.js +68 -0
- package/dist/baseline.js.map +1 -0
- package/dist/check-hardcoded.d.ts +1 -0
- package/dist/check-hardcoded.js +39 -0
- package/dist/check-hardcoded.js.map +1 -0
- package/dist/check-identical.d.ts +11 -0
- package/dist/check-identical.js +49 -0
- package/dist/check-identical.js.map +1 -0
- package/dist/check-placeholders.d.ts +13 -0
- package/dist/check-placeholders.js +52 -0
- package/dist/check-placeholders.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +294 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +237 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +12 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/locale/flatten-locale.d.ts +6 -0
- package/dist/locale/flatten-locale.js +26 -0
- package/dist/locale/flatten-locale.js.map +1 -0
- package/dist/locale/load-locales.d.ts +6 -0
- package/dist/locale/load-locales.js +44 -0
- package/dist/locale/load-locales.js.map +1 -0
- package/dist/reporters/combined.d.ts +5 -0
- package/dist/reporters/combined.js +69 -0
- package/dist/reporters/combined.js.map +1 -0
- package/dist/reporters/console.d.ts +1 -0
- package/dist/reporters/console.js +55 -0
- package/dist/reporters/console.js.map +1 -0
- package/dist/reporters/html.d.ts +1 -0
- package/dist/reporters/html.js +84 -0
- package/dist/reporters/html.js.map +1 -0
- package/dist/reporters/index.d.ts +1 -0
- package/dist/reporters/index.js +32 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/json.d.ts +1 -0
- package/dist/reporters/json.js +6 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/markdown.d.ts +1 -0
- package/dist/reporters/markdown.js +54 -0
- package/dist/reporters/markdown.js.map +1 -0
- package/dist/reporters/sarif.d.ts +1 -0
- package/dist/reporters/sarif.js +107 -0
- package/dist/reporters/sarif.js.map +1 -0
- package/dist/reporters/summary.d.ts +1 -0
- package/dist/reporters/summary.js +7 -0
- package/dist/reporters/summary.js.map +1 -0
- package/dist/rules/classify-hardcoded.d.ts +7 -0
- package/dist/rules/classify-hardcoded.js +49 -0
- package/dist/rules/classify-hardcoded.js.map +1 -0
- package/dist/rules/classify-identical.d.ts +24 -0
- package/dist/rules/classify-identical.js +74 -0
- package/dist/rules/classify-identical.js.map +1 -0
- package/dist/rules/match-pattern.d.ts +1 -0
- package/{src → dist}/rules/match-pattern.js +2 -2
- package/dist/rules/match-pattern.js.map +1 -0
- package/dist/rules/match-rule.d.ts +6 -0
- package/dist/rules/match-rule.js +21 -0
- package/dist/rules/match-rule.js.map +1 -0
- package/dist/run-checks.d.ts +2 -0
- package/dist/run-checks.js +43 -0
- package/dist/run-checks.js.map +1 -0
- package/dist/severity.d.ts +7 -0
- package/dist/severity.js +10 -0
- package/dist/severity.js.map +1 -0
- package/dist/source/scan-js-source.d.ts +6 -0
- package/dist/source/scan-js-source.js +166 -0
- package/dist/source/scan-js-source.js.map +1 -0
- package/dist/source/scan-vue-sfc.d.ts +1 -0
- package/dist/source/scan-vue-sfc.js +35 -0
- package/dist/source/scan-vue-sfc.js.map +1 -0
- package/dist/source/scan-vue-template.d.ts +5 -0
- package/dist/source/scan-vue-template.js +101 -0
- package/dist/source/scan-vue-template.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +26 -9
- package/src/baseline.js +0 -73
- package/src/check-hardcoded.js +0 -45
- package/src/check-identical.js +0 -53
- package/src/check-placeholders.js +0 -60
- package/src/cli.js +0 -257
- package/src/config.js +0 -235
- package/src/errors.js +0 -11
- package/src/locale/flatten-locale.js +0 -28
- package/src/locale/load-locales.js +0 -47
- package/src/reporters/combined.js +0 -68
- package/src/reporters/console.js +0 -56
- package/src/reporters/index.js +0 -19
- package/src/reporters/json.js +0 -6
- package/src/reporters/markdown.js +0 -61
- package/src/reporters/sarif.js +0 -105
- package/src/reporters/summary.js +0 -9
- package/src/rules/classify-hardcoded.js +0 -54
- package/src/rules/classify-identical.js +0 -85
- package/src/rules/match-rule.js +0 -21
- package/src/run-checks.js +0 -45
- package/src/severity.js +0 -10
- package/src/source/scan-js-source.js +0 -148
- package/src/source/scan-vue-sfc.js +0 -26
- package/src/source/scan-vue-template.js +0 -105
- 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
|
|
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
|
-
|
|
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
|
|
17
|
-
- Detect missing or extra placeholders
|
|
18
|
-
- Detect hardcoded strings in Vue
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
]
|
|
48
|
+
source: ['src/**/*.{vue,js,jsx,ts,tsx}'],
|
|
49
|
+
failOn: 'none',
|
|
50
50
|
};
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
+
Common options:
|
|
81
65
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
77
|
+
## Reports
|
|
107
78
|
|
|
108
79
|
```bash
|
|
109
|
-
npx i18n-smell-detector check --
|
|
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
|
-
|
|
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
|
-
##
|
|
87
|
+
## CI
|
|
464
88
|
|
|
465
|
-
|
|
89
|
+
Start in reporting mode:
|
|
466
90
|
|
|
467
91
|
```bash
|
|
468
|
-
npx i18n-smell-detector check --
|
|
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
|
-
|
|
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 --
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
## Exit codes
|
|
101
|
+
## TypeScript API
|
|
489
102
|
|
|
490
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
+
Full documentation lives in the GitHub Wiki:
|
|
566
113
|
|
|
567
|
-
-
|
|
568
|
-
-
|
|
569
|
-
-
|
|
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
|
|
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 '../
|
|
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>;
|