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,454 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Language Mismatch Detection Tool
5
+ *
6
+ * This script detects and reports language mismatches in translation files:
7
+ * - English text in foreign language files
8
+ * - Untranslated markers like [TRANSLATE], [DE], [FR], etc.
9
+ * - Missing translations that should be in native language
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { execSync } = require('child_process');
15
+
16
+ class LanguageMismatchDetector {
17
+ constructor() {
18
+ this.localesDir = path.join(__dirname, 'ui-locales');
19
+ this.sourceLanguage = 'en';
20
+ this.results = {
21
+ mismatches: {},
22
+ summary: {
23
+ totalFiles: 0,
24
+ filesWithMismatches: 0,
25
+ totalMismatches: 0
26
+ }
27
+ };
28
+
29
+ // Common English words that shouldn't appear in foreign translations
30
+ this.englishIndicators = [
31
+ 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by',
32
+ 'from', 'up', 'about', 'into', 'through', 'during', 'before', 'after',
33
+ 'above', 'below', 'between', 'among', 'under', 'over', 'inside', 'outside',
34
+ 'this', 'that', 'these', 'those', 'here', 'there', 'where', 'when', 'why',
35
+ 'how', 'what', 'which', 'who', 'whom', 'whose', 'all', 'any', 'some',
36
+ 'many', 'much', 'few', 'little', 'more', 'most', 'less', 'least',
37
+ 'good', 'better', 'best', 'bad', 'worse', 'worst', 'big', 'bigger',
38
+ 'biggest', 'small', 'smaller', 'smallest', 'new', 'newer', 'newest',
39
+ 'old', 'older', 'oldest', 'first', 'second', 'third', 'last', 'next',
40
+ 'previous', 'same', 'different', 'other', 'another', 'each', 'every',
41
+ 'both', 'either', 'neither', 'one', 'two', 'three', 'four', 'five',
42
+ 'Error', 'Warning', 'Success', 'Failed', 'Loading', 'Saving', 'Delete',
43
+ 'Create', 'Update', 'Add', 'Remove', 'Edit', 'View', 'Show', 'Hide',
44
+ 'Open', 'Close', 'Start', 'Stop', 'Run', 'Execute', 'Process', 'Generate',
45
+ 'Analysis', 'Report', 'Summary', 'Details', 'Configuration', 'Settings',
46
+ 'Options', 'Preferences', 'Tools', 'Debug', 'Test', 'Validate',
47
+ 'Check', 'Verify', 'Confirm', 'Cancel', 'Continue', 'Finish', 'Complete',
48
+ 'Please', 'try', 'again', 'Invalid', 'choice', 'Select', 'option',
49
+ 'Back', 'Main', 'Menu', 'File', 'files', 'found', 'not', 'available'
50
+ ];
51
+
52
+ // Language-specific patterns
53
+ this.languagePatterns = {
54
+ de: {
55
+ name: 'German',
56
+ prefixes: ['[DE]', '[GERMAN]'],
57
+ commonWords: ['der', 'die', 'das', 'und', 'oder', 'aber', 'mit', 'von', 'zu', 'für', 'auf', 'in', 'an', 'bei', 'nach', 'vor', 'über', 'unter', 'durch', 'gegen', 'ohne', 'um', 'bis', 'seit', 'während', 'wegen', 'trotz']
58
+ },
59
+ fr: {
60
+ name: 'French',
61
+ prefixes: ['[FR]', '[FRENCH]'],
62
+ commonWords: ['le', 'la', 'les', 'un', 'une', 'des', 'et', 'ou', 'mais', 'avec', 'de', 'du', 'pour', 'sur', 'dans', 'par', 'sans', 'sous', 'vers', 'chez', 'depuis', 'pendant', 'avant', 'après']
63
+ },
64
+ es: {
65
+ name: 'Spanish',
66
+ prefixes: ['[ES]', '[SPANISH]'],
67
+ commonWords: ['el', 'la', 'los', 'las', 'un', 'una', 'y', 'o', 'pero', 'con', 'de', 'del', 'para', 'por', 'en', 'a', 'desde', 'hasta', 'durante', 'antes', 'después', 'sobre', 'bajo', 'entre']
68
+ },
69
+ ru: {
70
+ name: 'Russian',
71
+ prefixes: ['[RU]', '[RUSSIAN]'],
72
+ commonWords: ['и', 'в', 'не', 'на', 'я', 'быть', 'он', 'с', 'что', 'а', 'по', 'это', 'она', 'к', 'но', 'они', 'мы', 'как', 'из', 'у', 'который', 'то', 'за', 'свой', 'что', 'её', 'так', 'же', 'от', 'может', 'когда', 'очень', 'где', 'уже', 'если', 'да', 'его', 'нет', 'ещё']
73
+ },
74
+ ja: {
75
+ name: 'Japanese',
76
+ prefixes: ['[JA]', '[JAPANESE]'],
77
+ commonWords: ['の', 'に', 'は', 'を', 'た', 'が', 'で', 'て', 'と', 'し', 'れ', 'さ', 'ある', 'いる', 'も', 'する', 'から', 'な', 'こと', 'として', 'い', 'や', 'れる', 'など', 'なっ', 'ない', 'この', 'ため', 'その', 'あっ', 'よう', 'また', 'もの', 'という', 'あり', 'まで', 'られ', 'なる', 'へ', 'か', 'だ', 'これ', 'によって', 'により', 'おり', 'より', 'による', 'ず', 'なり', 'られる', 'において', 'ば', 'なかっ', 'なく', 'しかし', 'について', 'せ', 'だっ', 'その後', 'できる', 'それ']
78
+ },
79
+ zh: {
80
+ name: 'Chinese',
81
+ prefixes: ['[ZH]', '[CHINESE]'],
82
+ commonWords: ['的', '一', '是', '在', '不', '了', '有', '和', '人', '这', '中', '大', '为', '上', '个', '国', '我', '以', '要', '他', '时', '来', '用', '们', '生', '到', '作', '地', '于', '出', '就', '分', '对', '成', '会', '可', '主', '发', '年', '动', '同', '工', '也', '能', '下', '过', '子', '说', '产', '种', '面', '而', '方', '后', '多', '定', '行', '学', '法', '所', '民', '得', '经', '十', '三', '之', '进', '着', '等', '部', '度', '家', '电', '力', '里', '如', '水', '化', '高', '自', '二', '理', '起', '小', '物', '现', '实', '加', '量', '都', '两', '体', '制', '机', '当', '使', '点', '从', '业', '本', '去', '把', '性', '好', '应', '开', '它', '合', '还', '因', '由', '其', '些', '然', '前', '外', '天', '政', '四', '日', '那', '社', '义', '事', '平', '形', '相', '全', '表', '间', '样', '与', '关', '各', '重', '新', '线', '内', '数', '正', '心', '反', '你', '明', '看', '原', '又', '么', '利', '比', '或', '但', '质', '气', '第', '向', '道', '命', '此', '变', '条', '只', '没', '结', '解', '问', '意', '建', '月', '公', '无', 'ä¸\u200d', 'ä¸\u200d', 'ä¸\u200d']
83
+ }
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Main detection method
89
+ */
90
+ async detectMismatches() {
91
+ console.log('🔍 Language Mismatch Detection Tool');
92
+ console.log('=====================================\n');
93
+
94
+ const localeFiles = this.getLocaleFiles();
95
+
96
+ for (const file of localeFiles) {
97
+ if (file.language === this.sourceLanguage) {
98
+ continue; // Skip source language
99
+ }
100
+
101
+ console.log(`📄 Analyzing ${file.language}.json...`);
102
+ await this.analyzeFile(file);
103
+ }
104
+
105
+ this.generateReport();
106
+ }
107
+
108
+ /**
109
+ * Get all locale files
110
+ */
111
+ getLocaleFiles() {
112
+ const files = fs.readdirSync(this.localesDir)
113
+ .filter(file => file.endsWith('.json'))
114
+ .map(file => ({
115
+ filename: file,
116
+ language: path.basename(file, '.json'),
117
+ path: path.join(this.localesDir, file)
118
+ }));
119
+
120
+ this.results.summary.totalFiles = files.length - 1; // Exclude source language
121
+ return files;
122
+ }
123
+
124
+ /**
125
+ * Analyze a single translation file
126
+ */
127
+ async analyzeFile(file) {
128
+ try {
129
+ const content = fs.readFileSync(file.path, 'utf8');
130
+ const translations = JSON.parse(content);
131
+
132
+ const mismatches = [];
133
+ this.analyzeObject(translations, '', mismatches, file.language);
134
+
135
+ if (mismatches.length > 0) {
136
+ this.results.mismatches[file.language] = {
137
+ filename: file.filename,
138
+ language: this.languagePatterns[file.language]?.name || file.language,
139
+ mismatches: mismatches,
140
+ count: mismatches.length
141
+ };
142
+
143
+ this.results.summary.filesWithMismatches++;
144
+ this.results.summary.totalMismatches += mismatches.length;
145
+ }
146
+
147
+ } catch (error) {
148
+ console.error(`❌ Error analyzing ${file.filename}: ${error.message}`);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Recursively analyze translation object
154
+ */
155
+ analyzeObject(obj, keyPath, mismatches, language) {
156
+ for (const [key, value] of Object.entries(obj)) {
157
+ const currentPath = keyPath ? `${keyPath}.${key}` : key;
158
+
159
+ if (typeof value === 'string') {
160
+ const issues = this.detectStringIssues(value, language);
161
+ if (issues.length > 0) {
162
+ mismatches.push({
163
+ key: currentPath,
164
+ value: value,
165
+ issues: issues
166
+ });
167
+ }
168
+ } else if (typeof value === 'object' && value !== null) {
169
+ this.analyzeObject(value, currentPath, mismatches, language);
170
+ } else if (Array.isArray(value)) {
171
+ value.forEach((item, index) => {
172
+ if (typeof item === 'string') {
173
+ const issues = this.detectStringIssues(item, language);
174
+ if (issues.length > 0) {
175
+ mismatches.push({
176
+ key: `${currentPath}[${index}]`,
177
+ value: item,
178
+ issues: issues
179
+ });
180
+ }
181
+ }
182
+ });
183
+ }
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Detect issues in a translation string
189
+ */
190
+ detectStringIssues(text, language) {
191
+ const issues = [];
192
+ const lowerText = text.toLowerCase();
193
+
194
+ // Check for translation markers
195
+ if (text.includes('[TRANSLATE]')) {
196
+ issues.push({
197
+ type: 'untranslated_marker',
198
+ description: 'Contains [TRANSLATE] marker - needs translation'
199
+ });
200
+ }
201
+
202
+ // Check for language prefixes
203
+ const langPattern = this.languagePatterns[language];
204
+ if (langPattern) {
205
+ for (const prefix of langPattern.prefixes) {
206
+ if (text.includes(prefix)) {
207
+ issues.push({
208
+ type: 'language_prefix',
209
+ description: `Contains ${prefix} prefix - should be translated to ${langPattern.name}`
210
+ });
211
+ }
212
+ }
213
+ }
214
+
215
+ // Check for English indicators
216
+ const words = lowerText.match(/\b\w+\b/g) || [];
217
+ const englishWords = words.filter(word =>
218
+ this.englishIndicators.includes(word) &&
219
+ word.length > 2 // Ignore very short words that might be common across languages
220
+ );
221
+
222
+ if (englishWords.length > 0) {
223
+ // Calculate English word ratio
224
+ const englishRatio = englishWords.length / words.length;
225
+
226
+ if (englishRatio > 0.3 || englishWords.length > 3) {
227
+ issues.push({
228
+ type: 'english_content',
229
+ description: `Contains English words: ${englishWords.slice(0, 5).join(', ')}${englishWords.length > 5 ? '...' : ''}`,
230
+ englishWords: englishWords,
231
+ ratio: englishRatio
232
+ });
233
+ }
234
+ }
235
+
236
+ // Check for common English phrases
237
+ const englishPhrases = [
238
+ 'error', 'warning', 'success', 'failed', 'loading', 'saving',
239
+ 'please try again', 'invalid choice', 'select option', 'back to main menu',
240
+ 'configuration debug', 'translation debug', 'performance debug',
241
+ 'full system debug', 'debug tools', 'admin pin setup',
242
+ 'would you like', 'enter admin pin', 'confirm admin pin',
243
+ 'pins do not match', 'authentication failed', 'access denied'
244
+ ];
245
+
246
+ for (const phrase of englishPhrases) {
247
+ if (lowerText.includes(phrase)) {
248
+ issues.push({
249
+ type: 'english_phrase',
250
+ description: `Contains English phrase: "${phrase}"`
251
+ });
252
+ }
253
+ }
254
+
255
+ return issues;
256
+ }
257
+
258
+ /**
259
+ * Generate and display the report
260
+ */
261
+ generateReport() {
262
+ console.log('\n📊 LANGUAGE MISMATCH DETECTION RESULTS');
263
+ console.log('========================================\n');
264
+
265
+ // Summary
266
+ console.log('📋 SUMMARY:');
267
+ console.log(` Total files analyzed: ${this.results.summary.totalFiles}`);
268
+ console.log(` Files with mismatches: ${this.results.summary.filesWithMismatches}`);
269
+ console.log(` Total mismatches found: ${this.results.summary.totalMismatches}\n`);
270
+
271
+ if (this.results.summary.totalMismatches === 0) {
272
+ console.log('✅ No language mismatches found! All translations appear to be in their correct languages.\n');
273
+ return;
274
+ }
275
+
276
+ // Detailed results
277
+ console.log('🔍 DETAILED RESULTS:\n');
278
+
279
+ for (const [language, data] of Object.entries(this.results.mismatches)) {
280
+ console.log(`📄 ${data.filename} (${data.language})`);
281
+ console.log(` Found ${data.count} mismatch(es):\n`);
282
+
283
+ data.mismatches.forEach((mismatch, index) => {
284
+ console.log(` ${index + 1}. Key: ${mismatch.key}`);
285
+ console.log(` Value: "${mismatch.value.substring(0, 100)}${mismatch.value.length > 100 ? '...' : ''}"}`);
286
+
287
+ mismatch.issues.forEach(issue => {
288
+ console.log(` Issue: ${issue.description}`);
289
+ });
290
+ console.log('');
291
+ });
292
+ }
293
+
294
+ // Recommendations
295
+ console.log('💡 RECOMMENDATIONS:\n');
296
+ console.log('1. Review and translate all entries marked with [TRANSLATE]');
297
+ console.log('2. Replace language prefixes (e.g., [DE], [FR]) with proper translations');
298
+ console.log('3. Translate English text to the appropriate target language');
299
+ console.log('4. Use native language equivalents for all user-facing text');
300
+ console.log('5. Run this tool again after making corrections to verify fixes\n');
301
+
302
+ // Save report
303
+ this.saveReport();
304
+ }
305
+
306
+ /**
307
+ * Save report to file
308
+ */
309
+ saveReport() {
310
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
311
+ const reportPath = path.join(__dirname, 'i18ntk-reports', 'language-mismatches', `language-mismatches-${timestamp}.json`);
312
+
313
+ // Ensure directory exists
314
+ const reportDir = path.dirname(reportPath);
315
+ if (!fs.existsSync(reportDir)) {
316
+ fs.mkdirSync(reportDir, { recursive: true });
317
+ }
318
+
319
+ const report = {
320
+ timestamp: new Date().toISOString(),
321
+ summary: this.results.summary,
322
+ mismatches: this.results.mismatches,
323
+ recommendations: [
324
+ 'Review and translate all entries marked with [TRANSLATE]',
325
+ 'Replace language prefixes (e.g., [DE], [FR]) with proper translations',
326
+ 'Translate English text to the appropriate target language',
327
+ 'Use native language equivalents for all user-facing text',
328
+ 'Run this tool again after making corrections to verify fixes'
329
+ ]
330
+ };
331
+
332
+ try {
333
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
334
+ console.log(`📄 Detailed report saved to: ${reportPath}\n`);
335
+ } catch (error) {
336
+ console.error(`❌ Error saving report: ${error.message}\n`);
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Auto-fix some common issues
342
+ */
343
+ async autoFix(dryRun = true) {
344
+ console.log(`🔧 Auto-fix mode ${dryRun ? '(DRY RUN)' : '(LIVE)'}`);
345
+ console.log('=====================================\n');
346
+
347
+ const fixes = [];
348
+
349
+ for (const [language, data] of Object.entries(this.results.mismatches)) {
350
+ const filePath = path.join(this.localesDir, data.filename);
351
+ const content = fs.readFileSync(filePath, 'utf8');
352
+ let translations = JSON.parse(content);
353
+ let modified = false;
354
+
355
+ for (const mismatch of data.mismatches) {
356
+ for (const issue of mismatch.issues) {
357
+ if (issue.type === 'language_prefix') {
358
+ // Remove language prefixes
359
+ const newValue = mismatch.value.replace(/\[\w+\]\s*/g, '');
360
+ if (newValue !== mismatch.value) {
361
+ this.setNestedValue(translations, mismatch.key, newValue);
362
+ modified = true;
363
+ fixes.push({
364
+ file: data.filename,
365
+ key: mismatch.key,
366
+ old: mismatch.value,
367
+ new: newValue,
368
+ type: 'prefix_removal'
369
+ });
370
+ }
371
+ }
372
+ }
373
+ }
374
+
375
+ if (modified && !dryRun) {
376
+ fs.writeFileSync(filePath, JSON.stringify(translations, null, 2));
377
+ console.log(`✅ Fixed ${data.filename}`);
378
+ }
379
+ }
380
+
381
+ console.log(`\n📊 Auto-fix Summary:`);
382
+ console.log(` Total fixes applied: ${fixes.length}`);
383
+
384
+ if (dryRun && fixes.length > 0) {
385
+ console.log('\n🔍 Preview of fixes:');
386
+ fixes.slice(0, 10).forEach(fix => {
387
+ console.log(` ${fix.file}: ${fix.key}`);
388
+ console.log(` Old: "${fix.old}"`);
389
+ console.log(` New: "${fix.new}"\n`);
390
+ });
391
+
392
+ if (fixes.length > 10) {
393
+ console.log(` ... and ${fixes.length - 10} more fixes\n`);
394
+ }
395
+
396
+ console.log('💡 Run with --apply to apply these fixes\n');
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Set nested object value by dot notation key
402
+ */
403
+ setNestedValue(obj, key, value) {
404
+ const keys = key.split('.');
405
+ let current = obj;
406
+
407
+ for (let i = 0; i < keys.length - 1; i++) {
408
+ if (!(keys[i] in current)) {
409
+ current[keys[i]] = {};
410
+ }
411
+ current = current[keys[i]];
412
+ }
413
+
414
+ current[keys[keys.length - 1]] = value;
415
+ }
416
+ }
417
+
418
+ // CLI Interface
419
+ if (require.main === module) {
420
+ const args = process.argv.slice(2);
421
+ const detector = new LanguageMismatchDetector();
422
+
423
+ if (args.includes('--help') || args.includes('-h')) {
424
+ console.log(`
425
+ Language Mismatch Detection Tool
426
+
427
+ Usage:
428
+ node detect-language-mismatches.js [options]
429
+
430
+ Options:
431
+ --auto-fix Show auto-fixable issues (dry run)
432
+ --apply Apply auto-fixes (live mode)
433
+ --help, -h Show this help message
434
+
435
+ Examples:
436
+ node detect-language-mismatches.js # Detect mismatches
437
+ node detect-language-mismatches.js --auto-fix # Preview auto-fixes
438
+ node detect-language-mismatches.js --apply # Apply auto-fixes
439
+ `);
440
+ process.exit(0);
441
+ }
442
+
443
+ detector.detectMismatches().then(() => {
444
+ if (args.includes('--auto-fix') || args.includes('--apply')) {
445
+ const dryRun = !args.includes('--apply');
446
+ return detector.autoFix(dryRun);
447
+ }
448
+ }).catch(error => {
449
+ console.error('❌ Error:', error.message);
450
+ process.exit(1);
451
+ });
452
+ }
453
+
454
+ module.exports = LanguageMismatchDetector;
@@ -0,0 +1,128 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const settingsManager = require('../settings/settings-manager');
4
+
5
+ // Get configuration from settings manager
6
+ function getConfig() {
7
+ const settings = settingsManager.getSettings();
8
+ return {
9
+ uiLocalesDir: settings.directories?.uiLocalesDir || path.join(__dirname, '..', 'ui-locales')
10
+ };
11
+ }
12
+
13
+ // Global translations object
14
+ let translations = {};
15
+ let currentLanguage = 'en';
16
+
17
+ /**
18
+ * Load translations from the ui-locales directory
19
+ * @param {string} language - Language code (default: 'en')
20
+ */
21
+ function loadTranslations(language = 'en') {
22
+ currentLanguage = language;
23
+ const config = getConfig();
24
+ const localesDir = path.resolve(config.uiLocalesDir);
25
+ const translationFile = path.join(localesDir, `${language}.json`);
26
+
27
+ try {
28
+ if (fs.existsSync(translationFile)) {
29
+ const content = fs.readFileSync(translationFile, 'utf8');
30
+ translations = JSON.parse(content);
31
+ } else {
32
+ console.warn(`Translation file not found: ${translationFile}`);
33
+ translations = {};
34
+ }
35
+ } catch (error) {
36
+ console.error(`Error loading translations: ${error.message}`);
37
+ translations = {};
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Get a translated string by key
43
+ * @param {string} key - Translation key (e.g., 'module.subkey')
44
+ * @param {object} params - Parameters to interpolate into the translation
45
+ * @returns {string} - Translated string or the key if translation not found
46
+ */
47
+ function t(key, params = {}) {
48
+ // Split the key into parts (e.g., 'module.subkey' -> ['module', 'subkey'])
49
+ const keyParts = key.split('.');
50
+ let value = translations;
51
+
52
+ // Navigate through the nested object
53
+ for (const part of keyParts) {
54
+ if (value && typeof value === 'object' && part in value) {
55
+ value = value[part];
56
+ } else {
57
+ // Return the key if translation not found
58
+ console.warn(`Translation not found for key: ${key}`);
59
+ return key;
60
+ }
61
+ }
62
+
63
+ // If we found a string, interpolate parameters
64
+ if (typeof value === 'string') {
65
+ return interpolateParams(value, params);
66
+ }
67
+
68
+ // Return the key if the final value is not a string
69
+ console.warn(`Translation key does not resolve to a string: ${key}`);
70
+ return key;
71
+ }
72
+
73
+ /**
74
+ * Interpolate parameters into a translation string
75
+ * @param {string} template - Template string with {{param}} or {param} placeholders
76
+ * @param {object} params - Parameters to interpolate
77
+ * @returns {string} - Interpolated string
78
+ */
79
+ function interpolateParams(template, params) {
80
+ // Handle both {{param}} and {param} formats
81
+ return template
82
+ .replace(/\{\{(\w+)\}\}/g, (match, paramName) => {
83
+ if (paramName in params) {
84
+ return params[paramName];
85
+ }
86
+ return match;
87
+ })
88
+ .replace(/\{(\w+)\}/g, (match, paramName) => {
89
+ if (paramName in params) {
90
+ return params[paramName];
91
+ }
92
+ return match;
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Get the current language
98
+ * @returns {string} - Current language code
99
+ */
100
+ function getCurrentLanguage() {
101
+ return currentLanguage;
102
+ }
103
+
104
+ /**
105
+ * Get all available languages
106
+ * @returns {string[]} - Array of available language codes
107
+ */
108
+ function getAvailableLanguages() {
109
+ const config = getConfig();
110
+ const localesDir = path.resolve(config.uiLocalesDir);
111
+ try {
112
+ if (fs.existsSync(localesDir)) {
113
+ return fs.readdirSync(localesDir)
114
+ .filter(file => file.endsWith('.json'))
115
+ .map(file => path.basename(file, '.json'));
116
+ }
117
+ } catch (error) {
118
+ console.error(`Error reading locales directory: ${error.message}`);
119
+ }
120
+ return ['en']; // Default fallback
121
+ }
122
+
123
+ module.exports = {
124
+ loadTranslations,
125
+ t,
126
+ getCurrentLanguage,
127
+ getAvailableLanguages
128
+ };