i18ntk 4.5.2 → 4.5.4
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/CHANGELOG.md +182 -37
- package/README.md +27 -14
- package/main/i18ntk-analyze.js +77 -42
- package/main/i18ntk-complete.js +56 -37
- package/main/i18ntk-report.js +22 -9
- package/main/i18ntk-scanner.js +3 -0
- package/main/i18ntk-sizing.js +9 -6
- package/main/i18ntk-summary.js +61 -39
- package/main/i18ntk-usage.js +30 -17
- package/main/i18ntk-validate.js +38 -24
- package/main/manage/commands/AnalyzeCommand.js +79 -44
- package/main/manage/commands/ScannerCommand.js +13 -7
- package/main/manage/commands/ValidateCommand.js +37 -23
- package/main/manage/index.js +13 -9
- package/package.json +13 -6
- package/utils/config-helper.js +41 -31
- package/utils/exit-codes.js +8 -6
- package/utils/prompt-helper.js +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# i18ntk v4.5.
|
|
1
|
+
# i18ntk v4.5.4
|
|
2
2
|
|
|
3
3
|
A zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, translation completion, automatic JSON locale translation, reporting, and runtime translation loading.
|
|
4
4
|
|
|
@@ -9,7 +9,7 @@ A zero-dependency internationalization toolkit for setup, scanning, analysis, va
|
|
|
9
9
|
[](https://nodejs.org)
|
|
10
10
|
[](https://www.npmjs.com/package/i18ntk)
|
|
11
11
|
[](LICENSE)
|
|
12
|
-
[](https://socket.dev/npm/package/i18ntk/overview/4.5.4)
|
|
13
13
|
|
|
14
14
|
[](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-workbench)
|
|
15
15
|
[](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-lens)
|
|
@@ -64,6 +64,17 @@ i18next is mainly a runtime internationalization library. i18ntk is mainly workf
|
|
|
64
64
|
| Validation reports | Yes | Limited |
|
|
65
65
|
| Auto-translation workflow | Yes | External tooling |
|
|
66
66
|
|
|
67
|
+
## What's New in 4.5.4
|
|
68
|
+
|
|
69
|
+
- **CLI RELIABILITY**: Direct and manager-routed commands now propagate runtime, validation, and report failures with non-zero exit codes and never print success after failure.
|
|
70
|
+
- **CI READY**: Commands skip prompts under `--no-prompt`, `CI=true`, or non-TTY stdin/stdout.
|
|
71
|
+
- **CLEAR FLAGS**: Added `--code-dir` / `--source-code-dir`, `--locales-dir` / `--i18n-dir`, and `--source-locale` while preserving legacy aliases.
|
|
72
|
+
- **COMMAND FIXES**: Fixed `i18ntk-analyze` setup guidance crash, `i18ntk-complete --help`, `i18ntk-summary` `NaN` averages, validation success/failure wording, and completion summary labels.
|
|
73
|
+
|
|
74
|
+
## What's New in 4.5.3
|
|
75
|
+
|
|
76
|
+
- **TSX/JSX SCANNING**: `supportedExtensions` default now includes `.tsx` and `.jsx`. Previously excluded from source scanning, causing React/Next.js projects to miss 97%+ of translation keys.
|
|
77
|
+
|
|
67
78
|
## What's New in 4.5.2
|
|
68
79
|
|
|
69
80
|
- The `complete` command now correctly inserts missing keys at the right nesting level when target locale files have namespace wrappers (e.g., `auth.json` containing `{ "auth": { ... } }`). Keys inside `auth.panel.sign_in` now go inside the `auth` wrapper, not at root level.
|
|
@@ -189,20 +200,22 @@ Note: manager route `i18ntk --command=backup` is available via the interactive m
|
|
|
189
200
|
|
|
190
201
|
Many commands support:
|
|
191
202
|
|
|
192
|
-
- `--source-dir <path>`
|
|
193
|
-
- `--i18n-dir <path>`
|
|
203
|
+
- `--code-dir <path>` or `--source-code-dir <path>` for application source files
|
|
204
|
+
- `--locales-dir <path>` or `--i18n-dir <path>` for locale files
|
|
194
205
|
- `--output-dir <path>`
|
|
195
|
-
- `--source-
|
|
206
|
+
- `--source-locale <code>`
|
|
196
207
|
- `--ui-language <code>`
|
|
197
208
|
- `--no-prompt`
|
|
198
209
|
- `--help`
|
|
199
210
|
|
|
211
|
+
Legacy `--source-dir` and `--source-language` remain supported. For scanner-style commands, `--source-dir` means source code. For locale-only commands, prefer `--locales-dir` to avoid ambiguity.
|
|
212
|
+
|
|
200
213
|
Command-specific tools add their own flags such as `--dry-run`, `--output-report`, `--cleanup`, `--predict-expansion`, or Auto Translate provider options.
|
|
201
214
|
|
|
202
215
|
Example:
|
|
203
216
|
|
|
204
217
|
```bash
|
|
205
|
-
i18ntk --command=analyze --
|
|
218
|
+
i18ntk --command=analyze --code-dir=./src --locales-dir=./locales --source-locale=en --output-dir=./i18ntk-reports
|
|
206
219
|
```
|
|
207
220
|
|
|
208
221
|
## Auto Translate
|
|
@@ -354,8 +367,8 @@ Tune warnings in `.i18ntk-config`:
|
|
|
354
367
|
`i18ntk-sizing` reports translation file sizes, key counts, average value length, and file-set mismatches across language folders.
|
|
355
368
|
|
|
356
369
|
```bash
|
|
357
|
-
i18ntk-sizing --
|
|
358
|
-
i18ntk-sizing --
|
|
370
|
+
i18ntk-sizing --locales-dir ./locales --format table
|
|
371
|
+
i18ntk-sizing --locales-dir ./locales --detailed --output-dir ./i18ntk-reports
|
|
359
372
|
```
|
|
360
373
|
|
|
361
374
|
Use `--detailed` to print per-file rows in the terminal.
|
|
@@ -365,7 +378,7 @@ Use `--detailed` to print per-file rows in the terminal.
|
|
|
365
378
|
Predict UI layout overflow risk by analyzing per-key character-count expansion across languages:
|
|
366
379
|
|
|
367
380
|
```bash
|
|
368
|
-
i18ntk-sizing --
|
|
381
|
+
i18ntk-sizing --locales-dir ./locales --predict-expansion --output-report
|
|
369
382
|
```
|
|
370
383
|
|
|
371
384
|
Expansion ratios are classified into risk tiers:
|
|
@@ -381,8 +394,8 @@ The report includes a built-in language-pair expansion reference table (EN→DE
|
|
|
381
394
|
`i18ntk-scanner` now supports detecting hardcoded text in multiple source languages beyond English:
|
|
382
395
|
|
|
383
396
|
```bash
|
|
384
|
-
i18ntk-scanner --
|
|
385
|
-
i18ntk-scanner --
|
|
397
|
+
i18ntk-scanner --code-dir ./src --source-locale de
|
|
398
|
+
i18ntk-scanner --code-dir ./src --source-locale ja --output-report
|
|
386
399
|
```
|
|
387
400
|
|
|
388
401
|
Supported language profiles (12+): English, German, French, Spanish, Japanese, Chinese, Russian, Korean, Arabic, Hindi, and more. Each profile includes language-specific character ranges, stopword lists for false-positive filtering, and transliteration rules for key generation.
|
|
@@ -392,8 +405,8 @@ Supported language profiles (12+): English, German, French, Spanish, Japanese, C
|
|
|
392
405
|
`i18ntk-usage` can identify translation keys that are defined but never referenced in source code:
|
|
393
406
|
|
|
394
407
|
```bash
|
|
395
|
-
i18ntk-usage --
|
|
396
|
-
i18ntk-usage --
|
|
408
|
+
i18ntk-usage --code-dir ./src --locales-dir ./locales --cleanup
|
|
409
|
+
i18ntk-usage --code-dir ./src --locales-dir ./locales --cleanup --dry-run-delete
|
|
397
410
|
```
|
|
398
411
|
|
|
399
412
|
Each dead key receives a confidence score (0.0–1.0) factoring:
|
|
@@ -542,7 +555,7 @@ Example:
|
|
|
542
555
|
|
|
543
556
|
```json
|
|
544
557
|
{
|
|
545
|
-
"version": "4.5.
|
|
558
|
+
"version": "4.5.4",
|
|
546
559
|
"sourceDir": "./locales",
|
|
547
560
|
"i18nDir": "./locales",
|
|
548
561
|
"outputDir": "./i18ntk-reports",
|
package/main/i18ntk-analyze.js
CHANGED
|
@@ -71,9 +71,16 @@ class I18nAnalyzer {
|
|
|
71
71
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
72
72
|
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
73
73
|
|
|
74
|
-
this.sourceDir = this.config.sourceDir;
|
|
75
|
-
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
76
|
-
this.outputDir = this.config.outputDir;
|
|
74
|
+
this.sourceDir = this.config.localesDir || this.config.i18nDir || this.config.sourceDir;
|
|
75
|
+
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
76
|
+
this.outputDir = this.config.outputDir;
|
|
77
|
+
|
|
78
|
+
const explicitLocaleDir = args.i18nDir || (!args.i18nDir && args.sourceDir ? args.sourceDir : null);
|
|
79
|
+
if (explicitLocaleDir && !SecurityUtils.safeExistsSync(this.sourceDir, process.cwd())) {
|
|
80
|
+
const err = new Error(`Locale directory not found: ${this.sourceDir}`);
|
|
81
|
+
err.exitCode = 2;
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
77
84
|
|
|
78
85
|
// Validate source directory exists
|
|
79
86
|
const { validateSourceDir } = require('../utils/config-helper');
|
|
@@ -710,10 +717,32 @@ try {
|
|
|
710
717
|
}
|
|
711
718
|
}
|
|
712
719
|
|
|
713
|
-
// Show help message
|
|
714
|
-
showHelp() {
|
|
715
|
-
console.log(t('analyze.help_message'));
|
|
716
|
-
}
|
|
720
|
+
// Show help message
|
|
721
|
+
showHelp() {
|
|
722
|
+
console.log(t('analyze.help_message'));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
provideSetupGuidance() {
|
|
726
|
+
return [
|
|
727
|
+
'Setup guidance:',
|
|
728
|
+
`- Ensure the locale root exists: ${this.sourceDir}`,
|
|
729
|
+
`- Ensure the source locale exists: ${path.join(this.sourceDir, this.config.sourceLanguage)}`,
|
|
730
|
+
'- Add at least one target locale, for example de/common.json or de.json.',
|
|
731
|
+
'- Prefer --locales-dir for locale files. Use --code-dir for application source files.'
|
|
732
|
+
].join('\n');
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
detectStructureType() {
|
|
736
|
+
const sourceLocaleDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
737
|
+
const sourceLocaleFile = path.join(this.sourceDir, `${this.config.sourceLanguage}.json`);
|
|
738
|
+
if (SecurityUtils.safeExistsSync(sourceLocaleFile, this.sourceDir)) {
|
|
739
|
+
return { type: 'monolith' };
|
|
740
|
+
}
|
|
741
|
+
if (SecurityUtils.safeExistsSync(sourceLocaleDir, this.sourceDir)) {
|
|
742
|
+
return { type: 'directory' };
|
|
743
|
+
}
|
|
744
|
+
return { type: 'unknown' };
|
|
745
|
+
}
|
|
717
746
|
|
|
718
747
|
// Main analyze method
|
|
719
748
|
async analyze() {
|
|
@@ -737,9 +766,9 @@ try {
|
|
|
737
766
|
|
|
738
767
|
const languages = this.getAvailableLanguages();
|
|
739
768
|
|
|
740
|
-
if (languages.length === 0) {
|
|
741
|
-
const error = t('analyze.noLanguages') || '⚠️ No target languages found.';
|
|
742
|
-
const guidance = this.provideSetupGuidance();
|
|
769
|
+
if (languages.length === 0) {
|
|
770
|
+
const error = t('analyze.noLanguages') || '⚠️ No target languages found.';
|
|
771
|
+
const guidance = this.provideSetupGuidance();
|
|
743
772
|
|
|
744
773
|
if (args.json) {
|
|
745
774
|
jsonOutput.setStatus('error', error);
|
|
@@ -751,10 +780,10 @@ try {
|
|
|
751
780
|
console.log(JSON.stringify(jsonOutput.data, null, args.indent || 2));
|
|
752
781
|
return;
|
|
753
782
|
}
|
|
754
|
-
console.log(error);
|
|
755
|
-
console.log('\n' + guidance);
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
783
|
+
console.log(error);
|
|
784
|
+
console.log('\n' + guidance);
|
|
785
|
+
return { success: true, warnings: 1, message: error };
|
|
786
|
+
}
|
|
758
787
|
|
|
759
788
|
if (!args.json) {
|
|
760
789
|
console.log(t('analyze.foundLanguages', { count: languages.length, languages: languages.join(', ') }) || `📋 Found ${languages.length} languages to analyze: ${languages.join(', ')}`);
|
|
@@ -773,8 +802,11 @@ try {
|
|
|
773
802
|
const report = this.generateLanguageReport(analysis);
|
|
774
803
|
|
|
775
804
|
// Save report
|
|
776
|
-
const reportPath = await this.saveReport(language, report);
|
|
777
|
-
|
|
805
|
+
const reportPath = await this.saveReport(language, report);
|
|
806
|
+
if (!reportPath) {
|
|
807
|
+
throw new Error(`Failed to save analysis report for ${language}`);
|
|
808
|
+
}
|
|
809
|
+
const processedCount = results.length + 1;
|
|
778
810
|
|
|
779
811
|
if (!args.json) {
|
|
780
812
|
console.log(t('analyze.completed', { language }) || `✅ Analysis completed for ${language}`);
|
|
@@ -842,9 +874,9 @@ try {
|
|
|
842
874
|
console.log(t('analyze.finished') || '\n✅ Analysis completed successfully!');
|
|
843
875
|
|
|
844
876
|
// Only prompt for input if running standalone and not in no-prompt mode
|
|
845
|
-
if (require.main === module && !this.noPrompt) {
|
|
846
|
-
await this.prompt('\nPress Enter to continue...');
|
|
847
|
-
}
|
|
877
|
+
if (require.main === module && !this.noPrompt && process.env.CI !== 'true' && process.stdin.isTTY && process.stdout.isTTY) {
|
|
878
|
+
await this.prompt('\nPress Enter to continue...');
|
|
879
|
+
}
|
|
848
880
|
this.closeReadline();
|
|
849
881
|
|
|
850
882
|
return results;
|
|
@@ -881,8 +913,8 @@ try {
|
|
|
881
913
|
const uiLanguage = this.config.uiLanguage || 'en';
|
|
882
914
|
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
883
915
|
|
|
884
|
-
this.sourceDir = this.config.sourceDir;
|
|
885
|
-
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
916
|
+
this.sourceDir = this.config.localesDir || this.config.i18nDir || this.config.sourceDir;
|
|
917
|
+
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
886
918
|
this.outputDir = this.config.outputDir;
|
|
887
919
|
}
|
|
888
920
|
|
|
@@ -921,19 +953,20 @@ try {
|
|
|
921
953
|
loadTranslations(args.uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));}
|
|
922
954
|
|
|
923
955
|
// Update config if source directory is provided
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
this.
|
|
927
|
-
this.
|
|
928
|
-
|
|
956
|
+
const localeDirArg = args.i18nDir || (!args.i18nDir && args.sourceDir ? args.sourceDir : null);
|
|
957
|
+
if (localeDirArg) {
|
|
958
|
+
this.config.i18nDir = localeDirArg;
|
|
959
|
+
this.sourceDir = path.resolve(PROJECT_ROOT, localeDirArg);
|
|
960
|
+
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
961
|
+
}
|
|
929
962
|
|
|
930
963
|
if (args.outputDir) {
|
|
931
964
|
this.config.outputDir = args.outputDir;
|
|
932
965
|
this.outputDir = path.resolve(this.config.outputDir);
|
|
933
966
|
}
|
|
934
|
-
const execute = async () => {
|
|
935
|
-
await this.analyze();
|
|
936
|
-
};
|
|
967
|
+
const execute = async () => {
|
|
968
|
+
return await this.analyze();
|
|
969
|
+
};
|
|
937
970
|
|
|
938
971
|
if (args.watch) {
|
|
939
972
|
await execute();
|
|
@@ -949,19 +982,21 @@ try {
|
|
|
949
982
|
});
|
|
950
983
|
console.log('��� Watching for translation changes. Press Ctrl+C to exit.');
|
|
951
984
|
} else {
|
|
952
|
-
await execute();
|
|
953
|
-
if (!fromMenu && require.main === module) {
|
|
954
|
-
process.exit(0);
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
985
|
+
const result = await execute();
|
|
986
|
+
if (!fromMenu && require.main === module) {
|
|
987
|
+
process.exit(0);
|
|
988
|
+
}
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
} catch (error) {
|
|
992
|
+
console.error(t('analyze.error') || '❌ Analysis failed:', error.message);
|
|
993
|
+
this.closeReadline();
|
|
994
|
+
if (!fromMenu && require.main === module) {
|
|
995
|
+
process.exit(1);
|
|
996
|
+
}
|
|
997
|
+
throw error;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
965
1000
|
|
|
966
1001
|
async runSetupWizard() {
|
|
967
1002
|
console.log('Translation Analysis Setup Wizard');
|
package/main/i18ntk-complete.js
CHANGED
|
@@ -16,9 +16,10 @@ const path = require('path');
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const SecurityUtils = require('../utils/security');
|
|
18
18
|
const { loadTranslations, t } = require('../utils/i18n-helper');
|
|
19
|
-
const { getGlobalReadline, closeGlobalReadline } = require('../utils/cli');
|
|
20
|
-
const SetupEnforcer = require('../utils/setup-enforcer');
|
|
21
|
-
const { getUnifiedConfig } = require('../utils/config-helper');
|
|
19
|
+
const { getGlobalReadline, closeGlobalReadline } = require('../utils/cli');
|
|
20
|
+
const SetupEnforcer = require('../utils/setup-enforcer');
|
|
21
|
+
const { getUnifiedConfig, displayHelp } = require('../utils/config-helper');
|
|
22
|
+
const { isInteractive } = require('../utils/prompt-helper');
|
|
22
23
|
|
|
23
24
|
// Ensure setup is complete before running, except for help output.
|
|
24
25
|
(async () => {
|
|
@@ -64,7 +65,7 @@ class I18nCompletionTool {
|
|
|
64
65
|
|
|
65
66
|
const baseConfig = await getUnifiedConfig('complete', args);
|
|
66
67
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
67
|
-
this.sourceDir = this.config.sourceDir;
|
|
68
|
+
this.sourceDir = this.config.localesDir || this.config.i18nDir || this.config.sourceDir;
|
|
68
69
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
69
70
|
|
|
70
71
|
// Validate source directory exists
|
|
@@ -99,24 +100,31 @@ class I18nCompletionTool {
|
|
|
99
100
|
const args = process.argv.slice(2);
|
|
100
101
|
const parsed = {};
|
|
101
102
|
|
|
102
|
-
args.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
for (let i = 0; i < args.length; i++) {
|
|
104
|
+
const arg = args[i];
|
|
105
|
+
if (arg.startsWith('--')) {
|
|
106
|
+
const [key, rawValue] = arg.substring(2).split('=');
|
|
107
|
+
const value = rawValue !== undefined
|
|
108
|
+
? rawValue
|
|
109
|
+
: (args[i + 1] && !args[i + 1].startsWith('--') ? args[++i] : true);
|
|
110
|
+
if (key === 'source-dir' || key === 'locales-dir' || key === 'i18n-dir') {
|
|
111
|
+
parsed.sourceDir = value;
|
|
112
|
+
parsed.i18nDir = value;
|
|
113
|
+
} else if (key === 'source-language' || key === 'source-locale') {
|
|
114
|
+
parsed.sourceLanguage = value;
|
|
115
|
+
} else if (key === 'code-dir' || key === 'source-code-dir') {
|
|
116
|
+
parsed.codeDir = value;
|
|
117
|
+
} else if (key === 'auto-translate') {
|
|
110
118
|
parsed.autoTranslate = true;
|
|
111
119
|
} else if (key === 'dry-run') {
|
|
112
120
|
parsed.dryRun = true;
|
|
113
121
|
} else if (key === 'no-prompt') {
|
|
114
122
|
parsed.noPrompt = true;
|
|
115
123
|
} else if (key === 'help' || key === 'h') {
|
|
116
|
-
parsed.help = true;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
124
|
+
parsed.help = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
120
128
|
|
|
121
129
|
return parsed;
|
|
122
130
|
}
|
|
@@ -567,22 +575,24 @@ class I18nCompletionTool {
|
|
|
567
575
|
|
|
568
576
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
569
577
|
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
570
|
-
this.sourceDir = this.config.sourceDir;
|
|
578
|
+
this.sourceDir = this.config.localesDir || this.config.i18nDir || this.config.sourceDir;
|
|
571
579
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
572
580
|
} else {
|
|
573
581
|
await this.initialize();
|
|
574
582
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
console.error(
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
this.sourceDir =
|
|
585
|
-
|
|
583
|
+
const localesDirArg = args.i18nDir || args.sourceDir;
|
|
584
|
+
if (localesDirArg) {
|
|
585
|
+
const resolvedSourceDir = path.resolve(localesDirArg);
|
|
586
|
+
const validatedSourceDir = SecurityUtils.validatePath(resolvedSourceDir, process.cwd());
|
|
587
|
+
if (!validatedSourceDir) {
|
|
588
|
+
console.error(t("complete.invalidSourceDir", { sourceDir: localesDirArg }));
|
|
589
|
+
console.error(`Path validation failed — source directory is outside the allowed project boundary.`);
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
this.config.sourceDir = resolvedSourceDir;
|
|
593
|
+
this.config.i18nDir = resolvedSourceDir;
|
|
594
|
+
this.sourceDir = validatedSourceDir;
|
|
595
|
+
}
|
|
586
596
|
|
|
587
597
|
if (args.sourceLanguage) {
|
|
588
598
|
this.config.sourceLanguage = SecurityUtils.sanitizeInput(args.sourceLanguage, {
|
|
@@ -643,15 +653,24 @@ class I18nCompletionTool {
|
|
|
643
653
|
}
|
|
644
654
|
|
|
645
655
|
console.log('\n');
|
|
646
|
-
console.log(t("complete.summaryTitle"));
|
|
647
|
-
console.log(t("complete.separator"));
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
656
|
+
console.log(t("complete.summaryTitle"));
|
|
657
|
+
console.log(t("complete.separator"));
|
|
658
|
+
const targetLocalesChanged = allChanges.length;
|
|
659
|
+
const filesModified = new Set(allChanges.flatMap(lang => lang.changes.map(change => `${lang.language}/${change.file}`))).size;
|
|
660
|
+
const filesSkipped = Math.max(0, targetLanguages.length - targetLocalesChanged);
|
|
661
|
+
console.log(`Locales scanned: ${languages.length}`);
|
|
662
|
+
console.log(`Target locales changed: ${targetLocalesChanged}`);
|
|
663
|
+
console.log(`Unique source keys added: ${missingKeys.length}`);
|
|
664
|
+
console.log(`Total key insertions: ${totalChanges}`);
|
|
665
|
+
console.log(`Files modified: ${args.dryRun ? 0 : filesModified}`);
|
|
666
|
+
console.log(`Files skipped: ${filesSkipped}`);
|
|
667
|
+
if (args.dryRun) {
|
|
668
|
+
console.log('Dry run only. No files were modified.');
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (!args.dryRun && allChanges.length > 0 && isInteractive(args)) {
|
|
672
|
+
const rl = this.rl || this.initReadline();
|
|
673
|
+
const answer = await this.prompt('\n' + t('complete.generateReportPrompt') + ' (Y/N): ');
|
|
655
674
|
|
|
656
675
|
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
657
676
|
await this.generateReport(allChanges, languages);
|
package/main/i18ntk-report.js
CHANGED
|
@@ -22,23 +22,32 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
22
22
|
help: false,
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
for (
|
|
25
|
+
for (let i = 0; i < argv.length; i++) {
|
|
26
|
+
const raw = argv[i];
|
|
26
27
|
const arg = raw === 'report' ? '' : raw;
|
|
27
28
|
if (!arg) continue;
|
|
29
|
+
const nextValue = () => {
|
|
30
|
+
if (i + 1 < argv.length && !String(argv[i + 1]).startsWith('--')) {
|
|
31
|
+
return argv[++i];
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
28
35
|
if (arg === '--help' || arg === '-h') args.help = true;
|
|
29
36
|
else if (arg === '--json') args.json = true;
|
|
30
37
|
else if (arg === '--markdown' || arg === '--md') args.markdown = true;
|
|
31
38
|
else if (arg === '--html') args.html = true;
|
|
32
39
|
else if (arg.startsWith('--out=')) args.out = arg.slice('--out='.length);
|
|
33
|
-
else if (arg === '--out') args.
|
|
34
|
-
else if (args.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
else if (arg === '--out') args.out = nextValue();
|
|
41
|
+
else if (arg.startsWith('--source-dir=')) args.sourceDir = arg.slice('--source-dir='.length);
|
|
42
|
+
else if (arg.startsWith('--code-dir=')) args.sourceDir = arg.slice('--code-dir='.length);
|
|
43
|
+
else if (arg.startsWith('--source-code-dir=')) args.sourceDir = arg.slice('--source-code-dir='.length);
|
|
44
|
+
else if (arg === '--source-dir' || arg === '--code-dir' || arg === '--source-code-dir') args.sourceDir = nextValue();
|
|
38
45
|
else if (arg.startsWith('--i18n-dir=')) args.i18nDir = arg.slice('--i18n-dir='.length);
|
|
39
46
|
else if (arg.startsWith('--locales-dir=')) args.i18nDir = arg.slice('--locales-dir='.length);
|
|
47
|
+
else if (arg === '--i18n-dir' || arg === '--locales-dir') args.i18nDir = nextValue();
|
|
40
48
|
else if (arg.startsWith('--source-language=')) args.sourceLocale = arg.slice('--source-language='.length);
|
|
41
49
|
else if (arg.startsWith('--source-locale=')) args.sourceLocale = arg.slice('--source-locale='.length);
|
|
50
|
+
else if (arg === '--source-language' || arg === '--source-locale') args.sourceLocale = nextValue();
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
if (!args.json && !args.markdown && !args.html) {
|
|
@@ -107,9 +116,13 @@ Options:
|
|
|
107
116
|
--markdown Print/write Markdown report output
|
|
108
117
|
--html Print/write HTML report output
|
|
109
118
|
--out <dir> Write selected report formats into a directory
|
|
110
|
-
--
|
|
111
|
-
--
|
|
112
|
-
--source-
|
|
119
|
+
--code-dir=<dir> Source code directory to scan
|
|
120
|
+
--source-code-dir=<dir> Alias for --code-dir
|
|
121
|
+
--source-dir=<dir> Legacy alias for --code-dir
|
|
122
|
+
--locales-dir=<dir> Locale directory
|
|
123
|
+
--i18n-dir=<dir> Alias for --locales-dir
|
|
124
|
+
--source-locale=<code> Source locale code (default: en)
|
|
125
|
+
--source-language=<code> Legacy alias for --source-locale
|
|
113
126
|
`;
|
|
114
127
|
}
|
|
115
128
|
|
package/main/i18ntk-scanner.js
CHANGED
|
@@ -133,6 +133,8 @@ class I18nTextScanner {
|
|
|
133
133
|
|
|
134
134
|
switch (key) {
|
|
135
135
|
case 'source-dir':
|
|
136
|
+
case 'code-dir':
|
|
137
|
+
case 'source-code-dir':
|
|
136
138
|
parsed.sourceDir = value || '';
|
|
137
139
|
if (value === args[i + 1]) i++;
|
|
138
140
|
break;
|
|
@@ -167,6 +169,7 @@ class I18nTextScanner {
|
|
|
167
169
|
parsed.includeTests = true;
|
|
168
170
|
break;
|
|
169
171
|
case 'source-language':
|
|
172
|
+
case 'source-locale':
|
|
170
173
|
parsed.sourceLanguage = value || '';
|
|
171
174
|
if (value === args[i + 1]) i++;
|
|
172
175
|
break;
|
package/main/i18ntk-sizing.js
CHANGED
|
@@ -1141,7 +1141,7 @@ Generated: ${new Date().toISOString()}
|
|
|
1141
1141
|
const key = keyValueMatch[1];
|
|
1142
1142
|
const value = keyValueMatch[2];
|
|
1143
1143
|
|
|
1144
|
-
if (key === 'source-dir' || key === 's') {
|
|
1144
|
+
if (key === 'source-dir' || key === 'locales-dir' || key === 'i18n-dir' || key === 's') {
|
|
1145
1145
|
options['source-dir'] = value;
|
|
1146
1146
|
options.s = value;
|
|
1147
1147
|
options.sourceDirExplicit = true;
|
|
@@ -1162,7 +1162,7 @@ Generated: ${new Date().toISOString()}
|
|
|
1162
1162
|
options.threshold = numValue;
|
|
1163
1163
|
options.t = numValue;
|
|
1164
1164
|
}
|
|
1165
|
-
} else if (key === 'source-language') {
|
|
1165
|
+
} else if (key === 'source-language' || key === 'source-locale') {
|
|
1166
1166
|
options['source-language'] = value;
|
|
1167
1167
|
} else if (key === 'detailed' || key === 'd') {
|
|
1168
1168
|
options.detailed = value.toLowerCase() !== 'false';
|
|
@@ -1184,7 +1184,7 @@ Generated: ${new Date().toISOString()}
|
|
|
1184
1184
|
const key = match[1];
|
|
1185
1185
|
const nextArg = args[i + 1];
|
|
1186
1186
|
|
|
1187
|
-
if (key === 'source-dir' || key === 's') {
|
|
1187
|
+
if (key === 'source-dir' || key === 'locales-dir' || key === 'i18n-dir' || key === 's') {
|
|
1188
1188
|
options['source-dir'] = nextArg || options['source-dir'];
|
|
1189
1189
|
options.s = options['source-dir'];
|
|
1190
1190
|
options.sourceDirExplicit = true;
|
|
@@ -1216,7 +1216,7 @@ Generated: ${new Date().toISOString()}
|
|
|
1216
1216
|
options.t = value;
|
|
1217
1217
|
}
|
|
1218
1218
|
if (nextArg && !nextArg.startsWith('-') && !isNaN(parseInt(nextArg))) i++;
|
|
1219
|
-
} else if (key === 'source-language') {
|
|
1219
|
+
} else if (key === 'source-language' || key === 'source-locale') {
|
|
1220
1220
|
options['source-language'] = nextArg || options['source-language'];
|
|
1221
1221
|
if (nextArg && !nextArg.startsWith('-')) i++;
|
|
1222
1222
|
} else if (key === 'detailed' || key === 'd') {
|
|
@@ -1257,12 +1257,15 @@ I18NTK Sizing Analysis Tool
|
|
|
1257
1257
|
Usage: i18ntk-sizing [options]
|
|
1258
1258
|
|
|
1259
1259
|
Options:
|
|
1260
|
-
-s, --
|
|
1260
|
+
-s, --locales-dir <dir> Locale directory containing translation files (default: ./locales)
|
|
1261
|
+
--i18n-dir <dir> Alias for --locales-dir
|
|
1262
|
+
--source-dir <dir> Legacy alias for --locales-dir
|
|
1261
1263
|
-l, --languages <langs> Comma-separated list of languages to analyze
|
|
1262
1264
|
-o, --output-report Generate detailed sizing report (default: true)
|
|
1263
1265
|
-f, --format <format> Output format: json, csv, table (default: table)
|
|
1264
1266
|
-t, --threshold <number> Size difference threshold for warnings (%) (default: 50)
|
|
1265
|
-
--source-
|
|
1267
|
+
--source-locale <code> Source locale baseline for comparisons (default: en)
|
|
1268
|
+
--source-language <code> Legacy alias for --source-locale
|
|
1266
1269
|
-d, --detailed Generate detailed report with more information
|
|
1267
1270
|
--detailed-keys Show detailed key-level analysis
|
|
1268
1271
|
--predict-expansion Predict UI layout expansion risk per language
|