i18ntk 4.1.0 → 4.2.1

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 (49) hide show
  1. package/CHANGELOG.md +64 -5
  2. package/README.md +73 -17
  3. package/SECURITY.md +10 -4
  4. package/main/i18ntk-analyze.js +10 -20
  5. package/main/i18ntk-backup.js +106 -44
  6. package/main/i18ntk-init.js +153 -157
  7. package/main/i18ntk-setup.js +36 -13
  8. package/main/i18ntk-sizing.js +44 -27
  9. package/main/i18ntk-translate.js +311 -41
  10. package/main/i18ntk-usage.js +272 -103
  11. package/main/i18ntk-validate.js +38 -31
  12. package/main/manage/commands/AnalyzeCommand.js +7 -17
  13. package/main/manage/commands/CommandRouter.js +6 -6
  14. package/main/manage/commands/SizingCommand.js +5 -2
  15. package/main/manage/commands/TranslateCommand.js +73 -56
  16. package/main/manage/commands/ValidateCommand.js +58 -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 +247 -96
  21. package/package.json +19 -14
  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 +175 -90
  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/report.js +32 -4
  46. package/utils/translate/safe-network.js +24 -4
  47. package/utils/usage-insights.js +435 -0
  48. package/utils/usage-source.js +50 -0
  49. package/utils/watch-locales.js +1 -8
@@ -13,30 +13,33 @@ const SecurityUtils = require('../../../utils/security');
13
13
  const configManager = require('../../../utils/config-manager');
14
14
  const { loadTranslations, t } = require('../../../utils/i18n-helper');
15
15
  const { detectFramework } = require('../../../utils/framework-detector');
16
- const { getFormatAdapter } = require('../../../utils/format-manager');
17
- const AdminAuth = require('../../../utils/admin-auth');
16
+ const { getFormatAdapter } = require('../../../utils/format-manager');
17
+ const AdminAuth = require('../../../utils/admin-auth');
18
+ const { parseConfirmation } = require('../../../utils/localized-confirm');
19
+ const { normalizeReportFormat, writeReportFile } = require('../../../utils/report-writer');
18
20
 
19
21
  // Language configurations with native names
20
- const LANGUAGE_CONFIG = {
21
- 'de': { name: 'German', nativeName: 'Deutsch' },
22
- 'es': { name: 'Spanish', nativeName: 'Español' },
23
- 'fr': { name: 'French', nativeName: 'Français' },
24
- 'ru': { name: 'Russian', nativeName: 'Русский' },
25
- 'it': { name: 'Italian', nativeName: 'Italiano' },
26
- 'ja': { name: 'Japanese', nativeName: '日本語' },
27
- 'ko': { name: 'Korean', nativeName: '한국어' },
28
- 'zh': { name: 'Chinese', nativeName: '中文' },
29
- 'ar': { name: 'Arabic', nativeName: 'العربية' },
30
- 'hi': { name: 'Hindi', nativeName: 'हिन्दी' },
31
- 'nl': { name: 'Dutch', nativeName: 'Nederlands' },
32
- 'sv': { name: 'Swedish', nativeName: 'Svenska' },
33
- 'da': { name: 'Danish', nativeName: 'Dansk' },
34
- 'no': { name: 'Norwegian', nativeName: 'Norsk' },
35
- 'fi': { name: 'Finnish', nativeName: 'Suomi' },
36
- 'pl': { name: 'Polish', nativeName: 'Polski' },
37
- 'cs': { name: 'Czech', nativeName: 'Čeština' },
38
- 'hu': { name: 'Hungarian', nativeName: 'Magyar' },
39
- 'tr': { name: 'Turkish', nativeName: 'Türkçe' }
22
+ const LANGUAGE_CONFIG = {
23
+ 'en': { name: 'English', nativeName: 'English' },
24
+ 'de': { name: 'German', nativeName: 'Deutsch' },
25
+ 'es': { name: 'Spanish', nativeName: 'Espa\u00f1ol' },
26
+ 'fr': { name: 'French', nativeName: 'Fran\u00e7ais' },
27
+ 'ru': { name: 'Russian', nativeName: '\u0420\u0443\u0441\u0441\u043a\u0438\u0439' },
28
+ 'it': { name: 'Italian', nativeName: 'Italiano' },
29
+ 'ja': { name: 'Japanese', nativeName: '\u65e5\u672c\u8a9e' },
30
+ 'ko': { name: 'Korean', nativeName: '\ud55c\uad6d\uc5b4' },
31
+ 'zh': { name: 'Chinese', nativeName: '\u4e2d\u6587' },
32
+ 'ar': { name: 'Arabic', nativeName: '\u0627\u0644\u0639\u0631\u0628\u064a\u0629' },
33
+ 'hi': { name: 'Hindi', nativeName: '\u0939\u093f\u0928\u094d\u0926\u0940' },
34
+ 'nl': { name: 'Dutch', nativeName: 'Nederlands' },
35
+ 'sv': { name: 'Swedish', nativeName: 'Svenska' },
36
+ 'da': { name: 'Danish', nativeName: 'Dansk' },
37
+ 'no': { name: 'Norwegian', nativeName: 'Norsk' },
38
+ 'fi': { name: 'Finnish', nativeName: 'Suomi' },
39
+ 'pl': { name: 'Polish', nativeName: 'Polski' },
40
+ 'cs': { name: 'Czech', nativeName: '\u010ce\u0161tina' },
41
+ 'hu': { name: 'Hungarian', nativeName: 'Magyar' },
42
+ 'tr': { name: 'Turkish', nativeName: 'T\u00fcrk\u00e7e' }
40
43
  };
41
44
 
42
45
  class InitService {
@@ -65,7 +68,7 @@ class InitService {
65
68
  : path.join(this.sourceDir, this.config.sourceLanguage);
66
69
 
67
70
  // Ensure defaultLanguages is properly initialized from config
68
- this.config.defaultLanguages = this.config.defaultLanguages || ['de', 'es', 'fr', 'ru'];
71
+ this.config.defaultLanguages = this.config.defaultLanguages || ['en', 'de', 'es', 'fr', 'ru'];
69
72
  }
70
73
 
71
74
  // Check i18n dependencies
@@ -694,7 +697,7 @@ class InitService {
694
697
  await flushStdout();
695
698
  const enableProtection = await ask('\n' + t('adminPin.setup_prompt'));
696
699
 
697
- if (enableProtection.toLowerCase() === 'y' || enableProtection.toLowerCase() === 'yes') {
700
+ if (parseConfirmation(enableProtection, { language: this.config.uiLanguage || this.config.language || 'en', defaultValue: false })) {
698
701
  try {
699
702
  const adminAuth = new AdminAuth();
700
703
  await adminAuth.initialize();
@@ -743,16 +746,16 @@ class InitService {
743
746
  }
744
747
 
745
748
  const { ask } = require('../../../utils/cli');
746
- console.log('\nBackup Settings');
747
- console.log('Backups are disabled by default to avoid backup recursion and repo pollution.');
748
- const enableAnswer = await ask('Enable automatic backups? (y/N): ');
749
- const enabled = ['y', 'yes'].includes(String(enableAnswer || '').trim().toLowerCase());
749
+ console.log('\n' + t('init.backup.title'));
750
+ console.log(t('init.backup.description'));
751
+ const enableAnswer = await ask(t('init.backup.enablePrompt'));
752
+ const enabled = parseConfirmation(enableAnswer, { language: this.config.uiLanguage || this.config.language || 'en', defaultValue: false });
750
753
 
751
754
  if (!enabled) {
752
755
  return defaultBackupConfig;
753
756
  }
754
757
 
755
- const keepAnswer = await ask('How many backups should be kept automatically (1-3, default 1): ');
758
+ const keepAnswer = await ask(t('init.backup.keepPrompt'));
756
759
  const parsedKeep = parseInt(String(keepAnswer || '').trim(), 10);
757
760
  const maxBackups = Number.isInteger(parsedKeep) ? Math.min(Math.max(parsedKeep, 1), 3) : 1;
758
761
 
@@ -822,7 +825,7 @@ class InitService {
822
825
  let perLanguage = [];
823
826
  if (structure !== 'existing') {
824
827
  const duplicateChoice = await ask('\n' + t('init.setup.apply_all_prompt'));
825
- duplicateStructure = duplicateChoice.toLowerCase() === 'y' || duplicateChoice.toLowerCase() === 'yes';
828
+ duplicateStructure = parseConfirmation(duplicateChoice, { language: this.config.uiLanguage || this.config.language || 'en', defaultValue: true });
826
829
  if (!duplicateStructure) {
827
830
  // Prompt for languages to include/exclude
828
831
  console.log(t('init.setup.per_language_intro'));
@@ -978,98 +981,91 @@ class InitService {
978
981
  console.log(t('init.nextStep3'));
979
982
  }
980
983
 
981
- // Generate completion summary with proper error handling
982
- async generateCompletionSummary(results, targetLanguages) {
983
- try {
984
- console.log('\n' + '='.repeat(50));
985
- console.log(t('init.initializationSummaryTitle'));
986
- console.log(t('common.separator'));
987
-
988
- let totalChanges = 0;
989
- let languagesProcessed = 0;
990
- let missingKeysAdded = 0;
991
-
992
- Object.entries(results || {}).forEach(([lang, data]) => {
993
- if (!data || typeof data !== 'object') return;
994
-
995
- const langName = LANGUAGE_CONFIG[lang]?.name || 'Unknown';
996
- const stats = data.totalStats || { total: 0, translated: 0, percentage: 0, missing: 0 };
997
-
998
- const statusIcon = stats.percentage === 100 ? '✅' : stats.percentage >= 80 ? '🟡' : '🔴';
999
-
1000
- console.log(
1001
- t('init.languageSummary', {
1002
- icon: statusIcon,
1003
- name: langName,
1004
- code: lang,
1005
- percentage: stats.percentage || 0,
1006
- })
1007
- );
1008
-
1009
- if (data.files && Array.isArray(data.files)) {
1010
- console.log(t('init.languageFiles', { count: data.files.length }));
1011
- }
1012
-
1013
- console.log(
1014
- t('init.languageKeys', {
1015
- translated: stats.translated || 0,
1016
- total: stats.total || 0,
1017
- })
1018
- );
1019
-
1020
- console.log(t('init.languageMissing', { count: stats.missing || 0 }));
1021
-
1022
- totalChanges += (stats.translated || 0) + (stats.missing || 0);
1023
- languagesProcessed += 1;
1024
- missingKeysAdded += stats.missing || 0;
1025
- });
1026
-
1027
- console.log('\n📊 COMPLETION SUMMARY');
1028
- console.log(t('common.separator'));
1029
- console.log(`📝 Total changes: ${totalChanges}`);
1030
- console.log(`🌍 Languages processed: ${languagesProcessed}`);
1031
- console.log(`➕ Missing keys added: ${missingKeysAdded}`);
1032
-
1033
- if (process.stdin.isTTY && !this.config?.noPrompt) {
1034
- const { ask } = require('../../../utils/cli');
1035
- const generateReport = await ask('\n🤖 Would you like a report generated? (Y/N): ');
1036
- if (generateReport.toLowerCase() === 'y' || generateReport.toLowerCase() === 'yes') {
1037
- await this.generateDetailedReport(results, targetLanguages);
1038
- }
1039
- }
1040
- } catch (error) {
1041
- console.error('\n❌ Error during completion:', error.message);
1042
- console.log('📊 COMPLETION SUMMARY (Basic)');
1043
- console.log(t('common.separator'));
1044
- console.log(`🌍 Languages processed: ${Object.keys(results || {}).length}`);
1045
- }
1046
- }
1047
-
1048
- // Generate detailed report
984
+ // Generate completion summary with proper error handling
985
+ async generateCompletionSummary(results, targetLanguages) {
986
+ return await this.generateLocalizedCompletionSummary(results, targetLanguages);
987
+ }
988
+
989
+ async generateLocalizedCompletionSummary(results, targetLanguages) {
990
+ try {
991
+ console.log('\n' + '='.repeat(50));
992
+ console.log(t('init.initializationSummaryTitle'));
993
+ console.log(t('common.separator'));
994
+
995
+ let totalChanges = 0;
996
+ let languagesProcessed = 0;
997
+ let missingKeysAdded = 0;
998
+
999
+ Object.entries(results || {}).forEach(([lang, data]) => {
1000
+ if (!data || typeof data !== 'object') return;
1001
+
1002
+ const langName = LANGUAGE_CONFIG[lang]?.name || 'Unknown';
1003
+ const stats = data.totalStats || { total: 0, translated: 0, percentage: 0, missing: 0 };
1004
+ const statusIcon = stats.percentage === 100 ? '✅' : stats.percentage >= 80 ? '🟡' : '🔴';
1005
+
1006
+ console.log(t('init.languageSummary', {
1007
+ icon: statusIcon,
1008
+ name: langName,
1009
+ code: lang,
1010
+ percentage: stats.percentage || 0,
1011
+ }));
1012
+
1013
+ if (Array.isArray(data.files)) {
1014
+ console.log(t('init.languageFiles', { count: data.files.length }));
1015
+ }
1016
+ console.log(t('init.languageKeys', { translated: stats.translated || 0, total: stats.total || 0 }));
1017
+ console.log(t('init.languageMissing', { count: stats.missing || 0 }));
1018
+
1019
+ totalChanges += (stats.translated || 0) + (stats.missing || 0);
1020
+ languagesProcessed += 1;
1021
+ missingKeysAdded += stats.missing || 0;
1022
+ });
1023
+
1024
+ console.log('\n' + t('init.completionSummaryTitle'));
1025
+ console.log(t('common.separator'));
1026
+ console.log(t('init.totalChanges', { count: totalChanges }));
1027
+ console.log(t('init.languagesProcessed', { count: languagesProcessed }));
1028
+ console.log(t('init.missingKeysAdded', { count: missingKeysAdded }));
1029
+
1030
+ if (process.stdin.isTTY && !this.config?.noPrompt) {
1031
+ const { ask } = require('../../../utils/cli');
1032
+ const generateReport = await ask('\n' + t('init.reportPrompt'));
1033
+ if (parseConfirmation(generateReport, { language: this.config.uiLanguage || this.config.language || 'en', defaultValue: true })) {
1034
+ await this.generateDetailedReport(results, targetLanguages);
1035
+ }
1036
+ }
1037
+ } catch (error) {
1038
+ console.error('\n' + t('init.completionError', { error: error.message }));
1039
+ console.log(t('init.completionSummaryBasicTitle'));
1040
+ console.log(t('common.separator'));
1041
+ console.log(t('init.languagesProcessed', { count: Object.keys(results || {}).length }));
1042
+ }
1043
+ }
1044
+
1045
+ // Generate detailed report
1049
1046
  async generateDetailedReport(results, targetLanguages) {
1050
1047
  try {
1051
1048
  const outputDir = this.config.outputDir || path.join(process.cwd(), 'i18ntk-reports');
1052
- if (!SecurityUtils.safeExistsSync(outputDir)) {
1053
- fs.mkdirSync(outputDir, { recursive: true });
1054
- }
1055
-
1056
- const reportPath = path.join(outputDir, 'init-report.json');
1057
- const report = {
1058
- timestamp: new Date().toISOString(),
1059
- languages: targetLanguages,
1060
- results: results,
1061
- summary: {
1062
- languagesProcessed: targetLanguages.length,
1063
- totalFiles: Object.values(results).reduce((sum, data) => sum + (data.files?.length || 0), 0),
1064
- totalKeys: Object.values(results).reduce((sum, data) => sum + (data.totalStats?.total || 0), 0),
1065
- totalMissing: Object.values(results).reduce((sum, data) => sum + (data.totalStats?.missing || 0), 0)
1066
- }
1067
- };
1068
-
1069
- await fs.promises.writeFile(reportPath, JSON.stringify(report, null, 2));
1070
- console.log(`✅ Report generated: ${reportPath}`);
1049
+ if (!SecurityUtils.safeExistsSync(outputDir)) {
1050
+ fs.mkdirSync(outputDir, { recursive: true });
1051
+ }
1052
+
1053
+ const reportPayload = {
1054
+ timestamp: new Date().toISOString(),
1055
+ languages: targetLanguages,
1056
+ results,
1057
+ summary: {
1058
+ languagesProcessed: targetLanguages.length,
1059
+ totalFiles: Object.values(results).reduce((sum, data) => sum + (data.files?.length || 0), 0),
1060
+ totalKeys: Object.values(results).reduce((sum, data) => sum + (data.totalStats?.total || 0), 0),
1061
+ totalMissing: Object.values(results).reduce((sum, data) => sum + (data.totalStats?.missing || 0), 0)
1062
+ }
1063
+ };
1064
+ const format = normalizeReportFormat(this.config.reports?.format || this.config.reportFormat || 'markdown');
1065
+ const writtenPath = await writeReportFile(outputDir, 'init-report', reportPayload, { format, title: 'I18NTK Init Report' });
1066
+ console.log(t('init.reportGenerated', { reportPath: writtenPath }));
1071
1067
  } catch (error) {
1072
- console.error(' Failed to generate report:', error.message);
1068
+ console.error(t('init.reportFailed', { error: error.message }));
1073
1069
  }
1074
1070
  }
1075
1071