@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 CHANGED
@@ -47,8 +47,6 @@ This package follows Domain-Driven Design principles:
47
47
 
48
48
  ```bash
49
49
  npm install @umituz/react-native-localization
50
- # or
51
- yarn add @umituz/react-native-localization
52
50
  ```
53
51
 
54
52
  ### Peer Dependencies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-localization",
3
- "version": "3.7.12",
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": "latest",
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 and dependencies for i18n keys and their default values
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
- function beautify(key) {
10
- const parts = key.split('.');
11
- const lastPart = parts[parts.length - 1];
12
- return lastPart
13
- .replace(/[_-]/g, ' ')
14
- .replace(/([A-Z])/g, ' $1')
15
- .replace(/^./, str => str.toUpperCase())
16
- .trim();
17
- }
18
-
19
- export function extractUsedKeys(srcDir) {
20
- const keyMap = new Map();
21
- if (!srcDir) return keyMap;
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
- const projectRoot = process.cwd();
24
- const absoluteSrcDir = path.resolve(projectRoot, srcDir);
25
-
26
- // 1. PROJECT SPECIFIC: Read Scenarios and Categories
27
- const enumFiles = [
28
- { name: 'domains/scenarios/domain/Scenario.ts', type: 'scenario' },
29
- { name: 'domains/scenarios/domain/CategoryHierarchy.ts', type: 'category' }
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
- function walk(dir) {
92
- if (!fs.existsSync(dir)) return;
93
- const files = fs.readdirSync(dir);
94
- for (const file of files) {
95
- const fullPath = path.join(dir, file);
96
- const stat = fs.statSync(fullPath);
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
- if (stat.isDirectory()) {
99
- const skipDirs = ['node_modules', '.expo', '.git', 'build', 'ios', 'android', 'assets', 'locales', '__tests__'];
100
- if (!skipDirs.includes(file)) walk(fullPath);
101
- } else if (/\.(ts|tsx|js|jsx)$/.test(file)) {
102
- const content = fs.readFileSync(fullPath, 'utf8');
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
- // Pattern 1: t('key')
105
- const tRegex = /(?:^|\W)t\(['"`]([^'"`]+)['"`]\)/g;
106
- let match;
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
- // Pattern 2: Global Dot-Notation Strings (non-template)
115
- const dotRegex = /['"`]([a-z][a-z0-9_]*\.(?:[a-z0-9_]+\.)+[a-z0-9_]+)['"`]/gi;
116
- while ((match = dotRegex.exec(content)) !== null) {
117
- const key = match[1];
118
- const isIgnoredDomain = IGNORED_DOMAINS.some(ext => key.toLowerCase().endsWith(ext));
119
- const isIgnoredExt = IGNORED_EXTENSIONS.some(ext => key.toLowerCase().endsWith(ext));
120
- if (!isIgnoredDomain && !isIgnoredExt && !key.includes(' ') && !key.includes('${') && !keyMap.has(key)) {
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
- // Pattern 3: Template Literals t(`prefix.${var}`)
126
- const templateRegex = /t\(\`([a-z0-9_.]*?)\.\$\{/g;
127
- while ((match = templateRegex.exec(content)) !== null) {
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
- scanDirs.forEach(dir => walk(dir));
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
  }