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
@@ -24,30 +24,33 @@ const { getFormatAdapter } = require('../utils/format-manager');
24
24
  const UIi18n = require('./i18ntk-ui');
25
25
  loadTranslations();
26
26
  const { getUnifiedConfig, parseCommonArgs, displayHelp } = require('../utils/config-helper');
27
- const { showFrameworkWarningOnce } = require('../utils/cli-helper');
28
- const { createPrompt, isInteractive } = require('../utils/prompt-helper');
27
+ const { showFrameworkWarningOnce } = require('../utils/cli-helper');
28
+ const { createPrompt, isInteractive } = require('../utils/prompt-helper');
29
+ const { parseConfirmation } = require('../utils/localized-confirm');
30
+ const { normalizeReportFormat, writeReportFile } = require('../utils/report-writer');
29
31
 
30
32
  // Language configurations with native names
31
- const LANGUAGE_CONFIG = {
32
- 'de': { name: 'German', nativeName: 'Deutsch' },
33
- 'es': { name: 'Spanish', nativeName: 'Español' },
34
- 'fr': { name: 'French', nativeName: 'Français' },
35
- 'ru': { name: 'Russian', nativeName: 'Русский' },
36
- 'it': { name: 'Italian', nativeName: 'Italiano' },
37
- 'ja': { name: 'Japanese', nativeName: '日本語' },
38
- 'ko': { name: 'Korean', nativeName: '한국어' },
39
- 'zh': { name: 'Chinese', nativeName: '中文' },
40
- 'ar': { name: 'Arabic', nativeName: 'العربية' },
41
- 'hi': { name: 'Hindi', nativeName: 'हिन्दी' },
42
- 'nl': { name: 'Dutch', nativeName: 'Nederlands' },
43
- 'sv': { name: 'Swedish', nativeName: 'Svenska' },
44
- 'da': { name: 'Danish', nativeName: 'Dansk' },
45
- 'no': { name: 'Norwegian', nativeName: 'Norsk' },
46
- 'fi': { name: 'Finnish', nativeName: 'Suomi' },
47
- 'pl': { name: 'Polish', nativeName: 'Polski' },
48
- 'cs': { name: 'Czech', nativeName: 'Čeština' },
49
- 'hu': { name: 'Hungarian', nativeName: 'Magyar' },
50
- 'tr': { name: 'Turkish', nativeName: 'Türkçe' }
33
+ const LANGUAGE_CONFIG = {
34
+ 'en': { name: 'English', nativeName: 'English' },
35
+ 'de': { name: 'German', nativeName: 'Deutsch' },
36
+ 'es': { name: 'Spanish', nativeName: 'Espa\u00f1ol' },
37
+ 'fr': { name: 'French', nativeName: 'Fran\u00e7ais' },
38
+ 'ru': { name: 'Russian', nativeName: '\u0420\u0443\u0441\u0441\u043a\u0438\u0439' },
39
+ 'it': { name: 'Italian', nativeName: 'Italiano' },
40
+ 'ja': { name: 'Japanese', nativeName: '\u65e5\u672c\u8a9e' },
41
+ 'ko': { name: 'Korean', nativeName: '\ud55c\uad6d\uc5b4' },
42
+ 'zh': { name: 'Chinese', nativeName: '\u4e2d\u6587' },
43
+ 'ar': { name: 'Arabic', nativeName: '\u0627\u0644\u0639\u0631\u0628\u064a\u0629' },
44
+ 'hi': { name: 'Hindi', nativeName: '\u0939\u093f\u0928\u094d\u0926\u0940' },
45
+ 'nl': { name: 'Dutch', nativeName: 'Nederlands' },
46
+ 'sv': { name: 'Swedish', nativeName: 'Svenska' },
47
+ 'da': { name: 'Danish', nativeName: 'Dansk' },
48
+ 'no': { name: 'Norwegian', nativeName: 'Norsk' },
49
+ 'fi': { name: 'Finnish', nativeName: 'Suomi' },
50
+ 'pl': { name: 'Polish', nativeName: 'Polski' },
51
+ 'cs': { name: 'Czech', nativeName: '\u010ce\u0161tina' },
52
+ 'hu': { name: 'Hungarian', nativeName: 'Magyar' },
53
+ 'tr': { name: 'Turkish', nativeName: 'T\u00fcrk\u00e7e' }
51
54
  };
52
55
 
53
56
  class I18nInitializer {
@@ -76,7 +79,7 @@ class I18nInitializer {
76
79
  : path.join(this.sourceDir, this.config.sourceLanguage);
77
80
 
78
81
  // Ensure defaultLanguages is properly initialized from config
79
- this.config.defaultLanguages = this.config.defaultLanguages || ['de', 'es', 'fr', 'ru'];
82
+ this.config.defaultLanguages = this.config.defaultLanguages || ['en', 'de', 'es', 'fr', 'ru'];
80
83
 
81
84
  // No longer create readline interface here - use CLI helpers
82
85
  this.rl = null;
@@ -153,7 +156,7 @@ class I18nInitializer {
153
156
  return true;
154
157
  }
155
158
  const answer = await this.prompt('\n' + t('init.continueWithoutI18nPrompt'));
156
- return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
159
+ return parseConfirmation(answer, { language: this.config.uiLanguage || this.config.language || 'en', defaultValue: false });
157
160
  }
158
161
 
159
162
  // Add the missing prompt method
@@ -767,7 +770,7 @@ class I18nInitializer {
767
770
  await flushStdout();
768
771
  const enableProtection = await ask('\n' + t('adminPin.setup_prompt'));
769
772
 
770
- if (enableProtection.toLowerCase() === 'y' || enableProtection.toLowerCase() === 'yes') {
773
+ if (parseConfirmation(enableProtection, { language: this.config.uiLanguage || this.config.language || 'en', defaultValue: false })) {
771
774
  try {
772
775
  const adminAuth = new AdminAuth();
773
776
  await adminAuth.initialize();
@@ -816,16 +819,16 @@ class I18nInitializer {
816
819
  }
817
820
 
818
821
  const { ask } = require('../utils/cli');
819
- console.log('\nBackup Settings');
820
- console.log('Backups are disabled by default to avoid backup recursion and repo pollution.');
821
- const enableAnswer = await ask('Enable automatic backups? (y/N): ');
822
- const enabled = ['y', 'yes'].includes(String(enableAnswer || '').trim().toLowerCase());
822
+ console.log('\n' + t('init.backup.title'));
823
+ console.log(t('init.backup.description'));
824
+ const enableAnswer = await ask(t('init.backup.enablePrompt'));
825
+ const enabled = parseConfirmation(enableAnswer, { language: this.config.uiLanguage || this.config.language || 'en', defaultValue: false });
823
826
 
824
827
  if (!enabled) {
825
828
  return defaultBackupConfig;
826
829
  }
827
830
 
828
- const keepAnswer = await ask('How many backups should be kept automatically (1-3, default 1): ');
831
+ const keepAnswer = await ask(t('init.backup.keepPrompt'));
829
832
  const parsedKeep = parseInt(String(keepAnswer || '').trim(), 10);
830
833
  const maxBackups = Number.isInteger(parsedKeep) ? Math.min(Math.max(parsedKeep, 1), 3) : 1;
831
834
 
@@ -895,7 +898,7 @@ class I18nInitializer {
895
898
  let perLanguage = [];
896
899
  if (structure !== 'existing') {
897
900
  const duplicateChoice = await ask('\n' + t('init.setup.apply_all_prompt'));
898
- duplicateStructure = duplicateChoice.toLowerCase() === 'y' || duplicateChoice.toLowerCase() === 'yes';
901
+ duplicateStructure = parseConfirmation(duplicateChoice, { language: this.config.uiLanguage || this.config.language || 'en', defaultValue: true });
899
902
  if (!duplicateStructure) {
900
903
  // Prompt for languages to include/exclude
901
904
  console.log(t('init.setup.per_language_intro'));
@@ -1054,138 +1057,131 @@ class I18nInitializer {
1054
1057
  console.log(t('init.nextStep3'));
1055
1058
  }
1056
1059
 
1057
- // Generate completion summary with proper error handling
1058
- async generateCompletionSummary(results, targetLanguages) {
1059
- try {
1060
- console.log('\n' + '='.repeat(50));
1061
- console.log(t('init.initializationSummaryTitle'));
1062
- console.log(t('common.separator'));
1063
-
1064
- let totalChanges = 0;
1065
- let languagesProcessed = 0;
1066
- let missingKeysAdded = 0;
1067
-
1068
- Object.entries(results || {}).forEach(([lang, data]) => {
1069
- if (!data || typeof data !== 'object') return;
1070
-
1071
- const langName = LANGUAGE_CONFIG[lang]?.name || 'Unknown';
1072
- const stats = data.totalStats || { total: 0, translated: 0, percentage: 0, missing: 0 };
1073
-
1074
- const statusIcon = stats.percentage === 100 ? '✅' : stats.percentage >= 80 ? '🟡' : '🔴';
1075
-
1076
- console.log(
1077
- t('init.languageSummary', {
1078
- icon: statusIcon,
1079
- name: langName,
1080
- code: lang,
1081
- percentage: stats.percentage || 0,
1082
- })
1083
- );
1084
-
1085
- if (data.files && Array.isArray(data.files)) {
1086
- console.log(t('init.languageFiles', { count: data.files.length }));
1087
- }
1088
-
1089
- console.log(
1090
- t('init.languageKeys', {
1091
- translated: stats.translated || 0,
1092
- total: stats.total || 0,
1093
- })
1094
- );
1095
-
1096
- console.log(t('init.languageMissing', { count: stats.missing || 0 }));
1097
-
1098
- totalChanges += (stats.translated || 0) + (stats.missing || 0);
1099
- languagesProcessed += 1;
1100
- missingKeysAdded += stats.missing || 0;
1101
- });
1102
-
1103
- console.log('\n📊 COMPLETION SUMMARY');
1104
- console.log(t('common.separator'));
1105
- console.log(`📝 Total changes: ${totalChanges}`);
1106
- console.log(`🌍 Languages processed: ${languagesProcessed}`);
1107
- console.log(`➕ Missing keys added: ${missingKeysAdded}`);
1108
-
1109
- if (process.stdin.isTTY && !this.config?.noPrompt) {
1110
- const { ask } = require('../utils/cli');
1111
- const generateReport = await ask('\n🤖 Would you like a report generated? (Y/N): ');
1112
- if (generateReport.toLowerCase() === 'y' || generateReport.toLowerCase() === 'yes') {
1113
- await this.generateDetailedReport(results, targetLanguages);
1114
- }
1115
- }
1116
- } catch (error) {
1117
- console.error('\n❌ Error during completion:', error.message);
1118
- console.log('📊 COMPLETION SUMMARY (Basic)');
1119
- console.log(t('common.separator'));
1120
- console.log(`🌍 Languages processed: ${Object.keys(results || {}).length}`);
1121
- }
1122
- }
1060
+ // Generate completion summary with proper error handling
1061
+ async generateCompletionSummary(results, targetLanguages) {
1062
+ return await this.generateLocalizedCompletionSummary(results, targetLanguages);
1063
+ }
1123
1064
 
1124
- // Generate detailed report
1065
+ async generateLocalizedCompletionSummary(results, targetLanguages) {
1066
+ try {
1067
+ console.log('\n' + '='.repeat(50));
1068
+ console.log(t('init.initializationSummaryTitle'));
1069
+ console.log(t('common.separator'));
1070
+
1071
+ let totalChanges = 0;
1072
+ let languagesProcessed = 0;
1073
+ let missingKeysAdded = 0;
1074
+
1075
+ Object.entries(results || {}).forEach(([lang, data]) => {
1076
+ if (!data || typeof data !== 'object') return;
1077
+
1078
+ const langName = LANGUAGE_CONFIG[lang]?.name || 'Unknown';
1079
+ const stats = data.totalStats || { total: 0, translated: 0, percentage: 0, missing: 0 };
1080
+ const statusIcon = stats.percentage === 100 ? '✅' : stats.percentage >= 80 ? '🟡' : '🔴';
1081
+
1082
+ console.log(t('init.languageSummary', {
1083
+ icon: statusIcon,
1084
+ name: langName,
1085
+ code: lang,
1086
+ percentage: stats.percentage || 0,
1087
+ }));
1088
+
1089
+ if (Array.isArray(data.files)) {
1090
+ console.log(t('init.languageFiles', { count: data.files.length }));
1091
+ }
1092
+ console.log(t('init.languageKeys', { translated: stats.translated || 0, total: stats.total || 0 }));
1093
+ console.log(t('init.languageMissing', { count: stats.missing || 0 }));
1094
+
1095
+ totalChanges += (stats.translated || 0) + (stats.missing || 0);
1096
+ languagesProcessed += 1;
1097
+ missingKeysAdded += stats.missing || 0;
1098
+ });
1099
+
1100
+ console.log('\n' + t('init.completionSummaryTitle'));
1101
+ console.log(t('common.separator'));
1102
+ console.log(t('init.totalChanges', { count: totalChanges }));
1103
+ console.log(t('init.languagesProcessed', { count: languagesProcessed }));
1104
+ console.log(t('init.missingKeysAdded', { count: missingKeysAdded }));
1105
+
1106
+ if (process.stdin.isTTY && !this.config?.noPrompt) {
1107
+ const { ask } = require('../utils/cli');
1108
+ const generateReport = await ask('\n' + t('init.reportPrompt'));
1109
+ if (parseConfirmation(generateReport, { language: this.config.uiLanguage || this.config.language || 'en', defaultValue: true })) {
1110
+ await this.generateDetailedReport(results, targetLanguages);
1111
+ }
1112
+ }
1113
+ } catch (error) {
1114
+ console.error('\n' + t('init.completionError', { error: error.message }));
1115
+ console.log(t('init.completionSummaryBasicTitle'));
1116
+ console.log(t('common.separator'));
1117
+ console.log(t('init.languagesProcessed', { count: Object.keys(results || {}).length }));
1118
+ }
1119
+ }
1120
+
1121
+ // Generate detailed report
1125
1122
  async generateDetailedReport(results, targetLanguages) {
1126
1123
  try {
1127
1124
  const outputDir = this.config.outputDir || path.join(process.cwd(), 'i18ntk-reports');
1128
- if (!SecurityUtils.safeExistsSync(outputDir)) {
1129
- fs.mkdirSync(outputDir, { recursive: true });
1130
- }
1131
-
1132
- const reportPath = path.join(outputDir, 'init-report.json');
1133
- const report = {
1134
- timestamp: new Date().toISOString(),
1135
- languages: targetLanguages,
1136
- results: results,
1137
- summary: {
1138
- languagesProcessed: targetLanguages.length,
1139
- totalFiles: Object.values(results).reduce((sum, data) => sum + (data.files?.length || 0), 0),
1140
- totalKeys: Object.values(results).reduce((sum, data) => sum + (data.totalStats?.total || 0), 0),
1141
- totalMissing: Object.values(results).reduce((sum, data) => sum + (data.totalStats?.missing || 0), 0)
1142
- }
1143
- };
1144
-
1145
- await fs.promises.writeFile(reportPath, JSON.stringify(report, null, 2));
1146
- console.log(`✅ Report generated: ${reportPath}`);
1147
- } catch (error) {
1148
- console.error('❌ Failed to generate report:', error.message);
1149
- }
1150
- }
1125
+ if (!SecurityUtils.safeExistsSync(outputDir)) {
1126
+ fs.mkdirSync(outputDir, { recursive: true });
1127
+ }
1128
+
1129
+ const reportPayload = {
1130
+ timestamp: new Date().toISOString(),
1131
+ languages: targetLanguages,
1132
+ results,
1133
+ summary: {
1134
+ languagesProcessed: targetLanguages.length,
1135
+ totalFiles: Object.values(results).reduce((sum, data) => sum + (data.files?.length || 0), 0),
1136
+ totalKeys: Object.values(results).reduce((sum, data) => sum + (data.totalStats?.total || 0), 0),
1137
+ totalMissing: Object.values(results).reduce((sum, data) => sum + (data.totalStats?.missing || 0), 0)
1138
+ }
1139
+ };
1140
+ const format = normalizeReportFormat(this.config.reports?.format || this.config.reportFormat || 'markdown');
1141
+ const writtenPath = await writeReportFile(outputDir, 'init-report', reportPayload, { format, title: 'I18NTK Init Report' });
1142
+ console.log(t('init.reportGenerated', { reportPath: writtenPath }));
1143
+ } catch (error) {
1144
+ console.error(t('init.reportFailed', { error: error.message }));
1145
+ }
1146
+ }
1151
1147
 
1152
1148
  // Offer interactive locale optimization after initialization
1153
- async offerLocaleOptimization() {
1154
- try {
1155
- console.log('\n' + '='.repeat(60));
1156
- console.log('🎯 **PACKAGE SIZE OPTIMIZATION**');
1157
- console.log('='.repeat(60));
1158
-
1159
- try {
1160
- // Import locale optimizer directly
1161
- const LocaleOptimizer = require('../utils/locale-optimizer');
1162
-
1163
- // First run dry run to show current state
1164
- console.log('\n🔍 Running locale optimization preview...');
1165
- const optimizer = new LocaleOptimizer();
1166
- await optimizer.run({ dryRun: true });
1167
-
1168
- console.log('\n💡 You can reduce package size by selecting only the languages you need');
1169
-
1170
- const answer = await this.prompt('\n🤖 Would you like to run interactive optimization now? (y/n): ');
1171
-
1172
- if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
1173
- console.log('\n🚀 Starting interactive locale optimization...');
1174
- await optimizer.run({ interactive: true });
1175
- console.log('\n Package optimization completed!');
1176
- } else {
1177
- console.log('\n💡 You can run locale optimization later with:');
1178
- console.log(' node utils/locale-optimizer.js --interactive');
1179
- }
1180
- } catch (error) {
1181
- console.log('\n⚠️ Could not offer locale optimization:', error.message);
1182
- console.log('\n💡 You can run locale optimization later with:');
1183
- console.log(' node utils/locale-optimizer.js --interactive');
1184
- }
1185
- } catch (error) {
1186
- console.log('\n⚠️ Could not offer locale optimization:', error.message);
1187
- }
1188
- }
1149
+ async offerLocaleOptimization() {
1150
+ try {
1151
+ console.log('\n' + '='.repeat(60));
1152
+ console.log(t('init.optimize.title'));
1153
+ console.log('='.repeat(60));
1154
+
1155
+ try {
1156
+ // Import locale optimizer directly
1157
+ const LocaleOptimizer = require('../utils/locale-optimizer');
1158
+
1159
+ // First run dry run to show current state
1160
+ console.log('\n' + t('init.optimize.preview'));
1161
+ const optimizer = new LocaleOptimizer();
1162
+ await optimizer.run({ dryRun: true });
1163
+
1164
+ console.log('\n' + t('init.optimize.reduceTip'));
1165
+
1166
+ const answer = await this.prompt('\n' + t('init.optimize.prompt'));
1167
+
1168
+ if (parseConfirmation(answer, { language: this.config.uiLanguage || this.config.language || 'en', defaultValue: false })) {
1169
+ console.log('\n' + t('init.optimize.starting'));
1170
+ await optimizer.run({ interactive: true });
1171
+ console.log('\n' + t('init.optimize.completed'));
1172
+ } else {
1173
+ console.log('\n' + t('init.optimize.later'));
1174
+ console.log(' node utils/locale-optimizer.js --interactive');
1175
+ }
1176
+ } catch (error) {
1177
+ console.log('\n' + t('init.optimize.unavailable', { error: error.message }));
1178
+ console.log('\n' + t('init.optimize.later'));
1179
+ console.log(' node utils/locale-optimizer.js --interactive');
1180
+ }
1181
+ } catch (error) {
1182
+ console.log('\n' + t('init.optimize.unavailable', { error: error.message }));
1183
+ }
1184
+ }
1189
1185
 
1190
1186
  // Run the initialization process with admin authentication
1191
1187
  async run(options = {}) {
@@ -423,12 +423,11 @@ class I18nTextScanner {
423
423
  for (const word of words) {
424
424
  if (profile.stopwords.includes(word)) return true;
425
425
  }
426
- const validChars = trimmed.match(/[\p{L}\p{N}\s\-,.!?':"()\[\]{}]/gu) || [];
427
- const validRatio = validChars.length / trimmed.length;
428
- return validRatio >= 0.5;
429
426
  }
430
427
 
431
- return true;
428
+ const validChars = trimmed.match(/[\p{L}\p{N}\s\-,.!?':"()\[\]{}]/gu) || [];
429
+ const validRatio = validChars.length / trimmed.length;
430
+ return validRatio >= 0.5;
432
431
  }
433
432
 
434
433
  scanFile(filePath, patterns, minLength, maxLength) {
@@ -436,7 +435,7 @@ class I18nTextScanner {
436
435
  const content = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
437
436
  const lines = content.split('\n');
438
437
  const results = [];
439
- const sourceLang = this.config.sourceLanguage || 'en';
438
+ const sourceLang = this.sourceLanguage || 'en';
440
439
 
441
440
  patterns.forEach(pattern => {
442
441
  let match;
@@ -475,7 +474,7 @@ class I18nTextScanner {
475
474
  }
476
475
 
477
476
  generateSuggestion(text) {
478
- const sourceLang = this.config.sourceLanguage || 'en';
477
+ const sourceLang = this.sourceLanguage || 'en';
479
478
  const transliterations = {
480
479
  ja: { 'あ': 'a', 'い': 'i', 'う': 'u', 'え': 'e', 'お': 'o', 'か': 'ka', 'き': 'ki', 'く': 'ku', 'け': 'ke', 'こ': 'ko', 'さ': 'sa', 'し': 'shi', 'す': 'su', 'せ': 'se', 'そ': 'so', 'た': 'ta', 'ち': 'chi', 'つ': 'tsu', 'て': 'te', 'と': 'to', 'な': 'na', 'に': 'ni', 'ぬ': 'nu', 'ね': 'ne', 'の': 'no', 'は': 'ha', 'ひ': 'hi', 'ふ': 'fu', 'へ': 'he', 'ほ': 'ho', 'ま': 'ma', 'み': 'mi', 'む': 'mu', 'め': 'me', 'も': 'mo', 'や': 'ya', 'ゆ': 'yu', 'よ': 'yo', 'ら': 'ra', 'り': 'ri', 'る': 'ru', 'れ': 're', 'ろ': 'ro', 'わ': 'wa', 'を': 'wo', 'ん': 'n' },
481
480
  ru: { 'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo', 'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'sch', 'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya' },
@@ -533,6 +532,9 @@ class I18nTextScanner {
533
532
  gettext: `import gettext\ngettext.gettext('${text}')`,
534
533
  underscore: `from gettext import gettext as _\n_('${text}')`,
535
534
  lazy: `from gettext import gettext_lazy as _\n_('${text}')`
535
+ },
536
+ vanilla: {
537
+ generic: `t('ui.${text.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, '_')}')`
536
538
  }
537
539
  };
538
540
 
@@ -696,7 +698,7 @@ class I18nTextScanner {
696
698
  this.sourceDir = this.config.sourceDir || './src';
697
699
 
698
700
  // Source language for multi-language detection
699
- this.sourceLanguage = args['source-language'] || this.config.sourceLanguage || 'en';
701
+ this.sourceLanguage = args.sourceLanguage || this.config.sourceLanguage || 'en';
700
702
 
701
703
  // Resolve framework with precedence: CLI arg > config.framework.preference|string > auto-detect > fallback
702
704
  const cliFramework = args.framework;
@@ -15,7 +15,7 @@ const SecurityUtils = require('../utils/security');
15
15
  const configManager = require('../utils/config-manager');
16
16
  const SetupService = require('./manage/services/SetupService');
17
17
 
18
- class I18nSetupManager {
18
+ class I18nSetupManager {
19
19
  constructor() {
20
20
  // Use the new SetupService for core business logic
21
21
  this.setupService = new SetupService();
@@ -26,18 +26,41 @@ class I18nSetupManager {
26
26
  return await this.setupService.setup();
27
27
  }
28
28
 
29
-
30
- }
31
-
32
- // CLI interface
33
- if (require.main === module) {
34
- const setupManager = new I18nSetupManager();
35
- setupManager.setup().catch(console.error);
36
- }
29
+
30
+ }
31
+
32
+ function printHelp() {
33
+ console.log([
34
+ '',
35
+ 'i18ntk-setup - foundational i18n toolkit setup',
36
+ '',
37
+ 'Usage:',
38
+ ' i18ntk-setup [options]',
39
+ ' node main/i18ntk-setup.js [options]',
40
+ '',
41
+ 'Options:',
42
+ ' -h, --help Show this help message',
43
+ '',
44
+ 'The setup command detects the current project, writes i18ntk settings,',
45
+ 'and generates i18ntk-setup-report.json in the current working directory.',
46
+ ].join('\n'));
47
+ }
48
+
49
+ // CLI interface
50
+ if (require.main === module) {
51
+ if (process.argv.slice(2).some(arg => arg === '--help' || arg === '-h')) {
52
+ printHelp();
53
+ process.exit(0);
54
+ }
55
+
56
+ const setupManager = new I18nSetupManager();
57
+ setupManager.setup().catch(console.error);
58
+ }
37
59
 
38
60
  // Export both the class and a run function for direct usage
39
61
  module.exports = I18nSetupManager;
40
- module.exports.run = async function() {
41
- const setupManager = new I18nSetupManager();
42
- return await setupManager.setup();
43
- };
62
+ module.exports.run = async function() {
63
+ const setupManager = new I18nSetupManager();
64
+ return await setupManager.setup();
65
+ };
66
+ module.exports.printHelp = printHelp;
@@ -41,15 +41,17 @@ const { logger } = require('../utils/logger');
41
41
  const { getGlobalReadline, closeGlobalReadline } = require('../utils/cli');
42
42
  const SetupEnforcer = require('../utils/setup-enforcer');
43
43
 
44
- // Ensure setup is complete before running
45
- (async () => {
46
- try {
47
- await SetupEnforcer.checkSetupCompleteAsync();
48
- } catch (error) {
49
- console.error('Setup check failed:', error.message);
50
- process.exit(1);
51
- }
52
- })();
44
+ // Ensure setup is complete before running (only when executed directly)
45
+ if (require.main === module) {
46
+ (async () => {
47
+ try {
48
+ await SetupEnforcer.checkSetupCompleteAsync();
49
+ } catch (error) {
50
+ console.error('Setup check failed:', error.message);
51
+ process.exit(1);
52
+ }
53
+ })();
54
+ }
53
55
 
54
56
  loadTranslations();
55
57
 
@@ -1100,45 +1102,7 @@ Generated: ${new Date().toISOString()}
1100
1102
  }
1101
1103
  }
1102
1104
 
1103
- // Main analysis method
1104
- async analyze() {
1105
- const startTime = Date.now();
1106
-
1107
- try {
1108
- logger.info(t("sizing.starting_i18n_sizing_analysis"));
1109
- logger.info(t("sizing.source_directory", { sourceDir: this.sourceDir }));
1110
-
1111
- const files = this.getLanguageFiles();
1112
-
1113
- if (files.length === 0) {
1114
- logger.warn(t("sizing.no_translation_files_found"));
1115
- return;
1116
- }
1117
-
1118
- logger.info(t("sizing.found_languages", { languages: files.map(f => f.language).join(', ') }));
1119
-
1120
- this.analyzeFileSizes(files);
1121
- this.analyzeTranslationContent(files);
1122
- this.generateSizeComparison();
1123
-
1124
- if (this.format === 'table') {
1125
- this.displayFolderResults();
1126
- } else if (this.format === 'json') {
1127
- logger.info(t("sizing.analysisStats", { stats: JSON.stringify(this.stats, null, 2) }));
1128
- }
1129
-
1130
- await this.generateHumanReadableReport();
1131
-
1132
- const endTime = Date.now();
1133
- logger.info(t("sizing.analysis_completed", { duration: ((endTime - startTime) / 1000).toFixed(2) }));
1134
-
1135
- } catch (error) {
1136
- logger.error(t("sizing.analysis_failed", { errorMessage: error.message }));
1137
- process.exit(1);
1138
- }
1139
- }
1140
-
1141
- // Parse command line arguments without yargs
1105
+ // Parse command line arguments without yargs
1142
1106
  parseArgs() {
1143
1107
  const args = process.argv.slice(2);
1144
1108
  const options = {
@@ -1335,7 +1299,7 @@ Options:
1335
1299
 
1336
1300
  const cliHelper = require('../utils/cli-helper');
1337
1301
  const pin = await cliHelper.promptPin(t('adminCli.enterPin'));
1338
- const isValid = await this.adminAuth.verifyPin(pin);
1302
+ const isValid = await adminAuth.verifyPin(pin);
1339
1303
 
1340
1304
  if (!isValid) {
1341
1305
  console.log(t('adminCli.invalidPin'));
@@ -1377,7 +1341,11 @@ Options:
1377
1341
  this.generateSizeComparison();
1378
1342
 
1379
1343
  // Display results
1380
- this.displayFolderResults();
1344
+ if (this.format === 'table') {
1345
+ this.displayFolderResults();
1346
+ } else if (this.format === 'json') {
1347
+ logger.info(t("sizing.analysisStats", { stats: JSON.stringify(this.stats, null, 2) }));
1348
+ }
1381
1349
 
1382
1350
  // Generate reports if requested
1383
1351
  await this.generateHumanReadableReport();