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.
Files changed (86) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/LICENSE +21 -0
  3. package/README.md +507 -0
  4. package/dev/README.md +37 -0
  5. package/dev/debug/README.md +30 -0
  6. package/dev/debug/complete-console-translations.js +295 -0
  7. package/dev/debug/console-key-checker.js +408 -0
  8. package/dev/debug/console-translations.js +335 -0
  9. package/dev/debug/debugger.js +408 -0
  10. package/dev/debug/export-missing-keys.js +432 -0
  11. package/dev/debug/final-normalize.js +236 -0
  12. package/dev/debug/find-extra-keys.js +68 -0
  13. package/dev/debug/normalize-locales.js +153 -0
  14. package/dev/debug/refactor-locales.js +240 -0
  15. package/dev/debug/reorder-locales.js +85 -0
  16. package/dev/debug/replace-hardcoded-console.js +378 -0
  17. package/docs/INSTALLATION.md +449 -0
  18. package/docs/README.md +222 -0
  19. package/docs/TODO_ROADMAP.md +279 -0
  20. package/docs/api/API_REFERENCE.md +377 -0
  21. package/docs/api/COMPONENTS.md +492 -0
  22. package/docs/api/CONFIGURATION.md +651 -0
  23. package/docs/api/NPM_PUBLISHING_GUIDE.md +434 -0
  24. package/docs/debug/DEBUG_README.md +30 -0
  25. package/docs/debug/DEBUG_TOOLS.md +494 -0
  26. package/docs/development/AGENTS.md +351 -0
  27. package/docs/development/DEVELOPMENT_RULES.md +165 -0
  28. package/docs/development/DEV_README.md +37 -0
  29. package/docs/release-notes/RELEASE_NOTES_v1.0.0.md +173 -0
  30. package/docs/release-notes/RELEASE_NOTES_v1.6.0.md +141 -0
  31. package/docs/release-notes/RELEASE_NOTES_v1.6.1.md +185 -0
  32. package/docs/release-notes/RELEASE_NOTES_v1.6.3.md +199 -0
  33. package/docs/reports/ANALYSIS_README.md +17 -0
  34. package/docs/reports/CONSOLE_MISMATCH_BUG_REPORT_v1.5.0.md +181 -0
  35. package/docs/reports/SIZING_README.md +18 -0
  36. package/docs/reports/SUMMARY_README.md +18 -0
  37. package/docs/reports/TRANSLATION_BUG_REPORT_v1.5.0.md +129 -0
  38. package/docs/reports/USAGE_README.md +18 -0
  39. package/docs/reports/VALIDATION_README.md +18 -0
  40. package/locales/de/auth.json +3 -0
  41. package/locales/de/common.json +16 -0
  42. package/locales/de/pagination.json +6 -0
  43. package/locales/en/auth.json +3 -0
  44. package/locales/en/common.json +16 -0
  45. package/locales/en/pagination.json +6 -0
  46. package/locales/es/auth.json +3 -0
  47. package/locales/es/common.json +16 -0
  48. package/locales/es/pagination.json +6 -0
  49. package/locales/fr/auth.json +3 -0
  50. package/locales/fr/common.json +16 -0
  51. package/locales/fr/pagination.json +6 -0
  52. package/locales/ru/auth.json +3 -0
  53. package/locales/ru/common.json +16 -0
  54. package/locales/ru/pagination.json +6 -0
  55. package/main/i18ntk-analyze.js +625 -0
  56. package/main/i18ntk-autorun.js +461 -0
  57. package/main/i18ntk-complete.js +494 -0
  58. package/main/i18ntk-init.js +686 -0
  59. package/main/i18ntk-manage.js +848 -0
  60. package/main/i18ntk-sizing.js +557 -0
  61. package/main/i18ntk-summary.js +671 -0
  62. package/main/i18ntk-usage.js +1282 -0
  63. package/main/i18ntk-validate.js +762 -0
  64. package/main/ui-i18n.js +332 -0
  65. package/package.json +152 -0
  66. package/scripts/fix-missing-translation-keys.js +214 -0
  67. package/scripts/verify-package.js +168 -0
  68. package/ui-locales/de.json +637 -0
  69. package/ui-locales/en.json +688 -0
  70. package/ui-locales/es.json +637 -0
  71. package/ui-locales/fr.json +637 -0
  72. package/ui-locales/ja.json +637 -0
  73. package/ui-locales/ru.json +637 -0
  74. package/ui-locales/zh.json +637 -0
  75. package/utils/admin-auth.js +317 -0
  76. package/utils/admin-cli.js +353 -0
  77. package/utils/admin-pin.js +409 -0
  78. package/utils/detect-language-mismatches.js +454 -0
  79. package/utils/i18n-helper.js +128 -0
  80. package/utils/maintain-language-purity.js +433 -0
  81. package/utils/native-translations.js +478 -0
  82. package/utils/security.js +384 -0
  83. package/utils/test-complete-system.js +356 -0
  84. package/utils/test-console-i18n.js +402 -0
  85. package/utils/translate-mismatches.js +571 -0
  86. 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();