@umituz/react-native-localization 3.7.7 → 3.7.9
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/package.json +2 -1
- package/src/scripts/prepublish.js +10 -8
- package/src/scripts/setup-languages.js +5 -14
- package/src/scripts/sync-translations.js +40 -90
- package/src/scripts/translate-missing.js +15 -49
- package/src/scripts/utils/file-parser.js +8 -13
- package/src/scripts/utils/key-detector.js +33 -40
- package/src/scripts/utils/key-extractor.js +43 -0
- package/src/scripts/utils/object-helper.js +29 -0
- package/src/scripts/utils/sync-helper.js +2 -9
- package/src/scripts/utils/translation-config.js +7 -19
- package/src/scripts/utils/translator.js +26 -59
package/package.json
CHANGED
|
@@ -5,30 +5,32 @@
|
|
|
5
5
|
* Basic checks before publishing
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
10
14
|
|
|
11
15
|
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
12
16
|
const SRC_DIR = path.join(PACKAGE_ROOT, 'src');
|
|
13
17
|
|
|
14
18
|
if (!fs.existsSync(SRC_DIR)) {
|
|
19
|
+
console.error('❌ src directory not found');
|
|
15
20
|
process.exit(1);
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
const mainFiles = [
|
|
19
24
|
'src/index.ts',
|
|
20
25
|
'src/infrastructure/config/i18n.ts',
|
|
21
|
-
'src/infrastructure/storage/LocalizationStore.ts',
|
|
22
26
|
];
|
|
23
27
|
|
|
24
|
-
let allFilesExist = true;
|
|
25
28
|
for (const file of mainFiles) {
|
|
26
29
|
const filePath = path.join(PACKAGE_ROOT, file);
|
|
27
30
|
if (!fs.existsSync(filePath)) {
|
|
28
|
-
|
|
31
|
+
console.error(`❌ Missing mandatory file: ${file}`);
|
|
32
|
+
process.exit(1);
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
36
|
+
console.log('✅ Pre-publish checks passed!');
|
|
@@ -6,16 +6,14 @@
|
|
|
6
6
|
* Usage: node setup-languages.js [locales-dir]
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
11
|
|
|
12
12
|
function main() {
|
|
13
|
-
const targetDir = process.argv[2] || 'src/domains/localization/
|
|
13
|
+
const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
|
|
14
14
|
const localesDir = path.resolve(process.cwd(), targetDir);
|
|
15
15
|
|
|
16
16
|
console.log('🚀 Setting up language files...\n');
|
|
17
|
-
console.log(`📂 Locales directory: ${localesDir}\n`);
|
|
18
|
-
|
|
19
17
|
if (!fs.existsSync(localesDir)) {
|
|
20
18
|
console.error(`❌ Locales directory not found: ${localesDir}`);
|
|
21
19
|
process.exit(1);
|
|
@@ -25,16 +23,12 @@ function main() {
|
|
|
25
23
|
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/))
|
|
26
24
|
.sort();
|
|
27
25
|
|
|
28
|
-
console.log(`📊 Found ${files.length} language files:\n`);
|
|
29
|
-
|
|
30
26
|
const imports = [];
|
|
31
27
|
const exports = [];
|
|
32
28
|
|
|
33
29
|
files.forEach(file => {
|
|
34
30
|
const code = file.replace('.ts', '');
|
|
35
31
|
const varName = code.replace(/-([a-z0-9])/g, (g) => g[1].toUpperCase()).replace('-', '');
|
|
36
|
-
|
|
37
|
-
console.log(` 📄 ${code}`);
|
|
38
32
|
imports.push(`import ${varName} from "./${code}";`);
|
|
39
33
|
exports.push(` "${code}": ${varName},`);
|
|
40
34
|
});
|
|
@@ -56,11 +50,8 @@ export type TranslationKey = keyof typeof translations;
|
|
|
56
50
|
export default translations;
|
|
57
51
|
`;
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
console.log(`\n✅ Generated index.ts with ${files.length} languages`);
|
|
63
|
-
console.log(` Output: ${indexPath}`);
|
|
53
|
+
fs.writeFileSync(path.join(localesDir, 'index.ts'), content);
|
|
54
|
+
console.log(`✅ Generated index.ts with ${files.length} languages`);
|
|
64
55
|
}
|
|
65
56
|
|
|
66
57
|
main();
|
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Sync Translations Script
|
|
5
5
|
* Synchronizes translation keys from en-US.ts to all other language files
|
|
6
|
-
* Usage: node sync-translations.js [locales-dir]
|
|
6
|
+
* Usage: node sync-translations.js [locales-dir] [src-dir-optional]
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { parseTypeScriptFile, generateTypeScriptContent } from './utils/file-parser.js';
|
|
12
|
+
import { addMissingKeys, removeExtraKeys } from './utils/sync-helper.js';
|
|
13
|
+
import { detectNewKeys } from './utils/key-detector.js';
|
|
14
|
+
import { getLangDisplayName } from './utils/translation-config.js';
|
|
15
|
+
import { extractUsedKeys } from './utils/key-extractor.js';
|
|
16
|
+
import { setDeep } from './utils/object-helper.js';
|
|
15
17
|
|
|
16
18
|
function syncLanguageFile(enUSPath, targetPath, langCode) {
|
|
17
19
|
const enUS = parseTypeScriptFile(enUSPath);
|
|
@@ -37,113 +39,61 @@ function syncLanguageFile(enUSPath, targetPath, langCode) {
|
|
|
37
39
|
fs.writeFileSync(targetPath, content);
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
return {
|
|
41
|
-
added: addStats.added,
|
|
42
|
-
removed: removeStats.removed,
|
|
43
|
-
newKeys,
|
|
44
|
-
removedKeys: removeStats.removedKeys,
|
|
45
|
-
changed,
|
|
46
|
-
};
|
|
42
|
+
return { ...addStats, ...removeStats, newKeys, changed };
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
const targetLangCode = process.argv[3];
|
|
52
|
-
const localesDir = path.resolve(process.cwd(), targetDir);
|
|
45
|
+
function processExtraction(srcDir, enUSPath) {
|
|
46
|
+
if (!srcDir) return;
|
|
53
47
|
|
|
54
|
-
console.log(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
console.log(`🔍 Scanning source code: ${srcDir}...`);
|
|
49
|
+
const usedKeys = extractUsedKeys(srcDir);
|
|
50
|
+
console.log(` Found ${usedKeys.size} unique keys in code.`);
|
|
51
|
+
|
|
52
|
+
const enUS = parseTypeScriptFile(enUSPath);
|
|
53
|
+
let addedCount = 0;
|
|
54
|
+
for (const key of usedKeys) {
|
|
55
|
+
if (setDeep(enUS, key, key)) addedCount++;
|
|
58
56
|
}
|
|
59
|
-
console.log('');
|
|
60
57
|
|
|
61
|
-
if (
|
|
62
|
-
console.
|
|
63
|
-
|
|
58
|
+
if (addedCount > 0) {
|
|
59
|
+
console.log(` ✨ Added ${addedCount} new keys to en-US.ts`);
|
|
60
|
+
const content = generateTypeScriptContent(enUS, 'en-US');
|
|
61
|
+
fs.writeFileSync(enUSPath, content);
|
|
64
62
|
}
|
|
63
|
+
}
|
|
65
64
|
|
|
65
|
+
function main() {
|
|
66
|
+
const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
|
|
67
|
+
const srcDir = process.argv[3];
|
|
68
|
+
const localesDir = path.resolve(process.cwd(), targetDir);
|
|
66
69
|
const enUSPath = path.join(localesDir, 'en-US.ts');
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
|
|
71
|
+
console.log('🚀 Starting translation synchronization...\n');
|
|
72
|
+
if (!fs.existsSync(localesDir) || !fs.existsSync(enUSPath)) {
|
|
73
|
+
console.error(`❌ Localization files not found in: ${localesDir}`);
|
|
69
74
|
process.exit(1);
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
processExtraction(srcDir, enUSPath);
|
|
78
|
+
|
|
72
79
|
const files = fs.readdirSync(localesDir)
|
|
73
|
-
.filter(f => {
|
|
74
|
-
const isLangFile = f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts';
|
|
75
|
-
if (!isLangFile) return false;
|
|
76
|
-
if (targetLangCode) {
|
|
77
|
-
return f === `${targetLangCode}.ts`;
|
|
78
|
-
}
|
|
79
|
-
return true;
|
|
80
|
-
})
|
|
80
|
+
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
|
|
81
81
|
.sort();
|
|
82
82
|
|
|
83
|
-
if (targetLangCode && files.length === 0) {
|
|
84
|
-
console.warn(`⚠️ Target language file ${targetLangCode}.ts not found in ${targetDir}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
83
|
console.log(`📊 Languages to sync: ${files.length}\n`);
|
|
88
|
-
|
|
89
|
-
let totalAdded = 0;
|
|
90
|
-
let totalRemoved = 0;
|
|
91
|
-
let totalChanged = 0;
|
|
92
|
-
const allNewKeys = [];
|
|
93
|
-
|
|
94
|
-
for (const file of files) {
|
|
84
|
+
files.forEach(file => {
|
|
95
85
|
const langCode = file.replace('.ts', '');
|
|
96
86
|
const targetPath = path.join(localesDir, file);
|
|
97
|
-
|
|
98
87
|
console.log(`🌍 Syncing ${langCode} (${getLangDisplayName(langCode)})...`);
|
|
99
|
-
|
|
100
88
|
const result = syncLanguageFile(enUSPath, targetPath, langCode);
|
|
101
|
-
|
|
102
89
|
if (result.changed) {
|
|
103
|
-
|
|
104
|
-
console.log(` 🆕 ${result.newKeys.length} new keys added:`);
|
|
105
|
-
result.newKeys.slice(0, 5).forEach(({ path }) => {
|
|
106
|
-
console.log(` • ${path}`);
|
|
107
|
-
});
|
|
108
|
-
if (result.newKeys.length > 5) {
|
|
109
|
-
console.log(` ... and ${result.newKeys.length - 5} more`);
|
|
110
|
-
}
|
|
111
|
-
allNewKeys.push(...result.newKeys.map(k => k.path));
|
|
112
|
-
}
|
|
113
|
-
if (result.removedKeys.length > 0) {
|
|
114
|
-
console.log(` 🗑️ ${result.removedKeys.length} obsolete keys removed`);
|
|
115
|
-
}
|
|
116
|
-
totalAdded += result.added;
|
|
117
|
-
totalRemoved += result.removed;
|
|
118
|
-
totalChanged++;
|
|
90
|
+
console.log(` ✏️ +${result.added} keys, -${result.removed} keys`);
|
|
119
91
|
} else {
|
|
120
92
|
console.log(` ✅ Already synchronized`);
|
|
121
93
|
}
|
|
122
|
-
}
|
|
94
|
+
});
|
|
123
95
|
|
|
124
|
-
console.log(`\n
|
|
125
|
-
console.log(` Languages processed: ${files.length}`);
|
|
126
|
-
console.log(` Files changed: ${totalChanged}`);
|
|
127
|
-
console.log(` Keys added: ${totalAdded}`);
|
|
128
|
-
console.log(` Keys removed: ${totalRemoved}`);
|
|
129
|
-
|
|
130
|
-
if (totalChanged > 0) {
|
|
131
|
-
console.log(`\n✅ Synchronization completed!`);
|
|
132
|
-
console.log(` Next: Run 'npm run i18n:translate' to translate new keys`);
|
|
133
|
-
|
|
134
|
-
if (allNewKeys.length > 0) {
|
|
135
|
-
const uniqueKeys = [...new Set(allNewKeys)];
|
|
136
|
-
console.log(`\n📝 New keys that need translation (${uniqueKeys.length}):`);
|
|
137
|
-
uniqueKeys.slice(0, 10).forEach(key => {
|
|
138
|
-
console.log(` • ${key}`);
|
|
139
|
-
});
|
|
140
|
-
if (uniqueKeys.length > 10) {
|
|
141
|
-
console.log(` ... and ${uniqueKeys.length - 10} more`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
} else {
|
|
145
|
-
console.log(`\n✅ All languages were already synchronized!`);
|
|
146
|
-
}
|
|
96
|
+
console.log(`\n✅ Synchronization completed!`);
|
|
147
97
|
}
|
|
148
98
|
|
|
149
99
|
main();
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Translate Missing Script
|
|
5
5
|
* Translates missing strings from en-US.ts to all other language files
|
|
6
|
-
* Usage: node translate-missing.js [locales-dir]
|
|
6
|
+
* Usage: node translate-missing.js [locales-dir] [lang-code-optional]
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { getTargetLanguage, isEnglishVariant, getLangDisplayName } from './utils/translation-config.js';
|
|
12
|
+
import { parseTypeScriptFile, generateTypeScriptContent } from './utils/file-parser.js';
|
|
13
|
+
import { translateObject } from './utils/translator.js';
|
|
14
14
|
|
|
15
15
|
async function translateLanguageFile(enUSPath, targetPath, langCode) {
|
|
16
16
|
const targetLang = getTargetLanguage(langCode);
|
|
@@ -46,78 +46,44 @@ async function translateLanguageFile(enUSPath, targetPath, langCode) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async function main() {
|
|
49
|
-
const targetDir = process.argv[2] || 'src/domains/localization/
|
|
49
|
+
const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
|
|
50
50
|
const targetLangCode = process.argv[3];
|
|
51
51
|
const localesDir = path.resolve(process.cwd(), targetDir);
|
|
52
|
+
const enUSPath = path.join(localesDir, 'en-US.ts');
|
|
52
53
|
|
|
53
54
|
console.log('🚀 Starting automatic translation...\n');
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log(`🎯 Target language: ${targetLangCode}`);
|
|
57
|
-
}
|
|
58
|
-
console.log('');
|
|
59
|
-
|
|
60
|
-
if (!fs.existsSync(localesDir)) {
|
|
61
|
-
console.error(`❌ Locales directory not found: ${localesDir}`);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const enUSPath = path.join(localesDir, 'en-US.ts');
|
|
66
|
-
if (!fs.existsSync(enUSPath)) {
|
|
67
|
-
console.error(`❌ Base file not found: ${enUSPath}`);
|
|
55
|
+
if (!fs.existsSync(localesDir) || !fs.existsSync(enUSPath)) {
|
|
56
|
+
console.error(`❌ Localization files not found in: ${localesDir}`);
|
|
68
57
|
process.exit(1);
|
|
69
58
|
}
|
|
70
59
|
|
|
71
60
|
const files = fs.readdirSync(localesDir)
|
|
72
61
|
.filter(f => {
|
|
73
62
|
const isLangFile = f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts';
|
|
74
|
-
|
|
75
|
-
if (targetLangCode) {
|
|
76
|
-
return f === `${targetLangCode}.ts`;
|
|
77
|
-
}
|
|
78
|
-
return true;
|
|
63
|
+
return isLangFile && (!targetLangCode || f === `${targetLangCode}.ts`);
|
|
79
64
|
})
|
|
80
65
|
.sort();
|
|
81
66
|
|
|
82
|
-
|
|
83
|
-
console.warn(`⚠️ Target language file ${targetLangCode}.ts not found in ${targetDir}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
console.log(`📊 Languages to translate: ${files.length}`);
|
|
87
|
-
console.log('⚡ Running with 200ms delay between API calls\n');
|
|
67
|
+
console.log(`📊 Languages to translate: ${files.length}\n`);
|
|
88
68
|
|
|
89
69
|
let totalTranslated = 0;
|
|
90
|
-
let totalNewKeys = 0;
|
|
91
|
-
|
|
92
70
|
for (const file of files) {
|
|
93
71
|
const langCode = file.replace('.ts', '');
|
|
94
72
|
const targetPath = path.join(localesDir, file);
|
|
95
|
-
|
|
96
|
-
console.log(`\n🌍 Translating ${langCode} (${getLangDisplayName(langCode)})...`);
|
|
97
|
-
|
|
73
|
+
console.log(`🌍 Translating ${langCode} (${getLangDisplayName(langCode)})...`);
|
|
98
74
|
const stats = await translateLanguageFile(enUSPath, targetPath, langCode);
|
|
99
75
|
totalTranslated += stats.count;
|
|
100
|
-
totalNewKeys += stats.newKeys.length;
|
|
101
|
-
|
|
102
76
|
if (stats.count > 0) {
|
|
103
77
|
console.log(` ✅ Translated ${stats.count} strings`);
|
|
104
|
-
if (stats.newKeys.length > 0) {
|
|
105
|
-
console.log(` 🆕 ${stats.newKeys.length} new keys translated`);
|
|
106
|
-
}
|
|
107
78
|
} else {
|
|
108
79
|
console.log(` ✓ Already complete`);
|
|
109
80
|
}
|
|
110
81
|
}
|
|
111
82
|
|
|
112
|
-
console.log(`\n✅ Translation completed
|
|
113
|
-
console.log(` Total strings translated: ${totalTranslated}`);
|
|
114
|
-
if (totalNewKeys > 0) {
|
|
115
|
-
console.log(` New keys translated: ${totalNewKeys}`);
|
|
116
|
-
}
|
|
117
|
-
console.log(`\n📝 Next: Run 'npm run i18n:setup' to update index.ts`);
|
|
83
|
+
console.log(`\n✅ Translation completed! (Total: ${totalTranslated})`);
|
|
118
84
|
}
|
|
119
85
|
|
|
120
|
-
main().catch(
|
|
86
|
+
main().catch(error => {
|
|
121
87
|
console.error('❌ Translation failed:', error.message);
|
|
122
88
|
process.exit(1);
|
|
123
89
|
});
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { getLangDisplayName } from './translation-config.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* File Parser
|
|
5
6
|
* Parse and generate TypeScript translation files
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
const { getLangDisplayName } = require('./translation-config');
|
|
10
|
-
|
|
11
|
-
function parseTypeScriptFile(filePath) {
|
|
9
|
+
export function parseTypeScriptFile(filePath) {
|
|
12
10
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
13
11
|
const match = content.match(/export\s+default\s+(\{[\s\S]*\});?\s*$/);
|
|
14
12
|
|
|
@@ -25,7 +23,7 @@ function parseTypeScriptFile(filePath) {
|
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
function stringifyValue(value, indent = 2) {
|
|
26
|
+
export function stringifyValue(value, indent = 2) {
|
|
29
27
|
if (typeof value === 'string') {
|
|
30
28
|
const escaped = value
|
|
31
29
|
.replace(/\\/g, '\\\\')
|
|
@@ -50,6 +48,7 @@ function stringifyValue(value, indent = 2) {
|
|
|
50
48
|
const spaces = ' '.repeat(indent);
|
|
51
49
|
const innerSpaces = ' '.repeat(indent + 2);
|
|
52
50
|
const entriesStr = entries
|
|
51
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
53
52
|
.map(([k, v]) => {
|
|
54
53
|
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : `"${k}"`;
|
|
55
54
|
return `${innerSpaces}${key}: ${stringifyValue(v, indent + 2)}`;
|
|
@@ -61,20 +60,16 @@ function stringifyValue(value, indent = 2) {
|
|
|
61
60
|
return String(value);
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
function generateTypeScriptContent(obj, langCode) {
|
|
63
|
+
export function generateTypeScriptContent(obj, langCode) {
|
|
65
64
|
const langName = getLangDisplayName(langCode);
|
|
65
|
+
const isBase = langCode === 'en-US';
|
|
66
66
|
const objString = stringifyValue(obj, 0);
|
|
67
67
|
|
|
68
68
|
return `/**
|
|
69
69
|
* ${langName} Translations
|
|
70
|
-
* Auto-
|
|
70
|
+
* ${isBase ? 'Base translations file' : 'Auto-synced from en-US.ts'}
|
|
71
71
|
*/
|
|
72
72
|
|
|
73
73
|
export default ${objString};
|
|
74
74
|
`;
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
module.exports = {
|
|
78
|
-
parseTypeScriptFile,
|
|
79
|
-
generateTypeScriptContent,
|
|
80
|
-
};
|
|
@@ -1,52 +1,45 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Key Detector
|
|
5
3
|
* Detects new, missing, and removed keys between source and target objects
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
|
-
function detectNewKeys(sourceObj, targetObj, path = '', newKeys = []) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
export function detectNewKeys(sourceObj, targetObj, path = '', newKeys = []) {
|
|
7
|
+
for (const key in sourceObj) {
|
|
8
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
9
|
+
const sourceValue = sourceObj[key];
|
|
10
|
+
const targetValue = targetObj[key];
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
12
|
+
if (!Object.prototype.hasOwnProperty.call(targetObj, key)) {
|
|
13
|
+
newKeys.push({ path: currentPath, value: sourceValue });
|
|
14
|
+
} else if (
|
|
15
|
+
typeof sourceValue === 'object' &&
|
|
16
|
+
sourceValue !== null &&
|
|
17
|
+
!Array.isArray(sourceValue)
|
|
18
|
+
) {
|
|
19
|
+
if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue)) {
|
|
20
|
+
detectNewKeys(sourceValue, targetValue, currentPath, newKeys);
|
|
21
|
+
}
|
|
25
22
|
}
|
|
26
|
-
|
|
23
|
+
}
|
|
24
|
+
return newKeys;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
function detectMissingKeys(sourceObj, targetObj, path = '', missingKeys = []) {
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
export function detectMissingKeys(sourceObj, targetObj, path = '', missingKeys = []) {
|
|
28
|
+
for (const key in targetObj) {
|
|
29
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
31
|
+
if (!Object.prototype.hasOwnProperty.call(sourceObj, key)) {
|
|
32
|
+
missingKeys.push(currentPath);
|
|
33
|
+
} else if (
|
|
34
|
+
typeof sourceObj[key] === 'object' &&
|
|
35
|
+
sourceObj[key] !== null &&
|
|
36
|
+
!Array.isArray(sourceObj[key]) &&
|
|
37
|
+
typeof targetObj[key] === 'object' &&
|
|
38
|
+
targetObj[key] !== null &&
|
|
39
|
+
!Array.isArray(targetObj[key])
|
|
40
|
+
) {
|
|
41
|
+
detectMissingKeys(sourceObj[key], targetObj[key], currentPath, missingKeys);
|
|
45
42
|
}
|
|
46
|
-
|
|
43
|
+
}
|
|
44
|
+
return missingKeys;
|
|
47
45
|
}
|
|
48
|
-
|
|
49
|
-
module.exports = {
|
|
50
|
-
detectNewKeys,
|
|
51
|
-
detectMissingKeys,
|
|
52
|
-
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Key Extractor
|
|
6
|
+
* Scans source code for i18n keys
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function extractUsedKeys(srcDir) {
|
|
10
|
+
const keys = new Set();
|
|
11
|
+
if (!srcDir) return keys;
|
|
12
|
+
|
|
13
|
+
const absoluteSrcDir = path.resolve(process.cwd(), srcDir);
|
|
14
|
+
if (!fs.existsSync(absoluteSrcDir)) {
|
|
15
|
+
return keys;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function walk(dir) {
|
|
19
|
+
const files = fs.readdirSync(dir);
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
const fullPath = path.join(dir, file);
|
|
22
|
+
const stat = fs.statSync(fullPath);
|
|
23
|
+
|
|
24
|
+
if (stat.isDirectory()) {
|
|
25
|
+
const skipDirs = ['node_modules', '.expo', '.git', 'build', 'ios', 'android', 'assets'];
|
|
26
|
+
if (!skipDirs.includes(file)) {
|
|
27
|
+
walk(fullPath);
|
|
28
|
+
}
|
|
29
|
+
} else if (/\.(ts|tsx|js|jsx)$/.test(file)) {
|
|
30
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
31
|
+
// Regex for t('key') or t("key") or i18n.t('key')
|
|
32
|
+
const regex = /(?:^|\W)t\(['"]([^'"]+)['"]\)/g;
|
|
33
|
+
let match;
|
|
34
|
+
while ((match = regex.exec(content)) !== null) {
|
|
35
|
+
keys.add(match[1]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
walk(absoluteSrcDir);
|
|
42
|
+
return keys;
|
|
43
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object Helper
|
|
3
|
+
* Utilities for deep object manipulation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Set a value in a nested object, creating intermediate objects if necessary
|
|
8
|
+
* Returns true if the key was newly added, false if it already existed
|
|
9
|
+
*/
|
|
10
|
+
export function setDeep(obj, path, value) {
|
|
11
|
+
const keys = path.split('.');
|
|
12
|
+
let current = obj;
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
15
|
+
const key = keys[i];
|
|
16
|
+
if (!current[key] || typeof current[key] !== 'object' || Array.isArray(current[key])) {
|
|
17
|
+
current[key] = {};
|
|
18
|
+
}
|
|
19
|
+
current = current[key];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const lastKey = keys[keys.length - 1];
|
|
23
|
+
if (current[lastKey] === undefined) {
|
|
24
|
+
current[lastKey] = value;
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Sync Helper
|
|
5
3
|
* Helper functions for synchronizing translation keys
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
|
-
function addMissingKeys(sourceObj, targetObj, stats = { added: 0, newKeys: [] }) {
|
|
6
|
+
export function addMissingKeys(sourceObj, targetObj, stats = { added: 0, newKeys: [] }) {
|
|
9
7
|
for (const key in sourceObj) {
|
|
10
8
|
const sourceValue = sourceObj[key];
|
|
11
9
|
const isNewKey = !Object.prototype.hasOwnProperty.call(targetObj, key);
|
|
@@ -28,7 +26,7 @@ function addMissingKeys(sourceObj, targetObj, stats = { added: 0, newKeys: [] })
|
|
|
28
26
|
return stats;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
function removeExtraKeys(sourceObj, targetObj, stats = { removed: 0, removedKeys: [] }) {
|
|
29
|
+
export function removeExtraKeys(sourceObj, targetObj, stats = { removed: 0, removedKeys: [] }) {
|
|
32
30
|
for (const key in targetObj) {
|
|
33
31
|
const isExtraKey = !Object.prototype.hasOwnProperty.call(sourceObj, key);
|
|
34
32
|
|
|
@@ -49,8 +47,3 @@ function removeExtraKeys(sourceObj, targetObj, stats = { removed: 0, removedKeys
|
|
|
49
47
|
}
|
|
50
48
|
return stats;
|
|
51
49
|
}
|
|
52
|
-
|
|
53
|
-
module.exports = {
|
|
54
|
-
addMissingKeys,
|
|
55
|
-
removeExtraKeys,
|
|
56
|
-
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Translation Configuration
|
|
5
3
|
* Language mappings and constants for translation system
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
|
-
const LANGUAGE_MAP = {
|
|
6
|
+
export const LANGUAGE_MAP = {
|
|
9
7
|
'ar-SA': 'ar',
|
|
10
8
|
'bg-BG': 'bg',
|
|
11
9
|
'cs-CZ': 'cs',
|
|
@@ -47,7 +45,7 @@ const LANGUAGE_MAP = {
|
|
|
47
45
|
'zh-TW': 'zh-TW',
|
|
48
46
|
};
|
|
49
47
|
|
|
50
|
-
const SKIP_WORDS = new Set([
|
|
48
|
+
export const SKIP_WORDS = new Set([
|
|
51
49
|
'Google',
|
|
52
50
|
'Apple',
|
|
53
51
|
'Facebook',
|
|
@@ -57,7 +55,7 @@ const SKIP_WORDS = new Set([
|
|
|
57
55
|
'WhatsApp',
|
|
58
56
|
]);
|
|
59
57
|
|
|
60
|
-
const LANGUAGE_NAMES = {
|
|
58
|
+
export const LANGUAGE_NAMES = {
|
|
61
59
|
'ar-SA': 'Arabic (Saudi Arabia)',
|
|
62
60
|
'bg-BG': 'Bulgarian',
|
|
63
61
|
'cs-CZ': 'Czech',
|
|
@@ -100,29 +98,19 @@ const LANGUAGE_NAMES = {
|
|
|
100
98
|
'zh-TW': 'Chinese (Traditional)',
|
|
101
99
|
};
|
|
102
100
|
|
|
103
|
-
function getLangDisplayName(code) {
|
|
101
|
+
export function getLangDisplayName(code) {
|
|
104
102
|
return LANGUAGE_NAMES[code] || code;
|
|
105
103
|
}
|
|
106
104
|
|
|
107
|
-
function getTargetLanguage(langCode) {
|
|
105
|
+
export function getTargetLanguage(langCode) {
|
|
108
106
|
return LANGUAGE_MAP[langCode];
|
|
109
107
|
}
|
|
110
108
|
|
|
111
|
-
function shouldSkipWord(word) {
|
|
109
|
+
export function shouldSkipWord(word) {
|
|
112
110
|
return SKIP_WORDS.has(word);
|
|
113
111
|
}
|
|
114
112
|
|
|
115
|
-
function isEnglishVariant(langCode) {
|
|
113
|
+
export function isEnglishVariant(langCode) {
|
|
116
114
|
const targetLang = LANGUAGE_MAP[langCode];
|
|
117
115
|
return targetLang === 'en';
|
|
118
116
|
}
|
|
119
|
-
|
|
120
|
-
module.exports = {
|
|
121
|
-
LANGUAGE_MAP,
|
|
122
|
-
SKIP_WORDS,
|
|
123
|
-
LANGUAGE_NAMES,
|
|
124
|
-
getLangDisplayName,
|
|
125
|
-
getTargetLanguage,
|
|
126
|
-
shouldSkipWord,
|
|
127
|
-
isEnglishVariant,
|
|
128
|
-
};
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import https from 'https';
|
|
2
|
+
import { shouldSkipWord } from './translation-config.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Translator
|
|
5
6
|
* Google Translate API integration and translation logic
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
const { shouldSkipWord } = require('./translation-config');
|
|
10
|
-
|
|
11
|
-
function delay(ms) {
|
|
9
|
+
export function delay(ms) {
|
|
12
10
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
async function translateText(text, targetLang) {
|
|
13
|
+
export async function translateText(text, targetLang) {
|
|
16
14
|
return new Promise((resolve) => {
|
|
17
15
|
if (shouldSkipWord(text)) {
|
|
18
16
|
resolve(text);
|
|
@@ -49,82 +47,51 @@ async function translateText(text, targetLang) {
|
|
|
49
47
|
|
|
50
48
|
function needsTranslation(value, enValue) {
|
|
51
49
|
if (typeof enValue !== 'string') return false;
|
|
52
|
-
|
|
53
50
|
if (shouldSkipWord(enValue)) return false;
|
|
54
|
-
|
|
55
51
|
if (!value || typeof value !== 'string') return true;
|
|
56
52
|
|
|
57
53
|
if (value === enValue) {
|
|
58
54
|
const isSingleWord = !enValue.includes(' ') && enValue.length < 20;
|
|
59
|
-
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
return true;
|
|
55
|
+
return !isSingleWord;
|
|
63
56
|
}
|
|
64
57
|
|
|
65
58
|
return false;
|
|
66
59
|
}
|
|
67
60
|
|
|
68
|
-
async function translateObject(enObj, targetObj, targetLang, path = '', stats = { count: 0, newKeys: [] }) {
|
|
61
|
+
export async function translateObject(enObj, targetObj, targetLang, path = '', stats = { count: 0, newKeys: [] }) {
|
|
69
62
|
for (const key in enObj) {
|
|
70
63
|
const currentPath = path ? `${path}.${key}` : key;
|
|
71
64
|
const enValue = enObj[key];
|
|
72
65
|
const targetValue = targetObj[key];
|
|
73
66
|
|
|
74
67
|
if (Array.isArray(enValue)) {
|
|
75
|
-
if (!Array.isArray(targetValue))
|
|
76
|
-
targetObj[key] = [];
|
|
77
|
-
}
|
|
68
|
+
if (!Array.isArray(targetValue)) targetObj[key] = [];
|
|
78
69
|
for (let i = 0; i < enValue.length; i++) {
|
|
79
|
-
if (typeof enValue[i] === 'string') {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
console.log(` ${prefix} ${currentPath}[${i}]: "${preview}"`);
|
|
88
|
-
|
|
89
|
-
targetObj[key][i] = translated;
|
|
90
|
-
stats.count++;
|
|
91
|
-
if (isNewKey) stats.newKeys.push(`${currentPath}[${i}]`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
await delay(200);
|
|
70
|
+
if (typeof enValue[i] === 'string' && needsTranslation(targetObj[key][i], enValue[i])) {
|
|
71
|
+
const translated = await translateText(enValue[i], targetLang);
|
|
72
|
+
if (translated !== enValue[i]) {
|
|
73
|
+
const isNewKey = targetObj[key][i] === enValue[i];
|
|
74
|
+
console.log(` ${isNewKey ? '🆕 NEW' : '🔄'} ${currentPath}[${i}]: "${enValue[i].substring(0, 40)}"`);
|
|
75
|
+
targetObj[key][i] = translated;
|
|
76
|
+
stats.count++;
|
|
77
|
+
if (isNewKey) stats.newKeys.push(`${currentPath}[${i}]`);
|
|
95
78
|
}
|
|
79
|
+
await delay(200);
|
|
96
80
|
}
|
|
97
81
|
}
|
|
98
82
|
} else if (typeof enValue === 'object' && enValue !== null) {
|
|
99
|
-
if (!targetObj[key] || typeof targetObj[key] !== 'object') {
|
|
100
|
-
targetObj[key] = {};
|
|
101
|
-
}
|
|
83
|
+
if (!targetObj[key] || typeof targetObj[key] !== 'object') targetObj[key] = {};
|
|
102
84
|
await translateObject(enValue, targetObj[key], targetLang, currentPath, stats);
|
|
103
|
-
} else if (typeof enValue === 'string') {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
console.log(` ${prefix} ${currentPath}: "${preview}"`);
|
|
112
|
-
|
|
113
|
-
targetObj[key] = translated;
|
|
114
|
-
stats.count++;
|
|
115
|
-
if (isNewKey) stats.newKeys.push(currentPath);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
await delay(200);
|
|
85
|
+
} else if (typeof enValue === 'string' && needsTranslation(targetValue, enValue)) {
|
|
86
|
+
const translated = await translateText(enValue, targetLang);
|
|
87
|
+
const isNewKey = targetValue === undefined;
|
|
88
|
+
if (translated !== enValue || isNewKey) {
|
|
89
|
+
console.log(` ${isNewKey ? '🆕 NEW' : '🔄'} ${currentPath}: "${enValue.substring(0, 40)}"`);
|
|
90
|
+
targetObj[key] = translated;
|
|
91
|
+
stats.count++;
|
|
92
|
+
if (isNewKey) stats.newKeys.push(currentPath);
|
|
119
93
|
}
|
|
94
|
+
await delay(200);
|
|
120
95
|
}
|
|
121
96
|
}
|
|
122
|
-
|
|
123
|
-
return stats.count;
|
|
124
97
|
}
|
|
125
|
-
|
|
126
|
-
module.exports = {
|
|
127
|
-
translateText,
|
|
128
|
-
translateObject,
|
|
129
|
-
delay,
|
|
130
|
-
};
|