react-i18next-scanner 0.1.7 → 0.1.8
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 +489 -132
- package/dist/core/actions/writeDynamicTranslationReport.js +1 -1
- package/dist/core/actions/writePossiblyDynamicallyUsedReport.js +1 -1
- package/dist/core/actions/writeResolvedImportedObjectUsageReport.js +2 -1
- package/dist/core/actions/writeUnusedTranslationsReport.js +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# react-i18next-scanner
|
|
2
2
|
|
|
3
|
-
CLI tool
|
|
3
|
+
CLI tool for React / `react-i18next` projects.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It helps you:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
7
|
+
- find missing translation keys
|
|
8
|
+
- find unused translation keys
|
|
9
|
+
- detect dynamic translation usages
|
|
10
|
+
- protect possibly used dynamic keys from removal
|
|
11
|
+
- generate reports
|
|
12
|
+
- sync missing keys
|
|
13
|
+
- remove definitely unused keys
|
|
14
|
+
- sort translation JSON files
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### 1. Install
|
|
19
21
|
|
|
20
22
|
```bash
|
|
21
23
|
npm install -D react-i18next-scanner
|
|
@@ -29,150 +31,139 @@ yarn add -D react-i18next-scanner
|
|
|
29
31
|
|
|
30
32
|
---
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Create one of these config files in your project root:
|
|
34
|
+
### 2. Create config
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
```bash
|
|
37
|
+
npx react-i18next-scanner init
|
|
38
|
+
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
This will create a config file for your project.
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Supported config formats:
|
|
43
43
|
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"./src/i18n/locales/translations.en.json",
|
|
49
|
-
"./src/i18n/locales/translations.es.json",
|
|
50
|
-
],
|
|
51
|
-
sortJsonFiles: [
|
|
52
|
-
"./src/i18n/locales/translations.en.json",
|
|
53
|
-
"./src/i18n/locales/translations.es.json",
|
|
54
|
-
],
|
|
55
|
-
outputDir: "./unused",
|
|
56
|
-
syncMissing: false,
|
|
57
|
-
removeUnused: false,
|
|
58
|
-
};
|
|
44
|
+
```txt
|
|
45
|
+
i18n-scanner.config.js
|
|
46
|
+
i18n-scanner.config.cjs
|
|
47
|
+
i18n-scanner.config.mjs
|
|
59
48
|
```
|
|
60
49
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
## Config Options
|
|
50
|
+
> `.ts` config is not supported yet.
|
|
64
51
|
|
|
65
|
-
|
|
52
|
+
---
|
|
66
53
|
|
|
67
|
-
|
|
54
|
+
### 3. Run scan
|
|
68
55
|
|
|
69
|
-
```
|
|
70
|
-
|
|
56
|
+
```bash
|
|
57
|
+
npx react-i18next-scanner scan
|
|
71
58
|
```
|
|
72
59
|
|
|
73
|
-
|
|
60
|
+
The scanner will check your source files and translation JSON files.
|
|
74
61
|
|
|
75
|
-
|
|
62
|
+
Reports will be created inside:
|
|
76
63
|
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
"./src/i18n/locales/translations.en.json",
|
|
80
|
-
"./src/i18n/locales/translations.es.json",
|
|
81
|
-
];
|
|
64
|
+
```txt
|
|
65
|
+
reports/
|
|
82
66
|
```
|
|
83
67
|
|
|
84
|
-
|
|
68
|
+
---
|
|
85
69
|
|
|
86
|
-
|
|
70
|
+
## Example Config
|
|
87
71
|
|
|
88
72
|
```js
|
|
89
|
-
|
|
90
|
-
"./src
|
|
91
|
-
"./src/i18n/locales/translations.es.json",
|
|
92
|
-
];
|
|
93
|
-
```
|
|
73
|
+
export default {
|
|
74
|
+
srcPaths: ["./src/**/*.{js,jsx,ts,tsx}"],
|
|
94
75
|
|
|
95
|
-
|
|
76
|
+
jsonDir: [
|
|
77
|
+
"./src/i18n/locales/translations.en.json",
|
|
78
|
+
"./src/i18n/locales/translations.es.json",
|
|
79
|
+
],
|
|
96
80
|
|
|
97
|
-
|
|
81
|
+
sortJsonFiles: [
|
|
82
|
+
"./src/i18n/locales/translations.en.json",
|
|
83
|
+
"./src/i18n/locales/translations.es.json",
|
|
84
|
+
],
|
|
98
85
|
|
|
99
|
-
|
|
86
|
+
outputDir: "./reports",
|
|
100
87
|
|
|
101
|
-
|
|
102
|
-
|
|
88
|
+
syncMissing: false,
|
|
89
|
+
removeUnused: false,
|
|
90
|
+
sortJson: true,
|
|
91
|
+
};
|
|
103
92
|
```
|
|
104
93
|
|
|
105
|
-
### `syncMissing`
|
|
106
|
-
|
|
107
|
-
If `true`, missing keys found in code will be added to translation JSON files.
|
|
108
|
-
|
|
109
|
-
### `removeUnused`
|
|
110
|
-
|
|
111
|
-
If `true`, unused keys will be removed from translation JSON files.
|
|
112
|
-
|
|
113
94
|
---
|
|
114
95
|
|
|
115
96
|
## Commands
|
|
116
97
|
|
|
117
|
-
###
|
|
98
|
+
### `init`
|
|
99
|
+
|
|
100
|
+
Creates config file automatically.
|
|
118
101
|
|
|
119
102
|
```bash
|
|
120
103
|
npx react-i18next-scanner init
|
|
121
104
|
```
|
|
122
105
|
|
|
123
|
-
This command creates a config file automatically and lets you choose:
|
|
124
|
-
|
|
125
|
-
- `.js`
|
|
126
|
-
- `.mjs`
|
|
127
|
-
- `.cjs`
|
|
128
|
-
|
|
129
106
|
---
|
|
130
107
|
|
|
131
|
-
###
|
|
108
|
+
### `scan`
|
|
109
|
+
|
|
110
|
+
Scans your project and creates reports.
|
|
132
111
|
|
|
133
112
|
```bash
|
|
134
113
|
npx react-i18next-scanner scan
|
|
135
114
|
```
|
|
136
115
|
|
|
137
|
-
|
|
116
|
+
With custom config:
|
|
138
117
|
|
|
139
118
|
```bash
|
|
140
119
|
npx react-i18next-scanner scan --config ./i18n-scanner.config.mjs
|
|
141
120
|
```
|
|
142
121
|
|
|
143
|
-
|
|
122
|
+
The `scan` command:
|
|
144
123
|
|
|
145
124
|
- scans source files
|
|
146
|
-
- extracts translation keys
|
|
147
|
-
- compares
|
|
148
|
-
-
|
|
149
|
-
-
|
|
125
|
+
- extracts used translation keys
|
|
126
|
+
- compares used keys with translation JSON files
|
|
127
|
+
- detects missing keys
|
|
128
|
+
- detects unused keys
|
|
129
|
+
- detects dynamic translation usages
|
|
130
|
+
- writes reports
|
|
150
131
|
- optionally syncs missing keys
|
|
151
|
-
- optionally removes unused keys
|
|
132
|
+
- optionally removes definitely unused keys
|
|
152
133
|
|
|
153
134
|
---
|
|
154
135
|
|
|
155
|
-
###
|
|
136
|
+
### `sync-missing`
|
|
137
|
+
|
|
138
|
+
Finds missing keys and adds them to translation JSON files.
|
|
156
139
|
|
|
157
140
|
```bash
|
|
158
141
|
npx react-i18next-scanner sync-missing
|
|
159
142
|
```
|
|
160
143
|
|
|
161
|
-
|
|
144
|
+
Missing keys are added with empty values.
|
|
145
|
+
|
|
146
|
+
Example:
|
|
162
147
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"common": {
|
|
151
|
+
"Save": ""
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
166
155
|
|
|
167
156
|
---
|
|
168
157
|
|
|
169
|
-
###
|
|
158
|
+
### `sort`
|
|
159
|
+
|
|
160
|
+
Sorts translation JSON files alphabetically.
|
|
170
161
|
|
|
171
162
|
```bash
|
|
172
163
|
npx react-i18next-scanner sort
|
|
173
164
|
```
|
|
174
165
|
|
|
175
|
-
If no files are passed
|
|
166
|
+
If no files are passed, the command uses `sortJsonFiles` from config.
|
|
176
167
|
|
|
177
168
|
You can also pass files manually:
|
|
178
169
|
|
|
@@ -182,62 +173,387 @@ npx react-i18next-scanner sort ./src/i18n/locales/translations.en.json ./src/i18
|
|
|
182
173
|
|
|
183
174
|
---
|
|
184
175
|
|
|
176
|
+
## Report Structure
|
|
177
|
+
|
|
178
|
+
All reports are written inside one root folder:
|
|
179
|
+
|
|
180
|
+
```txt
|
|
181
|
+
reports/
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Each report type has its own folder:
|
|
185
|
+
|
|
186
|
+
```txt
|
|
187
|
+
reports/
|
|
188
|
+
unused-translations/
|
|
189
|
+
unused.translations.en.json
|
|
190
|
+
unused.translations.es.json
|
|
191
|
+
|
|
192
|
+
dynamic-translations/
|
|
193
|
+
dynamic-translation-usages.json
|
|
194
|
+
|
|
195
|
+
possibly-dynamically-used/
|
|
196
|
+
possibly-dynamically-used.json
|
|
197
|
+
|
|
198
|
+
imported-object-usages/
|
|
199
|
+
resolved-imported-object-usages.json
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
185
204
|
## Unused Translation Reports
|
|
186
205
|
|
|
187
|
-
|
|
206
|
+
Folder:
|
|
207
|
+
|
|
208
|
+
```txt
|
|
209
|
+
reports/unused-translations/
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
These reports contain translation keys that look unused after deep verification.
|
|
188
213
|
|
|
189
214
|
Example:
|
|
190
215
|
|
|
191
|
-
|
|
192
|
-
-
|
|
216
|
+
```txt
|
|
217
|
+
reports/unused-translations/unused.translations.en.json
|
|
218
|
+
```
|
|
193
219
|
|
|
194
|
-
|
|
220
|
+
Example content:
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"common": {
|
|
225
|
+
"Old button text": "Old button text"
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
You can review this file before removing keys.
|
|
231
|
+
|
|
232
|
+
If `removeUnused: true`, only definitely unused keys will be removed.
|
|
195
233
|
|
|
196
234
|
---
|
|
197
235
|
|
|
198
236
|
## Dynamic Translation Report
|
|
199
237
|
|
|
200
|
-
|
|
238
|
+
Folder:
|
|
201
239
|
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
t(labelKey);
|
|
205
|
-
t(transaction.descriptionKey);
|
|
240
|
+
```txt
|
|
241
|
+
reports/dynamic-translations/
|
|
206
242
|
```
|
|
207
243
|
|
|
208
|
-
|
|
244
|
+
This report shows places where the scanner found dynamic translation usage.
|
|
209
245
|
|
|
210
|
-
|
|
211
|
-
|
|
246
|
+
Examples:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
t(labelKey);
|
|
250
|
+
t(getDescription());
|
|
251
|
+
t(`${section}.${name}`);
|
|
252
|
+
<Trans i18nKey={text} />;
|
|
253
|
+
<Trans i18nKey={`account.${text}`} />;
|
|
212
254
|
```
|
|
213
255
|
|
|
214
|
-
Example
|
|
256
|
+
Example report:
|
|
215
257
|
|
|
216
258
|
```json
|
|
217
259
|
{
|
|
218
|
-
"src/
|
|
260
|
+
"src/components/Profile.tsx": [
|
|
219
261
|
{
|
|
220
|
-
"namespaces": ["
|
|
221
|
-
"expression": "
|
|
222
|
-
"reason": "
|
|
223
|
-
"suggestion": "Translation key may come from
|
|
262
|
+
"namespaces": ["common"],
|
|
263
|
+
"expression": "labelKey",
|
|
264
|
+
"reason": "Non-literal translation key",
|
|
265
|
+
"suggestion": "Translation key may come from a variable or helper function. Manual or deeper analysis may be required."
|
|
224
266
|
}
|
|
225
267
|
]
|
|
226
268
|
}
|
|
227
269
|
```
|
|
228
270
|
|
|
271
|
+
This report answers:
|
|
272
|
+
|
|
273
|
+
```txt
|
|
274
|
+
Where do we use dynamic translation keys in source code?
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Possibly Dynamically Used Report
|
|
280
|
+
|
|
281
|
+
Folder:
|
|
282
|
+
|
|
283
|
+
```txt
|
|
284
|
+
reports/possibly-dynamically-used/
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
This report shows keys that looked unused at first, but were later detected as possibly used dynamically.
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
const titleKey = MODAL_DATA[type].TITLE;
|
|
293
|
+
|
|
294
|
+
t(String(titleKey));
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The key may not appear directly as:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
t("accountInformation.Account Number");
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
But the scanner can understand that it may come from an object config.
|
|
304
|
+
|
|
305
|
+
This report answers:
|
|
306
|
+
|
|
307
|
+
```txt
|
|
308
|
+
Which unused keys were saved from removal because they may be used dynamically?
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Imported Object Usages Report
|
|
314
|
+
|
|
315
|
+
Folder:
|
|
316
|
+
|
|
317
|
+
```txt
|
|
318
|
+
reports/imported-object-usages/
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
File:
|
|
322
|
+
|
|
323
|
+
```txt
|
|
324
|
+
resolved-imported-object-usages.json
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
This report shows where translation keys from imported object configs are used.
|
|
328
|
+
|
|
329
|
+
Example config object:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
export const MODAL_DATA = {
|
|
333
|
+
accountNumber: {
|
|
334
|
+
TITLE: "accountInformation.Account Number",
|
|
335
|
+
TEXT: "accountInformation.Your Account Number is",
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Example usage:
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import { MODAL_DATA } from "./modalData";
|
|
344
|
+
|
|
345
|
+
const additionalInformationData = MODAL_DATA[additionalInformationModalType];
|
|
346
|
+
|
|
347
|
+
t(String(additionalInformationData.TITLE));
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The scanner can resolve this as:
|
|
351
|
+
|
|
352
|
+
```txt
|
|
353
|
+
additionalInformationData.TITLE
|
|
354
|
+
→ MODAL_DATA[*].TITLE
|
|
355
|
+
→ accountInformation.Account Number
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Example report:
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
[
|
|
362
|
+
{
|
|
363
|
+
"key": "accountInformation.Account Number",
|
|
364
|
+
"sourceFilePath": "src/components/AdditionalInformationModal.tsx",
|
|
365
|
+
"importedFilePath": "src/config/modalData.ts",
|
|
366
|
+
"importedObjectName": "MODAL_DATA",
|
|
367
|
+
"accessPath": "MODAL_DATA.*.TITLE",
|
|
368
|
+
"usageKind": "t"
|
|
369
|
+
}
|
|
370
|
+
]
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
This report answers:
|
|
374
|
+
|
|
375
|
+
```txt
|
|
376
|
+
Where is this imported object translation key used?
|
|
377
|
+
```
|
|
378
|
+
|
|
229
379
|
---
|
|
230
380
|
|
|
231
381
|
## Supported Translation Patterns
|
|
232
382
|
|
|
233
|
-
The scanner
|
|
383
|
+
The scanner detects direct translation keys:
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
t("common.Save");
|
|
387
|
+
i18n.t("common.Save");
|
|
388
|
+
<Trans i18nKey="common.Save" />;
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
It also supports namespaces:
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
const { t } = useTranslation("common");
|
|
395
|
+
|
|
396
|
+
t("Save");
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Supported Dynamic Patterns
|
|
402
|
+
|
|
403
|
+
The scanner can detect and report dynamic usages:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
t(labelKey);
|
|
407
|
+
t(getTitleKey());
|
|
408
|
+
t(`${section}.${name}`);
|
|
409
|
+
<Trans i18nKey={labelKey} />;
|
|
410
|
+
<Trans i18nKey={`account.${name}`} />;
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
It can also resolve some imported object config usages:
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
t(String(MODAL_DATA[type].TITLE));
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
const data = MODAL_DATA[type];
|
|
421
|
+
|
|
422
|
+
t(String(data.TITLE));
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
```tsx
|
|
426
|
+
<Trans i18nKey={MODAL_DATA[type].TITLE} />
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
This works not only for `MODAL_DATA`, but also for similar exported object configs:
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
export const WARNING_CONFIG = {
|
|
433
|
+
deleteAccount: {
|
|
434
|
+
TITLE: "settings.Delete account",
|
|
435
|
+
TEXT: "settings.This action cannot be undone",
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Config Options
|
|
443
|
+
|
|
444
|
+
### `srcPaths`
|
|
445
|
+
|
|
446
|
+
Source files to scan.
|
|
447
|
+
|
|
448
|
+
```js
|
|
449
|
+
srcPaths: ["./src/**/*.{js,jsx,ts,tsx}"];
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
### `jsonDir`
|
|
455
|
+
|
|
456
|
+
Translation JSON files to analyze.
|
|
457
|
+
|
|
458
|
+
```js
|
|
459
|
+
jsonDir: [
|
|
460
|
+
"./src/i18n/locales/translations.en.json",
|
|
461
|
+
"./src/i18n/locales/translations.es.json",
|
|
462
|
+
];
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
### `sortJsonFiles`
|
|
468
|
+
|
|
469
|
+
JSON files used by the `sort` command.
|
|
470
|
+
|
|
471
|
+
```js
|
|
472
|
+
sortJsonFiles: [
|
|
473
|
+
"./src/i18n/locales/translations.en.json",
|
|
474
|
+
"./src/i18n/locales/translations.es.json",
|
|
475
|
+
];
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
### `outputDir`
|
|
481
|
+
|
|
482
|
+
Root folder for scanner reports.
|
|
483
|
+
|
|
484
|
+
Default:
|
|
485
|
+
|
|
486
|
+
```js
|
|
487
|
+
outputDir: "./reports";
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Reports will be grouped into subfolders:
|
|
491
|
+
|
|
492
|
+
```txt
|
|
493
|
+
reports/
|
|
494
|
+
unused-translations/
|
|
495
|
+
dynamic-translations/
|
|
496
|
+
possibly-dynamically-used/
|
|
497
|
+
imported-object-usages/
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
### `syncMissing`
|
|
503
|
+
|
|
504
|
+
If `true`, missing keys found in code will be added to translation JSON files.
|
|
505
|
+
|
|
506
|
+
```js
|
|
507
|
+
syncMissing: true;
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Values are created as empty strings.
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
### `removeUnused`
|
|
515
|
+
|
|
516
|
+
If `true`, definitely unused keys will be removed from translation JSON files.
|
|
517
|
+
|
|
518
|
+
```js
|
|
519
|
+
removeUnused: true;
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
The scanner does not remove keys that are possibly dynamically used.
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
### `sortJson`
|
|
527
|
+
|
|
528
|
+
If `true`, JSON files will be sorted after sync or remove actions.
|
|
529
|
+
|
|
530
|
+
```js
|
|
531
|
+
sortJson: true;
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Safe Remove Behavior
|
|
234
537
|
|
|
235
|
-
|
|
236
|
-
- `i18n.t("key")`
|
|
237
|
-
- `<Trans i18nKey="key" />`
|
|
238
|
-
- `useTranslation("namespace")`
|
|
538
|
+
The scanner tries to avoid removing keys that may still be used dynamically.
|
|
239
539
|
|
|
240
|
-
|
|
540
|
+
For example:
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
const data = MODAL_DATA[type];
|
|
544
|
+
|
|
545
|
+
t(String(data.TITLE));
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
If `MODAL_DATA[type].TITLE` contains:
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
"accountInformation.Account Number";
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
then this key will not be treated as definitely unused.
|
|
555
|
+
|
|
556
|
+
It will be moved to a dynamic safety report instead.
|
|
241
557
|
|
|
242
558
|
---
|
|
243
559
|
|
|
@@ -245,20 +561,20 @@ It can also report dynamic usages where translation keys are not passed directly
|
|
|
245
561
|
|
|
246
562
|
### Source files
|
|
247
563
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
564
|
+
```txt
|
|
565
|
+
.js
|
|
566
|
+
.jsx
|
|
567
|
+
.ts
|
|
568
|
+
.tsx
|
|
569
|
+
```
|
|
254
570
|
|
|
255
571
|
### Config files
|
|
256
572
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
573
|
+
```txt
|
|
574
|
+
.js
|
|
575
|
+
.cjs
|
|
576
|
+
.mjs
|
|
577
|
+
```
|
|
262
578
|
|
|
263
579
|
> `.ts` config is not supported yet.
|
|
264
580
|
|
|
@@ -266,12 +582,15 @@ The scanner currently supports:
|
|
|
266
582
|
|
|
267
583
|
## Example Workflow
|
|
268
584
|
|
|
269
|
-
### 1. Scan
|
|
585
|
+
### 1. Scan only
|
|
586
|
+
|
|
587
|
+
Use this when you want reports without modifying translation JSON files.
|
|
270
588
|
|
|
271
589
|
```js
|
|
272
590
|
export default {
|
|
273
591
|
srcPaths: ["./src/**/*.{js,jsx,ts,tsx}"],
|
|
274
592
|
jsonDir: ["./src/i18n/locales/translations.en.json"],
|
|
593
|
+
outputDir: "./reports",
|
|
275
594
|
syncMissing: false,
|
|
276
595
|
removeUnused: false,
|
|
277
596
|
};
|
|
@@ -283,12 +602,15 @@ Run:
|
|
|
283
602
|
npx react-i18next-scanner scan
|
|
284
603
|
```
|
|
285
604
|
|
|
605
|
+
---
|
|
606
|
+
|
|
286
607
|
### 2. Sync missing keys
|
|
287
608
|
|
|
288
609
|
```js
|
|
289
610
|
export default {
|
|
290
611
|
srcPaths: ["./src/**/*.{js,jsx,ts,tsx}"],
|
|
291
612
|
jsonDir: ["./src/i18n/locales/translations.en.json"],
|
|
613
|
+
outputDir: "./reports",
|
|
292
614
|
syncMissing: true,
|
|
293
615
|
removeUnused: false,
|
|
294
616
|
};
|
|
@@ -300,18 +622,21 @@ Run:
|
|
|
300
622
|
npx react-i18next-scanner scan
|
|
301
623
|
```
|
|
302
624
|
|
|
303
|
-
or
|
|
625
|
+
or:
|
|
304
626
|
|
|
305
627
|
```bash
|
|
306
628
|
npx react-i18next-scanner sync-missing
|
|
307
629
|
```
|
|
308
630
|
|
|
309
|
-
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
### 3. Remove definitely unused keys
|
|
310
634
|
|
|
311
635
|
```js
|
|
312
636
|
export default {
|
|
313
637
|
srcPaths: ["./src/**/*.{js,jsx,ts,tsx}"],
|
|
314
638
|
jsonDir: ["./src/i18n/locales/translations.en.json"],
|
|
639
|
+
outputDir: "./reports",
|
|
315
640
|
syncMissing: false,
|
|
316
641
|
removeUnused: true,
|
|
317
642
|
};
|
|
@@ -323,14 +648,46 @@ Run:
|
|
|
323
648
|
npx react-i18next-scanner scan
|
|
324
649
|
```
|
|
325
650
|
|
|
651
|
+
Before using `removeUnused: true`, review reports first.
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## Limitations
|
|
656
|
+
|
|
657
|
+
The scanner does not fully support every possible runtime data-flow case.
|
|
658
|
+
|
|
659
|
+
Currently not fully covered:
|
|
660
|
+
|
|
661
|
+
```ts
|
|
662
|
+
const data = getModalData(type);
|
|
663
|
+
t(data.TITLE);
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
```ts
|
|
667
|
+
const key = MODAL_DATA[type].TITLE + ".extra";
|
|
668
|
+
t(key);
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
const data = someWrapper(MODAL_DATA[type]);
|
|
673
|
+
t(data.TITLE);
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
export default MODAL_DATA;
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
The scanner is designed to be safe. If it is not sure, it reports dynamic usage instead of removing keys automatically.
|
|
681
|
+
|
|
326
682
|
---
|
|
327
683
|
|
|
328
684
|
## Notes
|
|
329
685
|
|
|
330
|
-
- `syncMissing` does not translate values automatically.
|
|
331
|
-
- `removeUnused` should be used carefully
|
|
332
|
-
-
|
|
686
|
+
- `syncMissing` does not translate values automatically.
|
|
687
|
+
- `removeUnused` should be used carefully.
|
|
688
|
+
- Possibly dynamic keys are not removed automatically.
|
|
333
689
|
- Config paths are resolved relative to the config file location.
|
|
690
|
+
- `.ts` config is not supported yet.
|
|
334
691
|
|
|
335
692
|
---
|
|
336
693
|
|
|
@@ -342,7 +699,7 @@ Build the project:
|
|
|
342
699
|
yarn build
|
|
343
700
|
```
|
|
344
701
|
|
|
345
|
-
Run
|
|
702
|
+
Run scanner in development:
|
|
346
703
|
|
|
347
704
|
```bash
|
|
348
705
|
yarn dev scan
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { sortObject } from "../utils/sortObject.js";
|
|
4
|
-
const DEFAULT_DYNAMIC_REPORT_DIR = path.resolve(process.cwd(), "reports-
|
|
4
|
+
const DEFAULT_DYNAMIC_REPORT_DIR = path.resolve(process.cwd(), "reports/dynamic-translations");
|
|
5
5
|
export async function writeDynamicTranslationReport(report, outputDir = DEFAULT_DYNAMIC_REPORT_DIR) {
|
|
6
6
|
if (Object.keys(report).length === 0) {
|
|
7
7
|
return;
|
|
@@ -3,7 +3,7 @@ import fs from "fs/promises";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { addKey } from "../utils/addKey.js";
|
|
5
5
|
import { sortObject } from "../utils/sortObject.js";
|
|
6
|
-
const DEFAULT_OUTPUT_DIR = path.resolve(process.cwd(), "
|
|
6
|
+
const DEFAULT_OUTPUT_DIR = path.resolve(process.cwd(), "dynamic-translations");
|
|
7
7
|
export async function writePossiblyDynamicallyUsedReport(keysByFile, jsonFiles, outputDir = DEFAULT_OUTPUT_DIR) {
|
|
8
8
|
const reportDataByFile = {};
|
|
9
9
|
for (const file of jsonFiles) {
|
|
@@ -5,7 +5,8 @@ export async function writeResolvedImportedObjectUsagesReport(usages, outputDir)
|
|
|
5
5
|
if (usages.length === 0) {
|
|
6
6
|
return;
|
|
7
7
|
}
|
|
8
|
-
const
|
|
8
|
+
const rootReportDir = outputDir ?? "reports";
|
|
9
|
+
const reportDir = path.join(rootReportDir, "imported-object-usages");
|
|
9
10
|
await fs.mkdir(reportDir, { recursive: true });
|
|
10
11
|
const reportPath = path.join(reportDir, "resolved-imported-object-usages.json");
|
|
11
12
|
await fs.writeFile(reportPath, JSON.stringify(usages, null, 2), "utf-8");
|
|
@@ -10,7 +10,9 @@ export async function writeUnusedTranslationsReport(keysByFile, jsonFiles, outpu
|
|
|
10
10
|
if (filesWithUnused.length === 0) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
const rootReportDir = outputDir ?? "reports";
|
|
14
|
+
const reportDir = path.join(rootReportDir, "unused-translations");
|
|
15
|
+
await fs.mkdir(reportDir, { recursive: true });
|
|
14
16
|
for (const file of filesWithUnused) {
|
|
15
17
|
const unusedKeys = keysByFile[file.filePath] ?? [];
|
|
16
18
|
const reportData = {};
|
|
@@ -22,7 +24,7 @@ export async function writeUnusedTranslationsReport(keysByFile, jsonFiles, outpu
|
|
|
22
24
|
}
|
|
23
25
|
const sorted = sortObject(reportData);
|
|
24
26
|
const reportFileName = `unused.${path.basename(file.filePath)}`;
|
|
25
|
-
const reportPath = path.join(
|
|
27
|
+
const reportPath = path.join(reportDir, reportFileName);
|
|
26
28
|
await fs.writeFile(reportPath, JSON.stringify(sorted, null, 2), "utf-8");
|
|
27
29
|
console.log(`📝 Unused report created: ${reportPath}`);
|
|
28
30
|
}
|