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,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 };