i18ntk 1.10.2 → 2.0.3

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 (108) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +141 -1191
  3. package/main/i18ntk-analyze.js +65 -84
  4. package/main/i18ntk-backup-class.js +420 -0
  5. package/main/i18ntk-backup.js +3 -3
  6. package/main/i18ntk-complete.js +90 -65
  7. package/main/i18ntk-doctor.js +123 -103
  8. package/main/i18ntk-fixer.js +61 -725
  9. package/main/i18ntk-go.js +14 -15
  10. package/main/i18ntk-init.js +77 -26
  11. package/main/i18ntk-java.js +27 -32
  12. package/main/i18ntk-js.js +70 -68
  13. package/main/i18ntk-manage.js +129 -30
  14. package/main/i18ntk-php.js +75 -75
  15. package/main/i18ntk-py.js +55 -56
  16. package/main/i18ntk-scanner.js +59 -57
  17. package/main/i18ntk-setup.js +9 -404
  18. package/main/i18ntk-sizing.js +6 -6
  19. package/main/i18ntk-summary.js +21 -18
  20. package/main/i18ntk-ui.js +11 -10
  21. package/main/i18ntk-usage.js +54 -18
  22. package/main/i18ntk-validate.js +13 -13
  23. package/main/manage/commands/AnalyzeCommand.js +1124 -0
  24. package/main/manage/commands/BackupCommand.js +62 -0
  25. package/main/manage/commands/CommandRouter.js +295 -0
  26. package/main/manage/commands/CompleteCommand.js +61 -0
  27. package/main/manage/commands/DoctorCommand.js +60 -0
  28. package/main/manage/commands/FixerCommand.js +624 -0
  29. package/main/manage/commands/InitCommand.js +62 -0
  30. package/main/manage/commands/ScannerCommand.js +654 -0
  31. package/main/manage/commands/SizingCommand.js +60 -0
  32. package/main/manage/commands/SummaryCommand.js +61 -0
  33. package/main/manage/commands/UsageCommand.js +60 -0
  34. package/main/manage/commands/ValidateCommand.js +978 -0
  35. package/main/manage/index-fixed.js +1447 -0
  36. package/main/manage/index.js +1462 -0
  37. package/main/manage/managers/DebugMenu.js +140 -0
  38. package/main/manage/managers/InteractiveMenu.js +177 -0
  39. package/main/manage/managers/LanguageMenu.js +62 -0
  40. package/main/manage/managers/SettingsMenu.js +53 -0
  41. package/main/manage/services/AuthenticationService.js +263 -0
  42. package/main/manage/services/ConfigurationService-fixed.js +449 -0
  43. package/main/manage/services/ConfigurationService.js +449 -0
  44. package/main/manage/services/FileManagementService.js +368 -0
  45. package/main/manage/services/FrameworkDetectionService.js +458 -0
  46. package/main/manage/services/InitService.js +1051 -0
  47. package/main/manage/services/SetupService.js +462 -0
  48. package/main/manage/services/SummaryService.js +450 -0
  49. package/main/manage/services/UsageService.js +1502 -0
  50. package/package.json +32 -29
  51. package/runtime/enhanced.d.ts +221 -221
  52. package/runtime/index.d.ts +29 -29
  53. package/runtime/index.full.d.ts +331 -331
  54. package/runtime/index.js +7 -6
  55. package/scripts/build-lite.js +17 -17
  56. package/scripts/deprecate-versions.js +23 -6
  57. package/scripts/export-translations.js +5 -5
  58. package/scripts/fix-all-i18n.js +3 -3
  59. package/scripts/fix-and-purify-i18n.js +3 -2
  60. package/scripts/fix-locale-control-chars.js +110 -0
  61. package/scripts/lint-locales.js +80 -0
  62. package/scripts/locale-optimizer.js +8 -8
  63. package/scripts/prepublish.js +21 -21
  64. package/scripts/security-check.js +117 -117
  65. package/scripts/sync-translations.js +4 -4
  66. package/scripts/sync-ui-locales.js +9 -8
  67. package/scripts/validate-all-translations.js +8 -7
  68. package/scripts/verify-deprecations.js +157 -161
  69. package/scripts/verify-translations.js +6 -5
  70. package/settings/i18ntk-config.json +282 -282
  71. package/settings/language-config.json +5 -5
  72. package/settings/settings-cli.js +9 -9
  73. package/settings/settings-manager.js +18 -18
  74. package/ui-locales/de.json +2417 -2348
  75. package/ui-locales/en.json +2415 -2352
  76. package/ui-locales/es.json +2425 -2353
  77. package/ui-locales/fr.json +2418 -2348
  78. package/ui-locales/ja.json +2463 -2361
  79. package/ui-locales/ru.json +2463 -2359
  80. package/ui-locales/zh.json +2418 -2351
  81. package/utils/admin-auth.js +2 -2
  82. package/utils/admin-cli.js +297 -297
  83. package/utils/admin-pin.js +9 -9
  84. package/utils/cli-helper.js +9 -9
  85. package/utils/config-helper.js +73 -104
  86. package/utils/config-manager.js +204 -171
  87. package/utils/config.js +5 -4
  88. package/utils/env-manager.js +249 -263
  89. package/utils/framework-detector.js +27 -24
  90. package/utils/i18n-helper.js +85 -41
  91. package/utils/init-helper.js +152 -94
  92. package/utils/json-output.js +98 -98
  93. package/utils/mini-commander.js +179 -0
  94. package/utils/missing-key-validator.js +5 -5
  95. package/utils/plugin-loader.js +40 -29
  96. package/utils/prompt.js +14 -44
  97. package/utils/safe-json.js +40 -0
  98. package/utils/secure-errors.js +3 -3
  99. package/utils/security-check-improved.js +390 -0
  100. package/utils/security-config.js +5 -5
  101. package/utils/security-fixed.js +607 -0
  102. package/utils/security.js +652 -602
  103. package/utils/setup-enforcer.js +136 -44
  104. package/utils/setup-validator.js +33 -32
  105. package/utils/ultra-performance-optimizer.js +11 -9
  106. package/utils/watch-locales.js +2 -1
  107. package/utils/prompt-fixed.js +0 -55
  108. package/utils/security-check.js +0 -454
@@ -0,0 +1,450 @@
1
+ /**
2
+ * Summary Service
3
+ * Handles summary report generation without circular dependencies
4
+ * @module services/SummaryService
5
+ */
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const SecurityUtils = require('../../../utils/security');
10
+ const { loadTranslations, t } = require('../../../utils/i18n-helper');
11
+ const { getUnifiedConfig } = require('../../../utils/config-helper');
12
+
13
+ module.exports = class SummaryService {
14
+ constructor(config = {}) {
15
+ this.config = config;
16
+ this.settings = null;
17
+ this.configManager = null;
18
+ this.stats = {
19
+ languages: [],
20
+ totalFiles: 0,
21
+ totalKeys: 0,
22
+ keysByLanguage: {},
23
+ filesByLanguage: {},
24
+ fileSizes: {},
25
+ folderSizes: {},
26
+ missingFiles: [],
27
+ inconsistentKeys: [],
28
+ emptyFiles: [],
29
+ malformedFiles: [],
30
+ duplicateKeys: []
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Initialize the service with required dependencies
36
+ * @param {Object} configManager - Configuration manager instance
37
+ */
38
+ initialize(configManager) {
39
+ this.configManager = configManager;
40
+ this.settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
41
+ }
42
+
43
+ /**
44
+ * Run summary analysis and generate report
45
+ * @param {Object} options - Execution options
46
+ * @returns {Promise<string>} Generated report
47
+ */
48
+ async run(options = {}) {
49
+ try {
50
+ // Get unified configuration
51
+ const baseConfig = await getUnifiedConfig('summary', {});
52
+ this.config = { ...this.config, ...baseConfig };
53
+
54
+ // Load translations
55
+ const uiLanguage = this.config.uiLanguage || 'en';
56
+ loadTranslations(uiLanguage);
57
+
58
+ // Validate source directory
59
+ if (!SecurityUtils.safeExistsSync(this.config.sourceDir, this.config.sourceDir)) {
60
+ throw new Error(`Source directory does not exist: ${this.config.sourceDir}`);
61
+ }
62
+
63
+ // Analyze structure and generate report
64
+ await this.analyzeStructure();
65
+ const report = this.generateReport();
66
+
67
+ return report;
68
+ } catch (error) {
69
+ console.error(`Error in SummaryService: ${error.message}`);
70
+ throw error;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Get available languages from the source directory
76
+ * @returns {string[]} Array of language codes
77
+ */
78
+ getAvailableLanguages() {
79
+ if (!SecurityUtils.safeExistsSync(this.config.sourceDir, this.config.sourceDir)) {
80
+ return [];
81
+ }
82
+
83
+ // Check for monolith JSON files (en.json, es.json, etc.)
84
+ const files = SecurityUtils.safeReaddirSync(this.config.sourceDir, this.config.sourceDir) || [];
85
+ const languages = files
86
+ .filter(file => file.endsWith('.json'))
87
+ .map(file => path.basename(file, '.json'));
88
+
89
+ // Also check for directory-based structure for backward compatibility
90
+ const directories = fs.readdirSync(this.config.sourceDir)
91
+ .filter(item => {
92
+ const itemPath = path.join(this.config.sourceDir, item);
93
+ const stats = SecurityUtils.safeStatSync(itemPath, this.config.sourceDir);
94
+ return stats && stats.isDirectory() &&
95
+ !item.startsWith('.') &&
96
+ item !== 'node_modules';
97
+ });
98
+
99
+ return [...new Set([...languages, ...directories])].sort();
100
+ }
101
+
102
+ /**
103
+ * Get translation files for a specific language
104
+ * @param {string} language - Language code
105
+ * @returns {string[]} Array of file names
106
+ */
107
+ getLanguageFiles(language) {
108
+ const languageDir = path.join(this.config.sourceDir, language);
109
+
110
+ if (!SecurityUtils.safeExistsSync(languageDir, this.config.sourceDir)) {
111
+ return [];
112
+ }
113
+
114
+ return SecurityUtils.safeReaddirSync(languageDir, this.config.sourceDir) || []
115
+ .filter(file => {
116
+ return this.config.supportedExtensions.some(ext => file.endsWith(ext)) &&
117
+ !this.config.excludeFiles.includes(file);
118
+ })
119
+ .sort();
120
+ }
121
+
122
+ /**
123
+ * Extract keys from a translation file
124
+ * @param {string} filePath - Path to the translation file
125
+ * @returns {Promise<string[]>} Array of translation keys
126
+ */
127
+ async extractKeysFromFile(filePath) {
128
+ try {
129
+ const content = await SecurityUtils.safeReadFile(filePath, this.config.sourceDir);
130
+ if (!content) {
131
+ return [];
132
+ }
133
+
134
+ if (filePath.endsWith('.json')) {
135
+ const data = JSON.parse(content);
136
+ return this.extractKeysFromObject(data);
137
+ }
138
+
139
+ return [];
140
+ } catch (error) {
141
+ return [];
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Extract keys recursively from an object
147
+ * @param {Object} obj - Object to extract keys from
148
+ * @param {string} prefix - Key prefix for nested objects
149
+ * @returns {string[]} Array of keys
150
+ */
151
+ extractKeysFromObject(obj, prefix = '') {
152
+ const keys = [];
153
+
154
+ for (const [key, value] of Object.entries(obj)) {
155
+ const fullKey = prefix ? `${prefix}.${key}` : key;
156
+
157
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
158
+ keys.push(...this.extractKeysFromObject(value, fullKey));
159
+ } else {
160
+ keys.push(fullKey);
161
+ }
162
+ }
163
+
164
+ return keys;
165
+ }
166
+
167
+ /**
168
+ * Analyze the translation file structure
169
+ * @returns {Promise<void>}
170
+ */
171
+ async analyzeStructure() {
172
+ console.log(t('summary.analyzingFolder'));
173
+
174
+ this.stats.languages = this.getAvailableLanguages();
175
+
176
+ if (this.stats.languages.length === 0) {
177
+ console.log(t('summary.noLanguageDirectoriesFound'));
178
+ return;
179
+ }
180
+
181
+ console.log(t('summary.foundLanguages', {count: this.stats.languages.length, languages: this.stats.languages.join(', ')}));
182
+
183
+ // Analyze each language
184
+ for (const language of this.stats.languages) {
185
+ console.log(t('summary.analyzingLanguage', {language}));
186
+
187
+ const files = this.getLanguageFiles(language);
188
+ this.stats.filesByLanguage[language] = files;
189
+ this.stats.keysByLanguage[language] = {};
190
+
191
+ let totalKeysForLanguage = 0;
192
+
193
+ // Check for missing files compared to reference
194
+ const referenceLanguage = this.stats.languages[0];
195
+ const referenceFiles = this.getLanguageFiles(referenceLanguage);
196
+ const missingFiles = referenceFiles.filter(file => !files.includes(file));
197
+ if (missingFiles.length > 0) {
198
+ this.stats.missingFiles.push({
199
+ language,
200
+ files: missingFiles
201
+ });
202
+ }
203
+
204
+ // Analyze each file
205
+ for (const file of files) {
206
+ const filePath = path.join(this.config.sourceDir, language, file);
207
+
208
+ // Extract keys
209
+ const keys = await this.extractKeysFromFile(filePath);
210
+ this.stats.keysByLanguage[language][file] = keys;
211
+ totalKeysForLanguage += keys.length;
212
+
213
+ this.stats.totalFiles++;
214
+ }
215
+
216
+ this.stats.totalKeys += totalKeysForLanguage;
217
+ console.log(t('summary.keysInFiles', {keys: totalKeysForLanguage, files: files.length}));
218
+ }
219
+
220
+ // Find inconsistent keys across languages
221
+ this.findInconsistentKeys();
222
+ }
223
+
224
+ /**
225
+ * Find keys that are inconsistent across languages
226
+ */
227
+ findInconsistentKeys() {
228
+ console.log(t('summary.checkingInconsistentKeys'));
229
+
230
+ const referenceLanguage = this.stats.languages[0];
231
+ const referenceKeys = this.stats.keysByLanguage[referenceLanguage];
232
+
233
+ for (const file of Object.keys(referenceKeys)) {
234
+ const refKeys = new Set(referenceKeys[file]);
235
+
236
+ for (const language of this.stats.languages.slice(1)) {
237
+ const langKeys = this.stats.keysByLanguage[language][file] || [];
238
+ const langKeySet = new Set(langKeys);
239
+
240
+ // Find missing keys in this language
241
+ const missingInLang = [...refKeys].filter(key => !langKeySet.has(key));
242
+ // Find extra keys in this language
243
+ const extraInLang = [...langKeySet].filter(key => !refKeys.has(key));
244
+
245
+ if (missingInLang.length > 0 || extraInLang.length > 0) {
246
+ this.stats.inconsistentKeys.push({
247
+ file,
248
+ language,
249
+ missing: missingInLang,
250
+ extra: extraInLang
251
+ });
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Generate table row for reports
259
+ * @param {string[]} cells - Table cells
260
+ * @param {number[]} widths - Column widths
261
+ * @returns {string} Formatted table row
262
+ */
263
+ generateTableRow(cells, widths) {
264
+ const row = cells.map((cell, index) => {
265
+ const width = widths[index] || 15;
266
+ return String(cell).padEnd(width);
267
+ }).join(' | ');
268
+ return row;
269
+ }
270
+
271
+ /**
272
+ * Generate table separator
273
+ * @param {number[]} widths - Column widths
274
+ * @returns {string} Table separator line
275
+ */
276
+ generateTableSeparator(widths) {
277
+ return widths.map(width => '-'.repeat(width)).join('-+-');
278
+ }
279
+
280
+ /**
281
+ * Format file size in human readable format
282
+ * @param {number} bytes - File size in bytes
283
+ * @returns {string} Formatted file size
284
+ */
285
+ formatFileSize(bytes) {
286
+ if (bytes === 0) return '0 B';
287
+ const k = 1024;
288
+ const sizes = ['B', 'KB', 'MB', 'GB'];
289
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
290
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
291
+ }
292
+
293
+ /**
294
+ * Generate the summary report
295
+ * @returns {string} Complete report
296
+ */
297
+ generateReport() {
298
+ const report = [];
299
+ const timestamp = new Date().toISOString();
300
+
301
+ report.push(t('summary.reportTitle'));
302
+ report.push('='.repeat(50));
303
+ report.push(t('summary.generated', {timestamp}));
304
+ report.push(t('summary.sourceDirectory', {dir: this.config.sourceDir}));
305
+ report.push('');
306
+
307
+ // Overview
308
+ report.push(t('summary.overview'));
309
+ report.push('='.repeat(30));
310
+ report.push(t('summary.languagesCount', {count: this.stats.languages.length}));
311
+ report.push(t('summary.totalFiles', {count: this.stats.totalFiles}));
312
+ report.push(t('summary.totalKeys', {count: this.stats.totalKeys}));
313
+ report.push(t('summary.avgKeysPerLanguage', {count: Math.round(this.stats.totalKeys / this.stats.languages.length)}));
314
+ report.push('');
315
+
316
+ // Languages breakdown
317
+ report.push(t('summary.languagesBreakdown'));
318
+ report.push('='.repeat(30));
319
+
320
+ const langTableWidths = [12, 8, 8, 12];
321
+ report.push(this.generateTableRow(['Language', 'Files', 'Keys', 'Avg Keys/File'], langTableWidths));
322
+ report.push(this.generateTableSeparator(langTableWidths));
323
+
324
+ for (const language of this.stats.languages) {
325
+ const files = this.stats.filesByLanguage[language];
326
+ const totalKeys = Object.values(this.stats.keysByLanguage[language])
327
+ .reduce((sum, keys) => sum + keys.length, 0);
328
+ const avgKeys = files.length > 0 ? Math.round(totalKeys / files.length) : 0;
329
+
330
+ report.push(this.generateTableRow([
331
+ language.toUpperCase(),
332
+ files.length,
333
+ totalKeys,
334
+ avgKeys
335
+ ], langTableWidths));
336
+ }
337
+ report.push('');
338
+
339
+ // File structure
340
+ report.push(t('summary.fileStructure'));
341
+ report.push('='.repeat(30));
342
+
343
+ const fileTableWidths = [20, 8, 12, 15, 15];
344
+ report.push(this.generateTableRow(['File', 'Keys', 'Languages', 'Missing In'], fileTableWidths));
345
+ report.push(this.generateTableSeparator(fileTableWidths));
346
+
347
+ const referenceLanguage = this.stats.languages[0];
348
+ const referenceFiles = this.stats.filesByLanguage[referenceLanguage] || [];
349
+
350
+ for (const file of referenceFiles) {
351
+ const keysInFile = this.stats.keysByLanguage[referenceLanguage][file]?.length || 0;
352
+
353
+ // Calculate languages with this file
354
+ let languagesWithFile = [];
355
+ let missingIn = [];
356
+
357
+ for (const language of this.stats.languages) {
358
+ if (this.stats.filesByLanguage[language].includes(file)) {
359
+ languagesWithFile.push(language.toUpperCase());
360
+ } else {
361
+ missingIn.push(language.toUpperCase());
362
+ }
363
+ }
364
+
365
+ report.push(this.generateTableRow([
366
+ file,
367
+ keysInFile,
368
+ languagesWithFile.join(', '),
369
+ missingIn.join(', ')
370
+ ], fileTableWidths));
371
+ }
372
+ report.push('');
373
+
374
+ // Issues
375
+ if (this.stats.missingFiles.length > 0 ||
376
+ this.stats.emptyFiles.length > 0 ||
377
+ this.stats.malformedFiles.length > 0 ||
378
+ this.stats.duplicateKeys.length > 0 ||
379
+ this.stats.inconsistentKeys.length > 0) {
380
+
381
+ report.push(t('summary.issuesFound'));
382
+ report.push('='.repeat(30));
383
+
384
+ if (this.stats.missingFiles.length > 0) {
385
+ report.push(t('summary.missingFiles'));
386
+ for (const item of this.stats.missingFiles) {
387
+ report.push(` ${item.language}: ${item.files.join(', ')}`);
388
+ }
389
+ report.push('');
390
+ }
391
+
392
+ if (this.stats.inconsistentKeys.length > 0) {
393
+ report.push(t('summary.inconsistentKeys'));
394
+ for (const item of this.stats.inconsistentKeys) {
395
+ report.push(` ${item.language}/${item.file}:`);
396
+ if (item.missing.length > 0) {
397
+ report.push(t('summary.missingKeys', {keys: item.missing.slice(0, 5).join(', '), more: item.missing.length > 5 ? '...' : ''}));
398
+ }
399
+ if (item.extra.length > 0) {
400
+ report.push(t('summary.extraKeys', {keys: item.extra.slice(0, 5).join(', '), more: item.extra.length > 5 ? '...' : ''}));
401
+ }
402
+ }
403
+ report.push('');
404
+ }
405
+ } else {
406
+ report.push(t('summary.noIssuesFound'));
407
+ report.push('='.repeat(30));
408
+ report.push(t('summary.allFilesConsistent'));
409
+ report.push('');
410
+ }
411
+
412
+ // Recommendations
413
+ report.push(t('summary.recommendations'));
414
+ report.push('='.repeat(30));
415
+ if (this.stats.missingFiles.length > 0) {
416
+ report.push(t('summary.createMissingFiles'));
417
+ }
418
+ if (this.stats.inconsistentKeys.length > 0) {
419
+ report.push(t('summary.synchronizeKeys'));
420
+ }
421
+ if (this.stats.languages.length === 1) {
422
+ report.push(t('summary.addMoreLanguages'));
423
+ }
424
+
425
+ report.push('');
426
+ report.push(t('summary.nextSteps'));
427
+ report.push(t('summary.nextStep1'));
428
+ report.push(t('summary.nextStep2'));
429
+ report.push(t('summary.nextStep3'));
430
+ report.push(t('summary.nextStep4'));
431
+
432
+ return report.join('\n');
433
+ }
434
+
435
+ /**
436
+ * Get current configuration
437
+ * @returns {Object} Current configuration
438
+ */
439
+ getConfig() {
440
+ return this.config;
441
+ }
442
+
443
+ /**
444
+ * Get current settings
445
+ * @returns {Object} Current settings
446
+ */
447
+ getSettings() {
448
+ return this.settings;
449
+ }
450
+ };