i18ntk 4.5.3 → 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 +31 -5
- package/README.md +23 -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 +29 -16
- 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 +40 -30
- package/utils/exit-codes.js +8 -6
- package/utils/prompt-helper.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,11 +3,37 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [4.5.
|
|
9
|
-
|
|
10
|
-
### Fixed
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [4.5.4] - 2026-06-19
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Analyze:** Fixed `this.provideSetupGuidance is not a function` when no target locales are available. Analyze now prints setup guidance instead of crashing.
|
|
13
|
+
- **Manager Route:** Manager-routed commands now propagate subcommand failures and no longer print `Operation completed successfully!` after a runtime failure.
|
|
14
|
+
- **Complete:** Fixed `i18ntk-complete --help` by wiring the shared help renderer and keeping help output side-effect free.
|
|
15
|
+
- **Validate:** Removed mixed failure/success output. Validation only prints successful completion when the validation result succeeds.
|
|
16
|
+
- **Summary:** Guarded empty locale roots so `Average keys per language` prints `0` instead of `NaN`, and missing locale roots exit as invalid setup/argument failures.
|
|
17
|
+
- **Analyze Reports:** Report write failures now fail analyze with a non-zero exit code instead of continuing with success wording.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **CLI Flags:** Added canonical aliases: `--code-dir` / `--source-code-dir` for application source files, `--locales-dir` / `--i18n-dir` for locale files, and `--source-locale` for source language. Legacy `--source-dir`, `--i18n-dir`, and `--source-language` remain supported.
|
|
22
|
+
- **Exit Codes:** Standardized automation behavior: `0` for success, `1` for validation/report/runtime failures, and `2` for invalid arguments or missing setup in non-interactive mode.
|
|
23
|
+
- **CI Behavior:** Commands skip prompts when `--no-prompt` is passed, `CI=true`, stdin is not a TTY, or stdout is not a TTY.
|
|
24
|
+
- **Complete Summary:** Completion output now distinguishes locales scanned, target locales changed, unique source keys added, total key insertions, files modified, files skipped, and dry-run status.
|
|
25
|
+
|
|
26
|
+
### Documentation
|
|
27
|
+
|
|
28
|
+
- Updated README, API, configuration, scanner, and environment-variable docs with canonical CLI flags, legacy alias notes, and CI exit-code behavior.
|
|
29
|
+
|
|
30
|
+
### Tests
|
|
31
|
+
|
|
32
|
+
- Added CLI reliability regression coverage for the analyze crash, manager failure propagation, complete help output, directory/source-locale aliases, summary `NaN`, non-interactive prompt suppression, validation wording, and completion summary labels.
|
|
33
|
+
|
|
34
|
+
## [4.5.3] - 2026-06-19
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
11
37
|
|
|
12
38
|
- **Usage Scanner (Critical):** Fixed `supportedExtensions` default fallback in `config-helper.js` that excluded `.tsx` and `.jsx` files from source scanning. The default was `['.json', '.js', '.ts']` — missing `.jsx` and `.tsx`. In a Next.js project with 2704 keys across 1087 files, this caused the scanner to find only 57 keys (2.1%) instead of 2702 (99.9%) because all `.tsx` component files were silently ignored. The fix adds `.jsx` and `.tsx` to the default fallback: `['.json', '.js', '.jsx', '.ts', '.tsx']`.
|
|
13
39
|
- **Usage Dead Code:** Cleaned up unreachable initializer in `i18ntk-usage.js` that was supposed to set default `includeExtensions` but never ran because `supportedExtensions` was always set by `config-helper.js`. Added `.vue` and `.svelte` to the fallback for completeness.
|
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,13 @@ 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
|
+
|
|
67
74
|
## What's New in 4.5.3
|
|
68
75
|
|
|
69
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.
|
|
@@ -193,20 +200,22 @@ Note: manager route `i18ntk --command=backup` is available via the interactive m
|
|
|
193
200
|
|
|
194
201
|
Many commands support:
|
|
195
202
|
|
|
196
|
-
- `--source-dir <path>`
|
|
197
|
-
- `--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
|
|
198
205
|
- `--output-dir <path>`
|
|
199
|
-
- `--source-
|
|
206
|
+
- `--source-locale <code>`
|
|
200
207
|
- `--ui-language <code>`
|
|
201
208
|
- `--no-prompt`
|
|
202
209
|
- `--help`
|
|
203
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
|
+
|
|
204
213
|
Command-specific tools add their own flags such as `--dry-run`, `--output-report`, `--cleanup`, `--predict-expansion`, or Auto Translate provider options.
|
|
205
214
|
|
|
206
215
|
Example:
|
|
207
216
|
|
|
208
217
|
```bash
|
|
209
|
-
i18ntk --command=analyze --
|
|
218
|
+
i18ntk --command=analyze --code-dir=./src --locales-dir=./locales --source-locale=en --output-dir=./i18ntk-reports
|
|
210
219
|
```
|
|
211
220
|
|
|
212
221
|
## Auto Translate
|
|
@@ -358,8 +367,8 @@ Tune warnings in `.i18ntk-config`:
|
|
|
358
367
|
`i18ntk-sizing` reports translation file sizes, key counts, average value length, and file-set mismatches across language folders.
|
|
359
368
|
|
|
360
369
|
```bash
|
|
361
|
-
i18ntk-sizing --
|
|
362
|
-
i18ntk-sizing --
|
|
370
|
+
i18ntk-sizing --locales-dir ./locales --format table
|
|
371
|
+
i18ntk-sizing --locales-dir ./locales --detailed --output-dir ./i18ntk-reports
|
|
363
372
|
```
|
|
364
373
|
|
|
365
374
|
Use `--detailed` to print per-file rows in the terminal.
|
|
@@ -369,7 +378,7 @@ Use `--detailed` to print per-file rows in the terminal.
|
|
|
369
378
|
Predict UI layout overflow risk by analyzing per-key character-count expansion across languages:
|
|
370
379
|
|
|
371
380
|
```bash
|
|
372
|
-
i18ntk-sizing --
|
|
381
|
+
i18ntk-sizing --locales-dir ./locales --predict-expansion --output-report
|
|
373
382
|
```
|
|
374
383
|
|
|
375
384
|
Expansion ratios are classified into risk tiers:
|
|
@@ -385,8 +394,8 @@ The report includes a built-in language-pair expansion reference table (EN→DE
|
|
|
385
394
|
`i18ntk-scanner` now supports detecting hardcoded text in multiple source languages beyond English:
|
|
386
395
|
|
|
387
396
|
```bash
|
|
388
|
-
i18ntk-scanner --
|
|
389
|
-
i18ntk-scanner --
|
|
397
|
+
i18ntk-scanner --code-dir ./src --source-locale de
|
|
398
|
+
i18ntk-scanner --code-dir ./src --source-locale ja --output-report
|
|
390
399
|
```
|
|
391
400
|
|
|
392
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.
|
|
@@ -396,8 +405,8 @@ Supported language profiles (12+): English, German, French, Spanish, Japanese, C
|
|
|
396
405
|
`i18ntk-usage` can identify translation keys that are defined but never referenced in source code:
|
|
397
406
|
|
|
398
407
|
```bash
|
|
399
|
-
i18ntk-usage --
|
|
400
|
-
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
|
|
401
410
|
```
|
|
402
411
|
|
|
403
412
|
Each dead key receives a confidence score (0.0–1.0) factoring:
|
|
@@ -546,7 +555,7 @@ Example:
|
|
|
546
555
|
|
|
547
556
|
```json
|
|
548
557
|
{
|
|
549
|
-
"version": "4.5.
|
|
558
|
+
"version": "4.5.4",
|
|
550
559
|
"sourceDir": "./locales",
|
|
551
560
|
"i18nDir": "./locales",
|
|
552
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
|