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,68 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ // Function to get all keys from nested object
5
+ function getAllKeys(obj, prefix = '') {
6
+ let keys = [];
7
+
8
+ for (const key in obj) {
9
+ const fullKey = prefix ? `${prefix}.${key}` : key;
10
+
11
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
12
+ if (Array.isArray(obj[key])) {
13
+ keys.push(fullKey);
14
+ obj[key].forEach((item, index) => {
15
+ if (typeof item === 'object' && item !== null) {
16
+ keys = keys.concat(getAllKeys(item, `${fullKey}[${index}]`));
17
+ }
18
+ });
19
+ } else {
20
+ keys = keys.concat(getAllKeys(obj[key], fullKey));
21
+ }
22
+ } else {
23
+ keys.push(fullKey);
24
+ }
25
+ }
26
+
27
+ return keys;
28
+ }
29
+
30
+ // Main function to find extra keys
31
+ function findExtraKeys() {
32
+ const localesDir = './ui-locales';
33
+ const enPath = path.join(localesDir, 'en.json');
34
+
35
+ // Load English reference
36
+ const enData = JSON.parse(fs.readFileSync(enPath, 'utf8'));
37
+ const enKeys = getAllKeys(enData);
38
+
39
+ console.log(`šŸ“‹ English reference has ${enKeys.length} keys`);
40
+
41
+ // Check other files
42
+ const files = ['es.json', 'fr.json', 'ja.json', 'ru.json', 'zh.json'];
43
+
44
+ files.forEach(file => {
45
+ const filePath = path.join(localesDir, file);
46
+ try {
47
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
48
+ const keys = getAllKeys(data);
49
+
50
+ const extraKeys = keys.filter(key => !enKeys.includes(key));
51
+
52
+ console.log(`\nšŸ” ${file}:`);
53
+ console.log(` Total keys: ${keys.length}`);
54
+ console.log(` Extra keys: ${extraKeys.length}`);
55
+
56
+ if (extraKeys.length > 0) {
57
+ console.log(` Extra keys list:`);
58
+ extraKeys.forEach(key => {
59
+ console.log(` - ${key}`);
60
+ });
61
+ }
62
+ } catch (error) {
63
+ console.log(`āŒ Error reading ${file}: ${error.message}`);
64
+ }
65
+ });
66
+ }
67
+
68
+ findExtraKeys();
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * normalize-locales.js
5
+ *
6
+ * Recursively aligns the structure and nesting of all locale files to match en.json.
7
+ * - Fills missing keys with a placeholder (e.g., "[MISSING]").
8
+ * - Reports extra keys not present in en.json.
9
+ * - Optionally can fix files in-place or just report (dry run).
10
+ *
11
+ * Usage:
12
+ * node normalize-locales.js [--dry-run]
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const LOCALES_DIR = path.resolve(__dirname, '../../ui-locales');
19
+ const REFERENCE_LANG = 'en.json';
20
+ const PLACEHOLDER = '[NOT TRANSLATED]';
21
+ const DRY_RUN = process.argv.includes('--dry-run');
22
+
23
+ function loadJson(filePath) {
24
+ try {
25
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
26
+ } catch (e) {
27
+ console.error(`Failed to parse ${filePath}:`, e.message);
28
+ return null;
29
+ }
30
+ }
31
+
32
+ function hasDuplicateKeys(jsonString) {
33
+ const keyStack = [];
34
+ const keySetStack = [new Set()];
35
+ let inString = false, key = '', expectingKey = false, depth = 0;
36
+ for (let i = 0; i < jsonString.length; i++) {
37
+ const char = jsonString[i];
38
+ if (char === '"' && jsonString[i - 1] !== '\\') {
39
+ inString = !inString;
40
+ if (!inString && expectingKey) {
41
+ if (keySetStack[depth].has(key)) return true;
42
+ keySetStack[depth].add(key);
43
+ key = '';
44
+ expectingKey = false;
45
+ }
46
+ continue;
47
+ }
48
+ if (inString) {
49
+ if (expectingKey) key += char;
50
+ continue;
51
+ }
52
+ if (char === '{') {
53
+ depth++;
54
+ keySetStack[depth] = new Set();
55
+ } else if (char === '}') {
56
+ keySetStack.pop();
57
+ depth--;
58
+ } else if (char === ':' && !inString) {
59
+ expectingKey = false;
60
+ } else if (char === ',' && !inString) {
61
+ expectingKey = true;
62
+ } else if (!inString && expectingKey && /[\w\d_]/.test(char)) {
63
+ key += char;
64
+ }
65
+ }
66
+ return false;
67
+ }
68
+
69
+ function saveJson(filePath, data) {
70
+ const jsonString = JSON.stringify(data, null, 2);
71
+ if (hasDuplicateKeys(jsonString)) {
72
+ console.error(`Duplicate keys detected in ${filePath}. File not saved.`);
73
+ return;
74
+ }
75
+ fs.writeFileSync(filePath, jsonString, 'utf8');
76
+ }
77
+
78
+ function normalizeObject(ref, target, pathArr = [], report = {missing: [], extra: []}) {
79
+ // Add missing keys from ref to target
80
+ for (const key of Object.keys(ref)) {
81
+ if (!(key in target)) {
82
+ target[key] = typeof ref[key] === 'object' && ref[key] !== null ? {} : PLACEHOLDER;
83
+ report.missing.push([...pathArr, key].join('.'));
84
+ }
85
+ if (typeof ref[key] === 'object' && ref[key] !== null) {
86
+ if (typeof target[key] !== 'object' || target[key] === null) {
87
+ target[key] = {};
88
+ }
89
+ normalizeObject(ref[key], target[key], [...pathArr, key], report);
90
+ }
91
+ }
92
+ // Find extra keys in target not in ref
93
+ for (const key of Object.keys(target)) {
94
+ if (!(key in ref)) {
95
+ report.extra.push([...pathArr, key].join('.'));
96
+ }
97
+ }
98
+ return report;
99
+ }
100
+
101
+ function replaceMissingPlaceholders(obj) {
102
+ for (const key in obj) {
103
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
104
+ replaceMissingPlaceholders(obj[key]);
105
+ } else if (obj[key] === '[MISSING]') {
106
+ obj[key] = PLACEHOLDER;
107
+ }
108
+ }
109
+ }
110
+
111
+ function main() {
112
+ const files = fs.readdirSync(LOCALES_DIR).filter(f => f.endsWith('.json'));
113
+ const refPath = path.join(LOCALES_DIR, REFERENCE_LANG);
114
+ const refJson = loadJson(refPath);
115
+ if (!refJson) {
116
+ console.error('Reference language file not found or invalid:', refPath);
117
+ process.exit(1);
118
+ }
119
+
120
+ for (const file of files) {
121
+ if (file === REFERENCE_LANG) continue;
122
+ const filePath = path.join(LOCALES_DIR, file);
123
+ const targetJson = loadJson(filePath);
124
+ if (!targetJson) {
125
+ console.warn(`Skipping invalid JSON: ${file}`);
126
+ continue;
127
+ }
128
+ const report = normalizeObject(refJson, targetJson);
129
+ replaceMissingPlaceholders(targetJson);
130
+ if (report.missing.length || report.extra.length) {
131
+ console.log(`\n[${file}]`);
132
+ if (report.missing.length) {
133
+ console.log(' Missing keys:');
134
+ report.missing.forEach(k => console.log(' -', k));
135
+ }
136
+ if (report.extra.length) {
137
+ console.log(' Extra keys:');
138
+ report.extra.forEach(k => console.log(' -', k));
139
+ }
140
+ if (!DRY_RUN) {
141
+ saveJson(filePath, targetJson);
142
+ console.log(' File updated.');
143
+ } else {
144
+ console.log(' (Dry run: no changes made)');
145
+ }
146
+ } else {
147
+ console.log(`\n[${file}] No changes needed.`);
148
+ }
149
+ }
150
+ console.log('\nNormalization complete.');
151
+ }
152
+
153
+ main();
@@ -0,0 +1,240 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ // Configuration
5
+ const LOCALES_DIR = path.join(__dirname, '../../ui-locales');
6
+ const REFERENCE_FILE = 'en.json';
7
+ const PLACEHOLDER = '[NOT TRANSLATED]';
8
+
9
+ /**
10
+ * Load and parse JSON file
11
+ */
12
+ function loadJson(filePath) {
13
+ try {
14
+ const content = fs.readFileSync(filePath, 'utf8');
15
+ return JSON.parse(content);
16
+ } catch (error) {
17
+ console.error(`Error loading ${filePath}:`, error.message);
18
+ return null;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Save JSON file with proper formatting
24
+ */
25
+ function saveJson(filePath, data) {
26
+ try {
27
+ const jsonString = JSON.stringify(data, null, 2);
28
+ fs.writeFileSync(filePath, jsonString, 'utf8');
29
+ return true;
30
+ } catch (error) {
31
+ console.error(`Error saving ${filePath}:`, error.message);
32
+ return false;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Get all translation keys from an object recursively
38
+ */
39
+ function getAllKeys(obj, prefix = '') {
40
+ const keys = [];
41
+
42
+ for (const [key, value] of Object.entries(obj)) {
43
+ const fullKey = prefix ? `${prefix}.${key}` : key;
44
+
45
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
46
+ keys.push(...getAllKeys(value, fullKey));
47
+ } else {
48
+ keys.push(fullKey);
49
+ }
50
+ }
51
+
52
+ return keys;
53
+ }
54
+
55
+ /**
56
+ * Get value from nested object using dot notation
57
+ */
58
+ function getValue(obj, path) {
59
+ return path.split('.').reduce((current, key) => {
60
+ return current && current[key] !== undefined ? current[key] : undefined;
61
+ }, obj);
62
+ }
63
+
64
+ /**
65
+ * Set value in nested object using dot notation
66
+ */
67
+ function setValue(obj, path, value) {
68
+ const keys = path.split('.');
69
+ const lastKey = keys.pop();
70
+
71
+ const target = keys.reduce((current, key) => {
72
+ if (!current[key] || typeof current[key] !== 'object') {
73
+ current[key] = {};
74
+ }
75
+ return current[key];
76
+ }, obj);
77
+
78
+ target[lastKey] = value;
79
+ }
80
+
81
+ /**
82
+ * Create a properly structured object based on reference structure
83
+ */
84
+ function createStructuredObject(referenceObj, sourceObj = {}) {
85
+ const result = {};
86
+
87
+ function processObject(refObj, srcObj, targetObj) {
88
+ for (const [key, value] of Object.entries(refObj)) {
89
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
90
+ targetObj[key] = {};
91
+ processObject(value, srcObj[key] || {}, targetObj[key]);
92
+ } else {
93
+ // Try to find the value in the source object
94
+ const sourceValue = srcObj[key];
95
+ if (sourceValue !== undefined && sourceValue !== PLACEHOLDER) {
96
+ targetObj[key] = sourceValue;
97
+ } else {
98
+ // Try to find it anywhere in the source object using dot notation
99
+ const allKeys = getAllKeys(srcObj);
100
+ const matchingKey = allKeys.find(k => k.endsWith(`.${key}`) || k === key);
101
+
102
+ if (matchingKey) {
103
+ const foundValue = getValue(srcObj, matchingKey);
104
+ if (foundValue !== undefined && foundValue !== PLACEHOLDER) {
105
+ targetObj[key] = foundValue;
106
+ } else {
107
+ targetObj[key] = PLACEHOLDER;
108
+ }
109
+ } else {
110
+ targetObj[key] = PLACEHOLDER;
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ processObject(referenceObj, sourceObj, result);
118
+ return result;
119
+ }
120
+
121
+ /**
122
+ * Extract existing translations from a locale file
123
+ */
124
+ function extractTranslations(localeObj) {
125
+ const translations = {};
126
+
127
+ function extract(obj, prefix = '') {
128
+ for (const [key, value] of Object.entries(obj)) {
129
+ const fullKey = prefix ? `${prefix}.${key}` : key;
130
+
131
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
132
+ extract(value, fullKey);
133
+ } else if (typeof value === 'string' && value !== PLACEHOLDER && value.trim() !== '') {
134
+ translations[fullKey] = value;
135
+ }
136
+ }
137
+ }
138
+
139
+ extract(localeObj);
140
+ return translations;
141
+ }
142
+
143
+ /**
144
+ * Apply extracted translations to a structured object
145
+ */
146
+ function applyTranslations(structuredObj, translations) {
147
+ for (const [key, value] of Object.entries(translations)) {
148
+ setValue(structuredObj, key, value);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Main refactoring function
154
+ */
155
+ function refactorLocales() {
156
+ console.log('šŸ”„ Starting locale files refactoring...');
157
+
158
+ // Load reference file (en.json)
159
+ const referencePath = path.join(LOCALES_DIR, REFERENCE_FILE);
160
+ const referenceObj = loadJson(referencePath);
161
+
162
+ if (!referenceObj) {
163
+ console.error('āŒ Could not load reference file:', REFERENCE_FILE);
164
+ return;
165
+ }
166
+
167
+ console.log('āœ… Reference file loaded:', REFERENCE_FILE);
168
+
169
+ // Get all locale files
170
+ const localeFiles = fs.readdirSync(LOCALES_DIR)
171
+ .filter(file => file.endsWith('.json') && file !== REFERENCE_FILE)
172
+ .sort();
173
+
174
+ console.log(`šŸ“ Found ${localeFiles.length} locale files to refactor:`, localeFiles.join(', '));
175
+
176
+ let processedCount = 0;
177
+
178
+ // Process each locale file
179
+ for (const file of localeFiles) {
180
+ console.log(`\nšŸ”„ Processing ${file}...`);
181
+
182
+ const filePath = path.join(LOCALES_DIR, file);
183
+ const localeObj = loadJson(filePath);
184
+
185
+ if (!localeObj) {
186
+ console.error(`āŒ Skipping ${file} due to load error`);
187
+ continue;
188
+ }
189
+
190
+ // Extract existing translations
191
+ const existingTranslations = extractTranslations(localeObj);
192
+ console.log(` šŸ“ Extracted ${Object.keys(existingTranslations).length} existing translations`);
193
+
194
+ // Create properly structured object
195
+ const structuredObj = createStructuredObject(referenceObj);
196
+
197
+ // Apply existing translations
198
+ applyTranslations(structuredObj, existingTranslations);
199
+
200
+ // Count translations vs placeholders
201
+ const allKeys = getAllKeys(structuredObj);
202
+ const translatedKeys = allKeys.filter(key => {
203
+ const value = getValue(structuredObj, key);
204
+ return value !== PLACEHOLDER && value !== '';
205
+ });
206
+
207
+ console.log(` āœ… Structure applied: ${translatedKeys.length}/${allKeys.length} keys translated`);
208
+
209
+ // Save the refactored file
210
+ if (saveJson(filePath, structuredObj)) {
211
+ console.log(` šŸ’¾ ${file} refactored successfully`);
212
+ processedCount++;
213
+ } else {
214
+ console.error(` āŒ Failed to save ${file}`);
215
+ }
216
+ }
217
+
218
+ console.log(`\nšŸŽ‰ Refactoring completed! Processed ${processedCount}/${localeFiles.length} files`);
219
+
220
+ // Show final file sizes
221
+ console.log('\nšŸ“Š Final file sizes:');
222
+ const allFiles = [REFERENCE_FILE, ...localeFiles];
223
+ for (const file of allFiles) {
224
+ const filePath = path.join(LOCALES_DIR, file);
225
+ try {
226
+ const content = fs.readFileSync(filePath, 'utf8');
227
+ const lines = content.split('\n').length;
228
+ console.log(` ${file}: ${lines} lines`);
229
+ } catch (error) {
230
+ console.log(` ${file}: Error reading file`);
231
+ }
232
+ }
233
+ }
234
+
235
+ // Run the refactoring
236
+ if (require.main === module) {
237
+ refactorLocales();
238
+ }
239
+
240
+ module.exports = { refactorLocales };
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * reorder-locales.js
4
+ *
5
+ * Reorders and nests all locale files to match en.json, moving existing translations into the correct structure/order.
6
+ * Reports extra keys per file (does not delete them).
7
+ *
8
+ * Usage:
9
+ * node reorder-locales.js [--dry-run]
10
+ */
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const LOCALES_DIR = path.resolve(__dirname, '../../ui-locales');
15
+ const REFERENCE_LANG = 'en.json';
16
+ const DRY_RUN = process.argv.includes('--dry-run');
17
+
18
+ function loadJson(filePath) {
19
+ try {
20
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
21
+ } catch (e) {
22
+ console.error(`Failed to parse ${filePath}:`, e.message);
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function saveJson(filePath, data) {
28
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
29
+ }
30
+
31
+ function reorderAndMerge(ref, target, pathArr = [], extraKeys = []) {
32
+ let result = Array.isArray(ref) ? [] : {};
33
+ // Add keys in reference order
34
+ for (const key of Object.keys(ref)) {
35
+ if (typeof ref[key] === 'object' && ref[key] !== null && !Array.isArray(ref[key])) {
36
+ result[key] = reorderAndMerge(ref[key], (target && target[key]) || {}, [...pathArr, key], extraKeys);
37
+ } else {
38
+ result[key] = (target && key in target) ? target[key] : '';
39
+ }
40
+ }
41
+ // Find extra keys in target not in ref
42
+ if (target && typeof target === 'object') {
43
+ for (const key of Object.keys(target)) {
44
+ if (!(key in ref)) {
45
+ extraKeys.push([...pathArr, key].join('.'));
46
+ result[key] = target[key]; // Keep extra keys at the end
47
+ }
48
+ }
49
+ }
50
+ return result;
51
+ }
52
+
53
+ function main() {
54
+ const files = fs.readdirSync(LOCALES_DIR).filter(f => f.endsWith('.json'));
55
+ const refPath = path.join(LOCALES_DIR, REFERENCE_LANG);
56
+ const refJson = loadJson(refPath);
57
+ if (!refJson) {
58
+ console.error('Reference language file not found or invalid:', refPath);
59
+ process.exit(1);
60
+ }
61
+ for (const file of files) {
62
+ if (file === REFERENCE_LANG) continue;
63
+ const filePath = path.join(LOCALES_DIR, file);
64
+ const targetJson = loadJson(filePath);
65
+ if (!targetJson) {
66
+ console.warn(`Skipping invalid JSON: ${file}`);
67
+ continue;
68
+ }
69
+ let extraKeys = [];
70
+ const reordered = reorderAndMerge(refJson, targetJson, [], extraKeys);
71
+ if (extraKeys.length) {
72
+ console.log(`\n[${file}] Extra keys not in en.json:`);
73
+ extraKeys.forEach(k => console.log(' -', k));
74
+ }
75
+ if (!DRY_RUN) {
76
+ saveJson(filePath, reordered);
77
+ console.log(`[${file}] File reordered and updated.`);
78
+ } else {
79
+ console.log(`[${file}] (Dry run: no changes made)`);
80
+ }
81
+ }
82
+ console.log('\nReordering complete.');
83
+ }
84
+
85
+ main();