@umituz/react-native-localization 3.5.3 ā 3.5.5
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 +5 -5
- package/src/scripts/prepublish.js +34 -0
- package/src/scripts/setup-languages.js +66 -0
- package/src/scripts/sync-translations.js +105 -0
- package/src/scripts/translate-missing.js +99 -0
- package/src/scripts/utils/file-parser.js +80 -0
- package/src/scripts/utils/sync-helper.js +49 -0
- package/src/scripts/utils/translation-config.js +128 -0
- package/src/scripts/utils/translator.js +101 -0
- package/src/scripts/prepublish.ts +0 -48
- package/src/scripts/setup-languages.ts +0 -56
- package/src/scripts/sync-translations.ts +0 -277
- package/src/scripts/translate-missing.ts +0 -403
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable no-console */
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Translate Missing Script
|
|
6
|
-
*
|
|
7
|
-
* Translates missing strings from en-US.ts to all other language files
|
|
8
|
-
* using Google Translate free API.
|
|
9
|
-
*
|
|
10
|
-
* Usage: node scripts/translate-missing.js [locales-dir]
|
|
11
|
-
* Default: src/domains/localization/infrastructure/locales
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
const https = require('https');
|
|
17
|
-
|
|
18
|
-
// Language codes mapping for Google Translate API
|
|
19
|
-
const LANGUAGE_MAP = {
|
|
20
|
-
'ar-SA': 'ar', // Arabic
|
|
21
|
-
'bg-BG': 'bg', // Bulgarian
|
|
22
|
-
'cs-CZ': 'cs', // Czech
|
|
23
|
-
'da-DK': 'da', // Danish
|
|
24
|
-
'de-DE': 'de', // German
|
|
25
|
-
'el-GR': 'el', // Greek
|
|
26
|
-
'en-AU': 'en', // English (skip)
|
|
27
|
-
'en-CA': 'en', // English (skip)
|
|
28
|
-
'en-GB': 'en', // English (skip)
|
|
29
|
-
'es-ES': 'es', // Spanish
|
|
30
|
-
'es-MX': 'es', // Spanish
|
|
31
|
-
'fi-FI': 'fi', // Finnish
|
|
32
|
-
'fr-CA': 'fr', // French
|
|
33
|
-
'fr-FR': 'fr', // French
|
|
34
|
-
'hi-IN': 'hi', // Hindi
|
|
35
|
-
'hr-HR': 'hr', // Croatian
|
|
36
|
-
'hu-HU': 'hu', // Hungarian
|
|
37
|
-
'id-ID': 'id', // Indonesian
|
|
38
|
-
'it-IT': 'it', // Italian
|
|
39
|
-
'ja-JP': 'ja', // Japanese
|
|
40
|
-
'ko-KR': 'ko', // Korean
|
|
41
|
-
'ms-MY': 'ms', // Malay
|
|
42
|
-
'nl-NL': 'nl', // Dutch
|
|
43
|
-
'no-NO': 'no', // Norwegian
|
|
44
|
-
'pl-PL': 'pl', // Polish
|
|
45
|
-
'pt-BR': 'pt', // Portuguese
|
|
46
|
-
'pt-PT': 'pt', // Portuguese
|
|
47
|
-
'ro-RO': 'ro', // Romanian
|
|
48
|
-
'ru-RU': 'ru', // Russian
|
|
49
|
-
'sk-SK': 'sk', // Slovak
|
|
50
|
-
'sv-SE': 'sv', // Swedish
|
|
51
|
-
'th-TH': 'th', // Thai
|
|
52
|
-
'tl-PH': 'tl', // Tagalog
|
|
53
|
-
'tr-TR': 'tr', // Turkish
|
|
54
|
-
'uk-UA': 'uk', // Ukrainian
|
|
55
|
-
'vi-VN': 'vi', // Vietnamese
|
|
56
|
-
'zh-CN': 'zh-CN', // Chinese Simplified
|
|
57
|
-
'zh-TW': 'zh-TW', // Chinese Traditional
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Common English words that don't need translation
|
|
61
|
-
const SKIP_WORDS = new Set([
|
|
62
|
-
'OK',
|
|
63
|
-
'Email',
|
|
64
|
-
'Google',
|
|
65
|
-
'Apple',
|
|
66
|
-
'Facebook',
|
|
67
|
-
'Premium',
|
|
68
|
-
'Pro',
|
|
69
|
-
'Plus',
|
|
70
|
-
'BPM',
|
|
71
|
-
]);
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Parse TypeScript translation file and extract the object
|
|
75
|
-
*/
|
|
76
|
-
function parseTypeScriptFile(filePath) {
|
|
77
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
78
|
-
|
|
79
|
-
// Extract the object from "export default { ... };"
|
|
80
|
-
const match = content.match(/export\s+default\s+(\{[\s\S]*\});?\s*$/);
|
|
81
|
-
if (!match) {
|
|
82
|
-
throw new Error(`Could not parse TypeScript file: ${filePath}`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Use eval to parse the object (safe since we control the files)
|
|
86
|
-
// Remove trailing semicolon if present
|
|
87
|
-
const objectStr = match[1].replace(/;$/, '');
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
// eslint-disable-next-line no-eval
|
|
91
|
-
return eval(`(${objectStr})`);
|
|
92
|
-
} catch (error) {
|
|
93
|
-
throw new Error(`Failed to parse object in ${filePath}: ${error.message}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Generate TypeScript file content from object
|
|
99
|
-
*/
|
|
100
|
-
function generateTypeScriptContent(obj, langCode) {
|
|
101
|
-
const langName = getLangDisplayName(langCode);
|
|
102
|
-
|
|
103
|
-
function stringifyValue(value, indent = 2) {
|
|
104
|
-
if (typeof value === 'string') {
|
|
105
|
-
// Escape quotes and handle multiline
|
|
106
|
-
const escaped = value
|
|
107
|
-
.replace(/\\/g, '\\\\')
|
|
108
|
-
.replace(/"/g, '\\"')
|
|
109
|
-
.replace(/\n/g, '\\n');
|
|
110
|
-
return `"${escaped}"`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (Array.isArray(value)) {
|
|
114
|
-
if (value.length === 0) return '[]';
|
|
115
|
-
const items = value.map(v => stringifyValue(v, indent + 2));
|
|
116
|
-
return `[${items.join(', ')}]`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (typeof value === 'object' && value !== null) {
|
|
120
|
-
const entries = Object.entries(value);
|
|
121
|
-
|
|
122
|
-
// Handle empty objects
|
|
123
|
-
if (entries.length === 0) {
|
|
124
|
-
return '{}';
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const spaces = ' '.repeat(indent);
|
|
128
|
-
const innerSpaces = ' '.repeat(indent + 2);
|
|
129
|
-
const entriesStr = entries
|
|
130
|
-
.map(([k, v]) => {
|
|
131
|
-
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : `"${k}"`;
|
|
132
|
-
return `${innerSpaces}${key}: ${stringifyValue(v, indent + 2)}`;
|
|
133
|
-
})
|
|
134
|
-
.join(',\n');
|
|
135
|
-
return `{\n${entriesStr},\n${spaces}}`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return String(value);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const objString = stringifyValue(obj, 0);
|
|
142
|
-
|
|
143
|
-
return `/**
|
|
144
|
-
* ${langName} Translations
|
|
145
|
-
* Auto-translated from en-US.ts
|
|
146
|
-
*/
|
|
147
|
-
|
|
148
|
-
export default ${objString};
|
|
149
|
-
`;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Get display name for language code
|
|
154
|
-
*/
|
|
155
|
-
function getLangDisplayName(code) {
|
|
156
|
-
const names = {
|
|
157
|
-
'ar-SA': 'Arabic (Saudi Arabia)',
|
|
158
|
-
'bg-BG': 'Bulgarian',
|
|
159
|
-
'cs-CZ': 'Czech',
|
|
160
|
-
'da-DK': 'Danish',
|
|
161
|
-
'de-DE': 'German',
|
|
162
|
-
'el-GR': 'Greek',
|
|
163
|
-
'en-AU': 'English (Australia)',
|
|
164
|
-
'en-CA': 'English (Canada)',
|
|
165
|
-
'en-GB': 'English (UK)',
|
|
166
|
-
'en-US': 'English (US)',
|
|
167
|
-
'es-ES': 'Spanish (Spain)',
|
|
168
|
-
'es-MX': 'Spanish (Mexico)',
|
|
169
|
-
'fi-FI': 'Finnish',
|
|
170
|
-
'fr-CA': 'French (Canada)',
|
|
171
|
-
'fr-FR': 'French (France)',
|
|
172
|
-
'hi-IN': 'Hindi',
|
|
173
|
-
'hr-HR': 'Croatian',
|
|
174
|
-
'hu-HU': 'Hungarian',
|
|
175
|
-
'id-ID': 'Indonesian',
|
|
176
|
-
'it-IT': 'Italian',
|
|
177
|
-
'ja-JP': 'Japanese',
|
|
178
|
-
'ko-KR': 'Korean',
|
|
179
|
-
'ms-MY': 'Malay',
|
|
180
|
-
'nl-NL': 'Dutch',
|
|
181
|
-
'no-NO': 'Norwegian',
|
|
182
|
-
'pl-PL': 'Polish',
|
|
183
|
-
'pt-BR': 'Portuguese (Brazil)',
|
|
184
|
-
'pt-PT': 'Portuguese (Portugal)',
|
|
185
|
-
'ro-RO': 'Romanian',
|
|
186
|
-
'ru-RU': 'Russian',
|
|
187
|
-
'sk-SK': 'Slovak',
|
|
188
|
-
'sv-SE': 'Swedish',
|
|
189
|
-
'th-TH': 'Thai',
|
|
190
|
-
'tl-PH': 'Tagalog',
|
|
191
|
-
'tr-TR': 'Turkish',
|
|
192
|
-
'uk-UA': 'Ukrainian',
|
|
193
|
-
'vi-VN': 'Vietnamese',
|
|
194
|
-
'zh-CN': 'Chinese (Simplified)',
|
|
195
|
-
'zh-TW': 'Chinese (Traditional)',
|
|
196
|
-
};
|
|
197
|
-
return names[code] || code;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Simple Google Translate API call using free endpoint
|
|
202
|
-
*/
|
|
203
|
-
async function translateText(text, targetLang) {
|
|
204
|
-
return new Promise((resolve) => {
|
|
205
|
-
if (SKIP_WORDS.has(text)) {
|
|
206
|
-
resolve(text);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const encodedText = encodeURIComponent(text);
|
|
211
|
-
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodedText}`;
|
|
212
|
-
|
|
213
|
-
https
|
|
214
|
-
.get(url, res => {
|
|
215
|
-
let data = '';
|
|
216
|
-
res.on('data', chunk => {
|
|
217
|
-
data += chunk;
|
|
218
|
-
});
|
|
219
|
-
res.on('end', () => {
|
|
220
|
-
try {
|
|
221
|
-
const parsed = JSON.parse(data);
|
|
222
|
-
const translated = parsed[0]
|
|
223
|
-
.map(item => item[0])
|
|
224
|
-
.join('')
|
|
225
|
-
.trim();
|
|
226
|
-
resolve(translated || text);
|
|
227
|
-
} catch (error) {
|
|
228
|
-
console.warn(` ā ļø Translation failed for "${text.substring(0, 30)}...": ${error.message}`);
|
|
229
|
-
resolve(text);
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
})
|
|
233
|
-
.on('error', err => {
|
|
234
|
-
console.warn(` ā ļø Network error: ${err.message}`);
|
|
235
|
-
resolve(text);
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Add delay between API calls to avoid rate limiting
|
|
242
|
-
*/
|
|
243
|
-
function delay(ms) {
|
|
244
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Check if a value needs translation (is still in English)
|
|
249
|
-
*/
|
|
250
|
-
function needsTranslation(value, enValue) {
|
|
251
|
-
if (typeof value !== 'string') return false;
|
|
252
|
-
if (value === enValue) return true;
|
|
253
|
-
if (SKIP_WORDS.has(value)) return false;
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Recursively translate object values
|
|
259
|
-
*/
|
|
260
|
-
async function translateObject(enObj, targetObj, targetLang, path = '', stats = { count: 0 }) {
|
|
261
|
-
for (const key in enObj) {
|
|
262
|
-
const currentPath = path ? `${path}.${key}` : key;
|
|
263
|
-
const enValue = enObj[key];
|
|
264
|
-
const targetValue = targetObj[key];
|
|
265
|
-
|
|
266
|
-
if (Array.isArray(enValue)) {
|
|
267
|
-
// Handle arrays
|
|
268
|
-
if (!Array.isArray(targetValue)) {
|
|
269
|
-
targetObj[key] = [];
|
|
270
|
-
}
|
|
271
|
-
for (let i = 0; i < enValue.length; i++) {
|
|
272
|
-
if (typeof enValue[i] === 'string') {
|
|
273
|
-
if (needsTranslation(targetObj[key][i], enValue[i])) {
|
|
274
|
-
console.log(` š ${currentPath}[${i}]: "${enValue[i].substring(0, 40)}..."`);
|
|
275
|
-
targetObj[key][i] = await translateText(enValue[i], targetLang);
|
|
276
|
-
stats.count++;
|
|
277
|
-
await delay(200);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
} else if (typeof enValue === 'object' && enValue !== null) {
|
|
282
|
-
// Handle nested objects
|
|
283
|
-
if (!targetObj[key] || typeof targetObj[key] !== 'object') {
|
|
284
|
-
targetObj[key] = {};
|
|
285
|
-
}
|
|
286
|
-
await translateObject(enValue, targetObj[key], targetLang, currentPath, stats);
|
|
287
|
-
} else if (typeof enValue === 'string') {
|
|
288
|
-
// Handle string values
|
|
289
|
-
if (needsTranslation(targetValue, enValue)) {
|
|
290
|
-
// Skip placeholders like {{count}}
|
|
291
|
-
if (enValue.includes('{{') && enValue.includes('}}')) {
|
|
292
|
-
// Translate but preserve placeholders
|
|
293
|
-
console.log(` š ${currentPath}: "${enValue.substring(0, 40)}${enValue.length > 40 ? '...' : ''}"`);
|
|
294
|
-
targetObj[key] = await translateText(enValue, targetLang);
|
|
295
|
-
stats.count++;
|
|
296
|
-
await delay(200);
|
|
297
|
-
} else {
|
|
298
|
-
console.log(` š ${currentPath}: "${enValue.substring(0, 40)}${enValue.length > 40 ? '...' : ''}"`);
|
|
299
|
-
targetObj[key] = await translateText(enValue, targetLang);
|
|
300
|
-
stats.count++;
|
|
301
|
-
await delay(200);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return stats.count;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Translate a single language file
|
|
312
|
-
*/
|
|
313
|
-
async function translateLanguageFile(enUSPath, targetPath, langCode) {
|
|
314
|
-
const targetLang = LANGUAGE_MAP[langCode];
|
|
315
|
-
|
|
316
|
-
if (!targetLang) {
|
|
317
|
-
console.log(` ā ļø No language mapping for ${langCode}, skipping`);
|
|
318
|
-
return 0;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Skip English variants
|
|
322
|
-
if (targetLang === 'en') {
|
|
323
|
-
console.log(` āļø Skipping English variant: ${langCode}`);
|
|
324
|
-
return 0;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const enUS = parseTypeScriptFile(enUSPath);
|
|
328
|
-
let target;
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
target = parseTypeScriptFile(targetPath);
|
|
332
|
-
} catch {
|
|
333
|
-
// If target file doesn't exist or is invalid, start fresh
|
|
334
|
-
target = {};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const stats = { count: 0 };
|
|
338
|
-
await translateObject(enUS, target, targetLang, '', stats);
|
|
339
|
-
|
|
340
|
-
if (stats.count > 0) {
|
|
341
|
-
const content = generateTypeScriptContent(target, langCode);
|
|
342
|
-
fs.writeFileSync(targetPath, content);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return stats.count;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Main function
|
|
350
|
-
*/
|
|
351
|
-
async function main() {
|
|
352
|
-
const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
|
|
353
|
-
const localesDir = path.resolve(process.cwd(), targetDir);
|
|
354
|
-
|
|
355
|
-
console.log('š Starting automatic translation...\n');
|
|
356
|
-
console.log(`š Locales directory: ${localesDir}\n`);
|
|
357
|
-
|
|
358
|
-
if (!fs.existsSync(localesDir)) {
|
|
359
|
-
console.error(`ā Locales directory not found: ${localesDir}`);
|
|
360
|
-
process.exit(1);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const enUSPath = path.join(localesDir, 'en-US.ts');
|
|
364
|
-
if (!fs.existsSync(enUSPath)) {
|
|
365
|
-
console.error(`ā Base file not found: ${enUSPath}`);
|
|
366
|
-
process.exit(1);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Find all language files
|
|
370
|
-
const files = fs.readdirSync(localesDir)
|
|
371
|
-
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
|
|
372
|
-
.sort();
|
|
373
|
-
|
|
374
|
-
console.log(`š Languages to translate: ${files.length}`);
|
|
375
|
-
console.log('ā” Running with 200ms delay between API calls\n');
|
|
376
|
-
|
|
377
|
-
let totalTranslated = 0;
|
|
378
|
-
|
|
379
|
-
for (const file of files) {
|
|
380
|
-
const langCode = file.replace('.ts', '');
|
|
381
|
-
const targetPath = path.join(localesDir, file);
|
|
382
|
-
|
|
383
|
-
console.log(`\nš Translating ${langCode}...`);
|
|
384
|
-
|
|
385
|
-
const count = await translateLanguageFile(enUSPath, targetPath, langCode);
|
|
386
|
-
totalTranslated += count;
|
|
387
|
-
|
|
388
|
-
if (count > 0) {
|
|
389
|
-
console.log(` ā
Translated ${count} strings`);
|
|
390
|
-
} else {
|
|
391
|
-
console.log(` ā Already complete`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
console.log(`\nā
Translation completed!`);
|
|
396
|
-
console.log(` Total strings translated: ${totalTranslated}`);
|
|
397
|
-
console.log(`\nš Next: Run 'node scripts/setup-languages.js' to update index.ts`);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
main().catch(error => {
|
|
401
|
-
console.error('ā Translation failed:', error);
|
|
402
|
-
process.exit(1);
|
|
403
|
-
});
|