@umituz/react-native-google-translate 1.0.4 → 1.0.6
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 +6 -0
- package/dist/domain/entities/Translation.entity.js +6 -0
- package/dist/domain/entities/index.js +6 -0
- package/dist/domain/index.js +24 -0
- package/dist/domain/interfaces/ITranslationService.interface.js +6 -0
- package/dist/domain/interfaces/index.js +6 -0
- package/dist/infrastructure/constants/api.constants.js +11 -0
- package/dist/infrastructure/constants/index.js +16 -0
- package/dist/infrastructure/constants/languages.constants.js +99 -0
- package/dist/infrastructure/index.js +25 -0
- package/dist/infrastructure/services/GoogleTranslate.service.js +203 -0
- package/dist/infrastructure/services/index.js +20 -0
- package/dist/infrastructure/utils/index.js +16 -0
- package/dist/infrastructure/utils/rateLimit.util.js +27 -0
- package/dist/infrastructure/utils/textValidator.util.js +58 -0
- package/dist/scripts/index.js +24 -0
- package/dist/scripts/setup.js +83 -0
- package/dist/scripts/sync.js +115 -0
- package/dist/scripts/translate.js +94 -0
- package/dist/scripts/utils/file-parser.js +98 -0
- package/dist/scripts/utils/index.js +25 -0
- package/dist/scripts/utils/key-detector.js +43 -0
- package/dist/scripts/utils/key-extractor.js +101 -0
- package/dist/scripts/utils/object-helper.js +47 -0
- package/dist/scripts/utils/sync-helper.js +51 -0
- package/package.json +8 -5
- package/src/scripts/setup.ts +4 -3
- package/src/scripts/sync.ts +2 -1
- package/src/scripts/translate.ts +2 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Sync Translations Script
|
|
5
|
+
* Synchronizes translation keys from en-US.ts to all other language files
|
|
6
|
+
*/
|
|
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);
|
|
23
|
+
let target;
|
|
24
|
+
try {
|
|
25
|
+
target = (0, file_parser_1.parseTypeScriptFile)(targetPath);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
target = {};
|
|
29
|
+
}
|
|
30
|
+
const detectedNewKeys = (0, key_detector_1.detectNewKeys)(enUS, target);
|
|
31
|
+
const addStats = { added: 0, newKeys: [] };
|
|
32
|
+
const removeStats = { removed: 0, removedKeys: [] };
|
|
33
|
+
(0, sync_helper_1.addMissingKeys)(enUS, target, addStats);
|
|
34
|
+
(0, sync_helper_1.removeExtraKeys)(enUS, target, removeStats);
|
|
35
|
+
const changed = (addStats.added || 0) > 0 || (removeStats.removed || 0) > 0;
|
|
36
|
+
if (changed) {
|
|
37
|
+
const content = (0, file_parser_1.generateTypeScriptContent)(target, langCode);
|
|
38
|
+
fs_1.default.writeFileSync(targetPath, content);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
added: addStats.added,
|
|
42
|
+
newKeys: addStats.newKeys,
|
|
43
|
+
removed: removeStats.removed,
|
|
44
|
+
removedKeys: removeStats.removedKeys,
|
|
45
|
+
changed,
|
|
46
|
+
detectedNewKeys,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function processExtraction(srcDir, enUSPath) {
|
|
50
|
+
if (!srcDir)
|
|
51
|
+
return;
|
|
52
|
+
console.log(`🔍 Scanning source code and dependencies: ${srcDir}...`);
|
|
53
|
+
const usedKeyMap = (0, key_extractor_1.extractUsedKeys)(srcDir);
|
|
54
|
+
console.log(` Found ${usedKeyMap.size} unique keys.`);
|
|
55
|
+
const oldEnUS = (0, file_parser_1.parseTypeScriptFile)(enUSPath);
|
|
56
|
+
const newEnUS = {};
|
|
57
|
+
let addedCount = 0;
|
|
58
|
+
for (const [key, defaultValue] of usedKeyMap) {
|
|
59
|
+
// Try to keep existing translation if it exists
|
|
60
|
+
const existingValue = key.split('.').reduce((obj, k) => {
|
|
61
|
+
if (obj && typeof obj === 'object' && k in obj) {
|
|
62
|
+
return obj[k];
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}, oldEnUS);
|
|
66
|
+
// We treat it as "not translated" if the value is exactly the key string
|
|
67
|
+
const isActuallyTranslated = typeof existingValue === 'string' && existingValue !== key;
|
|
68
|
+
const valueToSet = isActuallyTranslated ? existingValue : defaultValue;
|
|
69
|
+
if ((0, object_helper_1.setDeep)(newEnUS, key, valueToSet)) {
|
|
70
|
+
if (!isActuallyTranslated)
|
|
71
|
+
addedCount++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const oldTotal = (0, object_helper_1.countKeys)(oldEnUS);
|
|
75
|
+
const newTotal = (0, object_helper_1.countKeys)(newEnUS);
|
|
76
|
+
const removedCount = Math.max(0, oldTotal - (newTotal - addedCount));
|
|
77
|
+
console.log(` ✨ Optimized en-US.ts: ${addedCount} keys populated/updated, pruned ${removedCount} unused.`);
|
|
78
|
+
const content = (0, file_parser_1.generateTypeScriptContent)(newEnUS, 'en-US');
|
|
79
|
+
fs_1.default.writeFileSync(enUSPath, content);
|
|
80
|
+
}
|
|
81
|
+
function syncTranslations(options) {
|
|
82
|
+
const { targetDir, srcDir } = options;
|
|
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)) {
|
|
86
|
+
console.error(`❌ Localization files not found in: ${localesDir}`);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
processExtraction(srcDir, enUSPath);
|
|
90
|
+
const files = fs_1.default.readdirSync(localesDir)
|
|
91
|
+
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
|
|
92
|
+
.sort();
|
|
93
|
+
console.log(`📊 Languages to sync: ${files.length}\n`);
|
|
94
|
+
files.forEach(file => {
|
|
95
|
+
const langCode = file.replace('.ts', '');
|
|
96
|
+
const targetPath = path_1.default.join(localesDir, file);
|
|
97
|
+
const result = syncLanguageFile(enUSPath, targetPath, langCode);
|
|
98
|
+
if (result.changed) {
|
|
99
|
+
console.log(` 🌍 ${langCode}: ✏️ +${result.added || 0} keys, -${result.removed || 0} keys`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
console.log(`\n✅ Synchronization completed!`);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
// CLI interface
|
|
106
|
+
function runSyncTranslations() {
|
|
107
|
+
const targetDir = process.argv[2] || 'src/infrastructure/locales';
|
|
108
|
+
const srcDir = process.argv[3];
|
|
109
|
+
console.log('🚀 Starting translation synchronization...\n');
|
|
110
|
+
syncTranslations({ targetDir, srcDir });
|
|
111
|
+
}
|
|
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')) {
|
|
114
|
+
runSyncTranslations();
|
|
115
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Translate Missing Script
|
|
5
|
+
* Automatically translates missing strings using Google Translate
|
|
6
|
+
*/
|
|
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");
|
|
17
|
+
// Width of terminal line to clear for progress updates
|
|
18
|
+
const PROGRESS_LINE_WIDTH = 80;
|
|
19
|
+
async function translateMissing(options) {
|
|
20
|
+
const { targetDir, srcDir, skipSync = false } = options;
|
|
21
|
+
// Initialize the translation service
|
|
22
|
+
services_1.googleTranslateService.initialize({
|
|
23
|
+
minDelay: 100,
|
|
24
|
+
maxRetries: 3,
|
|
25
|
+
timeout: 10000,
|
|
26
|
+
});
|
|
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)) {
|
|
30
|
+
console.error(`❌ Localization files not found in: ${localesDir}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const files = fs_1.default.readdirSync(localesDir)
|
|
34
|
+
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
|
|
35
|
+
.sort();
|
|
36
|
+
console.log(`\n📊 Languages to translate: ${files.length}\n`);
|
|
37
|
+
const enUS = (0, file_parser_1.parseTypeScriptFile)(enUSPath);
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const langCode = file.replace('.ts', '');
|
|
40
|
+
// Skip English variants
|
|
41
|
+
const targetLang = (0, services_1.getTargetLanguage)(langCode);
|
|
42
|
+
if (!targetLang || targetLang === 'en') {
|
|
43
|
+
console.log(`⏭️ Skipping ${langCode} (English variant)`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const langName = (0, services_1.getLanguageDisplayName)(langCode);
|
|
47
|
+
console.log(`🌍 Translating ${langCode} (${langName})...`);
|
|
48
|
+
const targetPath = path_1.default.join(localesDir, file);
|
|
49
|
+
const target = (0, file_parser_1.parseTypeScriptFile)(targetPath);
|
|
50
|
+
const stats = {
|
|
51
|
+
totalCount: 0,
|
|
52
|
+
successCount: 0,
|
|
53
|
+
failureCount: 0,
|
|
54
|
+
skippedCount: 0,
|
|
55
|
+
translatedKeys: [],
|
|
56
|
+
};
|
|
57
|
+
await services_1.googleTranslateService.translateObject(enUS, target, targetLang, '', stats);
|
|
58
|
+
// Clear progress line
|
|
59
|
+
process.stdout.write('\r' + ' '.repeat(PROGRESS_LINE_WIDTH) + '\r');
|
|
60
|
+
if (stats.successCount > 0) {
|
|
61
|
+
const content = (0, file_parser_1.generateTypeScriptContent)(target, langCode);
|
|
62
|
+
fs_1.default.writeFileSync(targetPath, content);
|
|
63
|
+
console.log(` ✅ Successfully translated ${stats.successCount} keys:`);
|
|
64
|
+
// Detailed logging of translated keys
|
|
65
|
+
const displayCount = Math.min(stats.translatedKeys.length, 15);
|
|
66
|
+
stats.translatedKeys.slice(0, displayCount).forEach(item => {
|
|
67
|
+
console.log(` • ${item.key}: "${item.from}" → "${item.to}"`);
|
|
68
|
+
});
|
|
69
|
+
if (stats.translatedKeys.length > displayCount) {
|
|
70
|
+
console.log(` ... and ${stats.translatedKeys.length - displayCount} more.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.log(` ✨ Already up to date.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
console.log('\n✅ All translations completed!');
|
|
78
|
+
}
|
|
79
|
+
// CLI interface
|
|
80
|
+
function runTranslateMissing() {
|
|
81
|
+
const args = process.argv.slice(2).filter(arg => !arg.startsWith('--'));
|
|
82
|
+
const targetDir = args[0] || 'src/infrastructure/locales';
|
|
83
|
+
const srcDir = args[1];
|
|
84
|
+
const skipSync = process.argv.includes('--no-sync');
|
|
85
|
+
console.log('🚀 Starting integrated translation workflow...');
|
|
86
|
+
translateMissing({ targetDir, srcDir, skipSync }).catch(err => {
|
|
87
|
+
console.error('\n❌ Translation workflow failed:', err.message);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
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')) {
|
|
93
|
+
runTranslateMissing();
|
|
94
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
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");
|
|
12
|
+
/**
|
|
13
|
+
* File Parser
|
|
14
|
+
* Parse and generate TypeScript translation files
|
|
15
|
+
*/
|
|
16
|
+
function parseTypeScriptFile(filePath) {
|
|
17
|
+
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
18
|
+
// Match: export default { ... } OR export const NAME = { ... }
|
|
19
|
+
const match = content.match(/export\s+(?:default|const\s+\w+\s*=)\s*(\{[\s\S]*\});?\s*$/);
|
|
20
|
+
if (!match) {
|
|
21
|
+
throw new Error(`Could not parse TypeScript file: ${filePath}`);
|
|
22
|
+
}
|
|
23
|
+
const objectStr = match[1].replace(/;$/, '');
|
|
24
|
+
try {
|
|
25
|
+
// Basic evaluation for simple objects (safe for generated translation files)
|
|
26
|
+
// Only contains string literals, numbers, booleans, and nested objects
|
|
27
|
+
// eslint-disable-next-line no-eval
|
|
28
|
+
return eval(`(${objectStr})`);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
// File might be a barrel file with named imports
|
|
32
|
+
const dir = path_1.default.dirname(filePath);
|
|
33
|
+
const importMatches = [...content.matchAll(/import\s*\{\s*(\w+)\s*\}\s*from\s*["']\.\/(\w+)["']/g)];
|
|
34
|
+
if (importMatches.length > 0) {
|
|
35
|
+
const result = {};
|
|
36
|
+
for (const [, varName, moduleName] of importMatches) {
|
|
37
|
+
const subFilePath = path_1.default.join(dir, `${moduleName}.ts`);
|
|
38
|
+
if (fs_1.default.existsSync(subFilePath)) {
|
|
39
|
+
try {
|
|
40
|
+
result[varName] = parseTypeScriptFile(subFilePath);
|
|
41
|
+
}
|
|
42
|
+
catch (subError) {
|
|
43
|
+
// Log sub-file parse errors but continue with other files
|
|
44
|
+
console.warn(`Warning: Could not parse sub-file ${subFilePath}: ${subError instanceof Error ? subError.message : 'Unknown error'}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (Object.keys(result).length > 0) {
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
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
|
+
if (Array.isArray(value)) {
|
|
64
|
+
if (value.length === 0)
|
|
65
|
+
return '[]';
|
|
66
|
+
const items = value.map(v => stringifyValue(v, indent + 2));
|
|
67
|
+
return `[${items.join(', ')}]`;
|
|
68
|
+
}
|
|
69
|
+
if (typeof value === 'object' && value !== null) {
|
|
70
|
+
const entries = Object.entries(value);
|
|
71
|
+
if (entries.length === 0) {
|
|
72
|
+
return '{}';
|
|
73
|
+
}
|
|
74
|
+
const spaces = ' '.repeat(indent);
|
|
75
|
+
const innerSpaces = ' '.repeat(indent + 2);
|
|
76
|
+
const entriesStr = entries
|
|
77
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
78
|
+
.map(([k, v]) => {
|
|
79
|
+
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : `"${k}"`;
|
|
80
|
+
return `${innerSpaces}${key}: ${stringifyValue(v, indent + 2)}`;
|
|
81
|
+
})
|
|
82
|
+
.join(',\n');
|
|
83
|
+
return `{\n${entriesStr},\n${spaces}}`;
|
|
84
|
+
}
|
|
85
|
+
return String(value);
|
|
86
|
+
}
|
|
87
|
+
function generateTypeScriptContent(obj, langCode) {
|
|
88
|
+
const langName = (0, textValidator_util_1.getLanguageDisplayName)(langCode);
|
|
89
|
+
const isBase = langCode === 'en-US';
|
|
90
|
+
const objString = stringifyValue(obj, 0);
|
|
91
|
+
return `/**
|
|
92
|
+
* ${langName} Translations
|
|
93
|
+
* ${isBase ? 'Base translations file' : 'Auto-synced from en-US.ts'}
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
export default ${objString};
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Scripts Utils
|
|
4
|
+
* Utility functions for translation scripts
|
|
5
|
+
*/
|
|
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);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Key Detector
|
|
4
|
+
* Detects new, missing, and removed keys between source and target objects
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.detectNewKeys = detectNewKeys;
|
|
8
|
+
exports.detectMissingKeys = detectMissingKeys;
|
|
9
|
+
function detectNewKeys(sourceObj, targetObj, path = '', newKeys = []) {
|
|
10
|
+
for (const key in sourceObj) {
|
|
11
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
12
|
+
const sourceValue = sourceObj[key];
|
|
13
|
+
const targetValue = targetObj[key];
|
|
14
|
+
if (!Object.prototype.hasOwnProperty.call(targetObj, key)) {
|
|
15
|
+
newKeys.push({ path: currentPath, value: sourceValue });
|
|
16
|
+
}
|
|
17
|
+
else if (typeof sourceValue === 'object' &&
|
|
18
|
+
sourceValue !== null &&
|
|
19
|
+
!Array.isArray(sourceValue)) {
|
|
20
|
+
if (typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue)) {
|
|
21
|
+
detectNewKeys(sourceValue, targetValue, currentPath, newKeys);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return newKeys;
|
|
26
|
+
}
|
|
27
|
+
function detectMissingKeys(sourceObj, targetObj, path = '', missingKeys = []) {
|
|
28
|
+
for (const key in targetObj) {
|
|
29
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
30
|
+
if (!Object.prototype.hasOwnProperty.call(sourceObj, key)) {
|
|
31
|
+
missingKeys.push(currentPath);
|
|
32
|
+
}
|
|
33
|
+
else if (typeof sourceObj[key] === 'object' &&
|
|
34
|
+
sourceObj[key] !== null &&
|
|
35
|
+
!Array.isArray(sourceObj[key]) &&
|
|
36
|
+
typeof targetObj[key] === 'object' &&
|
|
37
|
+
targetObj[key] !== null &&
|
|
38
|
+
!Array.isArray(targetObj[key])) {
|
|
39
|
+
detectMissingKeys(sourceObj[key], targetObj[key], currentPath, missingKeys);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return missingKeys;
|
|
43
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
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"));
|
|
9
|
+
/**
|
|
10
|
+
* Generic Key Extractor
|
|
11
|
+
* Scans source code for i18n translation keys
|
|
12
|
+
*/
|
|
13
|
+
const IGNORED_DOMAINS = ['.com', '.org', '.net', '.io', '.co', '.app', '.ai', '.gov', '.edu'];
|
|
14
|
+
const IGNORED_EXTENSIONS = [
|
|
15
|
+
'.ts', '.tsx', '.js', '.jsx', '.json', '.yaml', '.yml',
|
|
16
|
+
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.pdf',
|
|
17
|
+
'.mp4', '.mov', '.avi', '.mp3', '.wav', '.css', '.scss', '.md'
|
|
18
|
+
];
|
|
19
|
+
const IGNORED_LAYOUT_VALS = new Set([
|
|
20
|
+
'center', 'row', 'column', 'flex', 'absolute', 'relative', 'hidden', 'visible',
|
|
21
|
+
'transparent', 'bold', 'normal', 'italic', 'contain', 'cover', 'stretch',
|
|
22
|
+
'top', 'bottom', 'left', 'right', 'middle', 'auto', 'none', 'underline',
|
|
23
|
+
'capitalize', 'uppercase', 'lowercase', 'solid', 'dotted', 'dashed', 'wrap',
|
|
24
|
+
'nowrap', 'space-between', 'space-around', 'flex-start', 'flex-end', 'baseline',
|
|
25
|
+
'react', 'index', 'default', 'string', 'number', 'boolean', 'key', 'id'
|
|
26
|
+
]);
|
|
27
|
+
function extractFromFile(content, keyMap) {
|
|
28
|
+
// Pattern 1: t('key') or t("key")
|
|
29
|
+
const tRegex = /(?:^|\W)t\(['"`]([^'"`]+)['"`]\)/g;
|
|
30
|
+
let match;
|
|
31
|
+
while ((match = tRegex.exec(content)) !== null) {
|
|
32
|
+
const key = match[1];
|
|
33
|
+
if (!key.includes('${') && !keyMap.has(key)) {
|
|
34
|
+
keyMap.set(key, key);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Pattern 2: Dot-notation strings (potential i18n keys)
|
|
38
|
+
const dotRegex = /['"`]([a-z][a-z0-9_]*\.(?:[a-z0-9_]+\.)+[a-z0-9_]+)['"`]/gi;
|
|
39
|
+
while ((match = dotRegex.exec(content)) !== null) {
|
|
40
|
+
const key = match[1];
|
|
41
|
+
const isIgnoredDomain = IGNORED_DOMAINS.some(ext => key.toLowerCase().endsWith(ext));
|
|
42
|
+
const isIgnoredExt = IGNORED_EXTENSIONS.some(ext => key.toLowerCase().endsWith(ext));
|
|
43
|
+
if (!isIgnoredDomain && !isIgnoredExt && !key.includes(' ') && !keyMap.has(key)) {
|
|
44
|
+
keyMap.set(key, key);
|
|
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()))
|
|
58
|
+
continue;
|
|
59
|
+
if (/^[0-9]+$/.test(id))
|
|
60
|
+
continue;
|
|
61
|
+
const dynamicKey = `${prefix}.${id}`;
|
|
62
|
+
if (!keyMap.has(dynamicKey)) {
|
|
63
|
+
keyMap.set(dynamicKey, dynamicKey);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function walkDirectory(dir, keyMap, skipDirs = ['node_modules', '.expo', '.git', 'build', 'ios', 'android', 'assets', 'locales', '__tests__']) {
|
|
70
|
+
if (!fs_1.default.existsSync(dir))
|
|
71
|
+
return;
|
|
72
|
+
const files = fs_1.default.readdirSync(dir);
|
|
73
|
+
for (const file of files) {
|
|
74
|
+
const fullPath = path_1.default.join(dir, file);
|
|
75
|
+
const stat = fs_1.default.statSync(fullPath);
|
|
76
|
+
if (stat.isDirectory()) {
|
|
77
|
+
if (!skipDirs.includes(file)) {
|
|
78
|
+
walkDirectory(fullPath, keyMap, skipDirs);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (/\.(ts|tsx|js|jsx)$/.test(file)) {
|
|
82
|
+
const content = fs_1.default.readFileSync(fullPath, 'utf8');
|
|
83
|
+
extractFromFile(content, keyMap);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function extractUsedKeys(srcDir) {
|
|
88
|
+
const keyMap = new Map();
|
|
89
|
+
if (!srcDir)
|
|
90
|
+
return keyMap;
|
|
91
|
+
const projectRoot = process.cwd();
|
|
92
|
+
const absoluteSrcDir = path_1.default.resolve(projectRoot, srcDir);
|
|
93
|
+
// Scan project source
|
|
94
|
+
walkDirectory(absoluteSrcDir, keyMap);
|
|
95
|
+
// Scan @umituz packages for shared keys
|
|
96
|
+
const packagesDir = path_1.default.resolve(projectRoot, 'node_modules/@umituz');
|
|
97
|
+
if (fs_1.default.existsSync(packagesDir)) {
|
|
98
|
+
walkDirectory(packagesDir, keyMap);
|
|
99
|
+
}
|
|
100
|
+
return keyMap;
|
|
101
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Object Helper
|
|
4
|
+
* Utilities for deep object manipulation
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.setDeep = setDeep;
|
|
8
|
+
exports.countKeys = countKeys;
|
|
9
|
+
/**
|
|
10
|
+
* Set a value in a nested object, creating intermediate objects if necessary
|
|
11
|
+
* Returns true if the key was newly added, false if it already existed
|
|
12
|
+
*/
|
|
13
|
+
function setDeep(obj, path, value) {
|
|
14
|
+
const keys = path.split('.');
|
|
15
|
+
let current = obj;
|
|
16
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
17
|
+
const key = keys[i];
|
|
18
|
+
if (!current[key] || typeof current[key] !== 'object' || Array.isArray(current[key])) {
|
|
19
|
+
current[key] = {};
|
|
20
|
+
}
|
|
21
|
+
current = current[key];
|
|
22
|
+
}
|
|
23
|
+
const lastKey = keys[keys.length - 1];
|
|
24
|
+
if (current[lastKey] === undefined) {
|
|
25
|
+
current[lastKey] = value;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Count all leaf keys in a nested object
|
|
32
|
+
*/
|
|
33
|
+
function countKeys(obj) {
|
|
34
|
+
let count = 0;
|
|
35
|
+
const walk = (o) => {
|
|
36
|
+
for (const k in o) {
|
|
37
|
+
if (typeof o[k] === 'object' && o[k] !== null) {
|
|
38
|
+
walk(o[k]);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
count++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
walk(obj);
|
|
46
|
+
return count;
|
|
47
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Sync Helper
|
|
4
|
+
* Helper functions for synchronizing translation keys
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.addMissingKeys = addMissingKeys;
|
|
8
|
+
exports.removeExtraKeys = removeExtraKeys;
|
|
9
|
+
function addMissingKeys(sourceObj, targetObj, stats = {}) {
|
|
10
|
+
stats.added = stats.added || 0;
|
|
11
|
+
stats.newKeys = stats.newKeys || [];
|
|
12
|
+
for (const key in sourceObj) {
|
|
13
|
+
const sourceValue = sourceObj[key];
|
|
14
|
+
const isNewKey = !Object.prototype.hasOwnProperty.call(targetObj, key);
|
|
15
|
+
if (isNewKey) {
|
|
16
|
+
targetObj[key] = sourceValue;
|
|
17
|
+
stats.added++;
|
|
18
|
+
stats.newKeys.push(key);
|
|
19
|
+
}
|
|
20
|
+
else if (typeof sourceValue === 'object' &&
|
|
21
|
+
sourceValue !== null &&
|
|
22
|
+
!Array.isArray(sourceValue)) {
|
|
23
|
+
if (!targetObj[key] || typeof targetObj[key] !== 'object') {
|
|
24
|
+
targetObj[key] = {};
|
|
25
|
+
}
|
|
26
|
+
addMissingKeys(sourceValue, targetObj[key], stats);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return stats;
|
|
30
|
+
}
|
|
31
|
+
function removeExtraKeys(sourceObj, targetObj, stats = {}) {
|
|
32
|
+
stats.removed = stats.removed || 0;
|
|
33
|
+
stats.removedKeys = stats.removedKeys || [];
|
|
34
|
+
for (const key in targetObj) {
|
|
35
|
+
const isExtraKey = !Object.prototype.hasOwnProperty.call(sourceObj, key);
|
|
36
|
+
if (isExtraKey) {
|
|
37
|
+
delete targetObj[key];
|
|
38
|
+
stats.removed++;
|
|
39
|
+
stats.removedKeys.push(key);
|
|
40
|
+
}
|
|
41
|
+
else if (typeof sourceObj[key] === 'object' &&
|
|
42
|
+
sourceObj[key] !== null &&
|
|
43
|
+
!Array.isArray(sourceObj[key]) &&
|
|
44
|
+
typeof targetObj[key] === 'object' &&
|
|
45
|
+
targetObj[key] !== null &&
|
|
46
|
+
!Array.isArray(targetObj[key])) {
|
|
47
|
+
removeExtraKeys(sourceObj[key], targetObj[key], stats);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return stats;
|
|
51
|
+
}
|
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.6",
|
|
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",
|
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
"./*": "./src/*/index.ts"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
19
|
+
"build:scripts": "tsc -p tsconfig.scripts.json",
|
|
20
|
+
"setup": "node dist/scripts/setup.js",
|
|
21
|
+
"translate": "node dist/scripts/translate.js",
|
|
22
|
+
"sync": "node dist/scripts/sync.js",
|
|
23
|
+
"i18n:setup": "node dist/scripts/setup.js",
|
|
24
|
+
"prepublishOnly": "npm run build:scripts",
|
|
23
25
|
"typecheck": "tsc --noEmit",
|
|
24
26
|
"lint": "echo 'Lint passed'",
|
|
25
27
|
"version:patch": "npm version patch -m 'chore: release v%s'",
|
|
@@ -56,6 +58,7 @@
|
|
|
56
58
|
},
|
|
57
59
|
"files": [
|
|
58
60
|
"src",
|
|
61
|
+
"dist",
|
|
59
62
|
"README.md",
|
|
60
63
|
"LICENSE"
|
|
61
64
|
]
|
package/src/scripts/setup.ts
CHANGED
|
@@ -51,8 +51,8 @@ export function setupLanguages(options: SetupLanguagesOptions): boolean {
|
|
|
51
51
|
.filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/))
|
|
52
52
|
.sort();
|
|
53
53
|
|
|
54
|
-
const imports = [];
|
|
55
|
-
const exports = [];
|
|
54
|
+
const imports: string[] = [];
|
|
55
|
+
const exports: string[] = [];
|
|
56
56
|
|
|
57
57
|
files.forEach(file => {
|
|
58
58
|
const code = file.replace('.ts', '');
|
|
@@ -90,6 +90,7 @@ export function runSetupLanguages(): void {
|
|
|
90
90
|
setupLanguages({ targetDir });
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
if
|
|
93
|
+
// Check if this file is being run directly
|
|
94
|
+
if (require.main === module || process.argv[1].endsWith('/setup.js') || process.argv[1].endsWith('\\setup.js')) {
|
|
94
95
|
runSetupLanguages();
|
|
95
96
|
}
|
package/src/scripts/sync.ts
CHANGED
|
@@ -148,6 +148,7 @@ export function runSyncTranslations(): void {
|
|
|
148
148
|
syncTranslations({ targetDir, srcDir });
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
if
|
|
151
|
+
// Check if this file is being run directly
|
|
152
|
+
if (require.main === module || process.argv[1].endsWith('/sync.js') || process.argv[1].endsWith('\\sync.js')) {
|
|
152
153
|
runSyncTranslations();
|
|
153
154
|
}
|
package/src/scripts/translate.ts
CHANGED
|
@@ -121,6 +121,7 @@ export function runTranslateMissing(): void {
|
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
if
|
|
124
|
+
// Check if this file is being run directly
|
|
125
|
+
if (require.main === module || process.argv[1].endsWith('/translate.js') || process.argv[1].endsWith('\\translate.js')) {
|
|
125
126
|
runTranslateMissing();
|
|
126
127
|
}
|