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,478 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Native Translation Replacer v1.5.1
5
+ *
6
+ * This script replaces [NOT TRANSLATED] placeholders with proper native translations
7
+ * in all language files. It includes improved key checking to prevent duplicates.
8
+ *
9
+ * Usage:
10
+ * node native-translations.js [options]
11
+ *
12
+ * Options:
13
+ * --dry-run Show what would be changed without making changes
14
+ * --backup Create backup files (default: true)
15
+ * --languages=<list> Specific languages to process (default: all)
16
+ * --verbose Show detailed progress
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ class NativeTranslator {
23
+ constructor() {
24
+ this.uiLocalesDir = path.join(__dirname, '..', 'ui-locales');
25
+ this.backupDir = path.join(__dirname, '..', 'backups', 'ui-locales');
26
+ this.supportedLanguages = ['de', 'es', 'fr', 'ja', 'ru', 'zh'];
27
+ this.dryRun = process.argv.includes('--dry-run');
28
+ this.createBackup = !process.argv.includes('--no-backup');
29
+ this.verbose = process.argv.includes('--verbose');
30
+
31
+ // Parse specific languages if provided
32
+ const langArg = process.argv.find(arg => arg.startsWith('--languages='));
33
+ if (langArg) {
34
+ this.supportedLanguages = langArg.split('=')[1].split(',').map(l => l.trim());
35
+ }
36
+
37
+ // Native translations mapping
38
+ this.translations = {
39
+ de: {
40
+ // Menu options
41
+ 'menu.options.sizing': 'šŸ“ Größenanalyse',
42
+ 'menu.options.debugger': 'šŸ”§ Debug-Tools',
43
+ 'menu.options.settings': 'āš™ļø Einstellungen',
44
+
45
+ // Operations
46
+ 'operations.init.title': 'šŸš€ I18N INITIALISIERUNG',
47
+ 'operations.init.separator': '============================================================',
48
+ 'operations.analyze.title': 'šŸ” ÜBERSETZUNGEN ANALYSIEREN',
49
+ 'operations.analyze.separator': '============================================================',
50
+ 'operations.validate.title': 'āœ… ÜBERSETZUNGEN VALIDIEREN',
51
+ 'operations.validate.separator': '============================================================',
52
+ 'operations.usage.title': 'šŸ“Š SCHLÜSSELVERWENDUNG ANALYSIEREN',
53
+ 'operations.usage.separator': '============================================================',
54
+ 'operations.complete.title': 'šŸŽÆ ÜBERSETZUNGEN VERVOLLSTƄNDIGEN',
55
+ 'operations.complete.separator': '============================================================',
56
+ 'operations.sizing.title': 'šŸ“ ÜBERSETZUNGSGRƖSSEN ANALYSIEREN',
57
+ 'operations.sizing.separator': '============================================================',
58
+ 'operations.workflow.title': 'šŸ”„ UMFASSENDEN I18N-WORKFLOW AUSFÜHREN',
59
+ 'operations.workflow.separator': '============================================================',
60
+
61
+ // Error messages
62
+ 'errors.invalidChoice': 'āŒ Ungültige Auswahl. Bitte versuchen Sie es erneut.',
63
+ 'errors.operationFailed': 'āŒ Vorgang fehlgeschlagen: {error}',
64
+ 'errors.fileNotFound': 'āŒ Datei nicht gefunden: {file}',
65
+ 'errors.directoryNotFound': 'āŒ Verzeichnis nicht gefunden: {directory}',
66
+
67
+ // Debugger
68
+ 'debugger.title': 'šŸ”§ DEBUG-TOOLS',
69
+ 'debugger.separator': '============================================================'
70
+ },
71
+
72
+ es: {
73
+ // Menu options
74
+ 'menu.options.sizing': 'šŸ“ AnĆ”lisis de tamaƱo',
75
+ 'menu.options.debugger': 'šŸ”§ Herramientas de depuración',
76
+ 'menu.options.settings': 'āš™ļø Configuración',
77
+
78
+ // Operations
79
+ 'operations.init.title': 'šŸš€ INICIALIZANDO I18N',
80
+ 'operations.init.separator': '============================================================',
81
+ 'operations.analyze.title': 'šŸ” ANALIZANDO TRADUCCIONES',
82
+ 'operations.analyze.separator': '============================================================',
83
+ 'operations.validate.title': 'āœ… VALIDANDO TRADUCCIONES',
84
+ 'operations.validate.separator': '============================================================',
85
+ 'operations.usage.title': 'šŸ“Š ANALIZANDO USO DE CLAVES',
86
+ 'operations.usage.separator': '============================================================',
87
+ 'operations.complete.title': 'šŸŽÆ COMPLETANDO TRADUCCIONES',
88
+ 'operations.complete.separator': '============================================================',
89
+ 'operations.sizing.title': 'šŸ“ ANALIZANDO TAMAƑOS DE TRADUCCIƓN',
90
+ 'operations.sizing.separator': '============================================================',
91
+ 'operations.workflow.title': 'šŸ”„ EJECUTANDO FLUJO DE TRABAJO COMPLETO DE I18N',
92
+ 'operations.workflow.separator': '============================================================',
93
+
94
+ // Error messages
95
+ 'errors.invalidChoice': 'āŒ Opción invĆ”lida. Por favor, intĆ©ntelo de nuevo.',
96
+ 'errors.operationFailed': 'āŒ Operación fallida: {error}',
97
+ 'errors.fileNotFound': 'āŒ Archivo no encontrado: {file}',
98
+ 'errors.directoryNotFound': 'āŒ Directorio no encontrado: {directory}',
99
+
100
+ // Debugger
101
+ 'debugger.title': 'šŸ”§ HERRAMIENTAS DE DEPURACIƓN',
102
+ 'debugger.separator': '============================================================'
103
+ },
104
+
105
+ fr: {
106
+ // Menu options
107
+ 'menu.options.sizing': 'šŸ“ Analyse de taille',
108
+ 'menu.options.debugger': 'šŸ”§ Outils de dĆ©bogage',
109
+ 'menu.options.settings': 'āš™ļø ParamĆØtres',
110
+
111
+ // Operations
112
+ 'operations.init.title': 'šŸš€ INITIALISATION I18N',
113
+ 'operations.init.separator': '============================================================',
114
+ 'operations.analyze.title': 'šŸ” ANALYSE DES TRADUCTIONS',
115
+ 'operations.analyze.separator': '============================================================',
116
+ 'operations.validate.title': 'āœ… VALIDATION DES TRADUCTIONS',
117
+ 'operations.validate.separator': '============================================================',
118
+ 'operations.usage.title': 'šŸ“Š ANALYSE DE L\'UTILISATION DES CLƉS',
119
+ 'operations.usage.separator': '============================================================',
120
+ 'operations.complete.title': 'šŸŽÆ COMPLƉTION DES TRADUCTIONS',
121
+ 'operations.complete.separator': '============================================================',
122
+ 'operations.sizing.title': 'šŸ“ ANALYSE DES TAILLES DE TRADUCTION',
123
+ 'operations.sizing.separator': '============================================================',
124
+ 'operations.workflow.title': 'šŸ”„ EXƉCUTION DU FLUX DE TRAVAIL I18N COMPLET',
125
+ 'operations.workflow.separator': '============================================================',
126
+
127
+ // Error messages
128
+ 'errors.invalidChoice': 'āŒ Choix invalide. Veuillez rĆ©essayer.',
129
+ 'errors.operationFailed': 'āŒ OpĆ©ration Ć©chouĆ©e : {error}',
130
+ 'errors.fileNotFound': 'āŒ Fichier non trouvĆ© : {file}',
131
+ 'errors.directoryNotFound': 'āŒ RĆ©pertoire non trouvĆ© : {directory}',
132
+
133
+ // Debugger
134
+ 'debugger.title': 'šŸ”§ OUTILS DE DƉBOGAGE',
135
+ 'debugger.separator': '============================================================'
136
+ },
137
+
138
+ ja: {
139
+ // Menu options
140
+ 'menu.options.sizing': 'šŸ“ ć‚µć‚¤ć‚ŗåˆ†ęž',
141
+ 'menu.options.debugger': 'šŸ”§ ćƒ‡ćƒćƒƒć‚°ćƒ„ćƒ¼ćƒ«',
142
+ 'menu.options.settings': 'āš™ļø 設定',
143
+
144
+ // Operations
145
+ 'operations.init.title': 'šŸš€ I18NåˆęœŸåŒ–',
146
+ 'operations.init.separator': '============================================================',
147
+ 'operations.analyze.title': 'šŸ” ēæ»čØ³ć®åˆ†ęž',
148
+ 'operations.analyze.separator': '============================================================',
149
+ 'operations.validate.title': 'āœ… 翻訳の検証',
150
+ 'operations.validate.separator': '============================================================',
151
+ 'operations.usage.title': 'šŸ“Š ć‚­ćƒ¼ä½æē”ØēŠ¶ę³ć®åˆ†ęž',
152
+ 'operations.usage.separator': '============================================================',
153
+ 'operations.complete.title': 'šŸŽÆ ēæ»čØ³ć®å®Œäŗ†',
154
+ 'operations.complete.separator': '============================================================',
155
+ 'operations.sizing.title': 'šŸ“ ēæ»čØ³ć‚µć‚¤ć‚ŗć®åˆ†ęž',
156
+ 'operations.sizing.separator': '============================================================',
157
+ 'operations.workflow.title': 'šŸ”„ å®Œå…ØćŖI18NćƒÆćƒ¼ć‚Æćƒ•ćƒ­ćƒ¼ć®å®Ÿč”Œ',
158
+ 'operations.workflow.separator': '============================================================',
159
+
160
+ // Error messages
161
+ 'errors.invalidChoice': 'āŒ ē„”åŠ¹ćŖéøęŠžć§ć™ć€‚ć‚‚ć†äø€åŗ¦ćŠč©¦ć—ćć ć•ć„ć€‚',
162
+ 'errors.operationFailed': 'āŒ ę“ä½œćŒå¤±ę•—ć—ć¾ć—ćŸļ¼š{error}',
163
+ 'errors.fileNotFound': 'āŒ ćƒ•ć‚”ć‚¤ćƒ«ćŒč¦‹ć¤ć‹ć‚Šć¾ć›ć‚“ļ¼š{file}',
164
+ 'errors.directoryNotFound': 'āŒ ćƒ‡ć‚£ćƒ¬ć‚ÆćƒˆćƒŖćŒč¦‹ć¤ć‹ć‚Šć¾ć›ć‚“ļ¼š{directory}',
165
+
166
+ // Debugger
167
+ 'debugger.title': 'šŸ”§ ćƒ‡ćƒćƒƒć‚°ćƒ„ćƒ¼ćƒ«',
168
+ 'debugger.separator': '============================================================'
169
+ },
170
+
171
+ ru: {
172
+ // Menu options
173
+ 'menu.options.sizing': 'šŸ“ Анализ размера',
174
+ 'menu.options.debugger': 'šŸ”§ Š˜Š½ŃŃ‚Ń€ŃƒŠ¼ŠµŠ½Ń‚Ń‹ отлаГки',
175
+ 'menu.options.settings': 'āš™ļø ŠŠ°ŃŃ‚Ń€Š¾Š¹ŠŗŠø',
176
+
177
+ // Operations
178
+ 'operations.init.title': 'šŸš€ Š˜ŠŠ˜Š¦Š˜ŠŠ›Š˜Š—ŠŠ¦Š˜ŠÆ I18N',
179
+ 'operations.init.separator': '============================================================',
180
+ 'operations.analyze.title': 'šŸ” ŠŠŠŠ›Š˜Š— ŠŸŠ•Š Š•Š’ŠžŠ”ŠžŠ’',
181
+ 'operations.analyze.separator': '============================================================',
182
+ 'operations.validate.title': 'āœ… ŠŸŠ ŠžŠ’Š•Š ŠšŠ ŠŸŠ•Š Š•Š’ŠžŠ”ŠžŠ’',
183
+ 'operations.validate.separator': '============================================================',
184
+ 'operations.usage.title': 'šŸ“Š ŠŠŠŠ›Š˜Š— Š˜Š”ŠŸŠžŠ›Š¬Š—ŠžŠ’ŠŠŠ˜ŠÆ ŠšŠ›Š®Š§Š•Š™',
185
+ 'operations.usage.separator': '============================================================',
186
+ 'operations.complete.title': 'šŸŽÆ Š—ŠŠ’Š•Š ŠØŠ•ŠŠ˜Š• ŠŸŠ•Š Š•Š’ŠžŠ”ŠžŠ’',
187
+ 'operations.complete.separator': '============================================================',
188
+ 'operations.sizing.title': 'šŸ“ ŠŠŠŠ›Š˜Š— Š ŠŠ—ŠœŠ•Š ŠžŠ’ ŠŸŠ•Š Š•Š’ŠžŠ”ŠžŠ’',
189
+ 'operations.sizing.separator': '============================================================',
190
+ 'operations.workflow.title': 'šŸ”„ Š’Š«ŠŸŠžŠ›ŠŠ•ŠŠ˜Š• ŠŸŠžŠ›ŠŠžŠ“Šž Š ŠŠ‘ŠžŠ§Š•Š“Šž ŠŸŠ ŠžŠ¦Š•Š”Š”Š I18N',
191
+ 'operations.workflow.separator': '============================================================',
192
+
193
+ // Error messages
194
+ 'errors.invalidChoice': 'āŒ ŠŠµŠ²ŠµŃ€Š½Ń‹Š¹ выбор. ŠŸŠ¾Š¶Š°Š»ŃƒŠ¹ŃŃ‚Š°, ŠæŠ¾ŠæŃ€Š¾Š±ŃƒŠ¹Ń‚Šµ снова.',
195
+ 'errors.operationFailed': 'āŒ ŠžŠæŠµŃ€Š°Ń†ŠøŃ не уГалась: {error}',
196
+ 'errors.fileNotFound': 'āŒ Файл не найГен: {file}',
197
+ 'errors.directoryNotFound': 'āŒ Š”ŠøŃ€ŠµŠŗŃ‚Š¾Ń€ŠøŃ не найГена: {directory}',
198
+
199
+ // Debugger
200
+ 'debugger.title': 'šŸ”§ Š˜ŠŠ”Š¢Š Š£ŠœŠ•ŠŠ¢Š« ŠžŠ¢Š›ŠŠ”ŠšŠ˜',
201
+ 'debugger.separator': '============================================================'
202
+ },
203
+
204
+ zh: {
205
+ // Menu options
206
+ 'menu.options.sizing': 'šŸ“ å¤§å°åˆ†ęž',
207
+ 'menu.options.debugger': 'šŸ”§ č°ƒčÆ•å·„å…·',
208
+ 'menu.options.settings': 'āš™ļø 设置',
209
+
210
+ // Operations
211
+ 'operations.init.title': 'šŸš€ I18N初始化',
212
+ 'operations.init.separator': '============================================================',
213
+ 'operations.analyze.title': 'šŸ” åˆ†ęžēæ»čÆ‘',
214
+ 'operations.analyze.separator': '============================================================',
215
+ 'operations.validate.title': 'āœ…éŖŒčÆēæ»čÆ‘',
216
+ 'operations.validate.separator': '============================================================',
217
+ 'operations.usage.title': 'šŸ“Š åˆ†ęžé”®ä½æē”Øęƒ…å†µ',
218
+ 'operations.usage.separator': '============================================================',
219
+ 'operations.complete.title': 'šŸŽÆ å®Œęˆēæ»čÆ‘',
220
+ 'operations.complete.separator': '============================================================',
221
+ 'operations.sizing.title': 'šŸ“ åˆ†ęžēæ»čÆ‘å¤§å°',
222
+ 'operations.sizing.separator': '============================================================',
223
+ 'operations.workflow.title': 'šŸ”„ ę‰§č”Œå®Œę•“ēš„I18Nå·„ä½œęµēØ‹',
224
+ 'operations.workflow.separator': '============================================================',
225
+
226
+ // Error messages
227
+ 'errors.invalidChoice': 'āŒ ę— ę•ˆé€‰ę‹©ć€‚čÆ·é‡čÆ•ć€‚',
228
+ 'errors.operationFailed': 'āŒ ę“ä½œå¤±č“„ļ¼š{error}',
229
+ 'errors.fileNotFound': 'āŒ ę–‡ä»¶ęœŖę‰¾åˆ°ļ¼š{file}',
230
+ 'errors.directoryNotFound': 'āŒ ē›®å½•ęœŖę‰¾åˆ°ļ¼š{directory}',
231
+
232
+ // Debugger
233
+ 'debugger.title': 'šŸ”§ č°ƒčÆ•å·„å…·',
234
+ 'debugger.separator': '============================================================'
235
+ }
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Get all keys from an object with dot notation
241
+ */
242
+ getKeysFromObject(obj, prefix = '') {
243
+ const keys = [];
244
+
245
+ for (const key in obj) {
246
+ const fullKey = prefix ? `${prefix}.${key}` : key;
247
+
248
+ if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
249
+ keys.push(...this.getKeysFromObject(obj[key], fullKey));
250
+ } else {
251
+ keys.push(fullKey);
252
+ }
253
+ }
254
+
255
+ return keys;
256
+ }
257
+
258
+ /**
259
+ * Check if a key exists in the object using dot notation
260
+ */
261
+ keyExists(obj, keyPath) {
262
+ const keys = keyPath.split('.');
263
+ let current = obj;
264
+
265
+ for (const key of keys) {
266
+ if (current === null || current === undefined || !(key in current)) {
267
+ return false;
268
+ }
269
+ current = current[key];
270
+ }
271
+
272
+ return true;
273
+ }
274
+
275
+ /**
276
+ * Get a value from an object using dot notation
277
+ */
278
+ getValueByPath(obj, keyPath) {
279
+ const keys = keyPath.split('.');
280
+ let current = obj;
281
+
282
+ for (const key of keys) {
283
+ if (current === null || current === undefined || !(key in current)) {
284
+ return undefined;
285
+ }
286
+ current = current[key];
287
+ }
288
+
289
+ return current;
290
+ }
291
+
292
+ /**
293
+ * Find similar keys in the object (for suggestions)
294
+ */
295
+ findSimilarKeys(obj, targetKey, threshold = 0.6) {
296
+ const allKeys = this.getKeysFromObject(obj);
297
+ const similar = [];
298
+
299
+ for (const key of allKeys) {
300
+ const similarity = this.calculateSimilarity(targetKey, key);
301
+ if (similarity >= threshold) {
302
+ similar.push({ key, similarity });
303
+ }
304
+ }
305
+
306
+ return similar.sort((a, b) => b.similarity - a.similarity);
307
+ }
308
+
309
+ /**
310
+ * Calculate string similarity (simple Levenshtein-based)
311
+ */
312
+ calculateSimilarity(str1, str2) {
313
+ const longer = str1.length > str2.length ? str1 : str2;
314
+ const shorter = str1.length > str2.length ? str2 : str1;
315
+
316
+ if (longer.length === 0) return 1.0;
317
+
318
+ const distance = this.levenshteinDistance(longer, shorter);
319
+ return (longer.length - distance) / longer.length;
320
+ }
321
+
322
+ /**
323
+ * Calculate Levenshtein distance
324
+ */
325
+ levenshteinDistance(str1, str2) {
326
+ const matrix = [];
327
+
328
+ for (let i = 0; i <= str2.length; i++) {
329
+ matrix[i] = [i];
330
+ }
331
+
332
+ for (let j = 0; j <= str1.length; j++) {
333
+ matrix[0][j] = j;
334
+ }
335
+
336
+ for (let i = 1; i <= str2.length; i++) {
337
+ for (let j = 1; j <= str1.length; j++) {
338
+ if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
339
+ matrix[i][j] = matrix[i - 1][j - 1];
340
+ } else {
341
+ matrix[i][j] = Math.min(
342
+ matrix[i - 1][j - 1] + 1,
343
+ matrix[i][j - 1] + 1,
344
+ matrix[i - 1][j] + 1
345
+ );
346
+ }
347
+ }
348
+ }
349
+
350
+ return matrix[str2.length][str1.length];
351
+ }
352
+
353
+ /**
354
+ * Replace [NOT TRANSLATED] placeholders with native translations
355
+ * Only replaces existing keys, never adds new ones
356
+ */
357
+ replaceInObject(obj, nativeObj, keyPath = '') {
358
+ let replacements = 0;
359
+
360
+ for (const key in obj) {
361
+ const currentKeyPath = keyPath ? `${keyPath}.${key}` : key;
362
+
363
+ if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
364
+ replacements += this.replaceInObject(obj[key], nativeObj, currentKeyPath);
365
+ } else if (typeof obj[key] === 'string') {
366
+ // Only replace if the current value is exactly [NOT TRANSLATED] and we have a native translation
367
+ if (obj[key] === '[NOT TRANSLATED]' && nativeObj[currentKeyPath]) {
368
+ obj[key] = nativeObj[currentKeyPath];
369
+ replacements++;
370
+ if (this.verbose) {
371
+ console.log(` āœ… ${currentKeyPath}: ${nativeObj[currentKeyPath]}`);
372
+ }
373
+ } else if (obj[key] === '[NOT TRANSLATED]' && !nativeObj[currentKeyPath]) {
374
+ // Suggest similar keys if exact match not found
375
+ const similarKeys = this.findSimilarKeys(nativeObj, currentKeyPath);
376
+ if (similarKeys.length > 0 && this.verbose) {
377
+ console.log(` āš ļø No exact translation for '${currentKeyPath}', similar keys found:`);
378
+ similarKeys.slice(0, 3).forEach(similar => {
379
+ console.log(` - ${similar.key} (${(similar.similarity * 100).toFixed(1)}% match)`);
380
+ });
381
+ }
382
+ }
383
+ }
384
+ }
385
+
386
+ return replacements;
387
+ }
388
+
389
+ async processLanguageFiles() {
390
+ const languageFiles = this.supportedLanguages.map(code => ({
391
+ code,
392
+ file: path.join(this.uiLocalesDir, `${code}.json`)
393
+ }));
394
+
395
+ let totalReplacements = 0;
396
+
397
+ console.log(`šŸ”„ Processing ${languageFiles.length} language files...\n`);
398
+
399
+ for (const { code, file } of languageFiles) {
400
+ if (!fs.existsSync(file)) {
401
+ console.log(`āš ļø File not found: ${file}`);
402
+ continue;
403
+ }
404
+
405
+ console.log(`šŸŒ Processing ${code.toUpperCase()} translations...`);
406
+
407
+ // Create backup if enabled and not in dry run mode
408
+ if (this.createBackup && !this.dryRun) {
409
+ const backupFile = file.replace('.json', '.backup.json');
410
+ fs.copyFileSync(file, backupFile);
411
+ console.log(`šŸ“‹ Backup created: ${path.basename(backupFile)}`);
412
+ }
413
+
414
+ // Load current translations
415
+ let currentTranslations;
416
+ try {
417
+ const fileContent = fs.readFileSync(file, 'utf8');
418
+ currentTranslations = JSON.parse(fileContent);
419
+ } catch (error) {
420
+ console.log(`āŒ Error parsing ${code}.json: ${error.message}`);
421
+ continue;
422
+ }
423
+
424
+ const nativeTranslations = this.translations[code] || {};
425
+
426
+ // Count existing keys before processing
427
+ const existingKeys = this.getKeysFromObject(currentTranslations);
428
+ console.log(`šŸ“Š ${code.toUpperCase()}: Found ${existingKeys.length} existing keys`);
429
+
430
+ // Replace [NOT TRANSLATED] placeholders with native translations
431
+ const replacements = this.replaceInObject(currentTranslations, nativeTranslations);
432
+
433
+ // Save updated translations if not in dry run mode
434
+ if (!this.dryRun && replacements > 0) {
435
+ try {
436
+ fs.writeFileSync(file, JSON.stringify(currentTranslations, null, 2), 'utf8');
437
+ } catch (error) {
438
+ console.log(`āŒ Error writing ${code}.json: ${error.message}`);
439
+ continue;
440
+ }
441
+ }
442
+
443
+ console.log(`šŸ“Š ${code.toUpperCase()}: ${replacements} translations replaced`);
444
+ totalReplacements += replacements;
445
+ }
446
+
447
+ console.log(`\nāœ… Translation replacement complete!`);
448
+ console.log(`šŸ“Š Total replacements: ${totalReplacements}`);
449
+
450
+ if (this.dryRun) {
451
+ console.log(`\nāš ļø DRY RUN MODE - No files were modified`);
452
+ }
453
+ }
454
+
455
+ async run() {
456
+ console.log('šŸŒ Native Translation Replacer v1.5.1');
457
+ console.log('======================================\n');
458
+
459
+ if (this.dryRun) {
460
+ console.log('āš ļø Running in DRY RUN mode - no files will be modified\n');
461
+ }
462
+
463
+ try {
464
+ await this.processLanguageFiles();
465
+ } catch (error) {
466
+ console.error('āŒ Error during translation replacement:', error);
467
+ process.exit(1);
468
+ }
469
+ }
470
+ }
471
+
472
+ // Run the script if called directly
473
+ if (require.main === module) {
474
+ const translator = new NativeTranslator();
475
+ translator.run();
476
+ }
477
+
478
+ module.exports = NativeTranslator;