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.
- package/README.md +58 -515
- 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 -321
- package/src/config.js +0 -262
- 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 -163
- package/src/source/scan-vue-sfc.js +0 -40
- 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,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
|
-
|
|
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
|
-
|
|
50
|
-
],
|
|
51
|
-
failOn: 'none'
|
|
48
|
+
source: ['src/**/*.{vue,js,jsx,ts,tsx}'],
|
|
49
|
+
failOn: 'none',
|
|
52
50
|
};
|
|
53
51
|
```
|
|
54
52
|
|
|
55
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
+
## Reports
|
|
430
78
|
|
|
431
79
|
```bash
|
|
432
|
-
npx i18n-smell-detector check --
|
|
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
|
-
|
|
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
|
-
##
|
|
87
|
+
## CI
|
|
476
88
|
|
|
477
|
-
|
|
89
|
+
Start in reporting mode:
|
|
478
90
|
|
|
479
91
|
```bash
|
|
480
|
-
npx i18n-smell-detector check --
|
|
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
|
-
|
|
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 --
|
|
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
|
-
|
|
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
|
-
|
|
103
|
+
The package exposes types and a small public API from the package root:
|
|
509
104
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
+
Full documentation lives in the GitHub Wiki:
|
|
579
113
|
|
|
580
|
-
-
|
|
581
|
-
-
|
|
582
|
-
-
|
|
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
|
|
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 '../
|
|
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>;
|