@umituz/react-native-settings 5.4.22 → 5.4.24
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 +1 -1
- package/src/domains/feedback/presentation/components/FeedbackForm.styles.ts +8 -2
- package/src/domains/gamification/components/AchievementItem.tsx +3 -2
- package/src/domains/gamification/components/GamificationScreen/GamificationScreen.tsx +2 -1
- package/src/domains/gamification/components/GamificationScreen/styles.ts +43 -37
- package/src/domains/gamification/components/styles/achievementItemStyles.ts +75 -68
- package/src/domains/localization/infrastructure/components/LanguageSwitcher.styles.ts +28 -25
- package/src/domains/localization/infrastructure/components/LanguageSwitcher.tsx +6 -6
- package/src/domains/localization/presentation/components/LanguageItem.styles.ts +47 -33
- package/src/domains/localization/presentation/components/LanguageItem.tsx +9 -29
- package/src/domains/localization/scripts/prepublish.js +0 -36
- package/src/domains/localization/scripts/setup-languages.js +0 -85
- package/src/domains/localization/scripts/sync-translations.js +0 -124
- package/src/domains/localization/scripts/translate-missing.js +0 -211
- package/src/domains/localization/scripts/utils/file-parser.js +0 -104
- package/src/domains/localization/scripts/utils/key-detector.js +0 -45
- package/src/domains/localization/scripts/utils/key-extractor.js +0 -105
- package/src/domains/localization/scripts/utils/object-helper.js +0 -29
- package/src/domains/localization/scripts/utils/sync-helper.js +0 -49
- package/src/domains/localization/scripts/utils/translation-config.js +0 -116
- package/src/domains/localization/scripts/utils/translator.js +0 -91
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Setup Languages Script
|
|
5
|
-
* Creates stub files for all supported languages (if not exist),
|
|
6
|
-
* then generates index.ts from all available translation files.
|
|
7
|
-
* Usage: node setup-languages.js [locales-dir]
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import fs from 'fs';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import { LANGUAGE_MAP, getLangDisplayName } from './utils/translation-config.js';
|
|
13
|
-
|
|
14
|
-
export function setupLanguages(targetDir) {
|
|
15
|
-
const localesDir = path.resolve(process.cwd(), targetDir);
|
|
16
|
-
|
|
17
|
-
if (!fs.existsSync(localesDir)) {
|
|
18
|
-
console.error(`❌ Locales directory not found: ${localesDir}`);
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Create stub files for all supported languages that don't exist yet
|
|
23
|
-
let created = 0;
|
|
24
|
-
for (const langCode of Object.keys(LANGUAGE_MAP)) {
|
|
25
|
-
// Skip English variants — en-US is the base, others (en-AU, en-GB) are redundant
|
|
26
|
-
if (langCode.startsWith('en-') && langCode !== 'en-US') continue;
|
|
27
|
-
|
|
28
|
-
const filePath = path.join(localesDir, `${langCode}.ts`);
|
|
29
|
-
if (!fs.existsSync(filePath)) {
|
|
30
|
-
const langName = getLangDisplayName(langCode);
|
|
31
|
-
fs.writeFileSync(
|
|
32
|
-
filePath,
|
|
33
|
-
`/**\n * ${langName} Translations\n * Auto-synced from en-US.ts\n */\n\nexport default {};\n`,
|
|
34
|
-
);
|
|
35
|
-
console.log(` ✅ Created ${langCode}.ts (${langName})`);
|
|
36
|
-
created++;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (created > 0) {
|
|
41
|
-
console.log(`\n📦 Created ${created} new language stubs.\n`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Generate index.ts from all language files
|
|
45
|
-
const files = fs.readdirSync(localesDir)
|
|
46
|
-
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/))
|
|
47
|
-
.sort();
|
|
48
|
-
|
|
49
|
-
const imports = [];
|
|
50
|
-
const exports = [];
|
|
51
|
-
|
|
52
|
-
files.forEach(file => {
|
|
53
|
-
const code = file.replace('.ts', '');
|
|
54
|
-
const varName = code.replace(/-([a-z0-9])/g, (g) => g[1].toUpperCase()).replace('-', '');
|
|
55
|
-
imports.push(`import ${varName} from "./${code}";`);
|
|
56
|
-
exports.push(` "${code}": ${varName},`);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const content = `/**
|
|
60
|
-
* Localization Index
|
|
61
|
-
* Exports all available translation files
|
|
62
|
-
* Auto-generated by scripts/setup-languages.js
|
|
63
|
-
*/
|
|
64
|
-
|
|
65
|
-
${imports.join('\n')}
|
|
66
|
-
|
|
67
|
-
export const translations = {
|
|
68
|
-
${exports.join('\n')}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export type TranslationKey = keyof typeof translations;
|
|
72
|
-
|
|
73
|
-
export default translations;
|
|
74
|
-
`;
|
|
75
|
-
|
|
76
|
-
fs.writeFileSync(path.join(localesDir, 'index.ts'), content);
|
|
77
|
-
console.log(`✅ Generated index.ts with ${files.length} languages`);
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
82
|
-
const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
|
|
83
|
-
console.log('🚀 Setting up language files...\n');
|
|
84
|
-
setupLanguages(targetDir);
|
|
85
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Sync Translations Script
|
|
5
|
-
* Synchronizes translation keys from en-US.ts to all other language files
|
|
6
|
-
* Usage: node sync-translations.js [locales-dir] [src-dir-optional]
|
|
7
|
-
*/
|
|
8
|
-
|
|
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 { extractUsedKeys } from './utils/key-extractor.js';
|
|
15
|
-
import { setDeep } from './utils/object-helper.js';
|
|
16
|
-
|
|
17
|
-
function syncLanguageFile(enUSPath, targetPath, langCode) {
|
|
18
|
-
const enUS = parseTypeScriptFile(enUSPath);
|
|
19
|
-
let target;
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
target = parseTypeScriptFile(targetPath);
|
|
23
|
-
} catch {
|
|
24
|
-
target = {};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const newKeys = detectNewKeys(enUS, target);
|
|
28
|
-
const addStats = { added: 0, newKeys: [] };
|
|
29
|
-
const removeStats = { removed: 0, removedKeys: [] };
|
|
30
|
-
|
|
31
|
-
addMissingKeys(enUS, target, addStats);
|
|
32
|
-
removeExtraKeys(enUS, target, removeStats);
|
|
33
|
-
|
|
34
|
-
const changed = addStats.added > 0 || removeStats.removed > 0;
|
|
35
|
-
|
|
36
|
-
if (changed) {
|
|
37
|
-
const content = generateTypeScriptContent(target, langCode);
|
|
38
|
-
fs.writeFileSync(targetPath, content);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return { ...addStats, ...removeStats, newKeys, changed };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function processExtraction(srcDir, enUSPath) {
|
|
45
|
-
if (!srcDir) return;
|
|
46
|
-
|
|
47
|
-
console.log(`🔍 Scanning source code and dependencies: ${srcDir}...`);
|
|
48
|
-
const usedKeyMap = extractUsedKeys(srcDir);
|
|
49
|
-
console.log(` Found ${usedKeyMap.size} unique keys.`);
|
|
50
|
-
|
|
51
|
-
const oldEnUS = parseTypeScriptFile(enUSPath);
|
|
52
|
-
const newEnUS = {};
|
|
53
|
-
|
|
54
|
-
let addedCount = 0;
|
|
55
|
-
for (const [key, defaultValue] of usedKeyMap) {
|
|
56
|
-
// Try to keep existing translation if it exists
|
|
57
|
-
const existingValue = key.split('.').reduce((obj, k) => (obj && obj[k]), oldEnUS);
|
|
58
|
-
|
|
59
|
-
// We treat it as "not translated" if the value is exactly the key string
|
|
60
|
-
const isActuallyTranslated = typeof existingValue === 'string' && existingValue !== key;
|
|
61
|
-
const valueToSet = isActuallyTranslated ? existingValue : defaultValue;
|
|
62
|
-
|
|
63
|
-
if (setDeep(newEnUS, key, valueToSet)) {
|
|
64
|
-
if (!isActuallyTranslated) addedCount++;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Count keys in objects
|
|
69
|
-
const getKeysCount = (obj) => {
|
|
70
|
-
let count = 0;
|
|
71
|
-
const walk = (o) => {
|
|
72
|
-
for (const k in o) {
|
|
73
|
-
if (typeof o[k] === 'object' && o[k] !== null) walk(o[k]);
|
|
74
|
-
else count++;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
walk(obj);
|
|
78
|
-
return count;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const oldTotal = getKeysCount(oldEnUS);
|
|
82
|
-
const newTotal = getKeysCount(newEnUS);
|
|
83
|
-
const removedCount = oldTotal - (newTotal - addedCount);
|
|
84
|
-
|
|
85
|
-
console.log(` ✨ Optimized en-US.ts: ${addedCount} keys populated/updated, pruned ${Math.max(0, removedCount)} unused.`);
|
|
86
|
-
const content = generateTypeScriptContent(newEnUS, 'en-US');
|
|
87
|
-
fs.writeFileSync(enUSPath, content);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function syncTranslations(targetDir, srcDir) {
|
|
91
|
-
const localesDir = path.resolve(process.cwd(), targetDir);
|
|
92
|
-
const enUSPath = path.join(localesDir, 'en-US.ts');
|
|
93
|
-
|
|
94
|
-
if (!fs.existsSync(localesDir) || !fs.existsSync(enUSPath)) {
|
|
95
|
-
console.error(`❌ Localization files not found in: ${localesDir}`);
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
processExtraction(srcDir, enUSPath);
|
|
100
|
-
|
|
101
|
-
const files = fs.readdirSync(localesDir)
|
|
102
|
-
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
|
|
103
|
-
.sort();
|
|
104
|
-
|
|
105
|
-
console.log(`📊 Languages to sync: ${files.length}\n`);
|
|
106
|
-
files.forEach(file => {
|
|
107
|
-
const langCode = file.replace('.ts', '');
|
|
108
|
-
const targetPath = path.join(localesDir, file);
|
|
109
|
-
const result = syncLanguageFile(enUSPath, targetPath, langCode);
|
|
110
|
-
if (result.changed) {
|
|
111
|
-
console.log(` 🌍 ${langCode}: ✏️ +${result.added} keys, -${result.removed} keys`);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
console.log(`\n✅ Synchronization completed!`);
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
120
|
-
const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
|
|
121
|
-
const srcDir = process.argv[3];
|
|
122
|
-
console.log('🚀 Starting translation synchronization...\n');
|
|
123
|
-
syncTranslations(targetDir, srcDir);
|
|
124
|
-
}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Translate Missing Script
|
|
5
|
-
* Automatically translates missing strings using Google Translate
|
|
6
|
-
* Refactored to use @umituz/react-native-google-translate package
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import fs from 'fs';
|
|
10
|
-
import path from 'path';
|
|
11
|
-
import { parseTypeScriptFile, generateTypeScriptContent } from './utils/file-parser.js';
|
|
12
|
-
import { googleTranslateService } from '@umituz/react-native-google-translate/services';
|
|
13
|
-
import { setupLanguages } from './setup-languages.js';
|
|
14
|
-
import { syncTranslations } from './sync-translations.js';
|
|
15
|
-
|
|
16
|
-
async function translateMissing(targetDir, srcDir) {
|
|
17
|
-
// Initialize the translation service
|
|
18
|
-
googleTranslateService.initialize({
|
|
19
|
-
minDelay: 100,
|
|
20
|
-
maxRetries: 3,
|
|
21
|
-
timeout: 10000,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const localesDir = path.resolve(process.cwd(), targetDir);
|
|
25
|
-
const enUSPath = path.join(localesDir, 'en-US.ts');
|
|
26
|
-
|
|
27
|
-
const skipSync = process.argv.includes('--no-sync');
|
|
28
|
-
|
|
29
|
-
if (!fs.existsSync(path.join(localesDir, 'index.ts'))) {
|
|
30
|
-
console.log('🔄 Initializing localization setup...');
|
|
31
|
-
setupLanguages(targetDir);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!skipSync) {
|
|
35
|
-
console.log('\n🔄 Checking synchronization...');
|
|
36
|
-
syncTranslations(targetDir, srcDir);
|
|
37
|
-
} else {
|
|
38
|
-
console.log('\n⏭️ Skipping synchronization check...');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const files = fs.readdirSync(localesDir)
|
|
42
|
-
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
|
|
43
|
-
.sort();
|
|
44
|
-
|
|
45
|
-
console.log(`\n📊 Languages to translate: ${files.length}\n`);
|
|
46
|
-
|
|
47
|
-
const enUS = parseTypeScriptFile(enUSPath);
|
|
48
|
-
|
|
49
|
-
for (const file of files) {
|
|
50
|
-
const langCode = file.replace('.ts', '');
|
|
51
|
-
|
|
52
|
-
// Skip English variants
|
|
53
|
-
const targetLang = getTargetLanguage(langCode);
|
|
54
|
-
if (!targetLang || targetLang === 'en') {
|
|
55
|
-
console.log(`⏭️ Skipping ${langCode} (English variant)`);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const langName = getLanguageDisplayName(langCode);
|
|
60
|
-
console.log(`🌍 Translating ${langCode} (${langName})...`);
|
|
61
|
-
|
|
62
|
-
const targetPath = path.join(localesDir, file);
|
|
63
|
-
const target = parseTypeScriptFile(targetPath);
|
|
64
|
-
|
|
65
|
-
const stats = {
|
|
66
|
-
totalCount: 0,
|
|
67
|
-
successCount: 0,
|
|
68
|
-
failureCount: 0,
|
|
69
|
-
skippedCount: 0,
|
|
70
|
-
translatedKeys: [],
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
await googleTranslateService.translateObject(
|
|
74
|
-
enUS,
|
|
75
|
-
target,
|
|
76
|
-
targetLang,
|
|
77
|
-
'',
|
|
78
|
-
stats
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// Clear progress line
|
|
82
|
-
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
83
|
-
|
|
84
|
-
if (stats.successCount > 0) {
|
|
85
|
-
const content = generateTypeScriptContent(target, langCode);
|
|
86
|
-
fs.writeFileSync(targetPath, content);
|
|
87
|
-
|
|
88
|
-
console.log(` ✅ Successfully translated ${stats.successCount} keys:`);
|
|
89
|
-
|
|
90
|
-
// Detailed logging of translated keys
|
|
91
|
-
const displayCount = Math.min(stats.translatedKeys.length, 15);
|
|
92
|
-
stats.translatedKeys.slice(0, displayCount).forEach(item => {
|
|
93
|
-
console.log(` • ${item.key}: "${item.from}" → "${item.to}"`);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
if (stats.translatedKeys.length > displayCount) {
|
|
97
|
-
console.log(` ... and ${stats.translatedKeys.length - displayCount} more.`);
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
console.log(` ✨ Already up to date.`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
console.log('\n✅ All translations completed!');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Helper functions (can be replaced with package imports later)
|
|
108
|
-
const LANGUAGE_MAP = {
|
|
109
|
-
'ar-SA': 'ar',
|
|
110
|
-
'bg-BG': 'bg',
|
|
111
|
-
'cs-CZ': 'cs',
|
|
112
|
-
'da-DK': 'da',
|
|
113
|
-
'de-DE': 'de',
|
|
114
|
-
'el-GR': 'el',
|
|
115
|
-
'en-AU': 'en',
|
|
116
|
-
'en-CA': 'en',
|
|
117
|
-
'en-GB': 'en',
|
|
118
|
-
'es-ES': 'es',
|
|
119
|
-
'es-MX': 'es',
|
|
120
|
-
'fi-FI': 'fi',
|
|
121
|
-
'fr-CA': 'fr',
|
|
122
|
-
'fr-FR': 'fr',
|
|
123
|
-
'hi-IN': 'hi',
|
|
124
|
-
'hr-HR': 'hr',
|
|
125
|
-
'hu-HU': 'hu',
|
|
126
|
-
'id-ID': 'id',
|
|
127
|
-
'it-IT': 'it',
|
|
128
|
-
'ja-JP': 'ja',
|
|
129
|
-
'ko-KR': 'ko',
|
|
130
|
-
'ms-MY': 'ms',
|
|
131
|
-
'nl-NL': 'nl',
|
|
132
|
-
'no-NO': 'no',
|
|
133
|
-
'pl-PL': 'pl',
|
|
134
|
-
'pt-BR': 'pt',
|
|
135
|
-
'pt-PT': 'pt',
|
|
136
|
-
'ro-RO': 'ro',
|
|
137
|
-
'ru-RU': 'ru',
|
|
138
|
-
'sk-SK': 'sk',
|
|
139
|
-
'sl-SI': 'sl',
|
|
140
|
-
'sv-SE': 'sv',
|
|
141
|
-
'th-TH': 'th',
|
|
142
|
-
'tl-PH': 'tl',
|
|
143
|
-
'tr-TR': 'tr',
|
|
144
|
-
'uk-UA': 'uk',
|
|
145
|
-
'vi-VN': 'vi',
|
|
146
|
-
'zh-CN': 'zh-CN',
|
|
147
|
-
'zh-TW': 'zh-TW',
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const LANGUAGE_NAMES = {
|
|
151
|
-
'ar-SA': 'Arabic (Saudi Arabia)',
|
|
152
|
-
'bg-BG': 'Bulgarian',
|
|
153
|
-
'cs-CZ': 'Czech',
|
|
154
|
-
'da-DK': 'Danish',
|
|
155
|
-
'de-DE': 'German',
|
|
156
|
-
'el-GR': 'Greek',
|
|
157
|
-
'en-AU': 'English (Australia)',
|
|
158
|
-
'en-CA': 'English (Canada)',
|
|
159
|
-
'en-GB': 'English (UK)',
|
|
160
|
-
'en-US': 'English (US)',
|
|
161
|
-
'es-ES': 'Spanish (Spain)',
|
|
162
|
-
'es-MX': 'Spanish (Mexico)',
|
|
163
|
-
'fi-FI': 'Finnish',
|
|
164
|
-
'fr-CA': 'French (Canada)',
|
|
165
|
-
'fr-FR': 'French (France)',
|
|
166
|
-
'hi-IN': 'Hindi',
|
|
167
|
-
'hr-HR': 'Croatian',
|
|
168
|
-
'hu-HU': 'Hungarian',
|
|
169
|
-
'id-ID': 'Indonesian',
|
|
170
|
-
'it-IT': 'Italian',
|
|
171
|
-
'ja-JP': 'Japanese',
|
|
172
|
-
'ko-KR': 'Korean',
|
|
173
|
-
'ms-MY': 'Malay',
|
|
174
|
-
'nl-NL': 'Dutch',
|
|
175
|
-
'no-NO': 'Norwegian',
|
|
176
|
-
'pl-PL': 'Polish',
|
|
177
|
-
'pt-BR': 'Portuguese (Brazil)',
|
|
178
|
-
'pt-PT': 'Portuguese (Portugal)',
|
|
179
|
-
'ro-RO': 'Romanian',
|
|
180
|
-
'ru-RU': 'Russian',
|
|
181
|
-
'sk-SK': 'Slovak',
|
|
182
|
-
'sl-SI': 'Slovenian',
|
|
183
|
-
'sv-SE': 'Swedish',
|
|
184
|
-
'th-TH': 'Thai',
|
|
185
|
-
'tl-PH': 'Tagalog',
|
|
186
|
-
'tr-TR': 'Turkish',
|
|
187
|
-
'uk-UA': 'Ukrainian',
|
|
188
|
-
'vi-VN': 'Vietnamese',
|
|
189
|
-
'zh-CN': 'Chinese (Simplified)',
|
|
190
|
-
'zh-TW': 'Chinese (Traditional)',
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
function getTargetLanguage(langCode) {
|
|
194
|
-
return LANGUAGE_MAP[langCode];
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function getLanguageDisplayName(code) {
|
|
198
|
-
return LANGUAGE_NAMES[code] || code;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const isMainModule = import.meta.url.endsWith('translate-missing.js');
|
|
202
|
-
if (isMainModule) {
|
|
203
|
-
const args = process.argv.slice(2).filter(arg => !arg.startsWith('--'));
|
|
204
|
-
const targetDir = args[0] || 'src/domains/localization/infrastructure/locales';
|
|
205
|
-
const srcDir = args[1];
|
|
206
|
-
console.log('🚀 Starting integrated translation workflow...');
|
|
207
|
-
translateMissing(targetDir, srcDir).catch(err => {
|
|
208
|
-
console.error('\n❌ Translation workflow failed:', err.message);
|
|
209
|
-
process.exit(1);
|
|
210
|
-
});
|
|
211
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { getLangDisplayName } from './translation-config.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* File Parser
|
|
7
|
-
* Parse and generate TypeScript translation files
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export function parseTypeScriptFile(filePath) {
|
|
11
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
12
|
-
|
|
13
|
-
// Match: export default { ... } OR export const NAME = { ... }
|
|
14
|
-
const match = content.match(/export\s+(?:default|const\s+\w+\s*=)\s*(\{[\s\S]*\});?\s*$/);
|
|
15
|
-
|
|
16
|
-
if (!match) {
|
|
17
|
-
throw new Error(`Could not parse TypeScript file: ${filePath}`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const objectStr = match[1].replace(/;$/, '');
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
// Basic evaluation for simple objects (works for generated language files like tr-TR.ts
|
|
24
|
-
// and sub-module files like common.ts, home.ts, etc.)
|
|
25
|
-
// eslint-disable-next-line no-eval
|
|
26
|
-
return eval(`(${objectStr})`);
|
|
27
|
-
} catch (error) {
|
|
28
|
-
// File might be a barrel file with named imports (e.g., en-US.ts that imports sub-modules)
|
|
29
|
-
// Try to resolve each import and merge into a single object
|
|
30
|
-
const dir = path.dirname(filePath);
|
|
31
|
-
const importMatches = [...content.matchAll(/import\s*\{\s*(\w+)\s*\}\s*from\s*["']\.\/(\w+)["']/g)];
|
|
32
|
-
|
|
33
|
-
if (importMatches.length > 0) {
|
|
34
|
-
const result = {};
|
|
35
|
-
for (const [, varName, moduleName] of importMatches) {
|
|
36
|
-
const subFilePath = path.join(dir, `${moduleName}.ts`);
|
|
37
|
-
if (fs.existsSync(subFilePath)) {
|
|
38
|
-
try {
|
|
39
|
-
result[varName] = parseTypeScriptFile(subFilePath);
|
|
40
|
-
} catch {
|
|
41
|
-
// ignore individual sub-file parse errors
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (Object.keys(result).length > 0) {
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
console.warn(`\n⚠️ Warning: Could not fully parse ${filePath}. Files with complex imports/spreads are currently limited.`);
|
|
51
|
-
return {};
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function stringifyValue(value, indent = 2) {
|
|
56
|
-
if (typeof value === 'string') {
|
|
57
|
-
const escaped = value
|
|
58
|
-
.replace(/\\/g, '\\\\')
|
|
59
|
-
.replace(/"/g, '\\"')
|
|
60
|
-
.replace(/\n/g, '\\n');
|
|
61
|
-
return `"${escaped}"`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (Array.isArray(value)) {
|
|
65
|
-
if (value.length === 0) return '[]';
|
|
66
|
-
const items = value.map(v => stringifyValue(v, indent + 2));
|
|
67
|
-
return `[${items.join(', ')}]`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (typeof value === 'object' && value !== null) {
|
|
71
|
-
const entries = Object.entries(value);
|
|
72
|
-
|
|
73
|
-
if (entries.length === 0) {
|
|
74
|
-
return '{}';
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const spaces = ' '.repeat(indent);
|
|
78
|
-
const innerSpaces = ' '.repeat(indent + 2);
|
|
79
|
-
const entriesStr = entries
|
|
80
|
-
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
81
|
-
.map(([k, v]) => {
|
|
82
|
-
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : `"${k}"`;
|
|
83
|
-
return `${innerSpaces}${key}: ${stringifyValue(v, indent + 2)}`;
|
|
84
|
-
})
|
|
85
|
-
.join(',\n');
|
|
86
|
-
return `{\n${entriesStr},\n${spaces}}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return String(value);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function generateTypeScriptContent(obj, langCode) {
|
|
93
|
-
const langName = getLangDisplayName(langCode);
|
|
94
|
-
const isBase = langCode === 'en-US';
|
|
95
|
-
const objString = stringifyValue(obj, 0);
|
|
96
|
-
|
|
97
|
-
return `/**
|
|
98
|
-
* ${langName} Translations
|
|
99
|
-
* ${isBase ? 'Base translations file' : 'Auto-synced from en-US.ts'}
|
|
100
|
-
*/
|
|
101
|
-
|
|
102
|
-
export default ${objString};
|
|
103
|
-
`;
|
|
104
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Key Detector
|
|
3
|
-
* Detects new, missing, and removed keys between source and target objects
|
|
4
|
-
*/
|
|
5
|
-
|
|
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];
|
|
11
|
-
|
|
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
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return newKeys;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function detectMissingKeys(sourceObj, targetObj, path = '', missingKeys = []) {
|
|
28
|
-
for (const key in targetObj) {
|
|
29
|
-
const currentPath = path ? `${path}.${key}` : key;
|
|
30
|
-
|
|
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);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return missingKeys;
|
|
45
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Generic Key Extractor
|
|
6
|
-
* Scans source code for i18n translation keys
|
|
7
|
-
* NO project-specific logic - works for any React Native app
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const IGNORED_DOMAINS = ['.com', '.org', '.net', '.io', '.co', '.app', '.ai', '.gov', '.edu'];
|
|
11
|
-
const IGNORED_EXTENSIONS = [
|
|
12
|
-
'.ts', '.tsx', '.js', '.jsx', '.json', '.yaml', '.yml',
|
|
13
|
-
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.pdf',
|
|
14
|
-
'.mp4', '.mov', '.avi', '.mp3', '.wav', '.css', '.scss', '.md'
|
|
15
|
-
];
|
|
16
|
-
const IGNORED_LAYOUT_VALS = new Set([
|
|
17
|
-
'center', 'row', 'column', 'flex', 'absolute', 'relative', 'hidden', 'visible',
|
|
18
|
-
'transparent', 'bold', 'normal', 'italic', 'contain', 'cover', 'stretch',
|
|
19
|
-
'top', 'bottom', 'left', 'right', 'middle', 'auto', 'none', 'underline',
|
|
20
|
-
'capitalize', 'uppercase', 'lowercase', 'solid', 'dotted', 'dashed', 'wrap',
|
|
21
|
-
'nowrap', 'space-between', 'space-around', 'flex-start', 'flex-end', 'baseline',
|
|
22
|
-
'react', 'index', 'default', 'string', 'number', 'boolean', 'key', 'id'
|
|
23
|
-
]);
|
|
24
|
-
|
|
25
|
-
function extractFromFile(content, keyMap) {
|
|
26
|
-
// Pattern 1: t('key') or t("key")
|
|
27
|
-
const tRegex = /(?:^|\W)t\(['"`]([^'"`]+)['"`]\)/g;
|
|
28
|
-
let match;
|
|
29
|
-
while ((match = tRegex.exec(content)) !== null) {
|
|
30
|
-
const key = match[1];
|
|
31
|
-
if (!key.includes('${') && !keyMap.has(key)) {
|
|
32
|
-
keyMap.set(key, key); // Use key itself as default
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Pattern 2: Dot-notation strings (potential i18n keys)
|
|
37
|
-
const dotRegex = /['"`]([a-z][a-z0-9_]*\.(?:[a-z0-9_]+\.)+[a-z0-9_]+)['"`]/gi;
|
|
38
|
-
while ((match = dotRegex.exec(content)) !== null) {
|
|
39
|
-
const key = match[1];
|
|
40
|
-
const isIgnoredDomain = IGNORED_DOMAINS.some(ext => key.toLowerCase().endsWith(ext));
|
|
41
|
-
const isIgnoredExt = IGNORED_EXTENSIONS.some(ext => key.toLowerCase().endsWith(ext));
|
|
42
|
-
if (!isIgnoredDomain && !isIgnoredExt && !key.includes(' ') && !keyMap.has(key)) {
|
|
43
|
-
keyMap.set(key, key);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Pattern 3: Template literals t(`prefix.${var}`)
|
|
48
|
-
const templateRegex = /t\(\`([a-z0-9_.]+)\.\$\{/g;
|
|
49
|
-
while ((match = templateRegex.exec(content)) !== null) {
|
|
50
|
-
const prefix = match[1];
|
|
51
|
-
const arrayMatches = content.matchAll(/\[([\s\S]*?)\]/g);
|
|
52
|
-
for (const arrayMatch of arrayMatches) {
|
|
53
|
-
const inner = arrayMatch[1];
|
|
54
|
-
const idMatches = inner.matchAll(/['"`]([a-z0-9_]{2,40})['"`]/g);
|
|
55
|
-
for (const idMatch of idMatches) {
|
|
56
|
-
const id = idMatch[1];
|
|
57
|
-
if (IGNORED_LAYOUT_VALS.has(id.toLowerCase())) continue;
|
|
58
|
-
if (/^[0-9]+$/.test(id)) continue;
|
|
59
|
-
|
|
60
|
-
const dynamicKey = `${prefix}.${id}`;
|
|
61
|
-
if (!keyMap.has(dynamicKey)) {
|
|
62
|
-
keyMap.set(dynamicKey, dynamicKey);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function walkDirectory(dir, keyMap, skipDirs = ['node_modules', '.expo', '.git', 'build', 'ios', 'android', 'assets', 'locales', '__tests__']) {
|
|
70
|
-
if (!fs.existsSync(dir)) return;
|
|
71
|
-
|
|
72
|
-
const files = fs.readdirSync(dir);
|
|
73
|
-
for (const file of files) {
|
|
74
|
-
const fullPath = path.join(dir, file);
|
|
75
|
-
const stat = fs.statSync(fullPath);
|
|
76
|
-
|
|
77
|
-
if (stat.isDirectory()) {
|
|
78
|
-
if (!skipDirs.includes(file)) {
|
|
79
|
-
walkDirectory(fullPath, keyMap, skipDirs);
|
|
80
|
-
}
|
|
81
|
-
} else if (/\.(ts|tsx|js|jsx)$/.test(file)) {
|
|
82
|
-
const content = fs.readFileSync(fullPath, 'utf8');
|
|
83
|
-
extractFromFile(content, keyMap);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function extractUsedKeys(srcDir) {
|
|
89
|
-
const keyMap = new Map();
|
|
90
|
-
if (!srcDir) return keyMap;
|
|
91
|
-
|
|
92
|
-
const projectRoot = process.cwd();
|
|
93
|
-
const absoluteSrcDir = path.resolve(projectRoot, srcDir);
|
|
94
|
-
|
|
95
|
-
// Scan project source
|
|
96
|
-
walkDirectory(absoluteSrcDir, keyMap);
|
|
97
|
-
|
|
98
|
-
// Scan @umituz packages for shared keys
|
|
99
|
-
const packagesDir = path.resolve(projectRoot, 'node_modules/@umituz');
|
|
100
|
-
if (fs.existsSync(packagesDir)) {
|
|
101
|
-
walkDirectory(packagesDir, keyMap);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return keyMap;
|
|
105
|
-
}
|