i18ntk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +401 -0
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/dev/README.md +37 -0
- package/dev/debug/README.md +30 -0
- package/dev/debug/complete-console-translations.js +295 -0
- package/dev/debug/console-key-checker.js +408 -0
- package/dev/debug/console-translations.js +335 -0
- package/dev/debug/debugger.js +408 -0
- package/dev/debug/export-missing-keys.js +432 -0
- package/dev/debug/final-normalize.js +236 -0
- package/dev/debug/find-extra-keys.js +68 -0
- package/dev/debug/normalize-locales.js +153 -0
- package/dev/debug/refactor-locales.js +240 -0
- package/dev/debug/reorder-locales.js +85 -0
- package/dev/debug/replace-hardcoded-console.js +378 -0
- package/docs/INSTALLATION.md +449 -0
- package/docs/README.md +222 -0
- package/docs/TODO_ROADMAP.md +279 -0
- package/docs/api/API_REFERENCE.md +377 -0
- package/docs/api/COMPONENTS.md +492 -0
- package/docs/api/CONFIGURATION.md +651 -0
- package/docs/api/NPM_PUBLISHING_GUIDE.md +434 -0
- package/docs/debug/DEBUG_README.md +30 -0
- package/docs/debug/DEBUG_TOOLS.md +494 -0
- package/docs/development/AGENTS.md +351 -0
- package/docs/development/DEVELOPMENT_RULES.md +165 -0
- package/docs/development/DEV_README.md +37 -0
- package/docs/release-notes/RELEASE_NOTES_v1.0.0.md +173 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.0.md +141 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.1.md +185 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.3.md +199 -0
- package/docs/reports/ANALYSIS_README.md +17 -0
- package/docs/reports/CONSOLE_MISMATCH_BUG_REPORT_v1.5.0.md +181 -0
- package/docs/reports/SIZING_README.md +18 -0
- package/docs/reports/SUMMARY_README.md +18 -0
- package/docs/reports/TRANSLATION_BUG_REPORT_v1.5.0.md +129 -0
- package/docs/reports/USAGE_README.md +18 -0
- package/docs/reports/VALIDATION_README.md +18 -0
- package/locales/de/auth.json +3 -0
- package/locales/de/common.json +16 -0
- package/locales/de/pagination.json +6 -0
- package/locales/en/auth.json +3 -0
- package/locales/en/common.json +16 -0
- package/locales/en/pagination.json +6 -0
- package/locales/es/auth.json +3 -0
- package/locales/es/common.json +16 -0
- package/locales/es/pagination.json +6 -0
- package/locales/fr/auth.json +3 -0
- package/locales/fr/common.json +16 -0
- package/locales/fr/pagination.json +6 -0
- package/locales/ru/auth.json +3 -0
- package/locales/ru/common.json +16 -0
- package/locales/ru/pagination.json +6 -0
- package/main/i18ntk-analyze.js +625 -0
- package/main/i18ntk-autorun.js +461 -0
- package/main/i18ntk-complete.js +494 -0
- package/main/i18ntk-init.js +686 -0
- package/main/i18ntk-manage.js +848 -0
- package/main/i18ntk-sizing.js +557 -0
- package/main/i18ntk-summary.js +671 -0
- package/main/i18ntk-usage.js +1282 -0
- package/main/i18ntk-validate.js +762 -0
- package/main/ui-i18n.js +332 -0
- package/package.json +152 -0
- package/scripts/fix-missing-translation-keys.js +214 -0
- package/scripts/verify-package.js +168 -0
- package/ui-locales/de.json +637 -0
- package/ui-locales/en.json +688 -0
- package/ui-locales/es.json +637 -0
- package/ui-locales/fr.json +637 -0
- package/ui-locales/ja.json +637 -0
- package/ui-locales/ru.json +637 -0
- package/ui-locales/zh.json +637 -0
- package/utils/admin-auth.js +317 -0
- package/utils/admin-cli.js +353 -0
- package/utils/admin-pin.js +409 -0
- package/utils/detect-language-mismatches.js +454 -0
- package/utils/i18n-helper.js +128 -0
- package/utils/maintain-language-purity.js +433 -0
- package/utils/native-translations.js +478 -0
- package/utils/security.js +384 -0
- package/utils/test-complete-system.js +356 -0
- package/utils/test-console-i18n.js +402 -0
- package/utils/translate-mismatches.js +571 -0
- package/utils/validate-language-purity.js +531 -0
|
@@ -0,0 +1,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;
|