i18ntk 1.0.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 +401 -0
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/dev/README.md +37 -0
- package/dev/debug/README.md +30 -0
- package/dev/debug/complete-console-translations.js +295 -0
- package/dev/debug/console-key-checker.js +408 -0
- package/dev/debug/console-translations.js +335 -0
- package/dev/debug/debugger.js +408 -0
- package/dev/debug/export-missing-keys.js +432 -0
- package/dev/debug/final-normalize.js +236 -0
- package/dev/debug/find-extra-keys.js +68 -0
- package/dev/debug/normalize-locales.js +153 -0
- package/dev/debug/refactor-locales.js +240 -0
- package/dev/debug/reorder-locales.js +85 -0
- package/dev/debug/replace-hardcoded-console.js +378 -0
- package/docs/INSTALLATION.md +449 -0
- package/docs/README.md +222 -0
- package/docs/TODO_ROADMAP.md +279 -0
- package/docs/api/API_REFERENCE.md +377 -0
- package/docs/api/COMPONENTS.md +492 -0
- package/docs/api/CONFIGURATION.md +651 -0
- package/docs/api/NPM_PUBLISHING_GUIDE.md +434 -0
- package/docs/debug/DEBUG_README.md +30 -0
- package/docs/debug/DEBUG_TOOLS.md +494 -0
- package/docs/development/AGENTS.md +351 -0
- package/docs/development/DEVELOPMENT_RULES.md +165 -0
- package/docs/development/DEV_README.md +37 -0
- package/docs/release-notes/RELEASE_NOTES_v1.0.0.md +173 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.0.md +141 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.1.md +185 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.3.md +199 -0
- package/docs/reports/ANALYSIS_README.md +17 -0
- package/docs/reports/CONSOLE_MISMATCH_BUG_REPORT_v1.5.0.md +181 -0
- package/docs/reports/SIZING_README.md +18 -0
- package/docs/reports/SUMMARY_README.md +18 -0
- package/docs/reports/TRANSLATION_BUG_REPORT_v1.5.0.md +129 -0
- package/docs/reports/USAGE_README.md +18 -0
- package/docs/reports/VALIDATION_README.md +18 -0
- package/locales/de/auth.json +3 -0
- package/locales/de/common.json +16 -0
- package/locales/de/pagination.json +6 -0
- package/locales/en/auth.json +3 -0
- package/locales/en/common.json +16 -0
- package/locales/en/pagination.json +6 -0
- package/locales/es/auth.json +3 -0
- package/locales/es/common.json +16 -0
- package/locales/es/pagination.json +6 -0
- package/locales/fr/auth.json +3 -0
- package/locales/fr/common.json +16 -0
- package/locales/fr/pagination.json +6 -0
- package/locales/ru/auth.json +3 -0
- package/locales/ru/common.json +16 -0
- package/locales/ru/pagination.json +6 -0
- package/main/i18ntk-analyze.js +625 -0
- package/main/i18ntk-autorun.js +461 -0
- package/main/i18ntk-complete.js +494 -0
- package/main/i18ntk-init.js +686 -0
- package/main/i18ntk-manage.js +848 -0
- package/main/i18ntk-sizing.js +557 -0
- package/main/i18ntk-summary.js +671 -0
- package/main/i18ntk-usage.js +1282 -0
- package/main/i18ntk-validate.js +762 -0
- package/main/ui-i18n.js +332 -0
- package/package.json +152 -0
- package/scripts/fix-missing-translation-keys.js +214 -0
- package/scripts/verify-package.js +168 -0
- package/ui-locales/de.json +637 -0
- package/ui-locales/en.json +688 -0
- package/ui-locales/es.json +637 -0
- package/ui-locales/fr.json +637 -0
- package/ui-locales/ja.json +637 -0
- package/ui-locales/ru.json +637 -0
- package/ui-locales/zh.json +637 -0
- package/utils/admin-auth.js +317 -0
- package/utils/admin-cli.js +353 -0
- package/utils/admin-pin.js +409 -0
- package/utils/detect-language-mismatches.js +454 -0
- package/utils/i18n-helper.js +128 -0
- package/utils/maintain-language-purity.js +433 -0
- package/utils/native-translations.js +478 -0
- package/utils/security.js +384 -0
- package/utils/test-complete-system.js +356 -0
- package/utils/test-console-i18n.js +402 -0
- package/utils/translate-mismatches.js +571 -0
- package/utils/validate-language-purity.js +531 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Console I18n Test Script
|
|
4
|
+
*
|
|
5
|
+
* This script scans all JavaScript files in the toolkit for console statements
|
|
6
|
+
* (log, warn, error, info) and checks if they're using the translation system
|
|
7
|
+
* (this.t() or i18n.t()) to identify any remaining hardcoded text.
|
|
8
|
+
*
|
|
9
|
+
* It provides detailed reports on translation coverage and recommendations
|
|
10
|
+
* for replacing hardcoded strings with translation keys.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { performance } = require('perf_hooks');
|
|
16
|
+
const settingsManager = require('../settings/settings-manager');
|
|
17
|
+
|
|
18
|
+
// Import the i18n helper
|
|
19
|
+
const { loadTranslations, t } = require('./utils/i18n-helper');
|
|
20
|
+
|
|
21
|
+
// Get configuration from settings manager
|
|
22
|
+
function getConfig() {
|
|
23
|
+
const settings = settingsManager.getSettings();
|
|
24
|
+
return {
|
|
25
|
+
excludeDirs: settings.processing?.excludeDirs || ['node_modules', '.git', 'i18ntk-reports', 'ui-locales'],
|
|
26
|
+
includeExtensions: settings.processing?.includeExtensions || ['.js']
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class ConsoleI18nTester {
|
|
31
|
+
constructor() {
|
|
32
|
+
const config = getConfig();
|
|
33
|
+
this.rootDir = path.resolve(__dirname);
|
|
34
|
+
this.excludeDirs = config.excludeDirs;
|
|
35
|
+
this.includeExtensions = config.includeExtensions;
|
|
36
|
+
this.targetFiles = [
|
|
37
|
+
'02-analyze-translations.js',
|
|
38
|
+
'03-validate-translations.js',
|
|
39
|
+
'04-check-usage.js',
|
|
40
|
+
'05-complete-translations.js',
|
|
41
|
+
'06-analyze-sizing.js'
|
|
42
|
+
];
|
|
43
|
+
this.consoleStatements = [];
|
|
44
|
+
this.hardcodedTexts = [];
|
|
45
|
+
this.translatedTexts = [];
|
|
46
|
+
this.fileStats = new Map();
|
|
47
|
+
this.ignoredPatterns = [
|
|
48
|
+
// Patterns to ignore (technical outputs, not user-facing messages)
|
|
49
|
+
/console\.(log|warn|error|info)\(['"\`]=+['"\`]\)/, // Separator lines like console.log('=======')
|
|
50
|
+
/console\.(log|warn|error|info)\(\)/, // Empty logs
|
|
51
|
+
/console\.(log|warn|error|info)\(['"\` \t\n'"\`]\)/, // Whitespace only
|
|
52
|
+
/console\.(log|warn|error|info)\(['"](\\n|\\t)['"\`]\)/, // Just newlines or tabs
|
|
53
|
+
/console\.debug/, // Debug logs
|
|
54
|
+
/console\.trace/, // Trace logs
|
|
55
|
+
/console\.time/, // Time logs
|
|
56
|
+
/console\.timeEnd/, // TimeEnd logs
|
|
57
|
+
/console\.group/, // Group logs
|
|
58
|
+
/console\.groupEnd/, // GroupEnd logs
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// Translation key suggestion patterns
|
|
62
|
+
this.translationKeyPatterns = {
|
|
63
|
+
'02-analyze-translations.js': 'analyzeTranslations',
|
|
64
|
+
'03-validate-translations.js': 'validateTranslations',
|
|
65
|
+
'04-check-usage.js': 'checkUsage',
|
|
66
|
+
'05-complete-translations.js': 'completeTranslations',
|
|
67
|
+
'06-analyze-sizing.js': 'sizing'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get all JavaScript files recursively
|
|
72
|
+
getAllFiles() {
|
|
73
|
+
const files = [];
|
|
74
|
+
return this.traverseDirectory(this.rootDir, files);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Traverse directory recursively to find all JavaScript files
|
|
78
|
+
traverseDirectory(dir, files = []) {
|
|
79
|
+
if (!fs.existsSync(dir)) return files;
|
|
80
|
+
|
|
81
|
+
const items = fs.readdirSync(dir);
|
|
82
|
+
|
|
83
|
+
for (const item of items) {
|
|
84
|
+
// Skip excluded directories
|
|
85
|
+
if (this.excludeDirs.includes(item)) continue;
|
|
86
|
+
|
|
87
|
+
const itemPath = path.join(dir, item);
|
|
88
|
+
const stat = fs.statSync(itemPath);
|
|
89
|
+
|
|
90
|
+
if (stat.isDirectory()) {
|
|
91
|
+
this.traverseDirectory(itemPath, files);
|
|
92
|
+
} else if (stat.isFile() && this.includeExtensions.includes(path.extname(item))) {
|
|
93
|
+
// Only include target files if specified
|
|
94
|
+
const fileName = path.basename(item);
|
|
95
|
+
if (this.targetFiles.length === 0 || this.targetFiles.includes(fileName)) {
|
|
96
|
+
files.push(itemPath);
|
|
97
|
+
console.log(t('consoleI18nTester.found_target_file', { fileName }));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return files;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Extract console statements from a file
|
|
106
|
+
extractConsoleStatements(filePath) {
|
|
107
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
108
|
+
const relativePath = path.relative(this.rootDir, filePath);
|
|
109
|
+
const fileName = path.basename(filePath);
|
|
110
|
+
|
|
111
|
+
// Initialize file stats
|
|
112
|
+
if (!this.fileStats.has(fileName)) {
|
|
113
|
+
this.fileStats.set(fileName, {
|
|
114
|
+
total: 0,
|
|
115
|
+
translated: 0,
|
|
116
|
+
hardcoded: 0
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const fileStats = this.fileStats.get(fileName);
|
|
121
|
+
|
|
122
|
+
// Regular expression to match console.log statements
|
|
123
|
+
// Improved regex to better handle whitespace and capture more variations
|
|
124
|
+
const consoleLogRegex = /console\.(log|warn|error|info)\s*\(([^;]*?)\)/g;
|
|
125
|
+
|
|
126
|
+
let match;
|
|
127
|
+
while ((match = consoleLogRegex.exec(content)) !== null) {
|
|
128
|
+
const statement = match[0];
|
|
129
|
+
const consoleType = match[1];
|
|
130
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
131
|
+
|
|
132
|
+
// Skip statements matching ignored patterns
|
|
133
|
+
if (this.ignoredPatterns.some(pattern => pattern.test(statement))) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check if the statement uses translation
|
|
138
|
+
const usesTranslation = /this\.t\(|i18n\.t\(|UIi18n\.t\(/.test(statement);
|
|
139
|
+
|
|
140
|
+
// Extract the text content from the console statement (simplified)
|
|
141
|
+
let textContent = 'Complex expression';
|
|
142
|
+
const textMatch = statement.match(/['"\`]([^'"\`]*)['"\`]/);
|
|
143
|
+
if (textMatch && textMatch[1]) {
|
|
144
|
+
textContent = textMatch[1];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const item = {
|
|
148
|
+
file: relativePath,
|
|
149
|
+
line: lineNumber,
|
|
150
|
+
statement,
|
|
151
|
+
consoleType,
|
|
152
|
+
textContent,
|
|
153
|
+
usesTranslation
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
this.consoleStatements.push(item);
|
|
157
|
+
fileStats.total++;
|
|
158
|
+
|
|
159
|
+
if (usesTranslation) {
|
|
160
|
+
this.translatedTexts.push(item);
|
|
161
|
+
fileStats.translated++;
|
|
162
|
+
} else {
|
|
163
|
+
this.hardcodedTexts.push(item);
|
|
164
|
+
fileStats.hardcoded++;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Get line number for a position in text
|
|
170
|
+
getLineNumber(text, position) {
|
|
171
|
+
const lines = text.slice(0, position).split('\n');
|
|
172
|
+
return lines.length;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Extract text content from a console statement
|
|
176
|
+
extractTextContent(statement) {
|
|
177
|
+
// Try to match single quotes
|
|
178
|
+
let match = statement.match(/console\.(log|warn|error|info)\s*\(\s*[']([^']*)[']/);
|
|
179
|
+
if (match && match[2]) return match[2];
|
|
180
|
+
|
|
181
|
+
// Try to match double quotes
|
|
182
|
+
match = statement.match(/console\.(log|warn|error|info)\s*\(\s*["]([^"]*)["]/);
|
|
183
|
+
if (match && match[2]) return match[2];
|
|
184
|
+
|
|
185
|
+
// Try to match template literals
|
|
186
|
+
match = statement.match(/console\.(log|warn|error|info)\s*\(\s*`([^`]*)`/);
|
|
187
|
+
if (match && match[2]) return match[2];
|
|
188
|
+
|
|
189
|
+
// If we can't extract a simple string, return a placeholder
|
|
190
|
+
return 'Complex expression';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Analyze all files
|
|
194
|
+
analyze() {
|
|
195
|
+
console.log(t('consoleI18nTester.scanning_files_for_console_statements'));
|
|
196
|
+
|
|
197
|
+
const files = this.getAllFiles();
|
|
198
|
+
console.log(t('consoleI18nTester.found_javascript_files', { count: files.length }));
|
|
199
|
+
|
|
200
|
+
for (const file of files) {
|
|
201
|
+
this.extractConsoleStatements(file);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.reportResults();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Generate a suggested translation key based on the text content
|
|
208
|
+
suggestTranslationKey(file, textContent) {
|
|
209
|
+
const fileName = path.basename(file);
|
|
210
|
+
const baseKey = this.translationKeyPatterns[fileName] || 'common';
|
|
211
|
+
|
|
212
|
+
// Convert the text content to a key format
|
|
213
|
+
// Remove special characters, convert to snake_case
|
|
214
|
+
let keyPart = textContent
|
|
215
|
+
.toLowerCase()
|
|
216
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
217
|
+
.trim()
|
|
218
|
+
.replace(/\s+/g, '_')
|
|
219
|
+
.substring(0, 30); // Limit length
|
|
220
|
+
|
|
221
|
+
if (!keyPart) {
|
|
222
|
+
keyPart = 'message';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return `${baseKey}.${keyPart}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Generate a suggested replacement for a hardcoded console statement
|
|
229
|
+
suggestReplacement(item) {
|
|
230
|
+
const key = this.suggestTranslationKey(item.file, item.textContent);
|
|
231
|
+
|
|
232
|
+
// Check if the statement likely contains variables
|
|
233
|
+
const hasVariables = item.statement.includes('${') ||
|
|
234
|
+
item.statement.includes('+') ||
|
|
235
|
+
item.statement.includes(',');
|
|
236
|
+
|
|
237
|
+
if (hasVariables) {
|
|
238
|
+
return `console.${item.consoleType}(this.t("${key}", { variables })); // Replace 'variables' with actual variables`;
|
|
239
|
+
} else {
|
|
240
|
+
return `console.${item.consoleType}(this.t("${key}"));`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Report the results of the analysis
|
|
245
|
+
reportResults() {
|
|
246
|
+
console.log(t('consoleI18nTester.console_i18n_analysis_results'));
|
|
247
|
+
console.log(`${'='.repeat(60)}`);
|
|
248
|
+
console.log(t('consoleI18nTester.total_console_statements', { count: this.consoleStatements.length }));
|
|
249
|
+
console.log(t('consoleI18nTester.translated_statements', { count: this.translatedTexts.length }));
|
|
250
|
+
console.log(t('consoleI18nTester.hardcoded_statements', { count: this.hardcodedTexts.length }));
|
|
251
|
+
|
|
252
|
+
const translationPercentage = this.consoleStatements.length > 0
|
|
253
|
+
? ((this.translatedTexts.length / this.consoleStatements.length) * 100).toFixed(2)
|
|
254
|
+
: 0;
|
|
255
|
+
|
|
256
|
+
console.log(t('consoleI18nTester.translation_coverage', { percentage: translationPercentage }));
|
|
257
|
+
|
|
258
|
+
// Report by file
|
|
259
|
+
console.log(t('consoleI18nTester.coverage_by_file'));
|
|
260
|
+
console.log('-------------------');
|
|
261
|
+
|
|
262
|
+
this.fileStats.forEach((stats, fileName) => {
|
|
263
|
+
const fileCoverage = stats.translated / stats.total * 100 || 0;
|
|
264
|
+
const coverageEmoji = fileCoverage === 100 ? '🟢' : fileCoverage > 50 ? '🟡' : '🔴';
|
|
265
|
+
console.log(t('consoleI18nTester.file_coverage_stats', {
|
|
266
|
+
emoji: coverageEmoji,
|
|
267
|
+
fileName,
|
|
268
|
+
coverage: fileCoverage.toFixed(2),
|
|
269
|
+
translated: stats.translated,
|
|
270
|
+
total: stats.total
|
|
271
|
+
}));
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (this.hardcodedTexts.length > 0) {
|
|
275
|
+
console.log(t('consoleI18nTester.hardcoded_console_statements'));
|
|
276
|
+
console.log(`${'='.repeat(60)}`);
|
|
277
|
+
|
|
278
|
+
// Group by file
|
|
279
|
+
const byFile = {};
|
|
280
|
+
this.hardcodedTexts.forEach(item => {
|
|
281
|
+
if (!byFile[item.file]) {
|
|
282
|
+
byFile[item.file] = [];
|
|
283
|
+
}
|
|
284
|
+
byFile[item.file].push(item);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Display hardcoded statements by file
|
|
288
|
+
Object.keys(byFile).sort().forEach(file => {
|
|
289
|
+
console.log(t('consoleI18nTester.file_header', { file }));
|
|
290
|
+
byFile[file].forEach(item => {
|
|
291
|
+
console.log(t('consoleI18nTester.line_statement', { line: item.line, statement: item.statement.trim() }));
|
|
292
|
+
console.log(t('consoleI18nTester.suggested_key', { key: this.suggestTranslationKey(item.file, item.textContent) }));
|
|
293
|
+
console.log(t('consoleI18nTester.suggested_replacement', { replacement: this.suggestReplacement(item) }));
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
console.log(t('consoleI18nTester.recommendations'));
|
|
298
|
+
console.log(`${'='.repeat(60)}`);
|
|
299
|
+
console.log(t('consoleI18nTester.recommendation_1'));
|
|
300
|
+
console.log(t('consoleI18nTester.recommendation_2'));
|
|
301
|
+
console.log(t('consoleI18nTester.recommendation_3'));
|
|
302
|
+
console.log(t('consoleI18nTester.recommendation_4'));
|
|
303
|
+
|
|
304
|
+
console.log(t('consoleI18nTester.translation_keys_to_add'));
|
|
305
|
+
console.log('------------------------');
|
|
306
|
+
console.log(t('consoleI18nTester.add_keys_instruction'));
|
|
307
|
+
console.log('{');
|
|
308
|
+
|
|
309
|
+
// Group suggested keys by module
|
|
310
|
+
const keysByModule = {};
|
|
311
|
+
this.hardcodedTexts.forEach(item => {
|
|
312
|
+
const key = this.suggestTranslationKey(item.file, item.textContent);
|
|
313
|
+
const [module, subKey] = key.split('.');
|
|
314
|
+
|
|
315
|
+
if (!keysByModule[module]) {
|
|
316
|
+
keysByModule[module] = {};
|
|
317
|
+
}
|
|
318
|
+
keysByModule[module][subKey] = item.textContent;
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Display suggested keys in JSON format
|
|
322
|
+
Object.keys(keysByModule).sort().forEach((module, moduleIndex) => {
|
|
323
|
+
console.log(` "${module}": {`);
|
|
324
|
+
|
|
325
|
+
const subKeys = keysByModule[module];
|
|
326
|
+
Object.keys(subKeys).sort().forEach((subKey, keyIndex) => {
|
|
327
|
+
const comma = keyIndex < Object.keys(subKeys).length - 1 ? ',' : '';
|
|
328
|
+
console.log(` "${subKey}": "${subKeys[subKey]}"${comma}`);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const comma = moduleIndex < Object.keys(keysByModule).length - 1 ? ',' : '';
|
|
332
|
+
console.log(` }${comma}`);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
console.log('}');
|
|
336
|
+
} else {
|
|
337
|
+
console.log(t('consoleI18nTester.perfect_translation_coverage'));
|
|
338
|
+
console.log(`${'='.repeat(60)}`);
|
|
339
|
+
console.log(t('consoleI18nTester.all_statements_using_translation'));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Main function to run the analysis
|
|
345
|
+
function main() {
|
|
346
|
+
// Initialize translations
|
|
347
|
+
loadTranslations();
|
|
348
|
+
|
|
349
|
+
console.log(t('consoleI18nTester.i18n_console_translation_checker'));
|
|
350
|
+
console.log('====================================');
|
|
351
|
+
console.log(t('consoleI18nTester.script_description_line1'));
|
|
352
|
+
console.log(t('consoleI18nTester.script_description_line2'));
|
|
353
|
+
|
|
354
|
+
const startTime = performance.now();
|
|
355
|
+
const tester = new ConsoleI18nTester();
|
|
356
|
+
tester.analyze();
|
|
357
|
+
const endTime = performance.now();
|
|
358
|
+
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
359
|
+
|
|
360
|
+
// Save report to file
|
|
361
|
+
const reportDir = path.join(__dirname, 'i18ntk-reports');
|
|
362
|
+
if (!fs.existsSync(reportDir)) {
|
|
363
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const reportPath = path.join(reportDir, 'console-i18n-report.json');
|
|
367
|
+
const report = {
|
|
368
|
+
timestamp: new Date().toISOString(),
|
|
369
|
+
duration: duration,
|
|
370
|
+
summary: {
|
|
371
|
+
total: tester.consoleStatements.length,
|
|
372
|
+
translated: tester.translatedTexts.length,
|
|
373
|
+
hardcoded: tester.hardcodedTexts.length,
|
|
374
|
+
coverage: tester.translatedTexts.length / tester.consoleStatements.length * 100 || 0
|
|
375
|
+
},
|
|
376
|
+
fileStats: Object.fromEntries(tester.fileStats),
|
|
377
|
+
hardcodedTexts: tester.hardcodedTexts.map(item => ({
|
|
378
|
+
file: item.file,
|
|
379
|
+
line: item.line,
|
|
380
|
+
statement: item.statement,
|
|
381
|
+
textContent: item.textContent,
|
|
382
|
+
suggestedKey: tester.suggestTranslationKey(item.file, item.textContent),
|
|
383
|
+
suggestedReplacement: tester.suggestReplacement(item)
|
|
384
|
+
}))
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
|
388
|
+
console.log(t('consoleI18nTester.analysis_completed_in_duration', { duration }));
|
|
389
|
+
console.log(t('consoleI18nTester.report_saved_to_path', { path: reportPath }));
|
|
390
|
+
|
|
391
|
+
// Exit with error code if hardcoded text found
|
|
392
|
+
if (tester.hardcodedTexts.length > 0) {
|
|
393
|
+
console.log(t('consoleI18nTester.found_hardcoded_messages', { count: tester.hardcodedTexts.length }));
|
|
394
|
+
process.exit(1);
|
|
395
|
+
} else {
|
|
396
|
+
console.log(t('consoleI18nTester.all_console_messages_use_translation'));
|
|
397
|
+
process.exit(0);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Run the analysis
|
|
402
|
+
main();
|