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,295 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Complete Console Translations
5
+ * Adds missing translation keys to all ui-locales language files
6
+ * to ensure 100% translation coverage for the i18n-management-toolkit package.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const ConsoleTranslationsChecker = require('./console-translations');
12
+ const UIi18n = require('../../main/ui-i18n');
13
+
14
+ class ConsoleTranslationsCompleter {
15
+ constructor() {
16
+ this.uiLocalesDir = path.join(__dirname, '..', '..', 'ui-locales');
17
+ this.referenceLanguage = 'en';
18
+ this.supportedLanguages = ['de', 'es', 'fr', 'ja', 'ru', 'zh'];
19
+ this.dryRun = process.argv.includes('--dry-run');
20
+ this.results = {
21
+ totalKeysAdded: 0,
22
+ languagesProcessed: 0,
23
+ changes: {}
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Load and parse a JSON translation file
29
+ */
30
+ loadTranslationFile(language) {
31
+ const filePath = path.join(this.uiLocalesDir, `${language}.json`);
32
+
33
+ try {
34
+ if (!fs.existsSync(filePath)) {
35
+ throw new Error(`Translation file not found: ${filePath}`);
36
+ }
37
+
38
+ const content = fs.readFileSync(filePath, 'utf8');
39
+ return JSON.parse(content);
40
+ } catch (error) {
41
+ console.log(`āŒ Error loading ${language}.json: ${error.message}`);
42
+ return null;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Save translation file with proper formatting
48
+ */
49
+ saveTranslationFile(language, data) {
50
+ const filePath = path.join(this.uiLocalesDir, `${language}.json`);
51
+
52
+ try {
53
+ const content = JSON.stringify(data, null, 2);
54
+
55
+ if (this.dryRun) {
56
+ console.log(` 🧪 DRY RUN: Would save ${language}.json`);
57
+ return true;
58
+ }
59
+
60
+ fs.writeFileSync(filePath, content, 'utf8');
61
+ return true;
62
+ } catch (error) {
63
+ console.log(`āŒ Error saving ${language}.json: ${error.message}`);
64
+ return false;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Get nested object value by dot notation path
70
+ */
71
+ getNestedValue(obj, path) {
72
+ return path.split('.').reduce((current, key) => {
73
+ return current && current[key] !== undefined ? current[key] : undefined;
74
+ }, obj);
75
+ }
76
+
77
+ /**
78
+ * Set nested object value by dot notation path
79
+ */
80
+ setNestedValue(obj, path, value) {
81
+ const keys = path.split('.');
82
+ const lastKey = keys.pop();
83
+
84
+ const target = keys.reduce((current, key) => {
85
+ if (!current[key] || typeof current[key] !== 'object') {
86
+ current[key] = {};
87
+ }
88
+ return current[key];
89
+ }, obj);
90
+
91
+ target[lastKey] = value;
92
+ }
93
+
94
+ /**
95
+ * Generate placeholder translation for missing keys
96
+ */
97
+ generatePlaceholderTranslation(key, referenceValue, targetLanguage) {
98
+ // Use consistent placeholder prefix for all languages
99
+ const prefix = '[NOT TRANSLATED]';
100
+
101
+ // For simple strings, add consistent prefix
102
+ if (typeof referenceValue === 'string') {
103
+ // Keep emojis and special characters, but mark as needing translation
104
+ if (referenceValue.length < 50) {
105
+ return `${prefix} ${referenceValue}`;
106
+ } else {
107
+ // For longer strings, just use the prefix
108
+ return `${prefix} ${referenceValue.substring(0, 30)}...`;
109
+ }
110
+ }
111
+
112
+ // For arrays, copy structure but mark items
113
+ if (Array.isArray(referenceValue)) {
114
+ return referenceValue.map(item =>
115
+ typeof item === 'string' ? `${prefix} ${item}` : item
116
+ );
117
+ }
118
+
119
+ // For other types, return as-is
120
+ return referenceValue;
121
+ }
122
+
123
+ /**
124
+ * Complete translations for a specific language
125
+ */
126
+ completeLanguage(language, missingKeys, referenceData) {
127
+ console.log(`šŸ”„ Processing ${language.toUpperCase()}...`);
128
+
129
+ const languageData = this.loadTranslationFile(language);
130
+ if (!languageData) {
131
+ return false;
132
+ }
133
+
134
+ let keysAdded = 0;
135
+ const changes = [];
136
+
137
+ for (const key of missingKeys) {
138
+ const referenceValue = this.getNestedValue(referenceData, key);
139
+ if (referenceValue !== undefined) {
140
+ const placeholderValue = this.generatePlaceholderTranslation(key, referenceValue, language);
141
+ this.setNestedValue(languageData, key, placeholderValue);
142
+ keysAdded++;
143
+ changes.push(key);
144
+ }
145
+ }
146
+
147
+ if (keysAdded > 0) {
148
+ const success = this.saveTranslationFile(language, languageData);
149
+ if (success) {
150
+ console.log(` āœ… Added ${keysAdded} missing keys`);
151
+ this.results.changes[language] = changes;
152
+ this.results.totalKeysAdded += keysAdded;
153
+ this.results.languagesProcessed++;
154
+
155
+ // Show sample of added keys
156
+ const sampleKeys = changes.slice(0, 3);
157
+ console.log(` šŸ“ Sample: ${sampleKeys.join(', ')}`);
158
+ if (changes.length > 3) {
159
+ console.log(` šŸ“ ... and ${changes.length - 3} more`);
160
+ }
161
+ } else {
162
+ console.log(` āŒ Failed to save changes`);
163
+ return false;
164
+ }
165
+ } else {
166
+ console.log(` āœ… No missing keys found`);
167
+ }
168
+
169
+ return true;
170
+ }
171
+
172
+ /**
173
+ * Run the completion process
174
+ */
175
+ async run() {
176
+ console.log('šŸ”§ CONSOLE TRANSLATIONS COMPLETION');
177
+ console.log('============================================================');
178
+ console.log(`šŸ“ UI Locales directory: ${this.uiLocalesDir}`);
179
+ console.log(`šŸ”¤ Reference language: ${this.referenceLanguage}`);
180
+
181
+ if (this.dryRun) {
182
+ console.log('🧪 DRY RUN MODE - No files will be modified');
183
+ }
184
+
185
+ console.log('');
186
+
187
+ // First, run analysis to get missing keys
188
+ console.log('šŸ” Analyzing current translation status...');
189
+ const checker = new ConsoleTranslationsChecker();
190
+ const analysisSuccess = await checker.run();
191
+
192
+ if (!analysisSuccess) {
193
+ console.log('āŒ Failed to analyze translations');
194
+ return false;
195
+ }
196
+
197
+ // Load reference data
198
+ const referenceData = this.loadTranslationFile(this.referenceLanguage);
199
+ if (!referenceData) {
200
+ console.log(`āŒ Cannot load reference language: ${this.referenceLanguage}`);
201
+ return false;
202
+ }
203
+
204
+ console.log('');
205
+ console.log('šŸ”„ COMPLETING MISSING TRANSLATIONS');
206
+ console.log('============================================================');
207
+
208
+ // Get missing keys from the analysis
209
+ const missingKeysData = checker.results.missingKeys;
210
+
211
+ // Process each language
212
+ for (const language of this.supportedLanguages) {
213
+ const missingKeys = missingKeysData[language] || [];
214
+
215
+ if (missingKeys.length === 0) {
216
+ console.log(`āœ… ${language.toUpperCase()}: Already complete`);
217
+ continue;
218
+ }
219
+
220
+ console.log(`šŸ“Š ${language.toUpperCase()}: ${missingKeys.length} missing keys`);
221
+ const success = this.completeLanguage(language, missingKeys, referenceData);
222
+
223
+ if (!success) {
224
+ console.log(`āŒ Failed to complete ${language}`);
225
+ return false;
226
+ }
227
+ }
228
+
229
+ // Generate summary
230
+ this.generateSummary();
231
+
232
+ // Run final verification
233
+ if (!this.dryRun && this.results.totalKeysAdded > 0) {
234
+ console.log('');
235
+ console.log('šŸ” VERIFICATION - Running final analysis...');
236
+ console.log('============================================================');
237
+
238
+ const finalChecker = new ConsoleTranslationsChecker();
239
+ await finalChecker.run();
240
+ }
241
+
242
+ return true;
243
+ }
244
+
245
+ /**
246
+ * Generate completion summary
247
+ */
248
+ generateSummary() {
249
+ console.log('');
250
+ console.log('šŸ“Š COMPLETION SUMMARY');
251
+ console.log('============================================================');
252
+ console.log(`āœ… Total keys added: ${this.results.totalKeysAdded}`);
253
+ console.log(`🌐 Languages processed: ${this.results.languagesProcessed}/${this.supportedLanguages.length}`);
254
+
255
+ if (this.results.totalKeysAdded > 0) {
256
+ console.log('');
257
+ console.log('šŸ“‹ CHANGES BY LANGUAGE:');
258
+ console.log('------------------------------------------------------------');
259
+
260
+ for (const [language, changes] of Object.entries(this.results.changes)) {
261
+ console.log(`šŸ”¤ ${language.toUpperCase()}: ${changes.length} keys added`);
262
+ }
263
+ }
264
+
265
+ console.log('');
266
+ console.log('šŸ’” NEXT STEPS:');
267
+ console.log('------------------------------------------------------------');
268
+
269
+ if (this.dryRun) {
270
+ console.log('šŸ”§ Run without --dry-run to apply changes:');
271
+ console.log(' node complete-console-translations.js');
272
+ } else if (this.results.totalKeysAdded > 0) {
273
+ console.log('šŸŒ Translation placeholders have been added!');
274
+ console.log('šŸ“ Next steps:');
275
+ console.log('1. Review and translate the placeholder values');
276
+ console.log('2. Replace [DE], [ES], [FR], [JA], [RU], [ZH] prefixes with actual translations');
277
+ console.log('3. Run console-translations.js to verify 100% completion');
278
+ } else {
279
+ console.log('šŸŽ‰ All translations are already complete!');
280
+ }
281
+ }
282
+ }
283
+
284
+ // Run the completer if called directly
285
+ if (require.main === module) {
286
+ const completer = new ConsoleTranslationsCompleter();
287
+ completer.run().then(success => {
288
+ process.exit(success ? 0 : 1);
289
+ }).catch(error => {
290
+ console.error('āŒ Fatal error:', error.message);
291
+ process.exit(1);
292
+ });
293
+ }
294
+
295
+ module.exports = ConsoleTranslationsCompleter;
@@ -0,0 +1,408 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Console Key Checker v1.5.1
5
+ *
6
+ * This script finds missing translation keys by comparing the source language (en.json)
7
+ * with other language files and adds [NOT TRANSLATED] placeholders for missing keys.
8
+ * Improved to prevent duplicate key additions and handle nested structures properly.
9
+ *
10
+ * Usage:
11
+ * node console-key-checker.js [options]
12
+ *
13
+ * Options:
14
+ * --dry-run Show what would be changed without making changes
15
+ * --backup Create backup files (default: true)
16
+ * --languages=<list> Specific languages to check (default: all)
17
+ * --verbose Show detailed progress
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+
23
+ class ConsoleKeyChecker {
24
+ constructor() {
25
+ this.uiLocalesDir = path.join(__dirname, '..', '..', 'ui-locales');
26
+ this.sourceLanguage = 'en';
27
+ this.supportedLanguages = ['de', 'es', 'fr', 'ja', 'ru', 'zh'];
28
+ this.dryRun = process.argv.includes('--dry-run');
29
+ this.createBackup = !process.argv.includes('--no-backup');
30
+ this.verbose = process.argv.includes('--verbose');
31
+ this.missingKeys = new Map();
32
+
33
+ // Parse specific languages if provided
34
+ const langArg = process.argv.find(arg => arg.startsWith('--languages='));
35
+ if (langArg) {
36
+ this.supportedLanguages = langArg.split('=')[1].split(',').map(l => l.trim());
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Get all keys from an object with dot notation
42
+ */
43
+ getKeysFromObject(obj, prefix = '') {
44
+ const keys = [];
45
+
46
+ for (const key in obj) {
47
+ const fullKey = prefix ? `${prefix}.${key}` : key;
48
+
49
+ if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
50
+ keys.push(...this.getKeysFromObject(obj[key], fullKey));
51
+ } else {
52
+ keys.push(fullKey);
53
+ }
54
+ }
55
+
56
+ return keys;
57
+ }
58
+
59
+ /**
60
+ * Check if a key exists in the object using dot notation
61
+ */
62
+ keyExists(obj, keyPath) {
63
+ const keys = keyPath.split('.');
64
+ let current = obj;
65
+
66
+ for (const key of keys) {
67
+ if (current === null || current === undefined || !(key in current)) {
68
+ return false;
69
+ }
70
+ current = current[key];
71
+ }
72
+
73
+ return true;
74
+ }
75
+
76
+ /**
77
+ * Get a value from an object using dot notation
78
+ */
79
+ getValueByPath(obj, keyPath) {
80
+ const keys = keyPath.split('.');
81
+ let current = obj;
82
+
83
+ for (const key of keys) {
84
+ if (current === null || current === undefined || !(key in current)) {
85
+ return undefined;
86
+ }
87
+ current = current[key];
88
+ }
89
+
90
+ return current;
91
+ }
92
+
93
+ /**
94
+ * Set a value in an object using dot notation, creating nested structure as needed
95
+ */
96
+ setValueByPath(obj, keyPath, value) {
97
+ const keys = keyPath.split('.');
98
+ let current = obj;
99
+
100
+ // Navigate to the parent of the target key
101
+ for (let i = 0; i < keys.length - 1; i++) {
102
+ const key = keys[i];
103
+
104
+ if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) {
105
+ current[key] = {};
106
+ }
107
+
108
+ current = current[key];
109
+ }
110
+
111
+ // Set the final value
112
+ const finalKey = keys[keys.length - 1];
113
+ current[finalKey] = value;
114
+ }
115
+
116
+ /**
117
+ * Find similar keys in the object (for suggestions)
118
+ */
119
+ findSimilarKeys(obj, targetKey, threshold = 0.6) {
120
+ const allKeys = this.getKeysFromObject(obj);
121
+ const similar = [];
122
+
123
+ for (const key of allKeys) {
124
+ const similarity = this.calculateSimilarity(targetKey, key);
125
+ if (similarity >= threshold) {
126
+ similar.push({ key, similarity });
127
+ }
128
+ }
129
+
130
+ return similar.sort((a, b) => b.similarity - a.similarity);
131
+ }
132
+
133
+ /**
134
+ * Calculate string similarity (simple Levenshtein-based)
135
+ */
136
+ calculateSimilarity(str1, str2) {
137
+ const longer = str1.length > str2.length ? str1 : str2;
138
+ const shorter = str1.length > str2.length ? str2 : str1;
139
+
140
+ if (longer.length === 0) return 1.0;
141
+
142
+ const distance = this.levenshteinDistance(longer, shorter);
143
+ return (longer.length - distance) / longer.length;
144
+ }
145
+
146
+ /**
147
+ * Calculate Levenshtein distance
148
+ */
149
+ levenshteinDistance(str1, str2) {
150
+ const matrix = [];
151
+
152
+ for (let i = 0; i <= str2.length; i++) {
153
+ matrix[i] = [i];
154
+ }
155
+
156
+ for (let j = 0; j <= str1.length; j++) {
157
+ matrix[0][j] = j;
158
+ }
159
+
160
+ for (let i = 1; i <= str2.length; i++) {
161
+ for (let j = 1; j <= str1.length; j++) {
162
+ if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
163
+ matrix[i][j] = matrix[i - 1][j - 1];
164
+ } else {
165
+ matrix[i][j] = Math.min(
166
+ matrix[i - 1][j - 1] + 1,
167
+ matrix[i][j - 1] + 1,
168
+ matrix[i - 1][j] + 1
169
+ );
170
+ }
171
+ }
172
+ }
173
+
174
+ return matrix[str2.length][str1.length];
175
+ }
176
+
177
+ /**
178
+ * Load source language keys
179
+ */
180
+ loadSourceKeys() {
181
+ const sourceFile = path.join(this.uiLocalesDir, `${this.sourceLanguage}.json`);
182
+
183
+ if (!fs.existsSync(sourceFile)) {
184
+ throw new Error(`Source language file not found: ${sourceFile}`);
185
+ }
186
+
187
+ const sourceData = JSON.parse(fs.readFileSync(sourceFile, 'utf8'));
188
+ return this.getKeysFromObject(sourceData);
189
+ }
190
+
191
+ /**
192
+ * Check a single language file for missing keys
193
+ */
194
+ checkLanguageFile(languageCode, sourceKeys) {
195
+ const languageFile = path.join(this.uiLocalesDir, `${languageCode}.json`);
196
+
197
+ if (!fs.existsSync(languageFile)) {
198
+ console.log(`āš ļø Language file not found: ${languageFile}`);
199
+ return { missingKeys: [], addedKeys: 0 };
200
+ }
201
+
202
+ console.log(`šŸ” Checking ${languageCode.toUpperCase()} for missing keys...`);
203
+
204
+ // Load current translations
205
+ let currentTranslations;
206
+ try {
207
+ const fileContent = fs.readFileSync(languageFile, 'utf8');
208
+ currentTranslations = JSON.parse(fileContent);
209
+ } catch (error) {
210
+ console.log(`āŒ Error parsing ${languageCode}.json: ${error.message}`);
211
+ return { missingKeys: [], addedKeys: 0 };
212
+ }
213
+
214
+ // Get current keys using proper nested key extraction
215
+ const currentKeys = this.getKeysFromObject(currentTranslations);
216
+
217
+ // Find missing keys by checking if each source key exists in current translations
218
+ const missingKeys = sourceKeys.filter(key => !this.keyExists(currentTranslations, key));
219
+
220
+ if (missingKeys.length === 0) {
221
+ console.log(`āœ… ${languageCode.toUpperCase()}: No missing keys found`);
222
+ return { missingKeys: [], addedKeys: 0 };
223
+ }
224
+
225
+ console.log(`šŸ“‹ ${languageCode.toUpperCase()}: Found ${missingKeys.length} missing keys`);
226
+
227
+ if (this.verbose) {
228
+ missingKeys.forEach(key => {
229
+ console.log(` āŒ Missing: ${key}`);
230
+
231
+ // Suggest similar keys if available
232
+ const similarKeys = this.findSimilarKeys(currentTranslations, key, 0.7);
233
+ if (similarKeys.length > 0) {
234
+ console.log(` šŸ’” Similar keys found:`);
235
+ similarKeys.slice(0, 2).forEach(similar => {
236
+ console.log(` - ${similar.key} (${(similar.similarity * 100).toFixed(1)}% match)`);
237
+ });
238
+ }
239
+ });
240
+ }
241
+
242
+ // Add missing keys with [NOT TRANSLATED] placeholder
243
+ let addedKeys = 0;
244
+ if (!this.dryRun) {
245
+ // Create backup if enabled
246
+ if (this.createBackup) {
247
+ const backupFile = languageFile.replace('.json', '.backup.json');
248
+ fs.copyFileSync(languageFile, backupFile);
249
+ console.log(`šŸ“‹ Backup created: ${path.basename(backupFile)}`);
250
+ }
251
+
252
+ // Add missing keys in their proper nested locations
253
+ missingKeys.forEach(key => {
254
+ // Double-check the key doesn't exist before adding
255
+ if (!this.keyExists(currentTranslations, key)) {
256
+ this.setValueByPath(currentTranslations, key, '[NOT TRANSLATED]');
257
+ addedKeys++;
258
+
259
+ if (this.verbose) {
260
+ console.log(` āœ… Added: ${key} = [NOT TRANSLATED]`);
261
+ }
262
+ } else if (this.verbose) {
263
+ console.log(` āš ļø Key already exists, skipping: ${key}`);
264
+ }
265
+ });
266
+
267
+ // Save updated translations with proper formatting
268
+ try {
269
+ fs.writeFileSync(languageFile, JSON.stringify(currentTranslations, null, 2), 'utf8');
270
+ console.log(`šŸ’¾ ${languageCode.toUpperCase()}: Added ${addedKeys} missing keys`);
271
+ } catch (error) {
272
+ console.log(`āŒ Error writing ${languageCode}.json: ${error.message}`);
273
+ return { missingKeys, addedKeys: 0 };
274
+ }
275
+ } else {
276
+ console.log(`šŸ” ${languageCode.toUpperCase()}: Would add ${missingKeys.length} missing keys`);
277
+ }
278
+
279
+ return { missingKeys, addedKeys };
280
+ }
281
+
282
+ /**
283
+ * Generate a report of missing keys
284
+ */
285
+ generateReport() {
286
+ if (this.missingKeys.size === 0) {
287
+ console.log('\nšŸ“Š No missing keys found across all languages.');
288
+ return;
289
+ }
290
+
291
+ console.log('\nšŸ“Š MISSING KEYS REPORT');
292
+ console.log('========================\n');
293
+
294
+ let totalMissing = 0;
295
+
296
+ for (const [language, keys] of this.missingKeys) {
297
+ if (keys.length > 0) {
298
+ console.log(`šŸŒ ${language.toUpperCase()}: ${keys.length} missing keys`);
299
+ totalMissing += keys.length;
300
+
301
+ if (this.verbose) {
302
+ keys.forEach(key => {
303
+ console.log(` - ${key}`);
304
+ });
305
+ console.log('');
306
+ }
307
+ }
308
+ }
309
+
310
+ console.log(`\nšŸ“ˆ Total missing keys across all languages: ${totalMissing}`);
311
+ }
312
+
313
+ /**
314
+ * Export missing keys to a JSON report file
315
+ */
316
+ exportMissingKeysReport() {
317
+ const reportsDir = path.join(__dirname, 'reports');
318
+ if (!fs.existsSync(reportsDir)) {
319
+ fs.mkdirSync(reportsDir, { recursive: true });
320
+ }
321
+
322
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
323
+ const reportFile = path.join(reportsDir, `missing-keys-${timestamp}.json`);
324
+
325
+ const report = {
326
+ timestamp: new Date().toISOString(),
327
+ sourceLanguage: this.sourceLanguage,
328
+ checkedLanguages: this.supportedLanguages,
329
+ summary: {
330
+ totalLanguages: this.supportedLanguages.length,
331
+ languagesWithMissingKeys: Array.from(this.missingKeys.keys()).filter(lang =>
332
+ this.missingKeys.get(lang).length > 0
333
+ ).length,
334
+ totalMissingKeys: Array.from(this.missingKeys.values()).reduce((sum, keys) => sum + keys.length, 0)
335
+ },
336
+ missingKeysByLanguage: Object.fromEntries(this.missingKeys),
337
+ options: {
338
+ dryRun: this.dryRun,
339
+ createBackup: this.createBackup,
340
+ verbose: this.verbose
341
+ }
342
+ };
343
+
344
+ fs.writeFileSync(reportFile, JSON.stringify(report, null, 2), 'utf8');
345
+ console.log(`\nšŸ“„ Missing keys report exported: ${path.basename(reportFile)}`);
346
+
347
+ return reportFile;
348
+ }
349
+
350
+ /**
351
+ * Main execution function
352
+ */
353
+ async run() {
354
+ console.log('šŸ” Console Key Checker v1.5.1');
355
+ console.log('===============================\n');
356
+
357
+ if (this.dryRun) {
358
+ console.log('āš ļø Running in DRY RUN mode - no files will be modified\n');
359
+ }
360
+
361
+ try {
362
+ // Load source keys
363
+ console.log(`šŸ“– Loading source keys from ${this.sourceLanguage}.json...`);
364
+ const sourceKeys = this.loadSourceKeys();
365
+ console.log(`šŸ“Š Found ${sourceKeys.length} keys in source language\n`);
366
+
367
+ let totalAdded = 0;
368
+
369
+ // Check each language file
370
+ for (const languageCode of this.supportedLanguages) {
371
+ const result = this.checkLanguageFile(languageCode, sourceKeys);
372
+ this.missingKeys.set(languageCode, result.missingKeys);
373
+ totalAdded += result.addedKeys;
374
+ }
375
+
376
+ // Generate and export report
377
+ this.generateReport();
378
+
379
+ if (!this.dryRun) {
380
+ this.exportMissingKeysReport();
381
+ }
382
+
383
+ console.log('\nāœ… Console key checking complete!');
384
+
385
+ if (!this.dryRun && totalAdded > 0) {
386
+ console.log(`šŸ“Š Total keys added: ${totalAdded}`);
387
+ console.log('šŸ’” Run the native-translations.js script to replace [NOT TRANSLATED] placeholders with proper translations.');
388
+ }
389
+
390
+ if (this.dryRun) {
391
+ console.log('\nāš ļø DRY RUN MODE - No files were modified');
392
+ console.log('šŸ’” Remove --dry-run flag to apply changes');
393
+ }
394
+
395
+ } catch (error) {
396
+ console.error('āŒ Error during key checking:', error);
397
+ process.exit(1);
398
+ }
399
+ }
400
+ }
401
+
402
+ // Run the script if called directly
403
+ if (require.main === module) {
404
+ const checker = new ConsoleKeyChecker();
405
+ checker.run();
406
+ }
407
+
408
+ module.exports = ConsoleKeyChecker;