i18ntk 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +116 -29
  2. package/README.md +83 -18
  3. package/SECURITY.md +13 -5
  4. package/main/i18ntk-analyze.js +10 -20
  5. package/main/i18ntk-backup.js +227 -111
  6. package/main/i18ntk-init.js +153 -157
  7. package/main/i18ntk-scanner.js +9 -7
  8. package/main/i18ntk-setup.js +36 -13
  9. package/main/i18ntk-sizing.js +18 -50
  10. package/main/i18ntk-translate.js +169 -21
  11. package/main/i18ntk-usage.js +298 -154
  12. package/main/i18ntk-validate.js +49 -37
  13. package/main/manage/commands/AnalyzeCommand.js +7 -17
  14. package/main/manage/commands/CommandRouter.js +6 -6
  15. package/main/manage/commands/TranslateCommand.js +65 -56
  16. package/main/manage/commands/ValidateCommand.js +34 -26
  17. package/main/manage/index.js +11 -42
  18. package/main/manage/managers/InteractiveMenu.js +11 -40
  19. package/main/manage/services/InitService.js +114 -118
  20. package/main/manage/services/UsageService.js +244 -85
  21. package/package.json +55 -4
  22. package/runtime/enhanced.d.ts +5 -5
  23. package/runtime/enhanced.js +49 -25
  24. package/runtime/i18ntk.d.ts +30 -7
  25. package/runtime/index.d.ts +48 -19
  26. package/runtime/index.js +188 -97
  27. package/settings/settings-cli.js +115 -38
  28. package/settings/settings-manager.js +24 -6
  29. package/ui-locales/de.json +192 -11
  30. package/ui-locales/en.json +182 -8
  31. package/ui-locales/es.json +193 -12
  32. package/ui-locales/fr.json +189 -8
  33. package/ui-locales/ja.json +190 -8
  34. package/ui-locales/ru.json +191 -9
  35. package/ui-locales/zh.json +194 -9
  36. package/utils/cli-helper.js +8 -12
  37. package/utils/config-helper.js +1 -1
  38. package/utils/config-manager.js +8 -6
  39. package/utils/localized-confirm.js +55 -0
  40. package/utils/menu-layout.js +41 -0
  41. package/utils/report-writer.js +110 -0
  42. package/utils/security.js +15 -22
  43. package/utils/translate/api.js +31 -3
  44. package/utils/translate/placeholder.js +42 -1
  45. package/utils/translate/protection.js +17 -12
  46. package/utils/translate/report.js +3 -2
  47. package/utils/translate/safe-network.js +24 -4
  48. package/utils/usage-insights.js +435 -0
  49. package/utils/usage-source.js +50 -0
  50. package/utils/watch-locales.js +13 -9
@@ -20,8 +20,10 @@ const SettingsManager = require('../../../settings/settings-manager');
20
20
  const settingsManager = new SettingsManager();
21
21
  const { getUnifiedConfig, parseCommonArgs, displayHelp, validateSourceDir, displayPaths } = require('../../../utils/config-helper');
22
22
  const I18nInitializer = require('../../i18ntk-init');
23
- const JsonOutput = require('../../../utils/json-output');
24
- const SetupEnforcer = require('../../../utils/setup-enforcer');
23
+ const JsonOutput = require('../../../utils/json-output');
24
+ const SetupEnforcer = require('../../../utils/setup-enforcer');
25
+ const { resolveUsageSourceDir } = require('../../../utils/usage-source');
26
+ const { analyzeSourceForUsageInsights } = require('../../../utils/usage-insights');
25
27
 
26
28
  class UsageService {
27
29
  constructor(config = {}) {
@@ -32,9 +34,14 @@ class UsageService {
32
34
 
33
35
  // Initialize class properties
34
36
  this.availableKeys = new Set();
35
- this.usedKeys = new Set();
36
- this.fileUsage = new Map();
37
- this.translationFiles = new Map(); // Track all translation files
37
+ this.usedKeys = new Set();
38
+ this.fileUsage = new Map();
39
+ this.keyUsageLocations = new Map();
40
+ this.hardcodedTextCandidates = [];
41
+ this.namespaceRecommendations = [];
42
+ this.unresolvedDynamicReferences = [];
43
+ this.translationValueIndex = new Map();
44
+ this.translationFiles = new Map(); // Track all translation files
38
45
  this.translationStats = new Map(); // Track translation completeness
39
46
  this.extractor = getExtractor(config.extractor);
40
47
  this.placeholderKeys = new Set();
@@ -324,11 +331,16 @@ class UsageService {
324
331
  }
325
332
 
326
333
  // Analyze usage in source files
327
- async analyzeUsage() {
328
- try {
329
- console.log(t('usage.checkUsage.analyzing_source_files'));
330
-
331
- // Check if source directory exists
334
+ async analyzeUsage() {
335
+ try {
336
+ console.log(t('usage.checkUsage.analyzing_source_files'));
337
+
338
+ if (!this.sourceDir) {
339
+ console.warn(t('usage.noSourceFilesFound'));
340
+ return;
341
+ }
342
+
343
+ // Check if source directory exists
332
344
  if (!SecurityUtils.safeExistsSync(this.sourceDir)) {
333
345
  throw new Error(this.t('usage.sourceDirectoryDoesNotExist', { dir: this.sourceDir }) || `Source directory not found: ${this.sourceDir}`);
334
346
  }
@@ -345,21 +357,25 @@ class UsageService {
345
357
  let totalKeysFound = 0;
346
358
  let processedFiles = 0;
347
359
 
348
- for (const filePath of sourceFiles) {
349
- try {
350
- const keys = this.extractKeysFromFile(filePath);
351
-
352
- if (keys.length > 0) {
353
- const relativePath = path.relative(this.sourceDir, filePath);
354
- this.fileUsage.set(relativePath, keys);
355
-
356
- keys.forEach(key => {
357
- this.usedKeys.add(key);
358
- totalKeysFound++;
359
- });
360
- }
361
-
362
- processedFiles++;
360
+ for (const filePath of sourceFiles) {
361
+ try {
362
+ const content = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
363
+ if (!content) continue;
364
+
365
+ const relativePath = path.relative(this.sourceDir, filePath);
366
+ const directKeys = this.extractKeysFromContent(content, filePath);
367
+ const insights = analyzeSourceForUsageInsights({
368
+ content,
369
+ relativePath,
370
+ availableKeys: this.availableKeys,
371
+ directKeys,
372
+ translationValueIndex: this.translationValueIndex,
373
+ });
374
+
375
+ this.detectFrameworkPatterns(content, relativePath);
376
+ totalKeysFound += this.recordUsageInsights(relativePath, insights);
377
+
378
+ processedFiles++;
363
379
 
364
380
  // Progress indicator for large numbers of files
365
381
  if (sourceFiles.length > 10 && processedFiles % Math.ceil(sourceFiles.length / 10) === 0) {
@@ -371,10 +387,22 @@ class UsageService {
371
387
  }
372
388
  }
373
389
 
374
- console.log(t("usage.checkUsage.found_thisusedkeyssize_unique_", { usedKeysSize: this.usedKeys.size }));
375
- console.log(t("usage.checkUsage.total_key_usages_totalkeysfoun", { totalKeysFound }));
376
-
377
- } catch (error) {
390
+ console.log(t("usage.checkUsage.found_thisusedkeyssize_unique_", { usedKeysSize: this.usedKeys.size }));
391
+ console.log(t("usage.checkUsage.total_key_usages_totalkeysfoun", { totalKeysFound }));
392
+ if (this.keyUsageLocations.size > 0) {
393
+ console.log(`🔎 Indexed ${this.keyUsageLocations.size} keys with source file locations`);
394
+ }
395
+ if (this.namespaceRecommendations.length > 0) {
396
+ console.log(`🧭 Namespace recommendations: ${this.namespaceRecommendations.length}`);
397
+ }
398
+ if (this.hardcodedTextCandidates.length > 0) {
399
+ console.log(`📝 Hardcoded text candidates: ${this.hardcodedTextCandidates.length}`);
400
+ }
401
+ if (this.unresolvedDynamicReferences.length > 0) {
402
+ console.log(`🧩 Unresolved dynamic key expressions: ${this.unresolvedDynamicReferences.length}`);
403
+ }
404
+
405
+ } catch (error) {
378
406
  console.error(t('usage.failedToAnalyzeUsage', { error: error.message }));
379
407
  throw error;
380
408
  }
@@ -498,11 +526,17 @@ class UsageService {
498
526
 
499
527
  if (value && typeof value === 'object' && !Array.isArray(value)) {
500
528
  keys.push(...this.extractKeysFromObject(value, fullKey, namespace));
501
- } else {
502
- // Add dot notation key (e.g., "pagination.showing")
503
- keys.push(fullKey);
504
- }
505
- }
529
+ } else {
530
+ // Add dot notation key (e.g., "pagination.showing")
531
+ keys.push(fullKey);
532
+ if (typeof value === 'string') {
533
+ const normalizedValue = value.replace(/\s+/g, ' ').trim();
534
+ if (normalizedValue && !this.translationValueIndex.has(normalizedValue)) {
535
+ this.translationValueIndex.set(normalizedValue, fullKey);
536
+ }
537
+ }
538
+ }
539
+ }
506
540
  } catch (error) {
507
541
  // Handle any unexpected errors during key extraction
508
542
  console.warn(`⚠️ Error during key extraction: ${error.message}`);
@@ -545,19 +579,68 @@ class UsageService {
545
579
  const content = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
546
580
  if (!content) return [];
547
581
 
548
- // Skip JSON files entirely to prevent scanning translation files
549
- if (filePath.endsWith('.json')) return [];
550
- const rawPatterns = Array.isArray(this.config.translationPatterns) ? this.config.translationPatterns : [];
551
- if (rawPatterns.length === 0) return [];
552
-
553
- return this.extractor.extract(content, rawPatterns);
582
+ return this.extractKeysFromContent(content, filePath);
554
583
 
555
584
  // Null-safe translation patterns handling
556
585
  } catch (error) {
557
586
  console.warn(`${t('usage.failedToExtractKeys')} ${filePath}: ${error.message}`);
558
587
  return [];
559
588
  }
560
- }
589
+ }
590
+
591
+ extractKeysFromContent(content, filePath = '') {
592
+ if (!content) return [];
593
+ if (filePath && filePath.endsWith('.json')) return [];
594
+ const rawPatterns = Array.isArray(this.config.translationPatterns) ? this.config.translationPatterns : [];
595
+ if (rawPatterns.length === 0) return [];
596
+ return this.extractor.extract(content, rawPatterns);
597
+ }
598
+
599
+ recordUsageInsights(relativePath, insights) {
600
+ const keys = [];
601
+ const seen = new Set();
602
+
603
+ for (const ref of insights.keyReferences || []) {
604
+ if (!ref.key || seen.has(ref.key)) continue;
605
+ seen.add(ref.key);
606
+ keys.push(ref.key);
607
+ this.usedKeys.add(ref.key);
608
+
609
+ if (!this.keyUsageLocations.has(ref.key)) {
610
+ this.keyUsageLocations.set(ref.key, []);
611
+ }
612
+ this.keyUsageLocations.get(ref.key).push({
613
+ filePath: relativePath,
614
+ line: ref.line,
615
+ column: ref.column,
616
+ matchType: ref.matchType,
617
+ });
618
+ }
619
+
620
+ if (keys.length > 0) {
621
+ this.fileUsage.set(relativePath, keys);
622
+ }
623
+
624
+ if (insights.namespaceRecommendation) {
625
+ this.namespaceRecommendations.push({
626
+ filePath: relativePath,
627
+ ...insights.namespaceRecommendation,
628
+ });
629
+ }
630
+
631
+ if (Array.isArray(insights.hardcodedTexts) && insights.hardcodedTexts.length > 0) {
632
+ this.hardcodedTextCandidates.push(...insights.hardcodedTexts);
633
+ }
634
+
635
+ if (Array.isArray(insights.unresolvedDynamicReferences) && insights.unresolvedDynamicReferences.length > 0) {
636
+ this.unresolvedDynamicReferences.push(...insights.unresolvedDynamicReferences.map(ref => ({
637
+ filePath: relativePath,
638
+ ...ref,
639
+ })));
640
+ }
641
+
642
+ return keys.length;
643
+ }
561
644
 
562
645
  // Analyze translation completeness across all languages
563
646
  async analyzeTranslationCompleteness() {
@@ -791,9 +874,19 @@ class UsageService {
791
874
  return missing;
792
875
  }
793
876
 
794
- // Find files that use specific keys
795
- findKeyUsage(searchKey) {
796
- const usage = [];
877
+ // Find files that use specific keys
878
+ findKeyUsage(searchKey) {
879
+ if (this.keyUsageLocations && this.keyUsageLocations.has(searchKey)) {
880
+ return this.keyUsageLocations.get(searchKey).map(location => ({
881
+ filePath: location.filePath,
882
+ keys: [searchKey],
883
+ line: location.line,
884
+ column: location.column,
885
+ matchType: location.matchType,
886
+ }));
887
+ }
888
+
889
+ const usage = [];
797
890
 
798
891
  for (const [filePath, keys] of this.fileUsage) {
799
892
  const matchingKeys = keys.filter(key => {
@@ -864,13 +957,14 @@ class UsageService {
864
957
  // Translation completeness with advanced scoring
865
958
  report += `${t('summary.usageReportTranslationCompleteness')}\n`;
866
959
  report += `${'='.repeat(50)}\n`;
867
- for (const [language, stats] of this.translationStats) {
868
- const translations = this.translationsByLanguage[language] || {};
869
- const score = this.calculateTranslationScore ? this.calculateTranslationScore(language, translations) : {
870
- completeness: ((stats.translated / stats.total) * 100).toFixed(1),
871
- quality: ((stats.translated / stats.total) * 100).toFixed(1),
872
- placeholderAccuracy: 100
873
- };
960
+ for (const [language, stats] of this.translationStats) {
961
+ const translations = this.translationsByLanguage ? this.translationsByLanguage[language] : null;
962
+ const completeness = stats.total > 0 ? ((stats.translated / stats.total) * 100).toFixed(1) : '0.0';
963
+ const score = translations && this.calculateTranslationScore ? this.calculateTranslationScore(language, translations) : {
964
+ completeness,
965
+ quality: completeness,
966
+ placeholderAccuracy: 100
967
+ };
874
968
 
875
969
  report += `${t('summary.usageReportLanguageCompleteness', { language: language.toUpperCase(), completeness: score.completeness, translated: stats.translated, total: stats.total })}\n`;
876
970
  report += ` Quality: ${score.quality}%\n`;
@@ -901,10 +995,81 @@ class UsageService {
901
995
 
902
996
  report += ` Simple keys: ${complexityStats.simple}\n`;
903
997
  report += ` Moderate keys: ${complexityStats.moderate}\n`;
904
- report += ` Complex keys: ${complexityStats.complex}\n\n`;
905
- }
906
-
907
- // Unused keys with complexity
998
+ report += ` Complex keys: ${complexityStats.complex}\n\n`;
999
+ }
1000
+
1001
+ const matchCounts = { direct: 0, literal: 0 };
1002
+ for (const locations of this.keyUsageLocations.values()) {
1003
+ for (const location of locations) {
1004
+ matchCounts[location.matchType] = (matchCounts[location.matchType] || 0) + 1;
1005
+ }
1006
+ }
1007
+
1008
+ report += `🔎 Usage Match Index\n`;
1009
+ report += `${'='.repeat(50)}\n`;
1010
+ report += `Direct i18n calls: ${matchCounts.direct || 0}\n`;
1011
+ report += `Known-key literal matches: ${matchCounts.literal || 0}\n`;
1012
+ report += `Resolved dynamic expressions: ${(matchCounts['dynamic-template'] || 0) + (matchCounts['dynamic-variable'] || 0)}\n`;
1013
+ report += `Unresolved dynamic expressions: ${this.unresolvedDynamicReferences.length}\n`;
1014
+ report += `Indexed keys with file locations: ${this.keyUsageLocations.size}\n\n`;
1015
+
1016
+ const indexedKeys = Array.from(this.keyUsageLocations.entries()).slice(0, 100);
1017
+ indexedKeys.forEach(([key, locations]) => {
1018
+ report += `- ${key}\n`;
1019
+ locations.slice(0, 5).forEach(location => {
1020
+ const line = location.line ? `:${location.line}` : '';
1021
+ report += ` - ${location.filePath}${line} (${location.matchType})\n`;
1022
+ });
1023
+ if (locations.length > 5) {
1024
+ report += ` - ... ${locations.length - 5} more locations\n`;
1025
+ }
1026
+ });
1027
+ if (this.keyUsageLocations.size > 100) {
1028
+ report += `... ${this.keyUsageLocations.size - 100} more indexed keys\n`;
1029
+ }
1030
+ report += `\n`;
1031
+
1032
+ if (this.namespaceRecommendations.length > 0) {
1033
+ report += `🧭 Namespace Recommendations\n`;
1034
+ report += `${'='.repeat(50)}\n`;
1035
+ this.namespaceRecommendations.slice(0, 30).forEach(item => {
1036
+ report += `- ${item.filePath}: ${item.message}\n`;
1037
+ });
1038
+ if (this.namespaceRecommendations.length > 30) {
1039
+ report += `... ${this.namespaceRecommendations.length - 30} more recommendations\n`;
1040
+ }
1041
+ report += `\n`;
1042
+ }
1043
+
1044
+ if (this.unresolvedDynamicReferences.length > 0) {
1045
+ report += `🧩 Unresolved Dynamic Key Expressions\n`;
1046
+ report += `${'='.repeat(50)}\n`;
1047
+ report += `These calls could not be resolved to exact keys without executing code. Review them manually or prefer bounded literal maps/arrays for analyzable dynamic keys.\n\n`;
1048
+ this.unresolvedDynamicReferences.slice(0, 50).forEach(item => {
1049
+ const prefix = item.prefix ? ` prefix: ${item.prefix}` : ' no static prefix';
1050
+ report += `- ${item.filePath}:${item.line} ${item.expression} (${prefix})\n`;
1051
+ });
1052
+ if (this.unresolvedDynamicReferences.length > 50) {
1053
+ report += `... ${this.unresolvedDynamicReferences.length - 50} more unresolved expressions\n`;
1054
+ }
1055
+ report += `\n`;
1056
+ }
1057
+
1058
+ if (this.hardcodedTextCandidates.length > 0) {
1059
+ report += `📝 Hardcoded Text Candidates\n`;
1060
+ report += `${'='.repeat(50)}\n`;
1061
+ report += `Inline user-facing text that may be moved into locale files.\n\n`;
1062
+ this.hardcodedTextCandidates.slice(0, 50).forEach(item => {
1063
+ const existing = item.existingKey ? ` existing key: ${item.existingKey}` : ` suggested key: ${item.suggestedKey}`;
1064
+ report += `- ${item.filePath}:${item.line} "${item.text}" (${existing})\n`;
1065
+ });
1066
+ if (this.hardcodedTextCandidates.length > 50) {
1067
+ report += `... ${this.hardcodedTextCandidates.length - 50} more candidates\n`;
1068
+ }
1069
+ report += `\n`;
1070
+ }
1071
+
1072
+ // Unused keys with complexity
908
1073
  if (unusedKeys.length > 0) {
909
1074
  report += `${t('summary.usageReportUnusedTranslationKeys')}\n`;
910
1075
  report += `${'='.repeat(50)}\n`;
@@ -1318,31 +1483,25 @@ class UsageService {
1318
1483
  });
1319
1484
  }
1320
1485
 
1321
- // Ensure sourceDir points to source code, not locales
1322
- if (!args.sourceDir && this.config.sourceDir === this.config.i18nDir) {
1323
- // Default to common source directories if not explicitly provided
1324
- const possibleSourceDirs = ['src', 'lib', 'app', 'source'];
1325
-
1326
- const projectRoot = this.config.projectRoot || '.';
1327
-
1328
- for (const dir of possibleSourceDirs) {
1329
- const testPath = path.resolve(projectRoot, dir);
1330
- if (SecurityUtils.safeExistsSync(testPath)) {
1331
- this.config.sourceDir = testPath;
1332
- this.sourceDir = testPath;
1333
- break;
1334
- }
1335
- }
1336
-
1337
- // If no common source directory found, use current directory
1338
- if (this.config.sourceDir === this.config.i18nDir) {
1339
- this.config.sourceDir = projectRoot;
1340
- this.sourceDir = projectRoot;
1341
- }
1342
- }
1486
+ const usageSource = resolveUsageSourceDir({
1487
+ sourceDir: this.sourceDir || this.config.sourceDir,
1488
+ i18nDir: this.i18nDir || this.config.i18nDir,
1489
+ projectRoot: this.config.projectRoot || process.cwd(),
1490
+ explicitSourceDir: Boolean(args.sourceDir),
1491
+ });
1492
+ if (usageSource.reason) {
1493
+ console.warn(t('usage.sourceEqualsI18nWarn', { reason: usageSource.reason }) || `Warning: ${usageSource.reason}`);
1494
+ }
1495
+ this.sourceDir = usageSource.sourceDir;
1496
+ this.config.sourceDir = usageSource.sourceDir;
1497
+ if (this.sourceDir) {
1498
+ await configManager.updateConfig({
1499
+ sourceDir: configManager.toRelative(this.sourceDir)
1500
+ });
1501
+ }
1343
1502
 
1344
1503
  // 🚧 prevent scanning locales as source
1345
- if (path.resolve(this.sourceDir) === path.resolve(this.i18nDir)) {
1504
+ if (this.sourceDir && !args.sourceDir && path.resolve(this.sourceDir) === path.resolve(this.i18nDir)) {
1346
1505
  const fallback = path.resolve(this.config.projectRoot || '.', 'src');
1347
1506
  console.warn(t('usage.sourceEqualsI18nWarn') ||
1348
1507
  `⚠️ sourceDir equals i18nDir (${this.sourceDir}). Falling back to ${fallback} for source scanning.`);
@@ -1358,17 +1517,14 @@ class UsageService {
1358
1517
  });
1359
1518
  }
1360
1519
 
1361
- console.log(t('usage.detectedSourceDirectory', { sourceDir: this.sourceDir }));
1520
+ console.log(t('usage.detectedSourceDirectory', { sourceDir: this.sourceDir || t('usage.noSourceDirectoryConfigured') || '(none)' }));
1362
1521
  console.log(t('usage.detectedI18nDirectory', { i18nDir: this.i18nDir }));
1363
1522
 
1364
1523
  // Load available translation keys first
1365
1524
  await this.loadAvailableKeys();
1366
1525
 
1367
- // NEW: Detect framework patterns before analysis
1368
- await this.detectFrameworkPatterns();
1369
-
1370
- // Perform usage analysis with enhanced features
1371
- await this.analyzeUsage();
1526
+ // Perform usage analysis with enhanced features
1527
+ await this.analyzeUsage();
1372
1528
 
1373
1529
  // NEW: Validate placeholder keys
1374
1530
  await this.validatePlaceholderKeys();
@@ -1504,7 +1660,10 @@ Analysis Features (v1.8.3):
1504
1660
  • Framework-specific pattern recognition (React, Vue, Angular)
1505
1661
  • Advanced translation completeness scoring
1506
1662
  • Performance metrics and optimization tracking
1507
- • Key complexity analysis
1663
+ • Key complexity analysis
1664
+ • Known-key literal matching with source file locations
1665
+ • Namespace/file naming recommendations (for example app/shop -> shop.json)
1666
+ • Hardcoded user-facing text candidates with suggested translation keys
1508
1667
  • Security-enhanced path validation
1509
1668
  • Detailed reporting with validation errors
1510
1669
  `);
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "i18ntk",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "description": "i18n Tool Kit - Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, auto translation, fixing, reporting, and runtime translation loading.",
5
5
  "readmeFilename": "README.md",
6
6
  "keywords": [
7
7
  "i18ntk",
8
8
  "i18n",
9
+ "i18n toolkit",
10
+ "i18n tool kit",
9
11
  "internationalization",
10
12
  "localization",
11
13
  "translation",
@@ -53,7 +55,7 @@
53
55
  "exports": {
54
56
  ".": "./main/manage/index.js",
55
57
  "./runtime": {
56
- "types": "./runtime/i18ntk.d.ts",
58
+ "types": "./runtime/index.d.ts",
57
59
  "require": "./runtime/index.js",
58
60
  "default": "./runtime/index.js"
59
61
  },
@@ -105,7 +107,6 @@
105
107
  "settings/settings-cli.js",
106
108
  "settings/settings-manager.js",
107
109
  "ui-locales/",
108
- "!main/manage/index-fixed.js",
109
110
  "utils/admin-auth.js",
110
111
  "utils/admin-cli.js",
111
112
  "utils/cli-helper.js",
@@ -131,16 +132,21 @@
131
132
  "utils/i18n-helper.js",
132
133
  "utils/init-helper.js",
133
134
  "utils/json-output.js",
135
+ "utils/localized-confirm.js",
134
136
  "utils/locale-optimizer.js",
135
137
  "utils/logger.js",
138
+ "utils/menu-layout.js",
136
139
  "utils/npm-version-warning.js",
137
140
  "utils/plugin-loader.js",
138
141
  "utils/prompt-helper.js",
139
142
  "utils/prompt.js",
143
+ "utils/report-writer.js",
140
144
  "utils/secure-errors.js",
141
145
  "utils/security.js",
142
146
  "utils/setup-enforcer.js",
143
147
  "utils/terminal-icons.js",
148
+ "utils/usage-source.js",
149
+ "utils/usage-insights.js",
144
150
  "utils/validation-risk.js",
145
151
  "utils/version-utils.js",
146
152
  "utils/watch-locales.js",
@@ -161,5 +167,50 @@
161
167
  "access": "public"
162
168
  },
163
169
  "preferGlobal": true,
164
- "readme": "# i18ntk v4.0.0\n\nA i18n toolkit - A zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, translation completion, automatic JSON locale translation, reporting, and runtime translation loading.\n\n![i18ntk Logo](https://raw.githubusercontent.com/vladnoskv/i18ntk/main/docs/screenshots/i18ntk-logo-public.PNG)\n\n[![npm version](https://img.shields.io/npm/v/i18ntk.svg?color=brightgreen)](https://www.npmjs.com/package/i18ntk)\n[![npm downloads](https://img.shields.io/npm/dt/i18ntk.svg)](https://www.npmjs.com/package/i18ntk)\n[![node](https://img.shields.io/badge/node-%3E%3D16-339933)](https://nodejs.org)\n[![dependencies](https://img.shields.io/badge/dependencies-0-success)](https://www.npmjs.com/package/i18ntk)\n[![license](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)\n[![socket](https://socket.dev/api/badge/npm/package/i18ntk/4.0.0)](https://socket.dev/npm/package/i18ntk/overview/4.0.0)\n\n## Install\n\n```bash\n# global CLI use\nnpm install -g i18ntk\n\n# local project use\nnpm install --save-dev i18ntk\n\n# one-off execution\nnpx i18ntk --help\n```\n\nRequirements:\n\n- Node.js `>=16.0.0`\n- npm `>=8.0.0`\n- No runtime dependencies\n\n## What's New in 4.0.0\n\n- **SIZING**: `--predict-expansion` flag computes per-key expansion ratios across languages with Safe/Warning/Critical risk tiers for UI layout planning.\n- **WATCH**: `watchLocales()` now returns an EventEmitter-compatible watcher with debounced `change`/`add`/`unlink`/`error` events and SHA-256 hash tracking.\n- **USAGE**: `--cleanup` and `--dry-run-delete` flags identify dead translation keys with confidence scores.\n- **VALIDATOR**: `--enforce-key-style` enforces dot.notation, snake_case, camelCase, kebab-case, or flat naming conventions.\n- **SCANNER**: `--source-language` supports multi-language hardcoded text detection with 12+ language profiles.\n- **BACKUP**: `--incremental` flag creates differential backups with SHA-256 hashing and chained restores.\n- **RUNTIME**: `lazy: true` option defers locale file loading until first key access for lower memory usage.\n- **PROTECTION**: Context-aware rules (`after:word`, `before:word`, `standalone`, `surrounded:left,right`) for precise term masking.\n- **FIX**: `initRuntime()` now returns independent instances with isolated language and cache state.\n\nSee [CHANGELOG.md](./CHANGELOG.md) for more release details.\n\n## Quick Start\n\nInitialize a project:\n\n```bash\ni18ntk\n# or with explicit command\ni18ntk --command=init\n```\n\nRun common checks:\n\n```bash\ni18ntk --command=analyze\ni18ntk --command=validate\ni18ntk --command=usage\ni18ntk --command=sizing\ni18ntk --command=summary\n```\n\nComplete or fix translation files:\n\n```bash\ni18ntk --command=complete\ni18ntk-fixer --help\n```\n\nAuto-translate locale JSON:\n\n```bash\ni18ntk --command=translate\n# or\ni18ntk-translate locales/en/common.json de --report-stdout\n```\n\nThe full onboarding guide is in [docs/getting-started.md](./docs/getting-started.md).\n\n## Main Commands\n\nPrimary CLI:\n\n```bash\ni18ntk\ni18ntk --help\ni18ntk --command=init\ni18ntk --command=analyze\ni18ntk --command=validate\ni18ntk --command=usage\ni18ntk --command=scanner\ni18ntk --command=sizing\ni18ntk --command=complete\ni18ntk --command=translate\ni18ntk --command=summary\ni18ntk --command=debug\n```\n\nStandalone executables:\n\n```bash\ni18ntk-init\ni18ntk-analyze\ni18ntk-validate\ni18ntk-usage\ni18ntk-scanner\ni18ntk-sizing\ni18ntk-complete\ni18ntk-summary\ni18ntk-doctor\ni18ntk-fixer\ni18ntk-backup\ni18ntk-translate\n```\n`n\nNote: manager route `i18ntk --command=backup` is disabled in current builds. Use `i18ntk-backup` (or legacy `i18ntk-backup`) directly for backup operations.\n\n## Common Options\n\nMost commands support:\n\n- `--source-dir <path>`\n- `--i18n-dir <path>`\n- `--output-dir <path>`\n- `--source-language <code>`\n- `--ui-language <code>`\n- `--no-prompt`\n- `--dry-run`\n- `--help`\n\nExample:\n\n```bash\ni18ntk --command=analyze --source-dir=./src --i18n-dir=./locales --output-dir=./i18ntk-reports\n```\n\n## Auto Translate\n\nInteractive manager flow:\n\n```bash\ni18ntk\n# choose \"Auto Translate (Beta)\"\n```\n\nDirect CLI examples:\n\n```bash\ni18ntk-translate locales/en/common.json de\ni18ntk-translate locales/en/common.json fr --dry-run --report-stdout\ni18ntk-translate locales/en es --source-dir locales/en --files \"*.json\" --no-confirm --preserve-placeholders\n```\n\nProvider examples:\n\n```bash\nexport DEEPL_API_KEY=\"your-deepl-api-key\"\ni18ntk-translate locales/en/common.json de --provider deepl --no-confirm --preserve-placeholders\n\nexport LIBRETRANSLATE_URL=\"https://libretranslate.com/translate\"\nexport LIBRETRANSLATE_API_KEY=\"optional-api-key\"\ni18ntk-translate locales/en/common.json es --provider libretranslate --no-confirm --preserve-placeholders\n```\n\n`google` remains the default provider. You can also set `I18NTK_TRANSLATE_PROVIDER=deepl` or `I18NTK_TRANSLATE_PROVIDER=libretranslate`.\n\nProvider requests are HTTPS-only and response-size limited, and security logs redact provider query strings and response bodies. DeepL is pinned to official DeepL hosts by default; set `I18NTK_ALLOW_CUSTOM_TRANSLATE_HOSTS=1` only for a trusted DeepL-compatible proxy. Custom LibreTranslate URLs are blocked for localhost/private IP ranges unless `I18NTK_ALLOW_PRIVATE_TRANSLATE_URLS=1` is set for trusted local testing. Keep provider API keys in environment variables or a secret manager.\n\nThe manager flow asks for:\n\n- source locale directory, either the folder with JSON files or a locale root such as `./locales`\n- source language code\n- one or more target languages, or `all`\n- one JSON file or all JSON files in the source directory\n\nIf you select a locale root such as `./locales` and choose source language `en`, the manager automatically uses `./locales/en` when that folder contains the source JSON files.\n\nBefore writing files, the manager can run a dry-run preview. After confirmation it writes translated files under sibling target-language folders, for example:\n\n```text\nlocales/en/common.json\nlocales/de/common.json\nlocales/fr/common.json\n```\n\n### Placeholder Handling\n\nAuto Translate detects common placeholders such as:\n\n- `{name}`\n- `{{count}}`\n- `%s`\n- `%d`\n- `:id`\n- `%{name}`\n- `${value}`\n\nUseful flags:\n\n- `--preserve-placeholders`: translate text around placeholders and reinsert original tokens\n- `--skip-placeholders`: copy placeholder-bearing strings unchanged\n- `--send-placeholders`: send placeholder-bearing strings through translation after masking\n- `--custom-regex <regex>`: add project-specific placeholder detection\n\n### Protected Terms and Keys\n\nAuto Translate can create and use a project-local protection file:\n\n```bash\ni18ntk-translate locales/en/common.json de --create-protection-file --protection-file ./i18ntk-auto-translate.json\n```\n\nExample `i18ntk-auto-translate.json`:\n\n```json\n{\n \"version\": 1,\n \"terms\": [\n \"BrandName\",\n \"PRODUCT_CODE\",\n { \"value\": \"OK\", \"context\": \"after:Click|Press|Tap\" },\n { \"value\": \"API\", \"context\": \"standalone\" }\n ],\n \"keys\": [\"app.brandName\", \"legal.companyName\", \"product.*.symbol\"],\n \"values\": [\"BrandName Ltd\", \"support@example.com\"],\n \"patterns\": [\"[A-Z]{2,}-\\\\d+\"]\n}\n```\n\n- `terms` are masked before translation and restored exactly afterward.\n - **Plain strings**: masked everywhere (backward compatible).\n - **Context objects**: masked only in specific contexts (`after:word`, `before:word`, `standalone`, `surrounded:left,right`).\n- `keys` are exact key paths or `*` wildcard paths copied unchanged.\n- `values` are exact source values copied unchanged.\n- `patterns` are JavaScript regex strings for advanced protected substrings.\n\nUseful flags:\n\n- `--protection-file <path>`\n- `--create-protection-file`\n- `--no-protection`\n\nOpen Settings and choose `Auto Translate Beta` to edit defaults for placeholder mode, concurrency, batch size, retry settings, report output, BOM output, protection file path, first-run setup prompt, and update prompt.\n\nSee [docs/auto-translate.md](./docs/auto-translate.md) for the full Auto Translate guide.\n\n## Validation\n\nValidation checks locale structure, completeness, placeholders, and content risks.\n\nIn 3.1.2, warning types are more specific:\n\n- `Potential risky content`: URL, email address, or secret-like value\n- `Possible untranslated English content`: target-language value appears to contain too much English\n\nEnglish-content warnings include:\n\n- detected English percentage\n- configured threshold\n- matched word count\n- sample matched words\n\nTune warnings in `.i18ntk-config`:\n\n```json\n{\n \"englishContentThresholdPercent\": 10,\n \"allowedEnglishTerms\": [\"BrandName\", \"PRODUCT_CODE\"]\n}\n```\n\n## Sizing Analysis\n\n`i18ntk-sizing` reports translation file sizes, key counts, average value length, and file-set mismatches across language folders.\n\n```bash\ni18ntk-sizing --source-dir ./locales --format table\ni18ntk-sizing --source-dir ./locales --detailed --output-dir ./i18ntk-reports\n```\n\nUse `--detailed` to print per-file rows in the terminal.\n\n### Expansion Prediction (New in 4.0.0)\n\nPredict UI layout overflow risk by analyzing per-key character-count expansion across languages:\n\n```bash\ni18ntk-sizing --source-dir ./locales --predict-expansion --output-report\n```\n\nExpansion ratios are classified into risk tiers:\n\n- **Safe** (<30% expansion): no UI impact expected\n- **Warning** (30–50%): may overflow in tight layouts — test on target languages\n- **Critical** (>50%): high risk of truncation — review UI element sizing\n\nThe report includes a built-in language-pair expansion reference table (EN→DE +35%, EN→RU +50%, EN→JA −40%, etc.) and lists the top-30 most-expanded keys.\n\n## Scanner: Multi-Language Detection (New in 4.0.0)\n\n`i18ntk-scanner` now supports detecting hardcoded text in multiple source languages beyond English:\n\n```bash\ni18ntk-scanner --source-dir ./src --source-language de\ni18ntk-scanner --source-dir ./src --source-language ja --output-report\n```\n\nSupported 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.\n\n## Usage: Dead Key Detection (New in 4.0.0)\n\n`i18ntk-usage` can identify translation keys that are defined but never referenced in source code:\n\n```bash\ni18ntk-usage --source-dir ./src --i18n-dir ./locales --cleanup\ni18ntk-usage --source-dir ./src --i18n-dir ./locales --cleanup --dry-run-delete\n```\n\nEach dead key receives a confidence score (0.0–1.0) factoring:\n- Dynamic key patterns (e.g., `` t(`prefix.${dynamic}`) ``) — lower score\n- Key appears in source code comments or JSDoc — medium score\n- Parent file recently modified (<30 days) — medium score\n- No references found anywhere — high score (>0.8)\n\nThe `--dry-run-delete` flag writes a `.dead-keys.json` report for review before any destructive action.\n\n## Validator: Key Naming Conventions (New in 4.0.0)\n\nEnforce consistent translation key naming across your project:\n\n```bash\ni18ntk-validate --enforce-key-style\n```\n\nConfigure the expected style in `.i18ntk-config`:\n\n```json\n{\n \"keyStyle\": \"dot.notation\"\n}\n```\n\nSupported styles: `dot.notation`, `snake_case`, `camelCase`, `kebab-case`, `flat`. Violations are reported as warnings with suggested canonical forms.\n\n## Watch: Hot Reload (New in 4.0.0)\n\n`utils/watch-locales.js` now provides debounced file watching with EventEmitter support:\n\n```js\nconst watchLocales = require('i18ntk/utils/watch-locales');\nconst watcher = watchLocales('./locales');\n\nwatcher.on('change', (filePath) => {\n console.log('Locale changed:', filePath);\n});\n\nwatcher.on('add', (filePath) => {\n console.log('Locale added:', filePath);\n});\n\n// Later:\nwatcher.stop();\n```\n\nFeatures: 300ms debounce (configurable), SHA-256 hash tracking to skip no-change saves, and a maximum of 50 watched directories.\n\n### Migration\n\nThe `watchLocales` return value gained EventEmitter methods in v4.0.0. Existing stop-function usage still works:\n\n```js\nconst stop = watchLocales('./locales', onChange);\n```\n\nCan be updated to:\n\n```js\nconst watcher = watchLocales('./locales');\nwatcher.on('change', onChange);\nwatcher.stop();\n```\n\nPassing a callback as the second argument is still supported — it auto-subscribes to `change` and `add` events.\n\n## Backup: Incremental Mode (New in 4.0.0)\n\nCreate differential backups that only include changed files:\n\n```bash\ni18ntk-backup create ./locales --incremental\n```\n\nIncremental backups store SHA-256 hashes per file and a parent-chain reference. Restoring an incremental backup automatically chains from the oldest full backup through each incremental diff in order. Chain depth is capped at 10 increments. Use `verify` to validate the hash chain.\n\n## Runtime: Lazy Loading (New in 4.0.0)\n\nReduce memory usage by deferring locale file loads until first key access:\n\n```js\nconst runtime = require('i18ntk/runtime');\n\nconst i18n = runtime.initRuntime({\n baseDir: './locales',\n language: 'en',\n lazy: true\n});\n\nconsole.log(i18n.t('common.hello')); // loads common.json on first access\n```\n\nWhen `lazy: true`, the runtime builds a key-to-file manifest on first access and loads individual files on demand. Files are loaded once and cached. If the manifest is missing or incomplete, the runtime falls back to full eager loading for that language. Manifest size is capped at 100KB with path containment validation.\n\n## Runtime API\n\nUse `i18ntk/runtime` when an application needs to read locale JSON files at runtime.\n\n```js\nconst runtime = require('i18ntk/runtime');\n\nconst i18n = runtime.initRuntime({\n baseDir: './locales',\n language: 'en',\n fallbackLanguage: 'en',\n keySeparator: '.',\n preload: true\n});\n\nconsole.log(i18n.t('common.hello'));\ni18n.setLanguage('fr');\nconsole.log(i18n.getLanguage());\nconsole.log(i18n.getAvailableLanguages());\ni18n.refresh('fr');\n```\n\nSee [docs/runtime.md](./docs/runtime.md) for runtime details.\n\n## Configuration\n\ni18ntk uses a project-local `.i18ntk-config` file.\n\nExample:\n\n```json\n{\n \"version\": \"4.0.0\",\n \"sourceDir\": \"./locales\",\n \"i18nDir\": \"./locales\",\n \"outputDir\": \"./i18ntk-reports\",\n \"sourceLanguage\": \"en\",\n \"defaultLanguages\": [\"de\", \"es\", \"fr\", \"ru\"],\n \"englishContentThresholdPercent\": 10,\n \"allowedEnglishTerms\": [\"BrandName\", \"PRODUCT_CODE\"],\n \"autoTranslate\": {\n \"placeholderMode\": \"preserve\",\n \"concurrency\": 6,\n \"batchSize\": 100,\n \"progressInterval\": 25,\n \"retryCount\": 3,\n \"retryDelay\": 1000,\n \"timeout\": 15000,\n \"dryRunFirst\": true,\n \"reportStdout\": true,\n \"bom\": false,\n \"protectionEnabled\": true,\n \"protectionFile\": \"./i18ntk-auto-translate.json\",\n \"promptProtectionSetup\": true,\n \"promptProtectionUpdate\": true\n },\n \"setup\": {\n \"completed\": true\n }\n}\n```\n\nSee [docs/api/CONFIGURATION.md](./docs/api/CONFIGURATION.md) for the full configuration model.\n\n## Public Package Contents\n\nThe public package intentionally ships runtime and CLI files only. The publish staging script excludes development-only content such as tests, scripts, docs, release staging folders, local config files, and generated protection files.\n\nThe package includes:\n\n- CLI entry points under `main/`\n- manager commands and services\n- runtime API files under `runtime/`\n- settings UI files required at runtime\n- bundled internal UI locales\n- shared utilities required by the shipped commands\n- `README.md`, `CHANGELOG.md`, `LICENSE`, and policy files\n\nThe public package manifest includes `readmeFilename: \"README.md\"`, and the release staging script fails if `README.md` is missing or empty.\n\n## Documentation\n\n- [Documentation Index](./docs/README.md)\n- [Getting Started](./docs/getting-started.md)\n- [API Reference](./docs/api/API_REFERENCE.md)\n- [Configuration Guide](./docs/api/CONFIGURATION.md)\n- [Runtime API Guide](./docs/runtime.md)\n- [Auto Translate Guide](./docs/auto-translate.md)\n- [Scanner Guide](./docs/scanner-guide.md)\n- [Environment Variables](./docs/environment-variables.md)\n- [Migration Guide v3.2.0](./docs/migration-guide-v3.2.0.md)\n- [Migration Guide v3.1.1](./docs/migration-guide-v3.1.1.md)\n- [Migration Guide v3.0.0](./docs/migration-guide-v3.0.0.md)\n\n## Security\n\n- No API key is required for the default Auto Translate flow.\n- Do not store secrets in locale files, `.i18ntk-config`, or protection files.\n- Project-specific brand/product terms should be configured by the user, not hardcoded into the package.\n- Report security issues using [SECURITY.md](./SECURITY.md).\n\n## Community\n\n- [Contributing](./CONTRIBUTING.md)\n- [Code of Conduct](./CODE_OF_CONDUCT.md)\n- [Funding](./FUNDING.md)\n\n## License\n\nMIT. See [LICENSE](./LICENSE).\n"
170
+ "versionInfo": {
171
+ "version": "4.2.0",
172
+ "releaseDate": "30/05/2026",
173
+ "lastUpdated": "30/05/2026",
174
+ "maintainer": "Vlad Noskov",
175
+ "changelog": "./CHANGELOG.md",
176
+ "documentation": "./README.md",
177
+ "apiReference": "./docs/api/API_REFERENCE.md",
178
+ "majorChanges": [
179
+ "SECURITY: Path containment, backup restore, runtime language loading, and Auto Translate provider URL validation are hardened.",
180
+ "AUTO TRANSLATE: The manager and docs no longer mark Auto Translate beta; existing translated target values are kept by default, and Google concurrency can now be raised up to 100.",
181
+ "RUNTIME: The lightweight runtime gains per-call language overrides, translateBatch(), cache helpers, and safer JSON parsing.",
182
+ "REPORTS: Init and analysis reports default to readable Markdown, with pretty JSON and text available through reports.format.",
183
+ "UX: Manager menu spacing is grouped and validation output no longer repeats source/i18n/output directory blocks.",
184
+ "USAGE: Usage analysis avoids scanning the project root when locale and source directories are the same.",
185
+ "USAGE: Usage reports now index known keys to source locations, resolve bounded dynamic key patterns, report unresolved dynamic expressions, recommend route-based namespaces, and flag hardcoded text candidates."
186
+ ],
187
+ "breakingChanges": [
188
+ "i18ntk/runtime module-level helpers keep the first initialized runtime configuration for compatibility instead of being overwritten by later initRuntime() calls.",
189
+ "utils/watch-locales.js returns a callable watcher object with EventEmitter methods and stop(); existing bare stop-function usage remains supported."
190
+ ],
191
+ "nextVersion": "4.2.1",
192
+ "supportedNodeVersions": ">=16.0.0",
193
+ "supportedFrameworks": {
194
+ "react-i18next": ">=11.0.0",
195
+ "vue-i18n": ">=9.0.0",
196
+ "angular-i18n": ">=12.0.0",
197
+ "next-i18next": ">=13.0.0",
198
+ "nuxt-i18n": ">=8.0.0",
199
+ "svelte-i18n": ">=3.0.0",
200
+ "sveltekit-i18n": ">=2.0.0",
201
+ "react-native-localize": ">=2.0.0",
202
+ "expo-localization": ">=14.0.0",
203
+ "ionic-angular": ">=6.0.0",
204
+ "ember-intl": ">=5.0.0",
205
+ "formatjs": ">=2.0.0",
206
+ "i18next": ">=21.0.0",
207
+ "django": ">=3.0.0",
208
+ "flask-babel": ">=2.0.0",
209
+ "fastapi": ">=0.70.0",
210
+ "spring-boot": ">=2.5.0",
211
+ "laravel": ">=8.0.0"
212
+ },
213
+ "supportPolicy": "Versions earlier than 4.2.0 may be unstable or insecure. Upgrade to 4.2.0 or newer."
214
+ },
215
+ "readme": "# i18ntk v4.2.0\n\nA i18n toolkit - A zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, translation completion, automatic JSON locale translation, reporting, and runtime translation loading.\n\n![i18ntk Logo](https://raw.githubusercontent.com/vladnoskv/i18ntk/main/docs/screenshots/i18ntk-logo-public.PNG)\n\n[![npm version](https://img.shields.io/npm/v/i18ntk.svg?color=brightgreen)](https://www.npmjs.com/package/i18ntk)\n[![npm downloads](https://img.shields.io/npm/dt/i18ntk.svg)](https://www.npmjs.com/package/i18ntk)\n[![node](https://img.shields.io/badge/node-%3E%3D16-339933)](https://nodejs.org)\n[![dependencies](https://img.shields.io/badge/dependencies-0-success)](https://www.npmjs.com/package/i18ntk)\n[![license](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)\n[![socket](https://socket.dev/api/badge/npm/package/i18ntk/4.2.0)](https://socket.dev/npm/package/i18ntk/overview/4.2.0)\n\n## Install\n\n```bash\n# global CLI use\nnpm install -g i18ntk\n\n# local project use\nnpm install --save-dev i18ntk\n\n# one-off execution\nnpx i18ntk --help\n```\n\nRequirements:\n\n- Node.js `>=16.0.0`\n- npm `>=8.0.0`\n- No runtime dependencies\n\n## What's New in 4.2.0\n\n- **SECURITY**: Hardened path containment for restore and shared filesystem helpers, including artifact-like filenames, environment-added internal prefixes, and Windows cross-drive paths.\n- **SECURITY**: Runtime locale loading now rejects unsafe language identifiers before resolving locale files, preventing `../` language names from reading JSON outside the configured locale base.\n- **SECURITY**: Auto Translate provider URL checks now block IPv4-mapped IPv6 private/loopback hosts.\n- **REPORTS**: Init and analysis reports now default to readable Markdown. Set `reports.format` to `markdown`, `json`, or `text` in Settings or `.i18ntk-config`.\n- **USAGE**: Usage analysis no longer scans the project root when locales are also configured as the source directory, avoiding inflated missing-key counts.\n- **I18N UX**: Init backup prompts, completion summaries, report prompts, default target languages, and native yes/no confirmations are now localized.\n- **AUTO TRANSLATE**: Auto Translate is out of beta, keeps existing translated target values by default, and only sends missing/source-copy/likely-English strings unless `--translate-all` is used.\n- **AUTO TRANSLATE**: Google Auto Translate concurrency now defaults to 12 and can be raised up to 100 for larger locale sets.\n- **AUTO TRANSLATE**: Corrupt target strings such as `?????`, replacement characters, and common mojibake are now repaired from the English source, and progress output distinguishes key translation from placeholder-safe text-segment translation.\n- **CLI UX**: Manager menu spacing is grouped and aligned, and validation no longer prints duplicate source/i18n/output directory blocks.\n- **DOCS**: Versioned docs and migration guidance now reflect the current 4.2.0 command surface.\n- **CLEANUP**: Removed stale duplicate fixed artifacts from the development tree to reduce audit and supply-chain drift.\n\n## What's New in 4.1.0\n\n- **FIX**: Critical and high-impact bugs resolved across the v4.0.0 feature set — runtime staleness crashes, backup hash-chain verification, sizing adminAuth crash, scanner `--source-language` propagation, watch callback subscriptions, dead key detection performance, validator key style enforcement, and protection Unicode boundary handling. See [CHANGELOG.md](./CHANGELOG.md) for complete details.\n\n## What's New in 4.0.0\n\n- **SIZING**: `--predict-expansion` flag computes per-key expansion ratios across languages with Safe/Warning/Critical risk tiers for UI layout planning.\n- **WATCH**: `watchLocales()` now returns an EventEmitter-compatible watcher with debounced `change`/`add`/`unlink`/`error` events and SHA-256 hash tracking.\n- **USAGE**: `--cleanup` and `--dry-run-delete` flags identify dead translation keys with confidence scores.\n- **VALIDATOR**: `--enforce-key-style` enforces dot.notation, snake_case, camelCase, kebab-case, or flat naming conventions.\n- **SCANNER**: `--source-language` supports multi-language hardcoded text detection with 12+ language profiles.\n- **BACKUP**: `--incremental` flag creates differential backups with SHA-256 hashing and chained restores.\n- **RUNTIME**: `lazy: true` option defers locale file loading until first key access for lower memory usage.\n- **PROTECTION**: Context-aware rules (`after:word`, `before:word`, `standalone`, `surrounded:left,right`) for precise term masking.\n\nSee [CHANGELOG.md](./CHANGELOG.md) for more release details.\n\n## Quick Start\n\nInitialize a project:\n\n```bash\ni18ntk\n# or with explicit command\ni18ntk --command=init\n```\n\nRun common checks:\n\n```bash\ni18ntk --command=analyze\ni18ntk --command=validate\ni18ntk --command=usage\ni18ntk --command=sizing\ni18ntk --command=summary\n```\n\nComplete or fix translation files:\n\n```bash\ni18ntk --command=complete\ni18ntk-fixer --help\n```\n\nAuto-translate locale JSON:\n\n```bash\ni18ntk --command=translate\n# or\ni18ntk-translate locales/en/common.json de --report-stdout\n```\n\nThe full onboarding guide is in [docs/getting-started.md](./docs/getting-started.md).\n\n## Main Commands\n\nPrimary CLI:\n\n```bash\ni18ntk\ni18ntk --help\ni18ntk --command=init\ni18ntk --command=analyze\ni18ntk --command=validate\ni18ntk --command=usage\ni18ntk --command=scanner\ni18ntk --command=sizing\ni18ntk --command=complete\ni18ntk --command=translate\ni18ntk --command=summary\n```\n\nStandalone executables:\n\n```bash\ni18ntk-init\ni18ntk-analyze\ni18ntk-validate\ni18ntk-usage\ni18ntk-scanner\ni18ntk-sizing\ni18ntk-complete\ni18ntk-summary\ni18ntk-doctor\ni18ntk-fixer\ni18ntk-backup\ni18ntk-translate\n```\n\nNote: manager route `i18ntk --command=backup` is disabled in current builds. Use `i18ntk-backup` directly for backup operations.\n\n## Command Reference\n\n| Command | What it does | Looks for | Writes or changes |\n| --- | --- | --- | --- |\n| `i18ntk` | Opens the interactive management menu. | Project config, setup state, available commands. | Only changes files after you choose a command that writes. |\n| `i18ntk --command=init` / `i18ntk-init` | Sets up locale folders and missing target-language files. | Source language files and selected target languages. | Locale JSON files, `.i18ntk-config`, optional reports/backups. |\n| `i18ntk --command=analyze` / `i18ntk-analyze` | Compares source and target translation coverage. | Missing keys, extra keys, untranslated markers, completion by language. | Markdown/JSON/text reports when report output is enabled. |\n| `i18ntk --command=validate` / `i18ntk-validate` | Validates structure and translation quality risks. | Placeholder mismatches, missing keys, risky URLs/emails/secrets, likely English target text. | Validation summary report. Does not edit locale files. |\n| `i18ntk --command=usage` / `i18ntk-usage` | Maps translation keys to source files and finds unused/missing keys. | Direct i18n calls, literal known-key references, bounded dynamic templates/object maps, unresolved dynamic expressions, hardcoded text candidates, namespace/file naming mismatches. | Usage report with key locations, namespace recommendations, unresolved dynamic expressions, hardcoded text suggestions, and optional dead-key report. Does not delete unless cleanup deletion is explicitly enabled. |\n| `i18ntk --command=scanner` / `i18ntk-scanner` | Scans source for i18n issues and hardcoded user-facing text. | JSX/template text, common text attributes, i18n usage patterns, source-language text profiles. | Scanner report. Does not edit files. |\n| `i18ntk --command=complete` / `i18ntk-complete` | Adds missing keys to target language files for 100% key coverage. | Source-language keys missing from targets. | Target locale JSON files, using missing translation markers/prefixes. |\n| `i18ntk --command=translate` / `i18ntk-translate` | Auto-translates locale JSON using configured provider behavior. | Missing, empty, untranslated-marker, source-copy, likely-English, or visibly corrupt target values by default. | Target locale JSON files and translation reports. Existing translated values are kept unless `--translate-all` is used. |\n| `i18ntk --command=sizing` / `i18ntk-sizing` | Estimates translated string length expansion and layout risk. | Text length, expansion ratios, placeholder-bearing strings. | Sizing report. Does not edit locale files. |\n| `i18ntk --command=summary` / `i18ntk-summary` | Shows project translation status. | Configured locales, reports, completeness status. | Console/report output only. |\n| `i18ntk-fixer` | Fixes placeholder and missing-marker issues. | Placeholder corruption, missing translation markers, configured language files. | Locale JSON files when fixes are applied. Use dry-run options where available before bulk edits. |\n| `i18ntk-backup` | Creates, verifies, restores, and cleans locale backups. | Locale JSON files and backup manifests. | Backup archives/manifests, or restored locale files when using restore. |\n\n## Common Options\n\nMany commands support:\n\n- `--source-dir <path>`\n- `--i18n-dir <path>`\n- `--output-dir <path>`\n- `--source-language <code>`\n- `--ui-language <code>`\n- `--no-prompt`\n- `--help`\n\nCommand-specific tools add their own flags such as `--dry-run`, `--output-report`, `--cleanup`, `--predict-expansion`, or Auto Translate provider options.\n\nExample:\n\n```bash\ni18ntk --command=analyze --source-dir=./src --i18n-dir=./locales --output-dir=./i18ntk-reports\n```\n\n## Auto Translate\n\nInteractive manager flow:\n\n```bash\ni18ntk\n# choose \"Auto Translate\"\n```\n\nDirect CLI examples:\n\n```bash\ni18ntk-translate locales/en/common.json de\ni18ntk-translate locales/en/common.json fr --dry-run --report-stdout\ni18ntk-translate locales/en es --source-dir locales/en --files \"*.json\" --no-confirm --preserve-placeholders\n```\n\nProvider examples:\n\n```bash\nexport DEEPL_API_KEY=\"your-deepl-api-key\"\ni18ntk-translate locales/en/common.json de --provider deepl --no-confirm --preserve-placeholders\n\nexport LIBRETRANSLATE_URL=\"https://libretranslate.com/translate\"\nexport LIBRETRANSLATE_API_KEY=\"optional-api-key\"\ni18ntk-translate locales/en/common.json es --provider libretranslate --no-confirm --preserve-placeholders\n```\n\n`google` remains the default provider. You can also set `I18NTK_TRANSLATE_PROVIDER=deepl` or `I18NTK_TRANSLATE_PROVIDER=libretranslate`.\n\nProvider requests are HTTPS-only and response-size limited, and security logs redact provider query strings and response bodies. DeepL is pinned to official DeepL hosts by default; set `I18NTK_ALLOW_CUSTOM_TRANSLATE_HOSTS=1` only for a trusted DeepL-compatible proxy. Custom LibreTranslate URLs are blocked for localhost/private IP ranges unless `I18NTK_ALLOW_PRIVATE_TRANSLATE_URLS=1` is set for trusted local testing. Keep provider API keys in environment variables or a secret manager.\n\nThe manager flow asks for:\n\n- source locale directory, either the folder with JSON files or a locale root such as `./locales`\n- source language code\n- one or more target languages, or `all`\n- one JSON file or all JSON files in the source directory\n\nIf you select a locale root such as `./locales` and choose source language `en`, the manager automatically uses `./locales/en` when that folder contains the source JSON files.\n\nBefore writing files, the manager can run a dry-run preview. After confirmation it writes translated files under sibling target-language folders, for example:\n\n```text\nlocales/en/common.json\nlocales/de/common.json\nlocales/fr/common.json\n```\n\nAuto Translate is target-aware by default. When a target file already exists, it keeps translated target values and only sends values that are missing, empty, marked as untranslated, still identical to the source, likely still English, or visibly corrupt from encoding damage such as `?????`, replacement characters, or common mojibake. Use `--translate-all` when you intentionally want to re-translate every source string.\n\n### Placeholder Handling\n\nAuto Translate detects common placeholders such as:\n\n- `{name}`\n- `{{count}}`\n- `%s`\n- `%d`\n- `:id`\n- `%{name}`\n- `${value}`\n- `{count, plural, one {# item} other {# items}}`\n- `$t(common.save)`\n- `%(total).2f`\n\nUseful flags:\n\n- `--preserve-placeholders`: translate text around placeholders and reinsert original tokens\n- `--skip-placeholders`: copy placeholder-bearing strings unchanged\n- `--send-placeholders`: send placeholder-bearing strings through translation after masking\n- `--custom-regex <regex>`: add project-specific placeholder detection\n- `--only-missing`: keep existing translated target values and translate only missing/source-copy/likely English values (default)\n- `--translate-all`: re-translate every source string\n\nProgress output is stage-aware for large files. Normal keys are reported as `Translating strings`, while preserve-mode placeholder work is reported as `Translating placeholder-safe text segments`; each progress update includes the current key path when available.\n\n### Protected Terms and Keys\n\nAuto Translate can create and use a project-local protection file:\n\n```bash\ni18ntk-translate locales/en/common.json de --create-protection-file --protection-file ./i18ntk-auto-translate.json\n```\n\nExample `i18ntk-auto-translate.json`:\n\n```json\n{\n \"version\": 1,\n \"terms\": [\n \"BrandName\",\n \"PRODUCT_CODE\",\n { \"value\": \"OK\", \"context\": \"after:Click|Press|Tap\" },\n { \"value\": \"API\", \"context\": \"standalone\" }\n ],\n \"keys\": [\"app.brandName\", \"legal.companyName\", \"product.*.symbol\"],\n \"values\": [\"BrandName Ltd\", \"support@example.com\"],\n \"patterns\": [\"[A-Z]{2,}-\\\\d+\"]\n}\n```\n\n- `terms` are masked before translation and restored exactly afterward.\n - **Plain strings**: masked everywhere (backward compatible).\n - **Context objects**: masked only in specific contexts (`after:word`, `before:word`, `standalone`, `surrounded:left,right`).\n- `keys` are exact key paths or `*` wildcard paths copied unchanged.\n- `values` are exact source values copied unchanged.\n- `patterns` are JavaScript regex strings for advanced protected substrings.\n\nUseful flags:\n\n- `--protection-file <path>`\n- `--create-protection-file`\n- `--no-protection`\n\nOpen Settings and choose `Auto Translate` to edit defaults for placeholder mode, translate-only-needed mode, concurrency, batch size, retry settings, report output, BOM output, protection file path, first-run setup prompt, and update prompt.\n\nSee [docs/auto-translate.md](./docs/auto-translate.md) for the full Auto Translate guide.\n\n## Validation\n\nValidation checks locale structure, completeness, placeholders, and content risks.\n\nValidation warning types are specific:\n\n- `Potential risky content`: URL, email address, or secret-like value\n- `Possible untranslated English content`: target-language value appears to contain too much English\n\nEnglish-content warnings include:\n\n- detected English percentage\n- configured threshold\n- matched word count\n- sample matched words\n\nTune warnings in `.i18ntk-config`:\n\n```json\n{\n \"englishContentThresholdPercent\": 10,\n \"allowedEnglishTerms\": [\"BrandName\", \"PRODUCT_CODE\"]\n}\n```\n\n## Sizing Analysis\n\n`i18ntk-sizing` reports translation file sizes, key counts, average value length, and file-set mismatches across language folders.\n\n```bash\ni18ntk-sizing --source-dir ./locales --format table\ni18ntk-sizing --source-dir ./locales --detailed --output-dir ./i18ntk-reports\n```\n\nUse `--detailed` to print per-file rows in the terminal.\n\n### Expansion Prediction (New in 4.0.0)\n\nPredict UI layout overflow risk by analyzing per-key character-count expansion across languages:\n\n```bash\ni18ntk-sizing --source-dir ./locales --predict-expansion --output-report\n```\n\nExpansion ratios are classified into risk tiers:\n\n- **Safe** (<30% expansion): no UI impact expected\n- **Warning** (30–50%): may overflow in tight layouts — test on target languages\n- **Critical** (>50%): high risk of truncation — review UI element sizing\n\nThe report includes a built-in language-pair expansion reference table (EN→DE +35%, EN→RU +50%, EN→JA −40%, etc.) and lists the top-30 most-expanded keys.\n\n## Scanner: Multi-Language Detection (New in 4.0.0)\n\n`i18ntk-scanner` now supports detecting hardcoded text in multiple source languages beyond English:\n\n```bash\ni18ntk-scanner --source-dir ./src --source-language de\ni18ntk-scanner --source-dir ./src --source-language ja --output-report\n```\n\nSupported 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.\n\n## Usage: Dead Key Detection (New in 4.0.0)\n\n`i18ntk-usage` can identify translation keys that are defined but never referenced in source code:\n\n```bash\ni18ntk-usage --source-dir ./src --i18n-dir ./locales --cleanup\ni18ntk-usage --source-dir ./src --i18n-dir ./locales --cleanup --dry-run-delete\n```\n\nEach dead key receives a confidence score (0.0–1.0) factoring:\n- Unresolved dynamic key patterns (e.g., `` t(`prefix.${dynamic}`) ``) — lower score and listed in the usage report; simple consts, bounded arrays, object maps, and ternaries are expanded to exact keys where possible\n- Key appears in source code comments or JSDoc — medium score\n- Parent file recently modified (<30 days) — medium score\n- No references found anywhere — high score (>0.8)\n\nThe `--dry-run-delete` flag writes a `.dead-keys.json` report for review before any destructive action.\n\n## Validator: Key Naming Conventions (New in 4.0.0)\n\nEnforce consistent translation key naming across your project:\n\n```bash\ni18ntk-validate --enforce-key-style\n```\n\nConfigure the expected style in `.i18ntk-config`:\n\n```json\n{\n \"keyStyle\": \"dot.notation\"\n}\n```\n\nSupported styles: `dot.notation`, `snake_case`, `camelCase`, `kebab-case`, `flat`. Violations are reported as warnings with suggested canonical forms.\n\n## Watch: Hot Reload (New in 4.0.0)\n\n`utils/watch-locales.js` now provides debounced file watching with EventEmitter support:\n\n```js\nconst watchLocales = require('i18ntk/utils/watch-locales');\nconst watcher = watchLocales('./locales');\n\nwatcher.on('change', (filePath) => {\n console.log('Locale changed:', filePath);\n});\n\nwatcher.on('add', (filePath) => {\n console.log('Locale added:', filePath);\n});\n\n// Later:\nwatcher.stop();\n```\n\nFeatures: 300ms debounce (configurable), SHA-256 hash tracking to skip no-change saves, and a maximum of 50 watched directories.\n\n### Migration\n\nThe `watchLocales` return value gained EventEmitter methods in v4.0.0. Existing stop-function usage still works:\n\n```js\nconst stop = watchLocales('./locales', onChange);\n```\n\nCan be updated to:\n\n```js\nconst watcher = watchLocales('./locales');\nwatcher.on('change', onChange);\nwatcher.stop();\n```\n\nPassing a callback as the second argument is still supported — it auto-subscribes to `change` and `add` events.\n\n## Backup: Incremental Mode (New in 4.0.0)\n\nCreate differential backups that only include changed files:\n\n```bash\ni18ntk-backup create ./locales --incremental\n```\n\nIncremental backups store SHA-256 hashes per file and a parent-chain reference. Restoring an incremental backup automatically chains from the oldest full backup through each incremental diff in order. Chain depth is capped at 10 increments. Use `verify` to validate the hash chain.\n\n## Runtime: Lazy Loading (New in 4.0.0)\n\nReduce memory usage by deferring locale file loads until first key access:\n\n```js\nconst runtime = require('i18ntk/runtime');\n\nconst i18n = runtime.initRuntime({\n baseDir: './locales',\n language: 'en',\n lazy: true\n});\n\nconsole.log(i18n.t('common.hello')); // loads common.json on first access\n```\n\nWhen `lazy: true`, the runtime builds a key-to-file manifest on first access and loads individual files on demand. Files are loaded once and cached. If the manifest is missing or incomplete, the runtime falls back to full eager loading for that language. Manifest size is capped at 100KB with path containment validation.\n\nProduction guidance:\n\n- Prefer the object returned from `initRuntime()` instead of module-level `runtime.t()` in apps with multiple tenants, projects, or locale roots.\n- Use `lazy: true` for large modular locale folders where lower steady-state memory matters more than a small first-key lookup cost.\n- Use `preload: true` without `lazy` for small locale sets or latency-sensitive startup paths.\n- Call `refresh(language)` after deploying or writing changed locale files so cached data and lazy manifests are rebuilt.\n- Use per-call language overrides when rendering one-off alternate-language strings: `i18n.t('common.hello', {}, { language: 'de' })`.\n- Use `translateBatch()` for small groups of labels and `clearCache()` / `getCacheInfo()` for cache maintenance and diagnostics.\n- `i18ntk/runtime/enhanced` remains available for compatibility with existing async/encryption users, but new production integrations should start with `i18ntk/runtime`.\n\n## Runtime API\n\nUse `i18ntk/runtime` when an application needs to read locale JSON files at runtime.\n\n```js\nconst runtime = require('i18ntk/runtime');\n\nconst i18n = runtime.initRuntime({\n baseDir: './locales',\n language: 'en',\n fallbackLanguage: 'en',\n keySeparator: '.',\n preload: true\n});\n\nconsole.log(i18n.t('common.hello'));\ni18n.setLanguage('fr');\nconsole.log(i18n.getLanguage());\nconsole.log(i18n.getAvailableLanguages());\ni18n.refresh('fr');\n```\n\nUseful production helpers:\n\n```js\ni18n.t('common.hello', {}, { language: 'de' }); // per-call language override\ni18n.translateBatch(['menu.home', 'menu.settings']);\ni18n.clearCache('fr');\nconsole.log(i18n.getCacheInfo());\n```\n\nSee [docs/runtime.md](./docs/runtime.md) for runtime details.\n\n## Configuration\n\ni18ntk uses a project-local `.i18ntk-config` file.\n\nExample:\n\n```json\n{\n \"version\": \"4.2.0\",\n \"sourceDir\": \"./locales\",\n \"i18nDir\": \"./locales\",\n \"outputDir\": \"./i18ntk-reports\",\n \"sourceLanguage\": \"en\",\n \"defaultLanguages\": [\"en\", \"de\", \"es\", \"fr\", \"ru\"],\n \"reports\": {\n \"format\": \"markdown\"\n },\n \"englishContentThresholdPercent\": 10,\n \"allowedEnglishTerms\": [\"BrandName\", \"PRODUCT_CODE\"],\n \"autoTranslate\": {\n \"placeholderMode\": \"preserve\",\n \"concurrency\": 12,\n \"batchSize\": 100,\n \"progressInterval\": 25,\n \"retryCount\": 3,\n \"retryDelay\": 1000,\n \"timeout\": 15000,\n \"dryRunFirst\": true,\n \"onlyMissingOrEnglish\": true,\n \"reportStdout\": true,\n \"bom\": false,\n \"protectionEnabled\": true,\n \"protectionFile\": \"./i18ntk-auto-translate.json\",\n \"promptProtectionSetup\": true,\n \"promptProtectionUpdate\": true\n },\n \"setup\": {\n \"completed\": true\n }\n}\n```\n\nSee [docs/api/CONFIGURATION.md](./docs/api/CONFIGURATION.md) for the full configuration model.\n\n## Public Package Contents\n\nThe public package intentionally ships runtime and CLI files only. The publish staging script excludes development-only content such as tests, scripts, docs, release staging folders, local config files, and generated protection files.\n\nThe package includes:\n\n- CLI entry points under `main/`\n- manager commands and services\n- runtime API files under `runtime/`\n- settings UI files required at runtime\n- bundled internal UI locales\n- shared utilities required by the shipped commands\n- `README.md`, `CHANGELOG.md`, `LICENSE`, and policy files\n\nThe public package manifest includes `readmeFilename: \"README.md\"`, and the release staging script fails if `README.md` is missing or empty.\n\n## Documentation\n\n- [Documentation Index](./docs/README.md)\n- [Getting Started](./docs/getting-started.md)\n- [API Reference](./docs/api/API_REFERENCE.md)\n- [Configuration Guide](./docs/api/CONFIGURATION.md)\n- [Runtime API Guide](./docs/runtime.md)\n- [Auto Translate Guide](./docs/auto-translate.md)\n- [Scanner Guide](./docs/scanner-guide.md)\n- [Environment Variables](./docs/environment-variables.md)\n- [Migration Guide v4.2.0](./docs/migration-guide-v4.2.0.md)\n\n## Security\n\n- No API key is required for the default Auto Translate flow.\n- Do not store secrets in locale files, `.i18ntk-config`, or protection files.\n- Project-specific brand/product terms should be configured by the user, not hardcoded into the package.\n- Report security issues using [SECURITY.md](./SECURITY.md).\n\n## Community\n\n- [Contributing](./CONTRIBUTING.md)\n- [Code of Conduct](./CODE_OF_CONDUCT.md)\n- [Funding](./FUNDING.md)\n\n## License\n\nMIT. See [LICENSE](./LICENSE).\n"
165
216
  }