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,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();
|