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.
- package/LICENSE +1 -1
- package/README.md +141 -1191
- package/main/i18ntk-analyze.js +65 -84
- package/main/i18ntk-backup-class.js +420 -0
- package/main/i18ntk-backup.js +3 -3
- package/main/i18ntk-complete.js +90 -65
- package/main/i18ntk-doctor.js +123 -103
- package/main/i18ntk-fixer.js +61 -725
- package/main/i18ntk-go.js +14 -15
- package/main/i18ntk-init.js +77 -26
- package/main/i18ntk-java.js +27 -32
- package/main/i18ntk-js.js +70 -68
- package/main/i18ntk-manage.js +129 -30
- package/main/i18ntk-php.js +75 -75
- package/main/i18ntk-py.js +55 -56
- package/main/i18ntk-scanner.js +59 -57
- package/main/i18ntk-setup.js +9 -404
- package/main/i18ntk-sizing.js +6 -6
- package/main/i18ntk-summary.js +21 -18
- package/main/i18ntk-ui.js +11 -10
- package/main/i18ntk-usage.js +54 -18
- package/main/i18ntk-validate.js +13 -13
- package/main/manage/commands/AnalyzeCommand.js +1124 -0
- package/main/manage/commands/BackupCommand.js +62 -0
- package/main/manage/commands/CommandRouter.js +295 -0
- package/main/manage/commands/CompleteCommand.js +61 -0
- package/main/manage/commands/DoctorCommand.js +60 -0
- package/main/manage/commands/FixerCommand.js +624 -0
- package/main/manage/commands/InitCommand.js +62 -0
- package/main/manage/commands/ScannerCommand.js +654 -0
- package/main/manage/commands/SizingCommand.js +60 -0
- package/main/manage/commands/SummaryCommand.js +61 -0
- package/main/manage/commands/UsageCommand.js +60 -0
- package/main/manage/commands/ValidateCommand.js +978 -0
- package/main/manage/index-fixed.js +1447 -0
- package/main/manage/index.js +1462 -0
- package/main/manage/managers/DebugMenu.js +140 -0
- package/main/manage/managers/InteractiveMenu.js +177 -0
- package/main/manage/managers/LanguageMenu.js +62 -0
- package/main/manage/managers/SettingsMenu.js +53 -0
- package/main/manage/services/AuthenticationService.js +263 -0
- package/main/manage/services/ConfigurationService-fixed.js +449 -0
- package/main/manage/services/ConfigurationService.js +449 -0
- package/main/manage/services/FileManagementService.js +368 -0
- package/main/manage/services/FrameworkDetectionService.js +458 -0
- package/main/manage/services/InitService.js +1051 -0
- package/main/manage/services/SetupService.js +462 -0
- package/main/manage/services/SummaryService.js +450 -0
- package/main/manage/services/UsageService.js +1502 -0
- package/package.json +32 -29
- package/runtime/enhanced.d.ts +221 -221
- package/runtime/index.d.ts +29 -29
- package/runtime/index.full.d.ts +331 -331
- package/runtime/index.js +7 -6
- package/scripts/build-lite.js +17 -17
- package/scripts/deprecate-versions.js +23 -6
- package/scripts/export-translations.js +5 -5
- package/scripts/fix-all-i18n.js +3 -3
- package/scripts/fix-and-purify-i18n.js +3 -2
- package/scripts/fix-locale-control-chars.js +110 -0
- package/scripts/lint-locales.js +80 -0
- package/scripts/locale-optimizer.js +8 -8
- package/scripts/prepublish.js +21 -21
- package/scripts/security-check.js +117 -117
- package/scripts/sync-translations.js +4 -4
- package/scripts/sync-ui-locales.js +9 -8
- package/scripts/validate-all-translations.js +8 -7
- package/scripts/verify-deprecations.js +157 -161
- package/scripts/verify-translations.js +6 -5
- package/settings/i18ntk-config.json +282 -282
- package/settings/language-config.json +5 -5
- package/settings/settings-cli.js +9 -9
- package/settings/settings-manager.js +18 -18
- package/ui-locales/de.json +2417 -2348
- package/ui-locales/en.json +2415 -2352
- package/ui-locales/es.json +2425 -2353
- package/ui-locales/fr.json +2418 -2348
- package/ui-locales/ja.json +2463 -2361
- package/ui-locales/ru.json +2463 -2359
- package/ui-locales/zh.json +2418 -2351
- package/utils/admin-auth.js +2 -2
- package/utils/admin-cli.js +297 -297
- package/utils/admin-pin.js +9 -9
- package/utils/cli-helper.js +9 -9
- package/utils/config-helper.js +73 -104
- package/utils/config-manager.js +204 -171
- package/utils/config.js +5 -4
- package/utils/env-manager.js +249 -263
- package/utils/framework-detector.js +27 -24
- package/utils/i18n-helper.js +85 -41
- package/utils/init-helper.js +152 -94
- package/utils/json-output.js +98 -98
- package/utils/mini-commander.js +179 -0
- package/utils/missing-key-validator.js +5 -5
- package/utils/plugin-loader.js +40 -29
- package/utils/prompt.js +14 -44
- package/utils/safe-json.js +40 -0
- package/utils/secure-errors.js +3 -3
- package/utils/security-check-improved.js +390 -0
- package/utils/security-config.js +5 -5
- package/utils/security-fixed.js +607 -0
- package/utils/security.js +652 -602
- package/utils/setup-enforcer.js +136 -44
- package/utils/setup-validator.js +33 -32
- package/utils/ultra-performance-optimizer.js +11 -9
- package/utils/watch-locales.js +2 -1
- package/utils/prompt-fixed.js +0 -55
- 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
|
+
};
|