@umituz/react-native-google-translate 1.0.5 → 1.0.7
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/dist/domain/entities/Language.entity.js +2 -1
- package/dist/domain/entities/Translation.entity.js +2 -1
- package/dist/domain/entities/index.js +2 -1
- package/dist/domain/index.js +18 -2
- package/dist/domain/interfaces/ITranslationService.interface.js +2 -1
- package/dist/domain/interfaces/index.js +2 -1
- package/dist/infrastructure/constants/api.constants.js +7 -4
- package/dist/infrastructure/constants/index.js +12 -2
- package/dist/infrastructure/constants/languages.constants.js +6 -3
- package/dist/infrastructure/index.js +25 -0
- package/dist/infrastructure/services/GoogleTranslate.service.js +123 -39
- package/dist/infrastructure/services/index.js +16 -3
- package/dist/infrastructure/utils/index.js +16 -0
- package/dist/infrastructure/utils/rateLimit.util.js +5 -1
- package/dist/infrastructure/utils/textValidator.util.js +18 -10
- package/dist/scripts/index.js +20 -4
- package/dist/scripts/setup.js +23 -15
- package/dist/scripts/sync.js +39 -30
- package/dist/scripts/translate.js +28 -20
- package/dist/scripts/utils/file-parser.js +19 -11
- package/dist/scripts/utils/index.js +21 -5
- package/dist/scripts/utils/key-detector.js +6 -2
- package/dist/scripts/utils/key-extractor.js +17 -11
- package/dist/scripts/utils/object-helper.js +6 -2
- package/dist/scripts/utils/sync-helper.js +6 -2
- package/package.json +1 -1
- package/src/infrastructure/services/GoogleTranslate.service.ts +123 -24
- package/src/scripts/setup.ts +2 -1
- package/src/scripts/sync.ts +2 -1
- package/src/scripts/translate.ts +2 -1
package/dist/scripts/sync.js
CHANGED
|
@@ -1,33 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
2
3
|
/**
|
|
3
4
|
* Sync Translations Script
|
|
4
5
|
* Synchronizes translation keys from en-US.ts to all other language files
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.syncLanguageFile = syncLanguageFile;
|
|
12
|
+
exports.syncTranslations = syncTranslations;
|
|
13
|
+
exports.runSyncTranslations = runSyncTranslations;
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const file_parser_1 = require("./utils/file-parser");
|
|
17
|
+
const sync_helper_1 = require("./utils/sync-helper");
|
|
18
|
+
const key_detector_1 = require("./utils/key-detector");
|
|
19
|
+
const key_extractor_1 = require("./utils/key-extractor");
|
|
20
|
+
const object_helper_1 = require("./utils/object-helper");
|
|
21
|
+
function syncLanguageFile(enUSPath, targetPath, langCode) {
|
|
22
|
+
const enUS = (0, file_parser_1.parseTypeScriptFile)(enUSPath);
|
|
15
23
|
let target;
|
|
16
24
|
try {
|
|
17
|
-
target = parseTypeScriptFile(targetPath);
|
|
25
|
+
target = (0, file_parser_1.parseTypeScriptFile)(targetPath);
|
|
18
26
|
}
|
|
19
27
|
catch {
|
|
20
28
|
target = {};
|
|
21
29
|
}
|
|
22
|
-
const detectedNewKeys = detectNewKeys(enUS, target);
|
|
30
|
+
const detectedNewKeys = (0, key_detector_1.detectNewKeys)(enUS, target);
|
|
23
31
|
const addStats = { added: 0, newKeys: [] };
|
|
24
32
|
const removeStats = { removed: 0, removedKeys: [] };
|
|
25
|
-
addMissingKeys(enUS, target, addStats);
|
|
26
|
-
removeExtraKeys(enUS, target, removeStats);
|
|
33
|
+
(0, sync_helper_1.addMissingKeys)(enUS, target, addStats);
|
|
34
|
+
(0, sync_helper_1.removeExtraKeys)(enUS, target, removeStats);
|
|
27
35
|
const changed = (addStats.added || 0) > 0 || (removeStats.removed || 0) > 0;
|
|
28
36
|
if (changed) {
|
|
29
|
-
const content = generateTypeScriptContent(target, langCode);
|
|
30
|
-
|
|
37
|
+
const content = (0, file_parser_1.generateTypeScriptContent)(target, langCode);
|
|
38
|
+
fs_1.default.writeFileSync(targetPath, content);
|
|
31
39
|
}
|
|
32
40
|
return {
|
|
33
41
|
added: addStats.added,
|
|
@@ -42,9 +50,9 @@ function processExtraction(srcDir, enUSPath) {
|
|
|
42
50
|
if (!srcDir)
|
|
43
51
|
return;
|
|
44
52
|
console.log(`🔍 Scanning source code and dependencies: ${srcDir}...`);
|
|
45
|
-
const usedKeyMap = extractUsedKeys(srcDir);
|
|
53
|
+
const usedKeyMap = (0, key_extractor_1.extractUsedKeys)(srcDir);
|
|
46
54
|
console.log(` Found ${usedKeyMap.size} unique keys.`);
|
|
47
|
-
const oldEnUS = parseTypeScriptFile(enUSPath);
|
|
55
|
+
const oldEnUS = (0, file_parser_1.parseTypeScriptFile)(enUSPath);
|
|
48
56
|
const newEnUS = {};
|
|
49
57
|
let addedCount = 0;
|
|
50
58
|
for (const [key, defaultValue] of usedKeyMap) {
|
|
@@ -58,34 +66,34 @@ function processExtraction(srcDir, enUSPath) {
|
|
|
58
66
|
// We treat it as "not translated" if the value is exactly the key string
|
|
59
67
|
const isActuallyTranslated = typeof existingValue === 'string' && existingValue !== key;
|
|
60
68
|
const valueToSet = isActuallyTranslated ? existingValue : defaultValue;
|
|
61
|
-
if (setDeep(newEnUS, key, valueToSet)) {
|
|
69
|
+
if ((0, object_helper_1.setDeep)(newEnUS, key, valueToSet)) {
|
|
62
70
|
if (!isActuallyTranslated)
|
|
63
71
|
addedCount++;
|
|
64
72
|
}
|
|
65
73
|
}
|
|
66
|
-
const oldTotal = countKeys(oldEnUS);
|
|
67
|
-
const newTotal = countKeys(newEnUS);
|
|
74
|
+
const oldTotal = (0, object_helper_1.countKeys)(oldEnUS);
|
|
75
|
+
const newTotal = (0, object_helper_1.countKeys)(newEnUS);
|
|
68
76
|
const removedCount = Math.max(0, oldTotal - (newTotal - addedCount));
|
|
69
77
|
console.log(` ✨ Optimized en-US.ts: ${addedCount} keys populated/updated, pruned ${removedCount} unused.`);
|
|
70
|
-
const content = generateTypeScriptContent(newEnUS, 'en-US');
|
|
71
|
-
|
|
78
|
+
const content = (0, file_parser_1.generateTypeScriptContent)(newEnUS, 'en-US');
|
|
79
|
+
fs_1.default.writeFileSync(enUSPath, content);
|
|
72
80
|
}
|
|
73
|
-
|
|
81
|
+
function syncTranslations(options) {
|
|
74
82
|
const { targetDir, srcDir } = options;
|
|
75
|
-
const localesDir =
|
|
76
|
-
const enUSPath =
|
|
77
|
-
if (!
|
|
83
|
+
const localesDir = path_1.default.resolve(process.cwd(), targetDir);
|
|
84
|
+
const enUSPath = path_1.default.join(localesDir, 'en-US.ts');
|
|
85
|
+
if (!fs_1.default.existsSync(localesDir) || !fs_1.default.existsSync(enUSPath)) {
|
|
78
86
|
console.error(`❌ Localization files not found in: ${localesDir}`);
|
|
79
87
|
return false;
|
|
80
88
|
}
|
|
81
89
|
processExtraction(srcDir, enUSPath);
|
|
82
|
-
const files =
|
|
90
|
+
const files = fs_1.default.readdirSync(localesDir)
|
|
83
91
|
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
|
|
84
92
|
.sort();
|
|
85
93
|
console.log(`📊 Languages to sync: ${files.length}\n`);
|
|
86
94
|
files.forEach(file => {
|
|
87
95
|
const langCode = file.replace('.ts', '');
|
|
88
|
-
const targetPath =
|
|
96
|
+
const targetPath = path_1.default.join(localesDir, file);
|
|
89
97
|
const result = syncLanguageFile(enUSPath, targetPath, langCode);
|
|
90
98
|
if (result.changed) {
|
|
91
99
|
console.log(` 🌍 ${langCode}: ✏️ +${result.added || 0} keys, -${result.removed || 0} keys`);
|
|
@@ -95,12 +103,13 @@ export function syncTranslations(options) {
|
|
|
95
103
|
return true;
|
|
96
104
|
}
|
|
97
105
|
// CLI interface
|
|
98
|
-
|
|
106
|
+
function runSyncTranslations() {
|
|
99
107
|
const targetDir = process.argv[2] || 'src/infrastructure/locales';
|
|
100
108
|
const srcDir = process.argv[3];
|
|
101
109
|
console.log('🚀 Starting translation synchronization...\n');
|
|
102
110
|
syncTranslations({ targetDir, srcDir });
|
|
103
111
|
}
|
|
104
|
-
if
|
|
112
|
+
// Check if this file is being run directly
|
|
113
|
+
if (require.main === module || process.argv[1].endsWith('/sync.js') || process.argv[1].endsWith('\\sync.js')) {
|
|
105
114
|
runSyncTranslations();
|
|
106
115
|
}
|
|
@@ -1,45 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
2
3
|
/**
|
|
3
4
|
* Translate Missing Script
|
|
4
5
|
* Automatically translates missing strings using Google Translate
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.translateMissing = translateMissing;
|
|
12
|
+
exports.runTranslateMissing = runTranslateMissing;
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const file_parser_1 = require("./utils/file-parser");
|
|
16
|
+
const services_1 = require("../infrastructure/services");
|
|
10
17
|
// Width of terminal line to clear for progress updates
|
|
11
18
|
const PROGRESS_LINE_WIDTH = 80;
|
|
12
|
-
|
|
19
|
+
async function translateMissing(options) {
|
|
13
20
|
const { targetDir, srcDir, skipSync = false } = options;
|
|
14
21
|
// Initialize the translation service
|
|
15
|
-
googleTranslateService.initialize({
|
|
22
|
+
services_1.googleTranslateService.initialize({
|
|
16
23
|
minDelay: 100,
|
|
17
24
|
maxRetries: 3,
|
|
18
25
|
timeout: 10000,
|
|
19
26
|
});
|
|
20
|
-
const localesDir =
|
|
21
|
-
const enUSPath =
|
|
22
|
-
if (!
|
|
27
|
+
const localesDir = path_1.default.resolve(process.cwd(), targetDir);
|
|
28
|
+
const enUSPath = path_1.default.join(localesDir, 'en-US.ts');
|
|
29
|
+
if (!fs_1.default.existsSync(localesDir) || !fs_1.default.existsSync(enUSPath)) {
|
|
23
30
|
console.error(`❌ Localization files not found in: ${localesDir}`);
|
|
24
31
|
return;
|
|
25
32
|
}
|
|
26
|
-
const files =
|
|
33
|
+
const files = fs_1.default.readdirSync(localesDir)
|
|
27
34
|
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
|
|
28
35
|
.sort();
|
|
29
36
|
console.log(`\n📊 Languages to translate: ${files.length}\n`);
|
|
30
|
-
const enUS = parseTypeScriptFile(enUSPath);
|
|
37
|
+
const enUS = (0, file_parser_1.parseTypeScriptFile)(enUSPath);
|
|
31
38
|
for (const file of files) {
|
|
32
39
|
const langCode = file.replace('.ts', '');
|
|
33
40
|
// Skip English variants
|
|
34
|
-
const targetLang = getTargetLanguage(langCode);
|
|
41
|
+
const targetLang = (0, services_1.getTargetLanguage)(langCode);
|
|
35
42
|
if (!targetLang || targetLang === 'en') {
|
|
36
43
|
console.log(`⏭️ Skipping ${langCode} (English variant)`);
|
|
37
44
|
continue;
|
|
38
45
|
}
|
|
39
|
-
const langName = getLanguageDisplayName(langCode);
|
|
46
|
+
const langName = (0, services_1.getLanguageDisplayName)(langCode);
|
|
40
47
|
console.log(`🌍 Translating ${langCode} (${langName})...`);
|
|
41
|
-
const targetPath =
|
|
42
|
-
const target = parseTypeScriptFile(targetPath);
|
|
48
|
+
const targetPath = path_1.default.join(localesDir, file);
|
|
49
|
+
const target = (0, file_parser_1.parseTypeScriptFile)(targetPath);
|
|
43
50
|
const stats = {
|
|
44
51
|
totalCount: 0,
|
|
45
52
|
successCount: 0,
|
|
@@ -47,12 +54,12 @@ export async function translateMissing(options) {
|
|
|
47
54
|
skippedCount: 0,
|
|
48
55
|
translatedKeys: [],
|
|
49
56
|
};
|
|
50
|
-
await googleTranslateService.translateObject(enUS, target, targetLang, '', stats);
|
|
57
|
+
await services_1.googleTranslateService.translateObject(enUS, target, targetLang, '', stats);
|
|
51
58
|
// Clear progress line
|
|
52
59
|
process.stdout.write('\r' + ' '.repeat(PROGRESS_LINE_WIDTH) + '\r');
|
|
53
60
|
if (stats.successCount > 0) {
|
|
54
|
-
const content = generateTypeScriptContent(target, langCode);
|
|
55
|
-
|
|
61
|
+
const content = (0, file_parser_1.generateTypeScriptContent)(target, langCode);
|
|
62
|
+
fs_1.default.writeFileSync(targetPath, content);
|
|
56
63
|
console.log(` ✅ Successfully translated ${stats.successCount} keys:`);
|
|
57
64
|
// Detailed logging of translated keys
|
|
58
65
|
const displayCount = Math.min(stats.translatedKeys.length, 15);
|
|
@@ -70,7 +77,7 @@ export async function translateMissing(options) {
|
|
|
70
77
|
console.log('\n✅ All translations completed!');
|
|
71
78
|
}
|
|
72
79
|
// CLI interface
|
|
73
|
-
|
|
80
|
+
function runTranslateMissing() {
|
|
74
81
|
const args = process.argv.slice(2).filter(arg => !arg.startsWith('--'));
|
|
75
82
|
const targetDir = args[0] || 'src/infrastructure/locales';
|
|
76
83
|
const srcDir = args[1];
|
|
@@ -81,6 +88,7 @@ export function runTranslateMissing() {
|
|
|
81
88
|
process.exit(1);
|
|
82
89
|
});
|
|
83
90
|
}
|
|
84
|
-
if
|
|
91
|
+
// Check if this file is being run directly
|
|
92
|
+
if (require.main === module || process.argv[1].endsWith('/translate.js') || process.argv[1].endsWith('\\translate.js')) {
|
|
85
93
|
runTranslateMissing();
|
|
86
94
|
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseTypeScriptFile = parseTypeScriptFile;
|
|
7
|
+
exports.stringifyValue = stringifyValue;
|
|
8
|
+
exports.generateTypeScriptContent = generateTypeScriptContent;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const textValidator_util_1 = require("../../infrastructure/utils/textValidator.util");
|
|
4
12
|
/**
|
|
5
13
|
* File Parser
|
|
6
14
|
* Parse and generate TypeScript translation files
|
|
7
15
|
*/
|
|
8
|
-
|
|
9
|
-
const content =
|
|
16
|
+
function parseTypeScriptFile(filePath) {
|
|
17
|
+
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
10
18
|
// Match: export default { ... } OR export const NAME = { ... }
|
|
11
19
|
const match = content.match(/export\s+(?:default|const\s+\w+\s*=)\s*(\{[\s\S]*\});?\s*$/);
|
|
12
20
|
if (!match) {
|
|
@@ -21,13 +29,13 @@ export function parseTypeScriptFile(filePath) {
|
|
|
21
29
|
}
|
|
22
30
|
catch (error) {
|
|
23
31
|
// File might be a barrel file with named imports
|
|
24
|
-
const dir =
|
|
32
|
+
const dir = path_1.default.dirname(filePath);
|
|
25
33
|
const importMatches = [...content.matchAll(/import\s*\{\s*(\w+)\s*\}\s*from\s*["']\.\/(\w+)["']/g)];
|
|
26
34
|
if (importMatches.length > 0) {
|
|
27
35
|
const result = {};
|
|
28
36
|
for (const [, varName, moduleName] of importMatches) {
|
|
29
|
-
const subFilePath =
|
|
30
|
-
if (
|
|
37
|
+
const subFilePath = path_1.default.join(dir, `${moduleName}.ts`);
|
|
38
|
+
if (fs_1.default.existsSync(subFilePath)) {
|
|
31
39
|
try {
|
|
32
40
|
result[varName] = parseTypeScriptFile(subFilePath);
|
|
33
41
|
}
|
|
@@ -44,7 +52,7 @@ export function parseTypeScriptFile(filePath) {
|
|
|
44
52
|
}
|
|
45
53
|
return {};
|
|
46
54
|
}
|
|
47
|
-
|
|
55
|
+
function stringifyValue(value, indent = 2) {
|
|
48
56
|
if (typeof value === 'string') {
|
|
49
57
|
const escaped = value
|
|
50
58
|
.replace(/\\/g, '\\\\')
|
|
@@ -76,8 +84,8 @@ export function stringifyValue(value, indent = 2) {
|
|
|
76
84
|
}
|
|
77
85
|
return String(value);
|
|
78
86
|
}
|
|
79
|
-
|
|
80
|
-
const langName = getLanguageDisplayName(langCode);
|
|
87
|
+
function generateTypeScriptContent(obj, langCode) {
|
|
88
|
+
const langName = (0, textValidator_util_1.getLanguageDisplayName)(langCode);
|
|
81
89
|
const isBase = langCode === 'en-US';
|
|
82
90
|
const objString = stringifyValue(obj, 0);
|
|
83
91
|
return `/**
|
|
@@ -1,9 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Scripts Utils
|
|
3
4
|
* Utility functions for translation scripts
|
|
4
5
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
__exportStar(require("./file-parser"), exports);
|
|
22
|
+
__exportStar(require("./key-detector"), exports);
|
|
23
|
+
__exportStar(require("./key-extractor"), exports);
|
|
24
|
+
__exportStar(require("./object-helper"), exports);
|
|
25
|
+
__exportStar(require("./sync-helper"), exports);
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Key Detector
|
|
3
4
|
* Detects new, missing, and removed keys between source and target objects
|
|
4
5
|
*/
|
|
5
|
-
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.detectNewKeys = detectNewKeys;
|
|
8
|
+
exports.detectMissingKeys = detectMissingKeys;
|
|
9
|
+
function detectNewKeys(sourceObj, targetObj, path = '', newKeys = []) {
|
|
6
10
|
for (const key in sourceObj) {
|
|
7
11
|
const currentPath = path ? `${path}.${key}` : key;
|
|
8
12
|
const sourceValue = sourceObj[key];
|
|
@@ -20,7 +24,7 @@ export function detectNewKeys(sourceObj, targetObj, path = '', newKeys = []) {
|
|
|
20
24
|
}
|
|
21
25
|
return newKeys;
|
|
22
26
|
}
|
|
23
|
-
|
|
27
|
+
function detectMissingKeys(sourceObj, targetObj, path = '', missingKeys = []) {
|
|
24
28
|
for (const key in targetObj) {
|
|
25
29
|
const currentPath = path ? `${path}.${key}` : key;
|
|
26
30
|
if (!Object.prototype.hasOwnProperty.call(sourceObj, key)) {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.extractUsedKeys = extractUsedKeys;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
3
9
|
/**
|
|
4
10
|
* Generic Key Extractor
|
|
5
11
|
* Scans source code for i18n translation keys
|
|
@@ -61,34 +67,34 @@ function extractFromFile(content, keyMap) {
|
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
function walkDirectory(dir, keyMap, skipDirs = ['node_modules', '.expo', '.git', 'build', 'ios', 'android', 'assets', 'locales', '__tests__']) {
|
|
64
|
-
if (!
|
|
70
|
+
if (!fs_1.default.existsSync(dir))
|
|
65
71
|
return;
|
|
66
|
-
const files =
|
|
72
|
+
const files = fs_1.default.readdirSync(dir);
|
|
67
73
|
for (const file of files) {
|
|
68
|
-
const fullPath =
|
|
69
|
-
const stat =
|
|
74
|
+
const fullPath = path_1.default.join(dir, file);
|
|
75
|
+
const stat = fs_1.default.statSync(fullPath);
|
|
70
76
|
if (stat.isDirectory()) {
|
|
71
77
|
if (!skipDirs.includes(file)) {
|
|
72
78
|
walkDirectory(fullPath, keyMap, skipDirs);
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
81
|
else if (/\.(ts|tsx|js|jsx)$/.test(file)) {
|
|
76
|
-
const content =
|
|
82
|
+
const content = fs_1.default.readFileSync(fullPath, 'utf8');
|
|
77
83
|
extractFromFile(content, keyMap);
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
|
-
|
|
87
|
+
function extractUsedKeys(srcDir) {
|
|
82
88
|
const keyMap = new Map();
|
|
83
89
|
if (!srcDir)
|
|
84
90
|
return keyMap;
|
|
85
91
|
const projectRoot = process.cwd();
|
|
86
|
-
const absoluteSrcDir =
|
|
92
|
+
const absoluteSrcDir = path_1.default.resolve(projectRoot, srcDir);
|
|
87
93
|
// Scan project source
|
|
88
94
|
walkDirectory(absoluteSrcDir, keyMap);
|
|
89
95
|
// Scan @umituz packages for shared keys
|
|
90
|
-
const packagesDir =
|
|
91
|
-
if (
|
|
96
|
+
const packagesDir = path_1.default.resolve(projectRoot, 'node_modules/@umituz');
|
|
97
|
+
if (fs_1.default.existsSync(packagesDir)) {
|
|
92
98
|
walkDirectory(packagesDir, keyMap);
|
|
93
99
|
}
|
|
94
100
|
return keyMap;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Object Helper
|
|
3
4
|
* Utilities for deep object manipulation
|
|
4
5
|
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.setDeep = setDeep;
|
|
8
|
+
exports.countKeys = countKeys;
|
|
5
9
|
/**
|
|
6
10
|
* Set a value in a nested object, creating intermediate objects if necessary
|
|
7
11
|
* Returns true if the key was newly added, false if it already existed
|
|
8
12
|
*/
|
|
9
|
-
|
|
13
|
+
function setDeep(obj, path, value) {
|
|
10
14
|
const keys = path.split('.');
|
|
11
15
|
let current = obj;
|
|
12
16
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
@@ -26,7 +30,7 @@ export function setDeep(obj, path, value) {
|
|
|
26
30
|
/**
|
|
27
31
|
* Count all leaf keys in a nested object
|
|
28
32
|
*/
|
|
29
|
-
|
|
33
|
+
function countKeys(obj) {
|
|
30
34
|
let count = 0;
|
|
31
35
|
const walk = (o) => {
|
|
32
36
|
for (const k in o) {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Sync Helper
|
|
3
4
|
* Helper functions for synchronizing translation keys
|
|
4
5
|
*/
|
|
5
|
-
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.addMissingKeys = addMissingKeys;
|
|
8
|
+
exports.removeExtraKeys = removeExtraKeys;
|
|
9
|
+
function addMissingKeys(sourceObj, targetObj, stats = {}) {
|
|
6
10
|
stats.added = stats.added || 0;
|
|
7
11
|
stats.newKeys = stats.newKeys || [];
|
|
8
12
|
for (const key in sourceObj) {
|
|
@@ -24,7 +28,7 @@ export function addMissingKeys(sourceObj, targetObj, stats = {}) {
|
|
|
24
28
|
}
|
|
25
29
|
return stats;
|
|
26
30
|
}
|
|
27
|
-
|
|
31
|
+
function removeExtraKeys(sourceObj, targetObj, stats = {}) {
|
|
28
32
|
stats.removed = stats.removed || 0;
|
|
29
33
|
stats.removedKeys = stats.removedKeys || [];
|
|
30
34
|
for (const key in targetObj) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-google-translate",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Google Translate integration for React Native apps with rate limiting, batch translation, and TypeScript support",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -117,6 +117,8 @@ class GoogleTranslateService implements ITranslationService {
|
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// Use batch API call for better performance
|
|
121
|
+
const batchSize = 50;
|
|
120
122
|
const stats: TranslationStats = {
|
|
121
123
|
totalCount: requests.length,
|
|
122
124
|
successCount: 0,
|
|
@@ -125,22 +127,52 @@ class GoogleTranslateService implements ITranslationService {
|
|
|
125
127
|
translatedKeys: [],
|
|
126
128
|
};
|
|
127
129
|
|
|
128
|
-
for (
|
|
129
|
-
const
|
|
130
|
+
for (let i = 0; i < requests.length; i += batchSize) {
|
|
131
|
+
const batch = requests.slice(i, i + batchSize);
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
// Wait for rate limiter once per batch instead of once per request
|
|
134
|
+
await this.rateLimiter!.waitForSlot();
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const translations = await this.callTranslateAPIBatch(batch, requests[0].targetLanguage);
|
|
138
|
+
|
|
139
|
+
for (let j = 0; j < batch.length; j++) {
|
|
140
|
+
const request = batch[j];
|
|
141
|
+
const translatedText = translations[j];
|
|
142
|
+
|
|
143
|
+
if (translatedText && translatedText !== request.text) {
|
|
144
|
+
stats.successCount++;
|
|
145
|
+
stats.translatedKeys.push({
|
|
146
|
+
key: request.text,
|
|
147
|
+
from: request.text,
|
|
148
|
+
to: translatedText,
|
|
149
|
+
});
|
|
150
|
+
} else if (!translatedText) {
|
|
151
|
+
stats.failureCount++;
|
|
152
|
+
} else {
|
|
153
|
+
stats.skippedCount++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
// Fallback to individual requests on batch failure
|
|
158
|
+
for (const request of batch) {
|
|
159
|
+
const result = await this.translate(request);
|
|
160
|
+
|
|
161
|
+
if (result.success) {
|
|
162
|
+
if (result.translatedText === result.originalText) {
|
|
163
|
+
stats.skippedCount++;
|
|
164
|
+
} else {
|
|
165
|
+
stats.successCount++;
|
|
166
|
+
stats.translatedKeys.push({
|
|
167
|
+
key: request.text,
|
|
168
|
+
from: result.originalText,
|
|
169
|
+
to: result.translatedText,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
stats.failureCount++;
|
|
174
|
+
}
|
|
141
175
|
}
|
|
142
|
-
} else {
|
|
143
|
-
stats.failureCount++;
|
|
144
176
|
}
|
|
145
177
|
}
|
|
146
178
|
|
|
@@ -174,6 +206,9 @@ class GoogleTranslateService implements ITranslationService {
|
|
|
174
206
|
|
|
175
207
|
const keys = Object.keys(sourceObject);
|
|
176
208
|
|
|
209
|
+
// Collect all texts to translate first
|
|
210
|
+
const textsToTranslate: Array<{key: string; enValue: string; currentPath: string}> = [];
|
|
211
|
+
|
|
177
212
|
for (const key of keys) {
|
|
178
213
|
const enValue = sourceObject[key];
|
|
179
214
|
const targetValue = targetObject[key];
|
|
@@ -197,28 +232,43 @@ class GoogleTranslateService implements ITranslationService {
|
|
|
197
232
|
stats.totalCount++;
|
|
198
233
|
|
|
199
234
|
if (needsTranslation(targetValue, enValue)) {
|
|
200
|
-
|
|
201
|
-
|
|
235
|
+
textsToTranslate.push({key, enValue, currentPath});
|
|
236
|
+
} else {
|
|
237
|
+
stats.skippedCount++;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Batch translate all texts at once
|
|
243
|
+
if (textsToTranslate.length > 0) {
|
|
244
|
+
const batchSize = 50; // Google Translate API can handle ~50 texts at once
|
|
245
|
+
for (let i = 0; i < textsToTranslate.length; i += batchSize) {
|
|
246
|
+
const batch = textsToTranslate.slice(i, i + batchSize);
|
|
247
|
+
const results = await this.translateBatch(
|
|
248
|
+
batch.map(item => ({
|
|
249
|
+
text: item.enValue,
|
|
202
250
|
targetLanguage,
|
|
203
|
-
}
|
|
251
|
+
}))
|
|
252
|
+
);
|
|
204
253
|
|
|
205
|
-
|
|
254
|
+
// Apply translations
|
|
255
|
+
for (let j = 0; j < batch.length; j++) {
|
|
256
|
+
const {key, enValue, currentPath} = batch[j];
|
|
257
|
+
const translatedKey = results.translatedKeys[j];
|
|
206
258
|
|
|
207
|
-
if (
|
|
208
|
-
targetObject[key] =
|
|
259
|
+
if (translatedKey && translatedKey.from !== enValue) {
|
|
260
|
+
targetObject[key] = translatedKey.to;
|
|
209
261
|
stats.successCount++;
|
|
210
262
|
stats.translatedKeys.push({
|
|
211
263
|
key: currentPath,
|
|
212
264
|
from: enValue,
|
|
213
|
-
to:
|
|
265
|
+
to: translatedKey.to,
|
|
214
266
|
});
|
|
215
|
-
} else if (!
|
|
267
|
+
} else if (!translatedKey) {
|
|
216
268
|
stats.failureCount++;
|
|
217
269
|
} else {
|
|
218
270
|
stats.skippedCount++;
|
|
219
271
|
}
|
|
220
|
-
} else {
|
|
221
|
-
stats.skippedCount++;
|
|
222
272
|
}
|
|
223
273
|
}
|
|
224
274
|
}
|
|
@@ -264,6 +314,55 @@ class GoogleTranslateService implements ITranslationService {
|
|
|
264
314
|
clearTimeout(timeoutId);
|
|
265
315
|
}
|
|
266
316
|
}
|
|
317
|
+
|
|
318
|
+
private async callTranslateAPIBatch(
|
|
319
|
+
requests: TranslationRequest[],
|
|
320
|
+
targetLanguage: string,
|
|
321
|
+
sourceLanguage: string = "en"
|
|
322
|
+
): Promise<string[]> {
|
|
323
|
+
const timeout = this.config?.timeout || DEFAULT_TIMEOUT;
|
|
324
|
+
|
|
325
|
+
// Build batch request URL
|
|
326
|
+
const queryParts = requests.map(req => encodeURIComponent(req.text)).join('&q=');
|
|
327
|
+
const url = `${GOOGLE_TRANSLATE_API_URL}?client=gtx&sl=${sourceLanguage}&tl=${targetLanguage}&dt=t&q=${queryParts}`;
|
|
328
|
+
|
|
329
|
+
const controller = new AbortController();
|
|
330
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const response = await fetch(url, {
|
|
334
|
+
signal: controller.signal,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
if (!response.ok) {
|
|
338
|
+
throw new Error(`Batch API request failed: ${response.status}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const data = await response.json();
|
|
342
|
+
|
|
343
|
+
// Extract translations from batch response
|
|
344
|
+
const translations: string[] = [];
|
|
345
|
+
|
|
346
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
347
|
+
for (let i = 0; i < requests.length; i++) {
|
|
348
|
+
if (
|
|
349
|
+
Array.isArray(data[i]) &&
|
|
350
|
+
data[i].length > 0 &&
|
|
351
|
+
Array.isArray(data[i][0]) &&
|
|
352
|
+
typeof data[i][0][0] === "string"
|
|
353
|
+
) {
|
|
354
|
+
translations.push(data[i][0][0]);
|
|
355
|
+
} else {
|
|
356
|
+
translations.push(requests[i].text); // Fallback to original
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return translations;
|
|
362
|
+
} finally {
|
|
363
|
+
clearTimeout(timeoutId);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
267
366
|
}
|
|
268
367
|
|
|
269
368
|
export const googleTranslateService = new GoogleTranslateService();
|