@umituz/react-native-localization 3.7.12 → 3.7.13
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/README.md +0 -2
- package/package.json +2 -2
- package/src/scripts/utils/key-extractor.js +84 -133
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-localization",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Generic localization system for React Native apps with i18n support",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@react-native-async-storage/async-storage": ">=2.0.0",
|
|
34
|
-
"@umituz/react-native-design-system": "
|
|
34
|
+
"@umituz/react-native-design-system": "*",
|
|
35
35
|
"expo-localization": ">=16.0.0",
|
|
36
36
|
"i18next": ">=23.0.0",
|
|
37
37
|
"react": ">=18.2.0",
|
|
@@ -2,153 +2,104 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Key Extractor
|
|
6
|
-
* Scans source code
|
|
5
|
+
* Generic Key Extractor
|
|
6
|
+
* Scans source code for i18n translation keys
|
|
7
|
+
* NO project-specific logic - works for any React Native app
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
]);
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
enumFiles.forEach(cfg => {
|
|
33
|
-
const fullPath = path.resolve(absoluteSrcDir, cfg.name);
|
|
34
|
-
if (fs.existsSync(fullPath)) {
|
|
35
|
-
const content = fs.readFileSync(fullPath, 'utf8');
|
|
36
|
-
|
|
37
|
-
if (cfg.type === 'scenario') {
|
|
38
|
-
const matches = content.matchAll(/([A-Z0-9_]+)\s*=\s*['"`]([a-z0-9_]+)['"`]/g);
|
|
39
|
-
for (const m of matches) {
|
|
40
|
-
const val = m[2];
|
|
41
|
-
const label = beautify(val);
|
|
42
|
-
keyMap.set(`scenario.${val}.title`, label);
|
|
43
|
-
keyMap.set(`scenario.${val}.description`, label);
|
|
44
|
-
keyMap.set(`scenario.${val}.details`, label);
|
|
45
|
-
keyMap.set(`scenario.${val}.tip`, label);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (cfg.type === 'category') {
|
|
50
|
-
const blockRegex = /\{[\s\S]*?id:\s*(?:MainCategory|SubCategory)\.([A-Z0-9_]+)[\s\S]*?title:\s*['"`](.*?)['"`][\s\S]*?description:\s*['"`](.*?)['"`]/g;
|
|
51
|
-
let blockMatch;
|
|
52
|
-
while ((blockMatch = blockRegex.exec(content)) !== null) {
|
|
53
|
-
const enumName = blockMatch[1];
|
|
54
|
-
const title = blockMatch[2];
|
|
55
|
-
const desc = blockMatch[3];
|
|
56
|
-
|
|
57
|
-
const valRegex = new RegExp(`${enumName}\\s*=\\s*['"\`]([a-z0-9_]+)['"\`]`, 'i');
|
|
58
|
-
const valMatch = content.match(valRegex);
|
|
59
|
-
const stringVal = valMatch ? valMatch[1] : enumName.toLowerCase();
|
|
60
|
-
|
|
61
|
-
keyMap.set(`scenario.main_category.${stringVal}.title`, title);
|
|
62
|
-
keyMap.set(`scenario.main_category.${stringVal}.description`, desc);
|
|
63
|
-
keyMap.set(`scenario.sub_category.${stringVal}.title`, title);
|
|
64
|
-
keyMap.set(`scenario.sub_category.${stringVal}.description`, desc);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
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
|
|
67
33
|
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 2. Scan directories
|
|
71
|
-
const scanDirs = [
|
|
72
|
-
absoluteSrcDir,
|
|
73
|
-
path.resolve(projectRoot, 'node_modules/@umituz')
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
const IGNORED_DOMAINS = ['.com', '.org', '.net', '.io', '.co', '.app', '.ai', '.gov', '.edu'];
|
|
77
|
-
const IGNORED_EXTENSIONS = [
|
|
78
|
-
'.ts', '.tsx', '.js', '.jsx', '.json', '.yaml', '.yml',
|
|
79
|
-
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.pdf',
|
|
80
|
-
'.mp4', '.mov', '.avi', '.mp3', '.wav', '.css', '.scss', '.md'
|
|
81
|
-
];
|
|
82
|
-
const IGNORED_LAYOUT_VALS = new Set([
|
|
83
|
-
'center', 'row', 'column', 'flex', 'absolute', 'relative', 'hidden', 'visible',
|
|
84
|
-
'transparent', 'bold', 'normal', 'italic', 'contain', 'cover', 'stretch',
|
|
85
|
-
'top', 'bottom', 'left', 'right', 'middle', 'auto', 'none', 'underline',
|
|
86
|
-
'capitalize', 'uppercase', 'lowercase', 'solid', 'dotted', 'dashed', 'wrap',
|
|
87
|
-
'nowrap', 'space-between', 'space-around', 'flex-start', 'flex-end', 'baseline',
|
|
88
|
-
'react', 'index', 'default', 'string', 'number', 'boolean', 'key', 'id'
|
|
89
|
-
]);
|
|
34
|
+
}
|
|
90
35
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
+
}
|
|
97
46
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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;
|
|
103
59
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
while ((match = tRegex.exec(content)) !== null) {
|
|
108
|
-
const key = match[1];
|
|
109
|
-
if (!key.includes('${') && !keyMap.has(key)) {
|
|
110
|
-
keyMap.set(key, beautify(key));
|
|
111
|
-
}
|
|
60
|
+
const dynamicKey = `${prefix}.${id}`;
|
|
61
|
+
if (!keyMap.has(dynamicKey)) {
|
|
62
|
+
keyMap.set(dynamicKey, dynamicKey);
|
|
112
63
|
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
113
68
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
keyMap.set(key, beautify(key));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
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);
|
|
124
76
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const prefix = match[1];
|
|
129
|
-
// Find potential option IDs in the same file
|
|
130
|
-
// We look specifically for strings in arrays [...] to reduce false positives
|
|
131
|
-
const arrayMatches = content.matchAll(/\[([\s\S]*?)\]/g);
|
|
132
|
-
for (const arrayMatch of arrayMatches) {
|
|
133
|
-
const inner = arrayMatch[1];
|
|
134
|
-
const idMatches = inner.matchAll(/['"`]([a-z0-9_]{2,40})['"`]/g);
|
|
135
|
-
for (const idMatch of idMatches) {
|
|
136
|
-
const id = idMatch[1];
|
|
137
|
-
if (IGNORED_LAYOUT_VALS.has(id.toLowerCase())) continue;
|
|
138
|
-
if (/^[0-9]+$/.test(id)) continue; // Skip numeric-only strings
|
|
139
|
-
|
|
140
|
-
const dynamicKey = `${prefix}.${id}`;
|
|
141
|
-
if (!keyMap.has(dynamicKey)) {
|
|
142
|
-
keyMap.set(dynamicKey, beautify(id));
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
77
|
+
if (stat.isDirectory()) {
|
|
78
|
+
if (!skipDirs.includes(file)) {
|
|
79
|
+
walkDirectory(fullPath, keyMap, skipDirs);
|
|
147
80
|
}
|
|
81
|
+
} else if (/\.(ts|tsx|js|jsx)$/.test(file)) {
|
|
82
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
83
|
+
extractFromFile(content, keyMap);
|
|
148
84
|
}
|
|
149
85
|
}
|
|
86
|
+
}
|
|
150
87
|
|
|
151
|
-
|
|
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
|
+
}
|
|
152
103
|
|
|
153
104
|
return keyMap;
|
|
154
105
|
}
|