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
package/main/ui-i18n.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Internationalization Module
|
|
3
|
+
* Handles loading and managing UI translations for the i18n management tool
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const settingsManager = require('../settings/settings-manager');
|
|
9
|
+
|
|
10
|
+
// Get configuration from settings manager
|
|
11
|
+
function getConfig() {
|
|
12
|
+
const settings = settingsManager.getSettings();
|
|
13
|
+
return {
|
|
14
|
+
uiLocalesDir: settings.directories?.uiLocalesDir || path.join(__dirname, '..', 'ui-locales'),
|
|
15
|
+
configFile: settings.directories?.configFile || path.join(__dirname, '..', 'settings', 'user-config.json')
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class UIi18n {
|
|
20
|
+
constructor() {
|
|
21
|
+
const config = getConfig();
|
|
22
|
+
this.currentLanguage = 'en';
|
|
23
|
+
this.translations = {};
|
|
24
|
+
this.availableLanguages = ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'];
|
|
25
|
+
this.uiLocalesDir = path.resolve(config.uiLocalesDir);
|
|
26
|
+
this.configFile = path.resolve(config.configFile);
|
|
27
|
+
|
|
28
|
+
// Use settings manager as the single source of truth
|
|
29
|
+
const settings = settingsManager.getSettings();
|
|
30
|
+
const configuredLanguage = settings.language;
|
|
31
|
+
|
|
32
|
+
// Load language from settings manager or fallback
|
|
33
|
+
if (configuredLanguage && this.availableLanguages.includes(configuredLanguage)) {
|
|
34
|
+
this.loadLanguage(configuredLanguage);
|
|
35
|
+
} else {
|
|
36
|
+
this.loadLanguage('en');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Load translations for a specific language
|
|
42
|
+
* @param {string} language - Language code (e.g., 'en', 'de', 'es')
|
|
43
|
+
*/
|
|
44
|
+
loadLanguage(language) {
|
|
45
|
+
if (!this.availableLanguages.includes(language)) {
|
|
46
|
+
console.warn(`ā ļø Language '${language}' not available. Using English as fallback.`);
|
|
47
|
+
language = 'en';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const translationFile = path.join(this.uiLocalesDir, `${language}.json`);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
if (fs.existsSync(translationFile)) {
|
|
54
|
+
const content = fs.readFileSync(translationFile, 'utf8');
|
|
55
|
+
this.translations = JSON.parse(content);
|
|
56
|
+
this.currentLanguage = language;
|
|
57
|
+
} else {
|
|
58
|
+
console.warn(`ā ļø Translation file not found: ${translationFile}`);
|
|
59
|
+
if (language !== 'en') {
|
|
60
|
+
this.loadLanguage('en'); // Fallback to English
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(`ā Error loading translation file for '${language}':`, error.message);
|
|
65
|
+
if (language !== 'en') {
|
|
66
|
+
this.loadLanguage('en'); // Fallback to English
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Load user config from file or return default
|
|
73
|
+
*/
|
|
74
|
+
loadUserConfig() {
|
|
75
|
+
try {
|
|
76
|
+
if (fs.existsSync(this.configFile)) {
|
|
77
|
+
const content = fs.readFileSync(this.configFile, 'utf8');
|
|
78
|
+
return JSON.parse(content);
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(`ā Error loading user config:`, error.message);
|
|
82
|
+
}
|
|
83
|
+
return { language: null, sizeLimit: null };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Save user config to file
|
|
88
|
+
*/
|
|
89
|
+
saveUserConfig() {
|
|
90
|
+
try {
|
|
91
|
+
fs.writeFileSync(this.configFile, JSON.stringify(this.userConfig, null, 2), 'utf8');
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(`ā Error saving user config:`, error.message);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Run language selector on first run or when forced
|
|
99
|
+
* @returns {Promise<string>} Selected language code
|
|
100
|
+
*/
|
|
101
|
+
async initializeLanguage() {
|
|
102
|
+
if (!this.userConfig.language) {
|
|
103
|
+
const selectedLang = await this.selectLanguage();
|
|
104
|
+
this.userConfig.language = selectedLang;
|
|
105
|
+
this.saveUserConfig();
|
|
106
|
+
this.changeLanguage(selectedLang);
|
|
107
|
+
return selectedLang;
|
|
108
|
+
} else {
|
|
109
|
+
this.changeLanguage(this.userConfig.language);
|
|
110
|
+
return this.userConfig.language;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get translated text by key path
|
|
116
|
+
* @param {string} keyPath - Dot-separated key path (e.g., 'menu.title')
|
|
117
|
+
* @param {object} replacements - Object with replacement values
|
|
118
|
+
* @returns {string|array|object} Translated text or data
|
|
119
|
+
*/
|
|
120
|
+
t(keyPath, replacements = {}) {
|
|
121
|
+
const keys = keyPath.split('.');
|
|
122
|
+
let value = this.translations;
|
|
123
|
+
|
|
124
|
+
for (const key of keys) {
|
|
125
|
+
if (value && typeof value === 'object' && key in value) {
|
|
126
|
+
value = value[key];
|
|
127
|
+
} else {
|
|
128
|
+
// Try to get English fallback if current language is not English
|
|
129
|
+
if (this.currentLanguage !== 'en') {
|
|
130
|
+
const englishFallback = this.getEnglishFallback(keyPath, replacements);
|
|
131
|
+
if (englishFallback) {
|
|
132
|
+
return englishFallback;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
console.warn(`ā ļø Translation key not found: ${keyPath}`);
|
|
136
|
+
return keyPath; // Return the key path as fallback
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Handle different data types
|
|
141
|
+
if (typeof value === 'string') {
|
|
142
|
+
// Check if the value is [NOT TRANSLATED] and provide English fallback
|
|
143
|
+
if (value === '[NOT TRANSLATED]' && this.currentLanguage !== 'en') {
|
|
144
|
+
const englishFallback = this.getEnglishFallback(keyPath, replacements);
|
|
145
|
+
if (englishFallback) {
|
|
146
|
+
return `${englishFallback} [NOT TRANSLATED]`;
|
|
147
|
+
}
|
|
148
|
+
return `[NOT TRANSLATED: ${keyPath}]`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Replace placeholders in strings
|
|
152
|
+
let result = value;
|
|
153
|
+
for (const [placeholder, replacement] of Object.entries(replacements)) {
|
|
154
|
+
result = result.replace(new RegExp(`\\{${placeholder}\\}`, 'g'), replacement);
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
} else if (Array.isArray(value)) {
|
|
158
|
+
// Return arrays as-is (for things like exampleList)
|
|
159
|
+
return value;
|
|
160
|
+
} else if (typeof value === 'object') {
|
|
161
|
+
// Return objects as-is
|
|
162
|
+
return value;
|
|
163
|
+
} else {
|
|
164
|
+
console.warn(`ā ļø Translation value has unexpected type: ${keyPath}`);
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get English fallback for a translation key
|
|
171
|
+
* @param {string} keyPath - Dot-separated key path
|
|
172
|
+
* @param {object} replacements - Object with replacement values
|
|
173
|
+
* @returns {string|null} English translation or null if not found
|
|
174
|
+
*/
|
|
175
|
+
getEnglishFallback(keyPath, replacements = {}) {
|
|
176
|
+
try {
|
|
177
|
+
const englishFile = path.join(this.uiLocalesDir, 'en.json');
|
|
178
|
+
if (fs.existsSync(englishFile)) {
|
|
179
|
+
const englishContent = fs.readFileSync(englishFile, 'utf8');
|
|
180
|
+
const englishTranslations = JSON.parse(englishContent);
|
|
181
|
+
|
|
182
|
+
const keys = keyPath.split('.');
|
|
183
|
+
let value = englishTranslations;
|
|
184
|
+
|
|
185
|
+
for (const key of keys) {
|
|
186
|
+
if (value && typeof value === 'object' && key in value) {
|
|
187
|
+
value = value[key];
|
|
188
|
+
} else {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (typeof value === 'string') {
|
|
194
|
+
// Replace placeholders in English fallback
|
|
195
|
+
let result = value;
|
|
196
|
+
for (const [placeholder, replacement] of Object.entries(replacements)) {
|
|
197
|
+
result = result.replace(new RegExp(`\\{${placeholder}\\}`, 'g'), replacement);
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// Silently fail and return null
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get current language code
|
|
210
|
+
* @returns {string} Current language code
|
|
211
|
+
*/
|
|
212
|
+
getCurrentLanguage() {
|
|
213
|
+
return this.currentLanguage;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get list of available languages
|
|
218
|
+
* @returns {Array} Array of language codes
|
|
219
|
+
*/
|
|
220
|
+
getAvailableLanguages() {
|
|
221
|
+
return this.availableLanguages;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Change the current UI language
|
|
226
|
+
* @param {string} language - New language code
|
|
227
|
+
*/
|
|
228
|
+
changeLanguage(language) {
|
|
229
|
+
this.loadLanguage(language);
|
|
230
|
+
this.saveLanguagePreference(language);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get language display name
|
|
235
|
+
* @param {string} langCode - Language code
|
|
236
|
+
* @returns {string} Display name of the language
|
|
237
|
+
*/
|
|
238
|
+
getLanguageDisplayName(langCode) {
|
|
239
|
+
const displayNames = {
|
|
240
|
+
'en': 'English',
|
|
241
|
+
'de': 'Deutsch (German)',
|
|
242
|
+
'es': 'EspaƱol (Spanish)',
|
|
243
|
+
'fr': 'FranƧais (French)',
|
|
244
|
+
'ru': 'Š ŃŃŃŠŗŠøŠ¹ (Russian)',
|
|
245
|
+
'ja': 'ę„ę¬čŖ (Japanese)',
|
|
246
|
+
'zh': 'äøę (Chinese)'
|
|
247
|
+
};
|
|
248
|
+
return displayNames[langCode] || langCode;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Interactive language selection menu
|
|
253
|
+
* @returns {Promise<string>} Selected language code
|
|
254
|
+
*/
|
|
255
|
+
async selectLanguage() {
|
|
256
|
+
// Use the existing readline interface from the manager if available
|
|
257
|
+
const rl = global.activeReadlineInterface;
|
|
258
|
+
|
|
259
|
+
if (!rl) {
|
|
260
|
+
console.error('ā No active readline interface available');
|
|
261
|
+
return this.currentLanguage;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return new Promise((resolve) => {
|
|
265
|
+
console.log('\n' + this.t('language.title'));
|
|
266
|
+
console.log(this.t('language.separator'));
|
|
267
|
+
console.log(this.t('language.current', { language: this.getLanguageDisplayName(this.currentLanguage) }));
|
|
268
|
+
console.log('\n' + this.t('language.available'));
|
|
269
|
+
|
|
270
|
+
this.availableLanguages.forEach((lang, index) => {
|
|
271
|
+
const displayName = this.getLanguageDisplayName(lang);
|
|
272
|
+
const current = lang === this.currentLanguage ? ' ā' : '';
|
|
273
|
+
console.log(` ${index + 1}. ${displayName}${current}`);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
rl.question('\n' + this.t('language.prompt'), (answer) => {
|
|
277
|
+
const choice = parseInt(answer);
|
|
278
|
+
|
|
279
|
+
if (choice === 0) {
|
|
280
|
+
console.log(this.t('language.cancelled'));
|
|
281
|
+
resolve(this.currentLanguage);
|
|
282
|
+
} else if (choice >= 1 && choice <= this.availableLanguages.length) {
|
|
283
|
+
const selectedLang = this.availableLanguages[choice - 1];
|
|
284
|
+
this.changeLanguage(selectedLang);
|
|
285
|
+
console.log(this.t('language.changed', { language: this.getLanguageDisplayName(selectedLang) }));
|
|
286
|
+
resolve(selectedLang);
|
|
287
|
+
} else {
|
|
288
|
+
console.log(this.t('language.invalid'));
|
|
289
|
+
resolve(this.currentLanguage);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Save language preference using settings manager
|
|
297
|
+
* @param {string} language - Language code to save
|
|
298
|
+
*/
|
|
299
|
+
saveLanguagePreference(language) {
|
|
300
|
+
try {
|
|
301
|
+
const settings = settingsManager.getSettings();
|
|
302
|
+
settings.language = language;
|
|
303
|
+
settingsManager.saveSettings(settings);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error(`ā Error saving language preference:`, error.message);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Refresh language from current settings
|
|
311
|
+
* Call this after settings changes to update UI language
|
|
312
|
+
*/
|
|
313
|
+
refreshLanguageFromSettings() {
|
|
314
|
+
const settings = settingsManager.getSettings();
|
|
315
|
+
const configuredLanguage = settings.language;
|
|
316
|
+
|
|
317
|
+
if (configuredLanguage && this.availableLanguages.includes(configuredLanguage)) {
|
|
318
|
+
if (configuredLanguage !== this.currentLanguage) {
|
|
319
|
+
this.loadLanguage(configuredLanguage);
|
|
320
|
+
console.log(`š UI language updated to: ${this.getLanguageDisplayName(configuredLanguage)}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Remove the old loadUserConfig and saveUserConfig methods
|
|
327
|
+
* as they're now handled by settings-manager
|
|
328
|
+
*/
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Export the class, not a singleton instance
|
|
332
|
+
module.exports = UIi18n;
|
package/package.json
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "i18ntk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "i18ntk (i18n Toolkit) - A comprehensive, enterprise-grade internationalization (i18n) management toolkit for JavaScript/TypeScript projects with advanced analysis, validation, and automation features",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"i18n",
|
|
7
|
+
"internationalization",
|
|
8
|
+
"localization",
|
|
9
|
+
"translation",
|
|
10
|
+
"management",
|
|
11
|
+
"toolkit",
|
|
12
|
+
"javascript",
|
|
13
|
+
"typescript",
|
|
14
|
+
"react",
|
|
15
|
+
"vue",
|
|
16
|
+
"angular",
|
|
17
|
+
"cli",
|
|
18
|
+
"automation",
|
|
19
|
+
"validation",
|
|
20
|
+
"analysis",
|
|
21
|
+
"enterprise",
|
|
22
|
+
"debugging",
|
|
23
|
+
"reporting"
|
|
24
|
+
],
|
|
25
|
+
"homepage": "https://github.com/vladnoskv/i18n-management-toolkit#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/vladnoskv/i18n-management-toolkit/issues"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/vladnoskv/i18n-management-toolkit.git"
|
|
32
|
+
},
|
|
33
|
+
"funding": {
|
|
34
|
+
"type": "github",
|
|
35
|
+
"url": "https://github.com/sponsors/vladnoskv"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"author": "Vladimir Noskov <vladnoskv@gmail.com> (https://github.com/vladnoskv)",
|
|
39
|
+
"type": "commonjs",
|
|
40
|
+
"main": "main/i18ntk-manage.js",
|
|
41
|
+
"bin": {
|
|
42
|
+
"i18ntk-manage": "main/i18ntk-manage.js",
|
|
43
|
+
"i18ntk-init": "main/i18ntk-init.js",
|
|
44
|
+
"i18ntk-analyze": "main/i18ntk-analyze.js",
|
|
45
|
+
"i18ntk-validate": "main/i18ntk-validate.js",
|
|
46
|
+
"i18ntk-usage": "main/i18ntk-usage.js",
|
|
47
|
+
"i18ntk-complete": "main/i18ntk-complete.js",
|
|
48
|
+
"i18ntk-sizing": "main/i18ntk-sizing.js",
|
|
49
|
+
"i18ntk-summary": "main/i18ntk-summary.js",
|
|
50
|
+
"i18ntk-autorun": "main/i18ntk-autorun.js",
|
|
51
|
+
"i18ntk-debug": "dev/debug/debugger.js"
|
|
52
|
+
},
|
|
53
|
+
"directories": {
|
|
54
|
+
"doc": "docs"
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"*.js",
|
|
58
|
+
"main/",
|
|
59
|
+
"utils/",
|
|
60
|
+
"scripts/",
|
|
61
|
+
"ui-locales/",
|
|
62
|
+
"locales/",
|
|
63
|
+
"docs/",
|
|
64
|
+
"README.md",
|
|
65
|
+
"CHANGELOG.md",
|
|
66
|
+
"LICENSE"
|
|
67
|
+
],
|
|
68
|
+
"scripts": {
|
|
69
|
+
"start": "node main/i18ntk-manage.js",
|
|
70
|
+
"i18ntk": "node main/i18ntk-manage.js",
|
|
71
|
+
"i18ntk:init": "node main/i18ntk-init.js",
|
|
72
|
+
"i18ntk:analyze": "node main/i18ntk-analyze.js",
|
|
73
|
+
"i18ntk:validate": "node main/i18ntk-validate.js",
|
|
74
|
+
"i18ntk:usage": "node main/i18ntk-usage.js",
|
|
75
|
+
"i18ntk:complete": "node main/i18ntk-complete.js",
|
|
76
|
+
"i18ntk:sizing": "node main/i18ntk-sizing.js",
|
|
77
|
+
"i18ntk:sizing:detailed": "node main/i18ntk-sizing.js --detailed",
|
|
78
|
+
"i18ntk:debug": "node main/i18ntk-manage.js --command=debug",
|
|
79
|
+
"i18ntk:settings": "node main/i18ntk-manage.js --command=settings",
|
|
80
|
+
"i18ntk:summary": "node main/i18ntk-summary.js",
|
|
81
|
+
"i18ntk:manage": "node main/i18ntk-manage.js",
|
|
82
|
+
"i18n:autorun": "node main/i18ntk-autorun.js",
|
|
83
|
+
"i18ntk:autorun": "node main/i18ntk-autorun.js",
|
|
84
|
+
"workflow": "node main/i18ntk-autorun.js",
|
|
85
|
+
"init": "node main/i18ntk-init.js",
|
|
86
|
+
"analyze": "node main/i18ntk-analyze.js",
|
|
87
|
+
"validate": "node main/i18ntk-validate.js",
|
|
88
|
+
"usage": "node main/i18ntk-usage.js",
|
|
89
|
+
"complete": "node main/i18ntk-complete.js",
|
|
90
|
+
"sizing": "node main/i18ntk-sizing.js",
|
|
91
|
+
"summary": "node main/i18ntk-summary.js",
|
|
92
|
+
"full-coverage": "npm run complete && npm run analyze",
|
|
93
|
+
"verify-package": "node scripts/verify-package.js",
|
|
94
|
+
"prepublishOnly": "npm run verify-package",
|
|
95
|
+
"test": "node utils/test-complete-system.js"
|
|
96
|
+
},
|
|
97
|
+
"dependencies": {
|
|
98
|
+
"html-parse-stringify": "^3.0.1",
|
|
99
|
+
"i18next": "^25.3.2",
|
|
100
|
+
"react": "^19.1.0",
|
|
101
|
+
"react-i18next": "^15.6.1",
|
|
102
|
+
"void-elements": "^3.1.0"
|
|
103
|
+
},
|
|
104
|
+
"devDependencies": {},
|
|
105
|
+
"peerDependencies": {
|
|
106
|
+
"i18next": ">=20.0.0",
|
|
107
|
+
"react-i18next": "^15.6.1"
|
|
108
|
+
},
|
|
109
|
+
"peerDependenciesMeta": {
|
|
110
|
+
"i18next": {
|
|
111
|
+
"optional": false
|
|
112
|
+
},
|
|
113
|
+
"react-i18next": {
|
|
114
|
+
"optional": true
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"engines": {
|
|
118
|
+
"node": ">=16.0.0"
|
|
119
|
+
},
|
|
120
|
+
"publishConfig": {
|
|
121
|
+
"access": "public"
|
|
122
|
+
},
|
|
123
|
+
"preferGlobal": true,
|
|
124
|
+
"versionInfo": {
|
|
125
|
+
"version": "1.0.0",
|
|
126
|
+
"releaseDate": "27/07/2025",
|
|
127
|
+
"lastUpdated": "27/07/2025",
|
|
128
|
+
"maintainer": "Vladimir Noskov",
|
|
129
|
+
"changelog": "./CHANGELOG.md",
|
|
130
|
+
"documentation": "./README.md",
|
|
131
|
+
"apiReference": "./docs/api/API_REFERENCE.md",
|
|
132
|
+
"majorChanges": [
|
|
133
|
+
"š First stable release of i18ntk - Enterprise-grade i18n management toolkit",
|
|
134
|
+
"ā
Complete CLI suite with 10 powerful commands for i18n management",
|
|
135
|
+
"š Multi-language support with 7 built-in UI locales (EN, DE, ES, FR, JA, RU, ZH)",
|
|
136
|
+
"š Advanced analysis and validation tools for translation quality",
|
|
137
|
+
"š Comprehensive reporting system with detailed insights",
|
|
138
|
+
"ā” Automated workflows for efficient i18n management",
|
|
139
|
+
"š ļø Framework support for React, Vue, Angular and more",
|
|
140
|
+
"š Complete documentation with installation guides and API reference"
|
|
141
|
+
],
|
|
142
|
+
"breakingChanges": [],
|
|
143
|
+
"deprecations": [],
|
|
144
|
+
"nextVersion": "1.1.0",
|
|
145
|
+
"supportedNodeVersions": ">=16.0.0",
|
|
146
|
+
"supportedFrameworks": {
|
|
147
|
+
"react-i18next": ">=11.0.0",
|
|
148
|
+
"vue-i18n": ">=9.0.0",
|
|
149
|
+
"angular-i18n": ">=12.0.0"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fix Missing Translation Keys - v1.6.1
|
|
5
|
+
*
|
|
6
|
+
* This script identifies and adds missing translation keys to all non-English
|
|
7
|
+
* language files to achieve 100% translation key coverage.
|
|
8
|
+
*
|
|
9
|
+
* Usage: node scripts/fix-missing-translation-keys.js
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const UI_LOCALES_DIR = path.join(__dirname, '..', 'ui-locales');
|
|
16
|
+
const ENGLISH_FILE = path.join(UI_LOCALES_DIR, 'en.json');
|
|
17
|
+
|
|
18
|
+
// Language configurations with their names
|
|
19
|
+
const LANGUAGES = {
|
|
20
|
+
'de': 'German',
|
|
21
|
+
'es': 'Spanish',
|
|
22
|
+
'fr': 'French',
|
|
23
|
+
'ja': 'Japanese',
|
|
24
|
+
'ru': 'Russian',
|
|
25
|
+
'zh': 'Chinese'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Recursively get all keys from an object with dot notation
|
|
30
|
+
*/
|
|
31
|
+
function getAllKeys(obj, prefix = '') {
|
|
32
|
+
const keys = [];
|
|
33
|
+
|
|
34
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
35
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
36
|
+
|
|
37
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
38
|
+
keys.push(...getAllKeys(value, fullKey));
|
|
39
|
+
} else {
|
|
40
|
+
keys.push(fullKey);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return keys;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get value from nested object using dot notation
|
|
49
|
+
*/
|
|
50
|
+
function getNestedValue(obj, path) {
|
|
51
|
+
return path.split('.').reduce((current, key) => current?.[key], obj);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Set value in nested object using dot notation
|
|
56
|
+
*/
|
|
57
|
+
function setNestedValue(obj, path, value) {
|
|
58
|
+
const keys = path.split('.');
|
|
59
|
+
const lastKey = keys.pop();
|
|
60
|
+
|
|
61
|
+
let current = obj;
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
if (!(key in current) || typeof current[key] !== 'object') {
|
|
64
|
+
current[key] = {};
|
|
65
|
+
}
|
|
66
|
+
current = current[key];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
current[lastKey] = value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Load and parse JSON file
|
|
74
|
+
*/
|
|
75
|
+
function loadJsonFile(filePath) {
|
|
76
|
+
try {
|
|
77
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
78
|
+
return JSON.parse(content);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`ā Error loading ${filePath}: ${error.message}`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Save JSON file with proper formatting
|
|
87
|
+
*/
|
|
88
|
+
function saveJsonFile(filePath, data) {
|
|
89
|
+
try {
|
|
90
|
+
const content = JSON.stringify(data, null, 2);
|
|
91
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
92
|
+
return true;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(`ā Error saving ${filePath}: ${error.message}`);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate placeholder translation for missing keys
|
|
101
|
+
*/
|
|
102
|
+
function generatePlaceholderTranslation(englishValue, language) {
|
|
103
|
+
// For simple strings, add a language prefix to indicate it needs translation
|
|
104
|
+
if (typeof englishValue === 'string') {
|
|
105
|
+
// Don't translate emoji-only strings or very short technical strings
|
|
106
|
+
if (/^[\u{1F300}-\u{1F9FF}\s]*$/u.test(englishValue) || englishValue.length <= 3) {
|
|
107
|
+
return englishValue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Add language indicator for strings that need translation
|
|
111
|
+
return `[${language.toUpperCase()}] ${englishValue}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return englishValue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Main function to fix missing translation keys
|
|
119
|
+
*/
|
|
120
|
+
async function fixMissingTranslationKeys() {
|
|
121
|
+
console.log('š§ I18N Translation Key Fixer - v1.6.1');
|
|
122
|
+
console.log('========================================\n');
|
|
123
|
+
|
|
124
|
+
// Load English reference file
|
|
125
|
+
console.log('š Loading English reference file...');
|
|
126
|
+
const englishData = loadJsonFile(ENGLISH_FILE);
|
|
127
|
+
if (!englishData) {
|
|
128
|
+
console.error('ā Failed to load English reference file');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const englishKeys = getAllKeys(englishData);
|
|
133
|
+
console.log(`ā
Found ${englishKeys.length} keys in English file\n`);
|
|
134
|
+
|
|
135
|
+
let totalKeysAdded = 0;
|
|
136
|
+
const results = {};
|
|
137
|
+
|
|
138
|
+
// Process each language
|
|
139
|
+
for (const [langCode, langName] of Object.entries(LANGUAGES)) {
|
|
140
|
+
console.log(`š Processing ${langName} (${langCode})...`);
|
|
141
|
+
|
|
142
|
+
const langFile = path.join(UI_LOCALES_DIR, `${langCode}.json`);
|
|
143
|
+
const langData = loadJsonFile(langFile);
|
|
144
|
+
|
|
145
|
+
if (!langData) {
|
|
146
|
+
console.log(`ā ļø Skipping ${langName} - file not found or invalid`);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const langKeys = getAllKeys(langData);
|
|
151
|
+
const missingKeys = englishKeys.filter(key => !langKeys.includes(key));
|
|
152
|
+
|
|
153
|
+
console.log(` š Current keys: ${langKeys.length}`);
|
|
154
|
+
console.log(` ā ļø Missing keys: ${missingKeys.length}`);
|
|
155
|
+
|
|
156
|
+
if (missingKeys.length === 0) {
|
|
157
|
+
console.log(` ā
No missing keys found\n`);
|
|
158
|
+
results[langCode] = { added: 0, total: langKeys.length };
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Add missing keys
|
|
163
|
+
let addedCount = 0;
|
|
164
|
+
for (const missingKey of missingKeys) {
|
|
165
|
+
const englishValue = getNestedValue(englishData, missingKey);
|
|
166
|
+
const translatedValue = generatePlaceholderTranslation(englishValue, langCode);
|
|
167
|
+
|
|
168
|
+
setNestedValue(langData, missingKey, translatedValue);
|
|
169
|
+
addedCount++;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Save updated file
|
|
173
|
+
if (saveJsonFile(langFile, langData)) {
|
|
174
|
+
console.log(` ā
Added ${addedCount} keys to ${langName}`);
|
|
175
|
+
console.log(` š¾ Updated ${langFile}\n`);
|
|
176
|
+
|
|
177
|
+
totalKeysAdded += addedCount;
|
|
178
|
+
results[langCode] = { added: addedCount, total: langKeys.length + addedCount };
|
|
179
|
+
} else {
|
|
180
|
+
console.log(` ā Failed to save ${langName} file\n`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Summary report
|
|
185
|
+
console.log('š SUMMARY REPORT');
|
|
186
|
+
console.log('==================');
|
|
187
|
+
console.log(`š English reference keys: ${englishKeys.length}`);
|
|
188
|
+
console.log(`ā Total keys added: ${totalKeysAdded}`);
|
|
189
|
+
console.log(`š Languages processed: ${Object.keys(results).length}\n`);
|
|
190
|
+
|
|
191
|
+
console.log('š Language Details:');
|
|
192
|
+
for (const [langCode, result] of Object.entries(results)) {
|
|
193
|
+
const langName = LANGUAGES[langCode];
|
|
194
|
+
const percentage = ((result.total / englishKeys.length) * 100).toFixed(1);
|
|
195
|
+
console.log(` ${langName} (${langCode}): ${result.total}/${englishKeys.length} keys (${percentage}%) - Added: ${result.added}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log('\nš Translation key fixing completed!');
|
|
199
|
+
console.log('\nš Next Steps:');
|
|
200
|
+
console.log('1. Review the added placeholder translations');
|
|
201
|
+
console.log('2. Replace [LANG] prefixed strings with proper translations');
|
|
202
|
+
console.log('3. Run npm test to verify translation consistency');
|
|
203
|
+
console.log('4. Update version to 1.6.1 when translations are complete');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Run the script
|
|
207
|
+
if (require.main === module) {
|
|
208
|
+
fixMissingTranslationKeys().catch(error => {
|
|
209
|
+
console.error('ā Script failed:', error.message);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = { fixMissingTranslationKeys };
|