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 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.3] - 2026-06-19
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.3
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
  [![node](https://img.shields.io/badge/node-%3E%3D16-339933)](https://nodejs.org)
10
10
  [![dependencies](https://img.shields.io/badge/dependencies-0-success)](https://www.npmjs.com/package/i18ntk)
11
11
  [![license](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)
12
- [![socket](https://socket.dev/api/badge/npm/package/i18ntk/4.5.3)](https://socket.dev/npm/package/i18ntk/overview/4.5.3)
12
+ [![socket](https://socket.dev/api/badge/npm/package/i18ntk/4.5.4)](https://socket.dev/npm/package/i18ntk/overview/4.5.4)
13
13
 
14
14
  [![i18ntk Workbench](https://img.shields.io/badge/VS_Code-i18ntk_Workbench-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-workbench)
15
15
  [![i18ntk Lens](https://img.shields.io/badge/VS_Code-i18ntk_Lens-007ACC?logo=visualstudiocode&logoColor=white)](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-language <code>`
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 --source-dir=./src --i18n-dir=./locales --output-dir=./i18ntk-reports
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 --source-dir ./locales --format table
362
- i18ntk-sizing --source-dir ./locales --detailed --output-dir ./i18ntk-reports
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 --source-dir ./locales --predict-expansion --output-report
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 --source-dir ./src --source-language de
389
- i18ntk-scanner --source-dir ./src --source-language ja --output-report
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 --source-dir ./src --i18n-dir ./locales --cleanup
400
- i18ntk-usage --source-dir ./src --i18n-dir ./locales --cleanup --dry-run-delete
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.3",
558
+ "version": "4.5.4",
550
559
  "sourceDir": "./locales",
551
560
  "i18nDir": "./locales",
552
561
  "outputDir": "./i18ntk-reports",
@@ -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
- const processedCount = results.length + 1;
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
- if (args.sourceDir) {
925
- this.config.sourceDir = args.sourceDir;
926
- this.sourceDir = path.resolve(PROJECT_ROOT, this.config.sourceDir);
927
- this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
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
- } catch (error) {
958
- console.error(t('analyze.error') || '❌ Analysis failed:', error.message);
959
- this.closeReadline();
960
- if (!fromMenu && require.main === module) {
961
- process.exit(1);
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');
@@ -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.forEach(arg => {
103
- if (arg.startsWith('--')) {
104
- const [key, value] = arg.substring(2).split('=');
105
- if (key === 'source-dir') {
106
- parsed.sourceDir = value;
107
- } else if (key === 'source-language') {
108
- parsed.sourceLanguage = value;
109
- } else if (key === 'auto-translate') {
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
- if (args.sourceDir) {
576
- const resolvedSourceDir = path.resolve(args.sourceDir);
577
- const validatedSourceDir = SecurityUtils.validatePath(resolvedSourceDir, process.cwd());
578
- if (!validatedSourceDir) {
579
- console.error(t("complete.invalidSourceDir", { sourceDir: args.sourceDir }));
580
- console.error(`Path validation failed source directory is outside the allowed project boundary.`);
581
- process.exit(1);
582
- }
583
- this.config.sourceDir = resolvedSourceDir;
584
- this.sourceDir = validatedSourceDir;
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
- console.log(t("complete.totalChanges", { totalChanges }));
649
- console.log(t("complete.languagesProcessed", { languagesProcessed: languages.length }));
650
- console.log(t("complete.missingKeysAdded", { missingKeysAdded: missingKeys.length }));
651
-
652
- if (!args.dryRun && allChanges.length > 0 && !args.noPrompt) {
653
- const rl = this.rl || this.initReadline();
654
- const answer = await this.prompt('\n' + t('complete.generateReportPrompt') + ' (Y/N): ');
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);
@@ -22,23 +22,32 @@ function parseArgs(argv = process.argv.slice(2)) {
22
22
  help: false,
23
23
  };
24
24
 
25
- for (const raw of argv) {
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.expectOut = true;
34
- else if (args.expectOut) {
35
- args.out = arg;
36
- args.expectOut = false;
37
- } else if (arg.startsWith('--source-dir=')) args.sourceDir = arg.slice('--source-dir='.length);
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
- --source-dir=<dir> Source code directory to scan
111
- --i18n-dir=<dir> Locale directory
112
- --source-language=<code> Source locale code (default: en)
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
 
@@ -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;
@@ -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, --source-dir <dir> Source directory containing translation files (default: ./locales)
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-language <code> Source language baseline for comparisons (default: en)
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