i18ntk 3.0.0 → 3.1.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.
- package/CHANGELOG.md +43 -16
- package/README.md +20 -17
- package/main/i18ntk-sizing.js +471 -218
- package/main/i18ntk-translate.js +399 -68
- package/main/i18ntk-validate.js +26 -14
- package/main/manage/commands/TranslateCommand.js +273 -52
- package/main/manage/commands/ValidateCommand.js +25 -13
- package/package.json +3 -1
- package/settings/settings-cli.js +75 -29
- package/settings/settings-manager.js +109 -1
- package/ui-locales/de.json +2 -2
- package/ui-locales/en.json +2 -2
- package/ui-locales/es.json +2 -2
- package/ui-locales/fr.json +2 -2
- package/ui-locales/ja.json +2 -2
- package/ui-locales/ru.json +4 -3
- package/ui-locales/zh.json +2 -2
- package/utils/config-manager.js +20 -4
- package/utils/security.js +4 -3
- package/utils/translate/cli.js +20 -16
- package/utils/translate/placeholder.js +60 -0
- package/utils/translate/protection.js +243 -0
- package/utils/translate/report.js +49 -22
- package/utils/validation-risk.js +175 -0
package/main/i18ntk-sizing.js
CHANGED
|
@@ -64,11 +64,12 @@ function getConfig() {
|
|
|
64
64
|
projectRoot: settings.projectRoot || '.',
|
|
65
65
|
sourceDir: sourceDir,
|
|
66
66
|
i18nDir: settings.i18nDir || settings.sourceDir || './locales',
|
|
67
|
-
outputDir: settings.outputDir || './i18ntk-reports',
|
|
68
|
-
threshold: settings.processing?.sizingThreshold || 50,
|
|
69
|
-
uiLanguage: settings.language || 'en'
|
|
70
|
-
|
|
71
|
-
}
|
|
67
|
+
outputDir: settings.outputDir || './i18ntk-reports',
|
|
68
|
+
threshold: settings.processing?.sizingThreshold || 50,
|
|
69
|
+
uiLanguage: settings.language || 'en',
|
|
70
|
+
sourceLanguage: settings.sourceLanguage || 'en'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
72
73
|
|
|
73
74
|
class I18nSizingAnalyzer {
|
|
74
75
|
constructor(options = {}) {
|
|
@@ -77,10 +78,13 @@ class I18nSizingAnalyzer {
|
|
|
77
78
|
this.sourceDir = path.resolve(projectRoot, options.sourceDir || config.sourceDir);
|
|
78
79
|
this.outputDir = path.resolve(projectRoot, options.outputDir || config.outputDir);
|
|
79
80
|
this.languages = options.languages || [];
|
|
80
|
-
this.threshold = options.threshold || config.threshold; // Size difference threshold in percentage
|
|
81
|
-
this.format = options.format || 'table';
|
|
82
|
-
this.outputReport = options.outputReport || false;
|
|
83
|
-
this.
|
|
81
|
+
this.threshold = options.threshold || config.threshold; // Size difference threshold in percentage
|
|
82
|
+
this.format = options.format || 'table';
|
|
83
|
+
this.outputReport = options.outputReport || false;
|
|
84
|
+
this.sourceLanguage = options.sourceLanguage || config.sourceLanguage || 'en';
|
|
85
|
+
this.detailed = options.detailed || false;
|
|
86
|
+
this.detailedKeys = options.detailedKeys || false;
|
|
87
|
+
this.rl = null;
|
|
84
88
|
|
|
85
89
|
// Initialize i18n with UI language from config
|
|
86
90
|
const uiLanguage = options.uiLanguage || config.uiLanguage || 'en';
|
|
@@ -109,9 +113,9 @@ class I18nSizingAnalyzer {
|
|
|
109
113
|
closeGlobalReadline();
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
// Get available language files
|
|
113
|
-
getLanguageFiles() {
|
|
114
|
-
const validatedSourceDir = SecurityUtils.validatePath(this.sourceDir, process.cwd());
|
|
116
|
+
// Get available language files
|
|
117
|
+
getLanguageFiles() {
|
|
118
|
+
const validatedSourceDir = SecurityUtils.validatePath(this.sourceDir, process.cwd());
|
|
115
119
|
if (!validatedSourceDir) {
|
|
116
120
|
throw new Error(t("sizing.invalidSourceDirectoryError", { sourceDir: this.sourceDir }));
|
|
117
121
|
}
|
|
@@ -132,11 +136,8 @@ class I18nSizingAnalyzer {
|
|
|
132
136
|
if (!stat) continue;
|
|
133
137
|
|
|
134
138
|
if (stat.isDirectory()) {
|
|
135
|
-
// This is a language directory, combine all JSON files
|
|
136
|
-
const langFiles =
|
|
137
|
-
.filter(file => file.endsWith('.json'))
|
|
138
|
-
.map(file => SecurityUtils.validatePath(path.join(itemPath, file), process.cwd()))
|
|
139
|
-
.filter(file => file !== null);
|
|
139
|
+
// This is a language directory, combine all JSON files
|
|
140
|
+
const langFiles = this.collectJsonFiles(itemPath);
|
|
140
141
|
|
|
141
142
|
if (langFiles.length > 0) {
|
|
142
143
|
files.push({
|
|
@@ -149,60 +150,130 @@ class I18nSizingAnalyzer {
|
|
|
149
150
|
} else if (item.endsWith('.json')) {
|
|
150
151
|
// Direct JSON file in root
|
|
151
152
|
const lang = path.basename(item, '.json');
|
|
152
|
-
files.push({
|
|
153
|
-
language: lang,
|
|
154
|
-
file: item,
|
|
155
|
-
path: itemPath
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
files.push({
|
|
154
|
+
language: lang,
|
|
155
|
+
file: item,
|
|
156
|
+
path: itemPath,
|
|
157
|
+
files: [itemPath],
|
|
158
|
+
isSingleFile: true
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
159
162
|
|
|
160
163
|
if (this.languages.length > 0) {
|
|
161
|
-
return files
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
return files
|
|
165
|
+
.filter(f => this.languages.includes(f.language))
|
|
166
|
+
.sort((a, b) => a.language.localeCompare(b.language));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return files.sort((a, b) => a.language.localeCompare(b.language));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
collectJsonFiles(dirPath) {
|
|
173
|
+
const jsonFiles = [];
|
|
174
|
+
const entries = SecurityUtils.safeReaddirSync(dirPath, process.cwd(), { withFileTypes: true });
|
|
175
|
+
|
|
176
|
+
entries.forEach(entry => {
|
|
177
|
+
const entryPath = SecurityUtils.validatePath(path.join(dirPath, entry.name), process.cwd());
|
|
178
|
+
if (!entryPath) return;
|
|
179
|
+
|
|
180
|
+
if (entry.isDirectory()) {
|
|
181
|
+
jsonFiles.push(...this.collectJsonFiles(entryPath));
|
|
182
|
+
} else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
183
|
+
jsonFiles.push(entryPath);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return jsonFiles.sort((a, b) => this.getFileSortName(dirPath, a).localeCompare(this.getFileSortName(dirPath, b)));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
getLanguageFileName(languageEntry, filePath) {
|
|
191
|
+
if (languageEntry.isSingleFile) {
|
|
192
|
+
return path.basename(filePath);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return this.getFileSortName(languageEntry.path, filePath);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
getFileSortName(basePath, filePath) {
|
|
199
|
+
return path.relative(basePath, filePath).replace(/\\/g, '/');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
createTable(columns, rows) {
|
|
203
|
+
const widths = columns.map(column => {
|
|
204
|
+
return Math.max(
|
|
205
|
+
column.label.length,
|
|
206
|
+
...rows.map(row => String(row[column.key] ?? '').length)
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const formatCell = (value, width, align = 'left') => {
|
|
211
|
+
const text = String(value ?? '');
|
|
212
|
+
return align === 'right' ? text.padStart(width) : text.padEnd(width);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const header = columns.map((column, index) => formatCell(column.label, widths[index], column.align)).join(' ');
|
|
216
|
+
const separator = widths.map(width => '-'.repeat(width)).join(' ');
|
|
217
|
+
const body = rows.map(row => columns
|
|
218
|
+
.map((column, index) => formatCell(row[column.key], widths[index], column.align))
|
|
219
|
+
.join(' '));
|
|
220
|
+
|
|
221
|
+
return [header, separator, ...body].join('\n');
|
|
222
|
+
}
|
|
166
223
|
|
|
167
224
|
// Analyze file sizes
|
|
168
225
|
analyzeFileSizes(files) {
|
|
169
226
|
logger.info(t("sizing.analyzing_file_sizes"));
|
|
170
227
|
|
|
171
|
-
files.forEach(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
let
|
|
176
|
-
let
|
|
177
|
-
let
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
228
|
+
files.forEach(languageEntry => {
|
|
229
|
+
const { language, file, path: filePath, files: langFiles } = languageEntry;
|
|
230
|
+
if (langFiles) {
|
|
231
|
+
// Handle nested directory structure
|
|
232
|
+
let totalSize = 0;
|
|
233
|
+
let totalLines = 0;
|
|
234
|
+
let totalCharacters = 0;
|
|
235
|
+
let lastModified = new Date(0);
|
|
236
|
+
const perFiles = {};
|
|
237
|
+
|
|
238
|
+
langFiles.forEach(langFile => {
|
|
239
|
+
const stats = SecurityUtils.safeStatSync(langFile, process.cwd());
|
|
240
|
+
if (!stats) return;
|
|
241
|
+
|
|
183
242
|
let content = SecurityUtils.safeReadFileSync(langFile, process.cwd(), 'utf8');
|
|
184
243
|
if (typeof content !== "string") content = "";
|
|
185
|
-
totalSize += stats.size;
|
|
186
|
-
totalLines += content.split('\n').length;
|
|
187
|
-
totalCharacters += content.length;
|
|
188
|
-
if (stats.mtime > lastModified) {
|
|
189
|
-
lastModified = stats.mtime;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
244
|
+
totalSize += stats.size;
|
|
245
|
+
totalLines += content.split('\n').length;
|
|
246
|
+
totalCharacters += content.length;
|
|
247
|
+
if (stats.mtime > lastModified) {
|
|
248
|
+
lastModified = stats.mtime;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const relativeName = this.getLanguageFileName(languageEntry, langFile);
|
|
252
|
+
perFiles[relativeName] = {
|
|
253
|
+
file: relativeName,
|
|
254
|
+
path: langFile,
|
|
255
|
+
size: stats.size,
|
|
256
|
+
sizeKB: (stats.size / 1024).toFixed(2),
|
|
257
|
+
lines: content.split('\n').length,
|
|
258
|
+
characters: content.length,
|
|
259
|
+
lastModified: stats.mtime
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
this.stats.files[language] = {
|
|
264
|
+
file,
|
|
265
|
+
size: totalSize,
|
|
196
266
|
sizeKB: (totalSize / 1024).toFixed(2),
|
|
197
267
|
lines: totalLines,
|
|
198
|
-
characters: totalCharacters,
|
|
199
|
-
lastModified: lastModified,
|
|
200
|
-
fileCount:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
268
|
+
characters: totalCharacters,
|
|
269
|
+
lastModified: lastModified,
|
|
270
|
+
fileCount: Object.keys(perFiles).length,
|
|
271
|
+
perFiles
|
|
272
|
+
};
|
|
273
|
+
} else {
|
|
274
|
+
// Handle single file structure
|
|
275
|
+
const stats = SecurityUtils.safeStatSync(filePath, process.cwd());
|
|
276
|
+
if (!stats) return;
|
|
206
277
|
|
|
207
278
|
let content = SecurityUtils.safeReadFileSync(filePath, process.cwd(), 'utf8');
|
|
208
279
|
if (typeof content !== "string") content = "";
|
|
@@ -211,37 +282,72 @@ class I18nSizingAnalyzer {
|
|
|
211
282
|
size: stats.size,
|
|
212
283
|
sizeKB: (stats.size / 1024).toFixed(2),
|
|
213
284
|
lines: content.split('\n').length,
|
|
214
|
-
characters: content.length,
|
|
215
|
-
lastModified: stats.mtime,
|
|
216
|
-
fileCount: 1
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
285
|
+
characters: content.length,
|
|
286
|
+
lastModified: stats.mtime,
|
|
287
|
+
fileCount: 1,
|
|
288
|
+
perFiles: {
|
|
289
|
+
[file]: {
|
|
290
|
+
file,
|
|
291
|
+
path: filePath,
|
|
292
|
+
size: stats.size,
|
|
293
|
+
sizeKB: (stats.size / 1024).toFixed(2),
|
|
294
|
+
lines: content.split('\n').length,
|
|
295
|
+
characters: content.length,
|
|
296
|
+
lastModified: stats.mtime
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
221
303
|
|
|
222
304
|
// Analyze translation content
|
|
223
305
|
analyzeTranslationContent(files) {
|
|
224
306
|
logger.info(t("sizing.analyzing_translation_content"));
|
|
225
307
|
|
|
226
|
-
files.forEach(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
308
|
+
files.forEach(languageEntry => {
|
|
309
|
+
const { language, path: filePath, files: langFiles } = languageEntry;
|
|
310
|
+
try {
|
|
311
|
+
let combinedContent = {};
|
|
312
|
+
const fileAnalyses = {};
|
|
313
|
+
|
|
314
|
+
if (langFiles && !languageEntry.isSingleFile) {
|
|
315
|
+
// Handle nested directory structure - combine all JSON files
|
|
316
|
+
langFiles.forEach(langFile => {
|
|
317
|
+
const rawContent = SecurityUtils.safeReadFileSync(langFile, process.cwd(), 'utf8');
|
|
318
|
+
const fileContent = SecurityUtils.safeParseJSON(rawContent);
|
|
319
|
+
if (fileContent) {
|
|
320
|
+
const fileName = this.getLanguageFileName(languageEntry, langFile);
|
|
321
|
+
const combinedKey = fileName.replace(/\.json$/i, '');
|
|
322
|
+
combinedContent[combinedKey] = fileContent;
|
|
323
|
+
const fileAnalysis = this.analyzeTranslationObject(fileContent, '');
|
|
324
|
+
fileAnalyses[fileName] = {
|
|
325
|
+
totalKeys: fileAnalysis.keyCount,
|
|
326
|
+
totalCharacters: fileAnalysis.charCount,
|
|
327
|
+
averageKeyLength: fileAnalysis.keyCount > 0 ? fileAnalysis.charCount / fileAnalysis.keyCount : 0,
|
|
328
|
+
maxKeyLength: fileAnalysis.maxLength,
|
|
329
|
+
minKeyLength: fileAnalysis.minLength,
|
|
330
|
+
emptyKeys: fileAnalysis.emptyKeys,
|
|
331
|
+
longKeys: fileAnalysis.longKeys
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
} else {
|
|
336
|
+
// Handle single file structure
|
|
337
|
+
const singleFilePath = languageEntry.isSingleFile && langFiles ? langFiles[0] : filePath;
|
|
338
|
+
const rawContent = SecurityUtils.safeReadFileSync(singleFilePath, process.cwd(), 'utf8');
|
|
339
|
+
combinedContent = SecurityUtils.safeParseJSON(rawContent) || {};
|
|
340
|
+
const fileAnalysis = this.analyzeTranslationObject(combinedContent, '');
|
|
341
|
+
fileAnalyses[path.basename(singleFilePath)] = {
|
|
342
|
+
totalKeys: fileAnalysis.keyCount,
|
|
343
|
+
totalCharacters: fileAnalysis.charCount,
|
|
344
|
+
averageKeyLength: fileAnalysis.keyCount > 0 ? fileAnalysis.charCount / fileAnalysis.keyCount : 0,
|
|
345
|
+
maxKeyLength: fileAnalysis.maxLength,
|
|
346
|
+
minKeyLength: fileAnalysis.minLength,
|
|
347
|
+
emptyKeys: fileAnalysis.emptyKeys,
|
|
348
|
+
longKeys: fileAnalysis.longKeys
|
|
349
|
+
};
|
|
350
|
+
}
|
|
245
351
|
|
|
246
352
|
const analysis = this.analyzeTranslationObject(combinedContent, '');
|
|
247
353
|
|
|
@@ -249,11 +355,12 @@ class I18nSizingAnalyzer {
|
|
|
249
355
|
totalKeys: analysis.keyCount,
|
|
250
356
|
totalCharacters: analysis.charCount,
|
|
251
357
|
averageKeyLength: analysis.keyCount > 0 ? analysis.charCount / analysis.keyCount : 0,
|
|
252
|
-
maxKeyLength: analysis.maxLength,
|
|
253
|
-
minKeyLength: analysis.minLength,
|
|
254
|
-
emptyKeys: analysis.emptyKeys,
|
|
255
|
-
longKeys: analysis.longKeys
|
|
256
|
-
|
|
358
|
+
maxKeyLength: analysis.maxLength,
|
|
359
|
+
minKeyLength: analysis.minLength,
|
|
360
|
+
emptyKeys: analysis.emptyKeys,
|
|
361
|
+
longKeys: analysis.longKeys,
|
|
362
|
+
files: fileAnalyses
|
|
363
|
+
};
|
|
257
364
|
|
|
258
365
|
// Store individual key sizes for comparison
|
|
259
366
|
Object.entries(analysis.keys).forEach(([key, value]) => {
|
|
@@ -320,7 +427,7 @@ class I18nSizingAnalyzer {
|
|
|
320
427
|
logger.info(t("sizing.generating_size_comparisons"));
|
|
321
428
|
|
|
322
429
|
const languages = Object.keys(this.stats.languages);
|
|
323
|
-
const baseLanguage = languages[0];
|
|
430
|
+
const baseLanguage = languages.includes(this.sourceLanguage) ? this.sourceLanguage : languages[0];
|
|
324
431
|
|
|
325
432
|
if (!baseLanguage) {
|
|
326
433
|
logger.warn(t("sizing.no_languages_found_for_comparison"));
|
|
@@ -342,7 +449,9 @@ class I18nSizingAnalyzer {
|
|
|
342
449
|
const baseStats = this.stats.languages[baseLanguage];
|
|
343
450
|
const langStats = this.stats.languages[lang];
|
|
344
451
|
|
|
345
|
-
const sizeDiff =
|
|
452
|
+
const sizeDiff = baseStats.totalCharacters > 0
|
|
453
|
+
? ((langStats.totalCharacters - baseStats.totalCharacters) / baseStats.totalCharacters) * 100
|
|
454
|
+
: 0;
|
|
346
455
|
|
|
347
456
|
this.stats.summary.sizeVariations[lang] = {
|
|
348
457
|
characterDifference: langStats.totalCharacters - baseStats.totalCharacters,
|
|
@@ -360,7 +469,7 @@ class I18nSizingAnalyzer {
|
|
|
360
469
|
Object.entries(langData).forEach(([lang, data]) => {
|
|
361
470
|
if (lang === baseLanguage) return;
|
|
362
471
|
|
|
363
|
-
const diff = ((data.length - baseLang.length) / baseLang.length) * 100;
|
|
472
|
+
const diff = baseLang.length > 0 ? ((data.length - baseLang.length) / baseLang.length) * 100 : 0;
|
|
364
473
|
if (Math.abs(diff) > this.threshold) {
|
|
365
474
|
variations.push({
|
|
366
475
|
language: lang,
|
|
@@ -379,10 +488,45 @@ class I18nSizingAnalyzer {
|
|
|
379
488
|
});
|
|
380
489
|
}
|
|
381
490
|
});
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
491
|
+
this.generateFileComparison();
|
|
492
|
+
|
|
493
|
+
// Generate recommendations
|
|
494
|
+
this.generateRecommendations();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
generateFileComparison() {
|
|
498
|
+
const languages = Object.keys(this.stats.files);
|
|
499
|
+
const baseLanguage = this.stats.summary.baseLanguage || languages[0];
|
|
500
|
+
const fileSets = {};
|
|
501
|
+
const allFiles = new Set();
|
|
502
|
+
|
|
503
|
+
languages.forEach(language => {
|
|
504
|
+
const files = Object.keys(this.stats.files[language]?.perFiles || {}).sort();
|
|
505
|
+
fileSets[language] = files;
|
|
506
|
+
files.forEach(file => allFiles.add(file));
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const baseFiles = new Set(fileSets[baseLanguage] || []);
|
|
510
|
+
const commonFiles = [...allFiles].filter(file => languages.every(language => fileSets[language].includes(file))).sort();
|
|
511
|
+
const missingFilesByLanguage = {};
|
|
512
|
+
const extraFilesByLanguage = {};
|
|
513
|
+
|
|
514
|
+
languages.forEach(language => {
|
|
515
|
+
const currentFiles = new Set(fileSets[language]);
|
|
516
|
+
missingFilesByLanguage[language] = [...allFiles].filter(file => !currentFiles.has(file)).sort();
|
|
517
|
+
extraFilesByLanguage[language] = fileSets[language].filter(file => !baseFiles.has(file)).sort();
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
this.stats.summary.fileComparison = {
|
|
521
|
+
baseLanguage,
|
|
522
|
+
totalUniqueFiles: allFiles.size,
|
|
523
|
+
commonFiles,
|
|
524
|
+
fileSets,
|
|
525
|
+
missingFilesByLanguage,
|
|
526
|
+
extraFilesByLanguage,
|
|
527
|
+
hasMismatches: Object.values(missingFilesByLanguage).some(files => files.length > 0)
|
|
528
|
+
};
|
|
529
|
+
}
|
|
386
530
|
|
|
387
531
|
// Generate optimization recommendations
|
|
388
532
|
generateRecommendations() {
|
|
@@ -414,58 +558,132 @@ class I18nSizingAnalyzer {
|
|
|
414
558
|
}
|
|
415
559
|
|
|
416
560
|
// Display concise folder-level results
|
|
417
|
-
displayFolderResults() {
|
|
561
|
+
displayFolderResults() {
|
|
418
562
|
console.log("\n" + t("sizing.sizing_analysis_results"));
|
|
419
563
|
console.log(t("sizing.lineSeparator"));
|
|
420
564
|
|
|
421
|
-
// Folder-level summary table
|
|
422
|
-
console.log("\n" + t("sizing.folder_summary_title"));
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const baseChars = Math.round(this.stats.languages[baseLang].totalKeys * this.stats.languages[baseLang].averageKeyLength);
|
|
443
|
-
|
|
444
|
-
this.languages.slice(1).forEach(lang => {
|
|
445
|
-
if (this.stats.languages[lang]) {
|
|
446
|
-
const langChars = Math.round(this.stats.languages[lang].totalKeys * this.stats.languages[lang].averageKeyLength);
|
|
447
|
-
const diff = langChars - baseChars;
|
|
448
|
-
const percent = baseChars > 0 ? ((diff / baseChars) * 100).toFixed(1) : 0;
|
|
449
|
-
const status = Math.abs(diff) > this.threshold ? "⚠️" : "✅";
|
|
450
|
-
console.log(`${lang}: ${diff > 0 ? '+' : ''}${diff} chars (${percent}%) ${status}`);
|
|
451
|
-
}
|
|
452
|
-
});
|
|
453
|
-
}
|
|
565
|
+
// Folder-level summary table
|
|
566
|
+
console.log("\n" + t("sizing.folder_summary_title"));
|
|
567
|
+
const folderRows = Object.entries(this.stats.files).map(([lang, data]) => {
|
|
568
|
+
const langData = this.stats.languages[lang];
|
|
569
|
+
return {
|
|
570
|
+
language: lang,
|
|
571
|
+
files: data.fileCount,
|
|
572
|
+
sizeKB: data.sizeKB,
|
|
573
|
+
totalKeys: langData.totalKeys,
|
|
574
|
+
avgLength: langData.averageKeyLength.toFixed(1),
|
|
575
|
+
totalChars: langData.totalCharacters
|
|
576
|
+
};
|
|
577
|
+
});
|
|
578
|
+
console.log(this.createTable([
|
|
579
|
+
{ key: 'language', label: 'Language' },
|
|
580
|
+
{ key: 'files', label: 'Files', align: 'right' },
|
|
581
|
+
{ key: 'sizeKB', label: 'Size(KB)', align: 'right' },
|
|
582
|
+
{ key: 'totalKeys', label: 'Keys', align: 'right' },
|
|
583
|
+
{ key: 'avgLength', label: 'Avg Len', align: 'right' },
|
|
584
|
+
{ key: 'totalChars', label: 'Total Chars', align: 'right' }
|
|
585
|
+
], folderRows));
|
|
454
586
|
|
|
455
|
-
//
|
|
587
|
+
// Language comparison summary
|
|
588
|
+
console.log("\n" + t("sizing.language_comparison_title"));
|
|
589
|
+
const comparisonRows = Object.entries(this.stats.summary.sizeVariations || {}).map(([lang, data]) => ({
|
|
590
|
+
language: lang,
|
|
591
|
+
charDiff: `${data.characterDifference > 0 ? '+' : ''}${data.characterDifference}`,
|
|
592
|
+
percent: `${data.percentageDifference > 0 ? '+' : ''}${data.percentageDifference}%`,
|
|
593
|
+
status: data.isProblematic ? 'WARN' : 'OK'
|
|
594
|
+
}));
|
|
595
|
+
|
|
596
|
+
if (comparisonRows.length > 0) {
|
|
597
|
+
console.log(this.createTable([
|
|
598
|
+
{ key: 'language', label: 'Language' },
|
|
599
|
+
{ key: 'charDiff', label: 'Char Diff', align: 'right' },
|
|
600
|
+
{ key: 'percent', label: 'Diff %', align: 'right' },
|
|
601
|
+
{ key: 'status', label: 'Status' }
|
|
602
|
+
], comparisonRows));
|
|
603
|
+
} else {
|
|
604
|
+
console.log("No language comparison available.");
|
|
605
|
+
}
|
|
606
|
+
this.displayFileComparison();
|
|
607
|
+
|
|
608
|
+
if (this.detailed) {
|
|
609
|
+
this.displayPerFileResults();
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Summary stats
|
|
456
613
|
console.log("\n" + t("sizing.summary_stats", {
|
|
457
614
|
totalLanguages: Object.keys(this.stats.languages).length,
|
|
458
615
|
totalKeys: Object.keys(this.stats.keys).length,
|
|
459
616
|
reportPath: this.outputDir
|
|
460
617
|
}));
|
|
461
618
|
|
|
462
|
-
if (this.detailedKeys) {
|
|
463
|
-
this.displayDetailedKeys();
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
619
|
+
if (this.detailedKeys) {
|
|
620
|
+
this.displayDetailedKeys();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
displayFileComparison() {
|
|
625
|
+
const comparison = this.stats.summary.fileComparison;
|
|
626
|
+
if (!comparison) return;
|
|
627
|
+
|
|
628
|
+
console.log("\nFile Set Comparison");
|
|
629
|
+
const rows = Object.keys(this.stats.files).map(language => ({
|
|
630
|
+
language,
|
|
631
|
+
files: this.stats.files[language].fileCount,
|
|
632
|
+
missing: comparison.missingFilesByLanguage[language]?.length || 0,
|
|
633
|
+
extra: comparison.extraFilesByLanguage[language]?.length || 0
|
|
634
|
+
}));
|
|
635
|
+
|
|
636
|
+
console.log(this.createTable([
|
|
637
|
+
{ key: 'language', label: 'Language' },
|
|
638
|
+
{ key: 'files', label: 'Files', align: 'right' },
|
|
639
|
+
{ key: 'missing', label: 'Missing', align: 'right' },
|
|
640
|
+
{ key: 'extra', label: 'Extra vs Base', align: 'right' }
|
|
641
|
+
], rows));
|
|
642
|
+
|
|
643
|
+
if (comparison.hasMismatches) {
|
|
644
|
+
Object.entries(comparison.missingFilesByLanguage).forEach(([language, files]) => {
|
|
645
|
+
if (files.length > 0) {
|
|
646
|
+
console.log(`${language} missing: ${files.join(', ')}`);
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
displayPerFileResults() {
|
|
653
|
+
console.log("\nPer-File Analysis");
|
|
654
|
+
const rows = [];
|
|
655
|
+
|
|
656
|
+
Object.entries(this.stats.languages).forEach(([language, languageData]) => {
|
|
657
|
+
Object.entries(languageData.files || {}).forEach(([fileName, fileData]) => {
|
|
658
|
+
const fileSizeData = this.stats.files[language]?.perFiles?.[fileName] || {};
|
|
659
|
+
rows.push({
|
|
660
|
+
language,
|
|
661
|
+
file: fileName,
|
|
662
|
+
sizeKB: fileSizeData.sizeKB || '0.00',
|
|
663
|
+
keys: fileData.totalKeys,
|
|
664
|
+
avgLength: fileData.averageKeyLength.toFixed(1),
|
|
665
|
+
totalChars: fileData.totalCharacters
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
if (rows.length === 0) {
|
|
671
|
+
console.log("No per-file analysis available.");
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
console.log(this.createTable([
|
|
676
|
+
{ key: 'language', label: 'Language' },
|
|
677
|
+
{ key: 'file', label: 'File' },
|
|
678
|
+
{ key: 'sizeKB', label: 'Size(KB)', align: 'right' },
|
|
679
|
+
{ key: 'keys', label: 'Keys', align: 'right' },
|
|
680
|
+
{ key: 'avgLength', label: 'Avg Len', align: 'right' },
|
|
681
|
+
{ key: 'totalChars', label: 'Total Chars', align: 'right' }
|
|
682
|
+
], rows));
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Display detailed key analysis (only when explicitly requested)
|
|
686
|
+
displayDetailedKeys() {
|
|
469
687
|
console.log("\n" + t("sizing.detailed_key_analysis_title"));
|
|
470
688
|
console.log(t("sizing.lineSeparator"));
|
|
471
689
|
|
|
@@ -478,14 +696,14 @@ class I18nSizingAnalyzer {
|
|
|
478
696
|
|
|
479
697
|
console.log(t("sizing.key_analysis_header", { key }));
|
|
480
698
|
|
|
481
|
-
Object.entries(data
|
|
482
|
-
const length =
|
|
483
|
-
const isEmpty = length === 0;
|
|
484
|
-
const isLong = length > this.threshold;
|
|
485
|
-
const status = isEmpty ? t("sizing.status_empty") : isLong ? t("sizing.status_long") : t("sizing.status_ok");
|
|
486
|
-
|
|
487
|
-
console.log(t("sizing.key_analysis_detail", { lang, length, status, translation:
|
|
488
|
-
});
|
|
699
|
+
Object.entries(data).forEach(([lang, keyData]) => {
|
|
700
|
+
const length = keyData.length;
|
|
701
|
+
const isEmpty = length === 0;
|
|
702
|
+
const isLong = length > this.threshold;
|
|
703
|
+
const status = isEmpty ? t("sizing.status_empty") : isLong ? t("sizing.status_long") : t("sizing.status_ok");
|
|
704
|
+
|
|
705
|
+
console.log(t("sizing.key_analysis_detail", { lang, length, status, translation: `${length} chars` }));
|
|
706
|
+
});
|
|
489
707
|
|
|
490
708
|
console.log("");
|
|
491
709
|
counter++;
|
|
@@ -503,10 +721,10 @@ class I18nSizingAnalyzer {
|
|
|
503
721
|
throw new Error(t("sizing.invalidOutputDirectoryError", { outputDir: this.outputDir }));
|
|
504
722
|
}
|
|
505
723
|
|
|
506
|
-
// Ensure output directory exists
|
|
507
|
-
if (!SecurityUtils.safeExistsSync(validatedOutputDir)) {
|
|
508
|
-
SecurityUtils.safeMkdirSync(validatedOutputDir, { recursive: true });
|
|
509
|
-
}
|
|
724
|
+
// Ensure output directory exists
|
|
725
|
+
if (!SecurityUtils.safeExistsSync(validatedOutputDir)) {
|
|
726
|
+
SecurityUtils.safeMkdirSync(validatedOutputDir, process.cwd(), { recursive: true });
|
|
727
|
+
}
|
|
510
728
|
|
|
511
729
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
512
730
|
|
|
@@ -536,11 +754,11 @@ class I18nSizingAnalyzer {
|
|
|
536
754
|
threshold: this.threshold
|
|
537
755
|
},
|
|
538
756
|
analysis: this.stats,
|
|
539
|
-
metadata: {
|
|
540
|
-
totalFiles: Object.
|
|
541
|
-
totalLanguages: Object.keys(this.stats.languages).length,
|
|
542
|
-
totalKeys: Object.keys(this.stats.keys).length
|
|
543
|
-
}
|
|
757
|
+
metadata: {
|
|
758
|
+
totalFiles: Object.values(this.stats.files).reduce((total, data) => total + (data.fileCount || 0), 0),
|
|
759
|
+
totalLanguages: Object.keys(this.stats.languages).length,
|
|
760
|
+
totalKeys: Object.keys(this.stats.keys).length
|
|
761
|
+
}
|
|
544
762
|
};
|
|
545
763
|
|
|
546
764
|
const jsonSuccess = await SecurityUtils.safeWriteFile(jsonReportPath, JSON.stringify(report, null, 2), process.cwd());
|
|
@@ -560,39 +778,66 @@ class I18nSizingAnalyzer {
|
|
|
560
778
|
Generated: ${new Date().toISOString()}
|
|
561
779
|
|
|
562
780
|
## Configuration
|
|
563
|
-
- Source Directory: ${this.sourceDir}
|
|
564
|
-
- Languages: ${this.languages.join(', ')}
|
|
565
|
-
- Threshold: ${this.threshold}%
|
|
566
|
-
|
|
567
|
-
## Summary Statistics
|
|
568
|
-
- Total Languages: ${Object.keys(this.stats.languages).length}
|
|
569
|
-
- Total Translation Keys: ${Object.keys(this.stats.keys).length}
|
|
570
|
-
- Total Files: ${Object.
|
|
571
|
-
|
|
572
|
-
## Language Overview
|
|
573
|
-
`;
|
|
574
|
-
|
|
575
|
-
Object.entries(this.stats.languages).forEach(([lang, data]) => {
|
|
576
|
-
const fileData = this.stats.files[lang] || { sizeKB: 0, lines: 0, characters: 0 };
|
|
577
|
-
report += `
|
|
578
|
-
### ${lang.toUpperCase()}
|
|
579
|
-
-
|
|
580
|
-
-
|
|
581
|
-
-
|
|
582
|
-
-
|
|
583
|
-
-
|
|
781
|
+
- Source Directory: ${this.sourceDir}
|
|
782
|
+
- Languages: ${(this.languages.length > 0 ? this.languages : Object.keys(this.stats.languages)).join(', ')}
|
|
783
|
+
- Threshold: ${this.threshold}%
|
|
784
|
+
|
|
785
|
+
## Summary Statistics
|
|
786
|
+
- Total Languages: ${Object.keys(this.stats.languages).length}
|
|
787
|
+
- Total Translation Keys: ${Object.keys(this.stats.keys).length}
|
|
788
|
+
- Total Files: ${Object.values(this.stats.files).reduce((total, data) => total + (data.fileCount || 0), 0)}
|
|
789
|
+
|
|
790
|
+
## Language Overview
|
|
791
|
+
`;
|
|
792
|
+
|
|
793
|
+
Object.entries(this.stats.languages).forEach(([lang, data]) => {
|
|
794
|
+
const fileData = this.stats.files[lang] || { sizeKB: 0, lines: 0, characters: 0 };
|
|
795
|
+
report += `
|
|
796
|
+
### ${lang.toUpperCase()}
|
|
797
|
+
- Files: ${fileData.fileCount || 0}
|
|
798
|
+
- File Size: ${fileData.sizeKB} KB
|
|
799
|
+
- Lines: ${fileData.lines}
|
|
800
|
+
- Total Characters: ${fileData.characters}
|
|
801
|
+
- Translation Keys: ${data.totalKeys}
|
|
802
|
+
- Average Key Length: ${data.averageKeyLength.toFixed(1)} characters
|
|
584
803
|
- Empty Translations: ${data.emptyKeys}
|
|
585
804
|
- Long Keys (> ${this.threshold} chars): ${data.longKeys}
|
|
586
|
-
`;
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
|
|
805
|
+
`;
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
const fileComparison = this.stats.summary.fileComparison;
|
|
809
|
+
if (fileComparison) {
|
|
810
|
+
report += `
|
|
811
|
+
## File Set Comparison
|
|
812
|
+
- Base Language: ${fileComparison.baseLanguage}
|
|
813
|
+
- Unique Files: ${fileComparison.totalUniqueFiles}
|
|
814
|
+
- Common Files: ${fileComparison.commonFiles.length}
|
|
815
|
+
- Mismatches: ${fileComparison.hasMismatches ? 'Yes' : 'No'}
|
|
816
|
+
`;
|
|
817
|
+
|
|
818
|
+
Object.entries(fileComparison.missingFilesByLanguage).forEach(([lang, files]) => {
|
|
819
|
+
report += `- ${lang}: ${files.length} missing${files.length > 0 ? ` (${files.join(', ')})` : ''}\n`;
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
report += `
|
|
824
|
+
## Per-File Analysis
|
|
825
|
+
`;
|
|
826
|
+
|
|
827
|
+
Object.entries(this.stats.languages).forEach(([lang, data]) => {
|
|
828
|
+
Object.entries(data.files || {}).forEach(([fileName, fileData]) => {
|
|
829
|
+
const sizeData = this.stats.files[lang]?.perFiles?.[fileName] || {};
|
|
830
|
+
report += `- ${lang}/${fileName}: ${fileData.totalKeys} keys, ${fileData.totalCharacters} chars, ${fileData.averageKeyLength.toFixed(1)} avg chars, ${sizeData.sizeKB || '0.00'} KB\n`;
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// Size variations
|
|
590
835
|
if (this.stats.summary.sizeVariations && Object.keys(this.stats.summary.sizeVariations).length > 0) {
|
|
591
836
|
report += `
|
|
592
|
-
## Size Variations (vs ${this.
|
|
837
|
+
## Size Variations (vs ${this.stats.summary.baseLanguage})
|
|
593
838
|
`;
|
|
594
839
|
Object.entries(this.stats.summary.sizeVariations).forEach(([lang, data]) => {
|
|
595
|
-
report += `- ${lang}: ${data.characterDifference > 0 ? '+' : ''}${data.characterDifference} chars (${data.percentageDifference > 0 ? '+' : ''}${data.percentageDifference}%) ${data.isProblematic ? '
|
|
840
|
+
report += `- ${lang}: ${data.characterDifference > 0 ? '+' : ''}${data.characterDifference} chars (${data.percentageDifference > 0 ? '+' : ''}${data.percentageDifference}%) ${data.isProblematic ? 'PROBLEMATIC' : 'OK'}\n`;
|
|
596
841
|
});
|
|
597
842
|
}
|
|
598
843
|
|
|
@@ -629,13 +874,13 @@ Generated: ${new Date().toISOString()}
|
|
|
629
874
|
report += `
|
|
630
875
|
### ${key}
|
|
631
876
|
`;
|
|
632
|
-
Object.entries(data
|
|
633
|
-
const length =
|
|
634
|
-
const isEmpty = length === 0;
|
|
635
|
-
const isLong = length > this.threshold;
|
|
636
|
-
const status = isEmpty ? 'EMPTY' : isLong ? 'LONG' : 'OK';
|
|
637
|
-
report += `- ${lang}: ${length} chars [${status}]
|
|
638
|
-
});
|
|
877
|
+
Object.entries(data).forEach(([lang, keyData]) => {
|
|
878
|
+
const length = keyData.length;
|
|
879
|
+
const isEmpty = length === 0;
|
|
880
|
+
const isLong = length > this.threshold;
|
|
881
|
+
const status = isEmpty ? 'EMPTY' : isLong ? 'LONG' : 'OK';
|
|
882
|
+
report += `- ${lang}: ${length} chars [${status}]\n`;
|
|
883
|
+
});
|
|
639
884
|
});
|
|
640
885
|
}
|
|
641
886
|
|
|
@@ -654,13 +899,13 @@ Generated: ${new Date().toISOString()}
|
|
|
654
899
|
throw new Error(t("sizing.invalidCsvFileError"));
|
|
655
900
|
}
|
|
656
901
|
|
|
657
|
-
let csvContent = 'Language,File Size (KB),Lines,Characters,Total Keys,Avg Key Length,Max Key Length,Empty Keys,Long Keys\n';
|
|
902
|
+
let csvContent = 'Language,File Count,File Size (KB),Lines,Characters,Total Keys,Avg Key Length,Max Key Length,Empty Keys,Long Keys\n';
|
|
658
903
|
|
|
659
904
|
Object.entries(this.stats.files).forEach(([lang]) => {
|
|
660
905
|
const fileData = this.stats.files[lang];
|
|
661
906
|
const langData = this.stats.languages[lang];
|
|
662
907
|
|
|
663
|
-
csvContent += `${lang},${fileData.sizeKB},${fileData.lines},${fileData.characters},${langData.totalKeys},${langData.averageKeyLength.toFixed(1)},${langData.maxKeyLength},${langData.emptyKeys},${langData.longKeys}\n`;
|
|
908
|
+
csvContent += `${lang},${fileData.fileCount},${fileData.sizeKB},${fileData.lines},${fileData.characters},${langData.totalKeys},${langData.averageKeyLength.toFixed(1)},${langData.maxKeyLength},${langData.emptyKeys},${langData.longKeys}\n`;
|
|
664
909
|
});
|
|
665
910
|
|
|
666
911
|
const success = await SecurityUtils.safeWriteFile(csvPath, csvContent, process.cwd());
|
|
@@ -718,8 +963,9 @@ Generated: ${new Date().toISOString()}
|
|
|
718
963
|
'languages': '',
|
|
719
964
|
'output-report': true,
|
|
720
965
|
'format': 'table',
|
|
721
|
-
'threshold': 50,
|
|
722
|
-
'
|
|
966
|
+
'threshold': 50,
|
|
967
|
+
'source-language': '',
|
|
968
|
+
'detailed': false,
|
|
723
969
|
'detailed-keys': false,
|
|
724
970
|
'output-dir': './i18ntk-reports',
|
|
725
971
|
'help': false,
|
|
@@ -759,13 +1005,15 @@ Generated: ${new Date().toISOString()}
|
|
|
759
1005
|
options.format = value;
|
|
760
1006
|
options.f = value;
|
|
761
1007
|
}
|
|
762
|
-
} else if (key === 'threshold' || key === 't') {
|
|
763
|
-
const numValue = parseInt(value);
|
|
764
|
-
if (!isNaN(numValue)) {
|
|
765
|
-
options.threshold = numValue;
|
|
766
|
-
options.t = numValue;
|
|
767
|
-
}
|
|
768
|
-
} else if (key === '
|
|
1008
|
+
} else if (key === 'threshold' || key === 't') {
|
|
1009
|
+
const numValue = parseInt(value);
|
|
1010
|
+
if (!isNaN(numValue)) {
|
|
1011
|
+
options.threshold = numValue;
|
|
1012
|
+
options.t = numValue;
|
|
1013
|
+
}
|
|
1014
|
+
} else if (key === 'source-language') {
|
|
1015
|
+
options['source-language'] = value;
|
|
1016
|
+
} else if (key === 'detailed' || key === 'd') {
|
|
769
1017
|
options.detailed = value.toLowerCase() !== 'false';
|
|
770
1018
|
options.d = options.detailed;
|
|
771
1019
|
} else if (key === 'detailed-keys') {
|
|
@@ -806,14 +1054,17 @@ Generated: ${new Date().toISOString()}
|
|
|
806
1054
|
options.f = value;
|
|
807
1055
|
}
|
|
808
1056
|
if (nextArg && !nextArg.startsWith('-') && ['json', 'csv', 'table'].includes(nextArg)) i++;
|
|
809
|
-
} else if (key === 'threshold' || key === 't') {
|
|
810
|
-
const value = parseInt(nextArg);
|
|
811
|
-
if (!isNaN(value)) {
|
|
812
|
-
options.threshold = value;
|
|
813
|
-
options.t = value;
|
|
814
|
-
}
|
|
815
|
-
if (nextArg && !nextArg.startsWith('-') && !isNaN(parseInt(nextArg))) i++;
|
|
816
|
-
} else if (key === '
|
|
1057
|
+
} else if (key === 'threshold' || key === 't') {
|
|
1058
|
+
const value = parseInt(nextArg);
|
|
1059
|
+
if (!isNaN(value)) {
|
|
1060
|
+
options.threshold = value;
|
|
1061
|
+
options.t = value;
|
|
1062
|
+
}
|
|
1063
|
+
if (nextArg && !nextArg.startsWith('-') && !isNaN(parseInt(nextArg))) i++;
|
|
1064
|
+
} else if (key === 'source-language') {
|
|
1065
|
+
options['source-language'] = nextArg || options['source-language'];
|
|
1066
|
+
if (nextArg && !nextArg.startsWith('-')) i++;
|
|
1067
|
+
} else if (key === 'detailed' || key === 'd') {
|
|
817
1068
|
if (nextArg && !nextArg.startsWith('-') && ['true', 'false'].includes(nextArg.toLowerCase())) {
|
|
818
1069
|
options.detailed = nextArg.toLowerCase() !== 'false';
|
|
819
1070
|
options.d = options.detailed;
|
|
@@ -847,8 +1098,9 @@ Options:
|
|
|
847
1098
|
-l, --languages <langs> Comma-separated list of languages to analyze
|
|
848
1099
|
-o, --output-report Generate detailed sizing report (default: true)
|
|
849
1100
|
-f, --format <format> Output format: json, csv, table (default: table)
|
|
850
|
-
-t, --threshold <number> Size difference threshold for warnings (%) (default: 50)
|
|
851
|
-
-
|
|
1101
|
+
-t, --threshold <number> Size difference threshold for warnings (%) (default: 50)
|
|
1102
|
+
--source-language <code> Source language baseline for comparisons (default: en)
|
|
1103
|
+
-d, --detailed Generate detailed report with more information
|
|
852
1104
|
--detailed-keys Show detailed key-level analysis
|
|
853
1105
|
--output-dir <dir> Output directory for reports (default: ./i18ntk-reports)
|
|
854
1106
|
--help Show this help message
|
|
@@ -868,12 +1120,13 @@ Options:
|
|
|
868
1120
|
|
|
869
1121
|
this.sourceDir = path.resolve(config.projectRoot || '.', config.sourceDir || './locales');
|
|
870
1122
|
this.outputDir = path.resolve(config.projectRoot || '.', config.outputDir || './i18ntk-reports');
|
|
871
|
-
this.threshold = args.threshold ?? config.processing?.sizingThreshold ?? 50;
|
|
872
|
-
this.languages = args.languages ? args.languages.split(',').map(l => l.trim()) : [];
|
|
873
|
-
this.outputReport = args['output-report'] !== undefined ? args['output-report'] : false;
|
|
874
|
-
this.format = args.format || 'table';
|
|
875
|
-
this.detailed = args.detailed;
|
|
876
|
-
this.detailedKeys = args['detailed-keys'];
|
|
1123
|
+
this.threshold = args.threshold ?? config.processing?.sizingThreshold ?? 50;
|
|
1124
|
+
this.languages = args.languages ? args.languages.split(',').map(l => l.trim()) : [];
|
|
1125
|
+
this.outputReport = args['output-report'] !== undefined ? args['output-report'] : false;
|
|
1126
|
+
this.format = args.format || 'table';
|
|
1127
|
+
this.detailed = args.detailed;
|
|
1128
|
+
this.detailedKeys = args['detailed-keys'];
|
|
1129
|
+
this.sourceLanguage = args['source-language'] || config.sourceLanguage || this.sourceLanguage || 'en';
|
|
877
1130
|
|
|
878
1131
|
if (!fromMenu) {
|
|
879
1132
|
|