react-native-inapp-inspector 1.1.21 → 1.1.23

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.
Files changed (49) hide show
  1. package/dist/commonjs/components/AnalyticsDetail.js +5 -3
  2. package/dist/commonjs/components/AnalyticsEventCard.d.ts +0 -1
  3. package/dist/commonjs/components/AnalyticsEventCard.js +89 -97
  4. package/dist/commonjs/components/ErrorBoundary.js +17 -8
  5. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -0
  6. package/dist/commonjs/components/Inspector/BundleTab.js +607 -685
  7. package/dist/commonjs/components/Inspector/LogDetail.js +12 -6
  8. package/dist/commonjs/components/Inspector/PerformanceTab.js +195 -254
  9. package/dist/commonjs/components/Inspector/ReduxDetail.js +2 -2
  10. package/dist/commonjs/components/Inspector/ReduxTab.js +9 -6
  11. package/dist/commonjs/components/NetworkIcons.d.ts +12 -0
  12. package/dist/commonjs/components/NetworkIcons.js +92 -1
  13. package/dist/commonjs/components/TreeNode.js +1 -1
  14. package/dist/commonjs/constants/version.d.ts +1 -1
  15. package/dist/commonjs/constants/version.js +1 -1
  16. package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +115 -0
  17. package/dist/commonjs/customHooks/bundleAnalyzer.js +561 -0
  18. package/dist/commonjs/customHooks/performanceTracker.d.ts +84 -0
  19. package/dist/commonjs/customHooks/performanceTracker.js +541 -0
  20. package/dist/commonjs/helpers/index.d.ts +15 -0
  21. package/dist/commonjs/helpers/index.js +112 -1
  22. package/dist/commonjs/i18n/locales/en.json +225 -2
  23. package/dist/commonjs/styles/AppColors.d.ts +110 -0
  24. package/dist/commonjs/styles/AppColors.js +114 -0
  25. package/dist/esm/components/AnalyticsDetail.js +5 -3
  26. package/dist/esm/components/AnalyticsEventCard.d.ts +0 -1
  27. package/dist/esm/components/AnalyticsEventCard.js +89 -96
  28. package/dist/esm/components/ErrorBoundary.js +17 -8
  29. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -0
  30. package/dist/esm/components/Inspector/BundleTab.js +612 -690
  31. package/dist/esm/components/Inspector/LogDetail.js +13 -7
  32. package/dist/esm/components/Inspector/PerformanceTab.js +196 -255
  33. package/dist/esm/components/Inspector/ReduxDetail.js +2 -2
  34. package/dist/esm/components/Inspector/ReduxTab.js +10 -7
  35. package/dist/esm/components/NetworkIcons.d.ts +12 -0
  36. package/dist/esm/components/NetworkIcons.js +79 -0
  37. package/dist/esm/components/TreeNode.js +2 -2
  38. package/dist/esm/constants/version.d.ts +1 -1
  39. package/dist/esm/constants/version.js +1 -1
  40. package/dist/esm/customHooks/bundleAnalyzer.d.ts +115 -0
  41. package/dist/esm/customHooks/bundleAnalyzer.js +554 -0
  42. package/dist/esm/customHooks/performanceTracker.d.ts +84 -0
  43. package/dist/esm/customHooks/performanceTracker.js +537 -0
  44. package/dist/esm/helpers/index.d.ts +15 -0
  45. package/dist/esm/helpers/index.js +107 -0
  46. package/dist/esm/i18n/locales/en.json +225 -2
  47. package/dist/esm/styles/AppColors.d.ts +110 -0
  48. package/dist/esm/styles/AppColors.js +114 -0
  49. package/package.json +1 -1
@@ -1,13 +1,14 @@
1
- import React, { useState, useMemo, useCallback } from 'react';
2
- import { Image, Linking, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native';
3
- import { useTranslation } from '../../i18n';
1
+ import React, { useState, useEffect, useMemo, useCallback } from 'react';
2
+ import { ActivityIndicator, Image, Linking, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native';
3
+ import { useTranslation, t } from '../../i18n';
4
4
  import TouchableScale from '../TouchableScale';
5
5
  import CopyButton from '../CopyButton';
6
6
  import HighlightText from '../HighlightText';
7
7
  import { AppColors } from '../../styles/AppColors';
8
8
  import { AppFonts } from '../../styles/AppFonts';
9
- import { PackageIcon, SearchIcon, ClearIcon, CircleAlertIcon, LayersIcon, LiveStateIcon, StorageIcon, MetadataIcon, } from '../NetworkIcons';
10
- import Svg, { Path, Rect, Circle, Ellipse, Defs, LinearGradient, Stop, } from 'react-native-svg';
9
+ import { PackageIcon, SearchIcon, ClearIcon, CircleAlertIcon, LayersIcon, LiveStateIcon, StorageIcon, MetadataIcon, FolderIcon, FolderOpenIcon, TrashIcon, LightbulbIcon, SparkleIcon, ClockIcon, } from '../NetworkIcons';
10
+ import Svg, { Path, Rect, Circle, Ellipse, G, Defs, LinearGradient, Stop, } from 'react-native-svg';
11
+ import { analyzeHostAppBundle, getCachedBundleAnalysis, } from '../../customHooks/bundleAnalyzer';
11
12
  // ─── High-Fidelity Vector Package & NPM Logos ────────────────────────────────
12
13
  const PackageLogoRenderer = ({ name, size = 28 }) => {
13
14
  const lowerName = name.toLowerCase();
@@ -19,13 +20,13 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
19
20
  !lowerName.includes('gradient'))) {
20
21
  return (<View style={[
21
22
  bundleStyles.pkgLogoWrap,
22
- { width: size, height: size, backgroundColor: '#20232A' },
23
+ { width: size, height: size, backgroundColor: AppColors.npmDark },
23
24
  ]}>
24
25
  <Svg width={size - 6} height={size - 6} viewBox="0 0 100 100" fill="none">
25
- <Circle cx="50" cy="50" r="9" fill="#61DAFB"/>
26
- <Ellipse cx="50" cy="50" rx="42" ry="16" stroke="#61DAFB" strokeWidth="6"/>
27
- <Ellipse cx="50" cy="50" rx="42" ry="16" stroke="#61DAFB" strokeWidth="6" transform="rotate(60 50 50)"/>
28
- <Ellipse cx="50" cy="50" rx="42" ry="16" stroke="#61DAFB" strokeWidth="6" transform="rotate(120 50 50)"/>
26
+ <Circle cx="50" cy="50" r="9" fill={AppColors.reactCyan}/>
27
+ <Ellipse cx="50" cy="50" rx="42" ry="16" stroke={AppColors.reactCyan} strokeWidth="6"/>
28
+ <Ellipse cx="50" cy="50" rx="42" ry="16" stroke={AppColors.reactCyan} strokeWidth="6" transform="rotate(60 50 50)"/>
29
+ <Ellipse cx="50" cy="50" rx="42" ry="16" stroke={AppColors.reactCyan} strokeWidth="6" transform="rotate(120 50 50)"/>
29
30
  </Svg>
30
31
  </View>);
31
32
  }
@@ -33,12 +34,12 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
33
34
  if (lowerName.includes('navigation')) {
34
35
  return (<View style={[
35
36
  bundleStyles.pkgLogoWrap,
36
- { width: size, height: size, backgroundColor: '#5B44E0' },
37
+ { width: size, height: size, backgroundColor: AppColors.hermesPurple },
37
38
  ]}>
38
39
  <Svg width={size - 8} height={size - 8} viewBox="0 0 100 100" fill="none">
39
- <Circle cx="50" cy="50" r="38" stroke="#FFFFFF" strokeWidth="8"/>
40
- <Path d="M50 20 L68 50 L50 80 L32 50 Z" fill="#61DAFB"/>
41
- <Circle cx="50" cy="50" r="6" fill="#FFFFFF"/>
40
+ <Circle cx="50" cy="50" r="38" stroke={AppColors.white} strokeWidth="8"/>
41
+ <Path d="M50 20 L68 50 L50 80 L32 50 Z" fill={AppColors.reactCyan}/>
42
+ <Circle cx="50" cy="50" r="6" fill={AppColors.white}/>
42
43
  </Svg>
43
44
  </View>);
44
45
  }
@@ -46,13 +47,13 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
46
47
  if (lowerName.includes('svg')) {
47
48
  return (<View style={[
48
49
  bundleStyles.pkgLogoWrap,
49
- { width: size, height: size, backgroundColor: '#F472B6' },
50
+ { width: size, height: size, backgroundColor: AppColors.pink400 },
50
51
  ]}>
51
52
  <Svg width={size - 8} height={size - 8} viewBox="0 0 100 100" fill="none">
52
- <Path d="M20 75 C35 25, 65 25, 80 75" stroke="#FFFFFF" strokeWidth="10" strokeLinecap="round"/>
53
- <Circle cx="20" cy="75" r="8" fill="#FFFFFF"/>
54
- <Circle cx="80" cy="75" r="8" fill="#FFFFFF"/>
55
- <Circle cx="50" cy="38" r="7" fill="#FCE7F3" stroke="#FFFFFF" strokeWidth="3"/>
53
+ <Path d="M20 75 C35 25, 65 25, 80 75" stroke={AppColors.white} strokeWidth="10" strokeLinecap="round"/>
54
+ <Circle cx="20" cy="75" r="8" fill={AppColors.white}/>
55
+ <Circle cx="80" cy="75" r="8" fill={AppColors.white}/>
56
+ <Circle cx="50" cy="38" r="7" fill="#FCE7F3" stroke={AppColors.white} strokeWidth="3"/>
56
57
  </Svg>
57
58
  </View>);
58
59
  }
@@ -60,11 +61,11 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
60
61
  if (lowerName.includes('axios')) {
61
62
  return (<View style={[
62
63
  bundleStyles.pkgLogoWrap,
63
- { width: size, height: size, backgroundColor: '#5A29E4' },
64
+ { width: size, height: size, backgroundColor: AppColors.expoViolet },
64
65
  ]}>
65
66
  <Svg width={size - 6} height={size - 6} viewBox="0 0 100 100" fill="none">
66
- <Path d="M22 75 L50 20 L78 75 M33 58 L67 58" stroke="#FFFFFF" strokeWidth="12" strokeLinecap="round" strokeLinejoin="round"/>
67
- <Circle cx="50" cy="20" r="7" fill="#61DAFB"/>
67
+ <Path d="M22 75 L50 20 L78 75 M33 58 L67 58" stroke={AppColors.white} strokeWidth="12" strokeLinecap="round" strokeLinejoin="round"/>
68
+ <Circle cx="50" cy="20" r="7" fill={AppColors.reactCyan}/>
68
69
  </Svg>
69
70
  </View>);
70
71
  }
@@ -72,11 +73,11 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
72
73
  if (lowerName.includes('screens') || lowerName.includes('software-mansion')) {
73
74
  return (<View style={[
74
75
  bundleStyles.pkgLogoWrap,
75
- { width: size, height: size, backgroundColor: '#001A72' },
76
+ { width: size, height: size, backgroundColor: AppColors.reanimatedNavy },
76
77
  ]}>
77
78
  <Svg width={size - 8} height={size - 8} viewBox="0 0 100 100" fill="none">
78
- <Path d="M50 15 L88 35 L50 55 L12 35 Z" fill="#38BDF8"/>
79
- <Path d="M12 50 L50 70 L88 50" stroke="#FFFFFF" strokeWidth="8" strokeLinecap="round"/>
79
+ <Path d="M50 15 L88 35 L50 55 L12 35 Z" fill={AppColors.sky400}/>
80
+ <Path d="M12 50 L50 70 L88 50" stroke={AppColors.white} strokeWidth="8" strokeLinecap="round"/>
80
81
  <Path d="M12 65 L50 85 L88 65" stroke="#93C5FD" strokeWidth="8" strokeLinecap="round"/>
81
82
  </Svg>
82
83
  </View>);
@@ -85,7 +86,7 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
85
86
  if (lowerName.includes('gradient')) {
86
87
  return (<View style={[
87
88
  bundleStyles.pkgLogoWrap,
88
- { width: size, height: size, backgroundColor: '#FB7185' },
89
+ { width: size, height: size, backgroundColor: AppColors.rose600 },
89
90
  ]}>
90
91
  <Svg width={size - 4} height={size - 4} viewBox="0 0 100 100" fill="none">
91
92
  <Defs>
@@ -103,26 +104,97 @@ const PackageLogoRenderer = ({ name, size = 28 }) => {
103
104
  if (lowerName.includes('i18n')) {
104
105
  return (<View style={[
105
106
  bundleStyles.pkgLogoWrap,
106
- { width: size, height: size, backgroundColor: '#059669' },
107
+ { width: size, height: size, backgroundColor: AppColors.emerald600 },
107
108
  ]}>
108
109
  <Svg width={size - 8} height={size - 8} viewBox="0 0 100 100" fill="none">
109
- <Circle cx="50" cy="50" r="38" stroke="#FFFFFF" strokeWidth="8"/>
110
- <Ellipse cx="50" cy="50" rx="18" ry="38" stroke="#FFFFFF" strokeWidth="6"/>
111
- <Path d="M14 50 L86 50" stroke="#FFFFFF" strokeWidth="7"/>
110
+ <Circle cx="50" cy="50" r="38" stroke={AppColors.white} strokeWidth="8"/>
111
+ <Ellipse cx="50" cy="50" rx="18" ry="38" stroke={AppColors.white} strokeWidth="6"/>
112
+ <Path d="M14 50 L86 50" stroke={AppColors.white} strokeWidth="7"/>
112
113
  </Svg>
113
114
  </View>);
114
115
  }
115
116
  // 8. Official Red NPM Logo Box (Universal Default NPM Branding)
116
117
  return (<View style={[
117
118
  bundleStyles.pkgLogoWrap,
118
- { width: size, height: size, backgroundColor: '#CB3837' },
119
+ { width: size, height: size, backgroundColor: AppColors.npmRed },
119
120
  ]}>
120
121
  <Svg width={size - 4} height={size - 4} viewBox="0 0 100 100" fill="none">
121
- <Rect width="100" height="100" rx="14" fill="#CB3837"/>
122
- <Path d="M18 24 H82 V76 H50 V40 H38 V76 H18 Z" fill="#FFFFFF"/>
122
+ <Rect width="100" height="100" rx="14" fill={AppColors.npmRed}/>
123
+ <Path d="M18 24 H82 V76 H50 V40 H38 V76 H18 Z" fill={AppColors.white}/>
123
124
  </Svg>
124
125
  </View>);
125
126
  };
127
+ const BundleCategoryIcon = ({ type, size = 16, color = AppColors.purple }) => {
128
+ const stroke = { stroke: color, strokeWidth: 1.7, strokeLinecap: 'round', strokeLinejoin: 'round' };
129
+ const renderGlyph = () => {
130
+ switch (type) {
131
+ case 'folder':
132
+ return (<G>
133
+ <Path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" {...stroke}/>
134
+ </G>);
135
+ case 'ban':
136
+ return (<G>
137
+ <Circle cx="12" cy="12" r="8.5" {...stroke}/>
138
+ <Path d="M5.5 5.5 18.5 18.5" {...stroke}/>
139
+ </G>);
140
+ case 'bolt':
141
+ return (<G>
142
+ <Path d="M13 2 4.5 13.5 H10.5 L9 22 19.5 9.5 H13.5 Z" {...stroke}/>
143
+ </G>);
144
+ case 'source':
145
+ return (<G>
146
+ <Path d="M8.5 7 L4 12 L8.5 17" {...stroke}/>
147
+ <Path d="M15.5 7 L20 12 L15.5 17" {...stroke}/>
148
+ <Path d="M13.5 4.5 L10.5 19.5" {...stroke}/>
149
+ </G>);
150
+ case 'deps':
151
+ return (<G>
152
+ <Rect x="4" y="4" width="16" height="16" rx="2.5" {...stroke}/>
153
+ <Path d="M4 9.5 H20" {...stroke}/>
154
+ <Path d="M9.5 9.5 V20" {...stroke}/>
155
+ </G>);
156
+ case 'media':
157
+ case 'image':
158
+ return (<G>
159
+ <Rect x="3.5" y="4.5" width="17" height="15" rx="2" {...stroke}/>
160
+ <Circle cx="9" cy="10" r="1.5" {...stroke}/>
161
+ <Path d="M3.5 17.5 L9.5 12.5 L14 16.5 L17 13.5 L20.5 17.5" {...stroke}/>
162
+ </G>);
163
+ case 'overhead':
164
+ return (<G>
165
+ <Circle cx="12" cy="12" r="3.2" {...stroke}/>
166
+ <Path d="M12 2.5 V5.5 M12 18.5 V21.5 M2.5 12 H5.5 M18.5 12 H21.5 M5.3 5.3 L7.4 7.4 M16.6 16.6 L18.7 18.7 M18.7 5.3 L16.6 7.4 M7.4 16.6 L5.3 18.7" {...stroke}/>
167
+ </G>);
168
+ case 'typescript':
169
+ return (<G>
170
+ <Path d="M6.5 3.5 H14 L18.5 8 V20.5 H6.5 Z" {...stroke}/>
171
+ <Path d="M14 3.5 V8 H18.5" {...stroke}/>
172
+ <Path d="M9.5 13.5 H15 M12 13.5 V16.5 M9.5 16.5 H13" {...stroke}/>
173
+ </G>);
174
+ case 'javascript':
175
+ return (<G>
176
+ <Path d="M9.5 3.5 C7.5 3.5 7.5 6.5 7.5 9 C7.5 11 6.5 12 5 12 C6.5 12 7.5 13 7.5 15 C7.5 17.5 7.5 20.5 9.5 20.5" {...stroke}/>
177
+ <Path d="M14.5 3.5 C16.5 3.5 16.5 6.5 16.5 9 C16.5 11 17.5 12 19 12 C17.5 12 16.5 13 16.5 15 C16.5 17.5 16.5 20.5 14.5 20.5" {...stroke}/>
178
+ </G>);
179
+ case 'font':
180
+ return (<G>
181
+ <Path d="M8 19 L12 5.5 L16 19" {...stroke}/>
182
+ <Path d="M9.4 14.5 H14.6" {...stroke}/>
183
+ </G>);
184
+ case 'json':
185
+ return (<G>
186
+ <Path d="M9 3.5 C7 3.5 7 6 7 8.5 C7 10.5 6 11.5 5 12 C6 12.5 7 13.5 7 15.5 C7 18 7 20.5 9 20.5" {...stroke}/>
187
+ <Circle cx="12" cy="12" r="1.1" fill={color}/>
188
+ <Path d="M15 3.5 C17 3.5 17 6 17 8.5 C17 10.5 18 11.5 19 12 C18 12.5 17 13.5 17 15.5 C17 18 17 20.5 15 20.5" {...stroke}/>
189
+ </G>);
190
+ default:
191
+ return null;
192
+ }
193
+ };
194
+ return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
195
+ {renderGlyph()}
196
+ </Svg>);
197
+ };
126
198
  // ─── Media File Thumbnail Component ──────────────────────────────────────────
127
199
  const MediaPreviewThumbnail = ({ file }) => {
128
200
  const [loadError, setLoadError] = useState(false);
@@ -131,25 +203,25 @@ const MediaPreviewThumbnail = ({ file }) => {
131
203
  return (<View style={[
132
204
  bundleStyles.mediaThumbWrap,
133
205
  {
134
- backgroundColor: '#FFF1F2',
206
+ backgroundColor: AppColors.errorCardBg,
135
207
  alignItems: 'center',
136
208
  justifyContent: 'center',
137
- borderColor: '#FECDD3',
209
+ borderColor: AppColors.errorBorder,
138
210
  },
139
211
  ]}>
140
212
  <Svg width={22} height={22} viewBox="0 0 100 100" fill="none">
141
213
  <Defs>
142
214
  <LinearGradient id="svgGradThumb" x1="0" y1="0" x2="100" y2="100" gradientUnits="userSpaceOnUse">
143
- <Stop offset="0" stopColor="#F43F5E"/>
144
- <Stop offset="1" stopColor="#FB7185"/>
215
+ <Stop offset="0" stopColor={AppColors.rose500}/>
216
+ <Stop offset="1" stopColor={AppColors.rose600}/>
145
217
  </LinearGradient>
146
218
  </Defs>
147
219
  <Circle cx="50" cy="50" r="44" fill="url(#svgGradThumb)"/>
148
220
  {/* Vector bezier path pen graphic */}
149
- <Path d="M25 70 C35 30, 65 30, 75 70" stroke="#FFFFFF" strokeWidth="8" strokeLinecap="round"/>
150
- <Circle cx="25" cy="70" r="7" fill="#FFFFFF"/>
151
- <Circle cx="75" cy="70" r="7" fill="#FFFFFF"/>
152
- <Circle cx="50" cy="40" r="6" fill="#FFE4E6" stroke="#FFFFFF" strokeWidth="2"/>
221
+ <Path d="M25 70 C35 30, 65 30, 75 70" stroke={AppColors.white} strokeWidth="8" strokeLinecap="round"/>
222
+ <Circle cx="25" cy="70" r="7" fill={AppColors.white}/>
223
+ <Circle cx="75" cy="70" r="7" fill={AppColors.white}/>
224
+ <Circle cx="50" cy="40" r="6" fill="#FFE4E6" stroke={AppColors.white} strokeWidth="2"/>
153
225
  </Svg>
154
226
  </View>);
155
227
  }
@@ -243,11 +315,11 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
243
315
  {isCollapsed ? '▶' : '▼'}
244
316
  </Text>
245
317
  </View>
246
- <Text style={{ fontSize: 16 }}>{isCollapsed ? '📁' : '📂'}</Text>
318
+ {isCollapsed ? (<FolderIcon color={AppColors.amber500} size={16}/>) : (<FolderOpenIcon color={AppColors.amber500} size={16}/>)}
247
319
  <HighlightText text={node.name + '/'} search={search} style={bundleStyles.treeFolderName} highlightStyle={bundleStyles.searchHighlight}/>
248
320
  <View style={bundleStyles.treeCountBadge}>
249
321
  <Text style={bundleStyles.treeCountBadgeText}>
250
- {node.fileCount} {node.fileCount === 1 ? 'file' : 'files'}
322
+ {node.fileCount} {node.fileCount === 1 ? t('bundle.file') : t('bundle.files')}
251
323
  </Text>
252
324
  </View>
253
325
  </View>
@@ -255,14 +327,14 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
255
327
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
256
328
  <Text style={bundleStyles.treeFolderSize}>
257
329
  {node.sizeKb >= 1024
258
- ? `${(node.sizeKb / 1024).toFixed(2)} MB`
259
- : `${node.sizeKb} KB`}
330
+ ? t('bundle.fileSizeMb', { size: (node.sizeKb / 1024).toFixed(2) })
331
+ : t('bundle.fileSizeKb', { size: node.sizeKb })}
260
332
  </Text>
261
333
  <CopyButton value={() => ({
262
334
  folder: node.fullPath,
263
335
  sizeKb: node.sizeKb,
264
336
  fileCount: node.fileCount,
265
- })} label={`Copy ${node.name} Info`}/>
337
+ })} label={t('bundle.copyFileInfo', { name: node.name })}/>
266
338
  </View>
267
339
  </TouchableScale>
268
340
 
@@ -291,9 +363,9 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
291
363
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
292
364
  <HighlightText text={file.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
293
365
  {isUnused ? (<View style={bundleStyles.unusedBadge}>
294
- <Text style={bundleStyles.unusedBadgeText}>🚫 Not Consumed</Text>
366
+ <Text style={bundleStyles.unusedBadgeText}>{t('bundle.notConsumed')}</Text>
295
367
  </View>) : (<View style={bundleStyles.consumedBadge}>
296
- <Text style={bundleStyles.consumedBadgeText}>⚡ Consumed</Text>
368
+ <Text style={bundleStyles.consumedBadgeText}>{t('bundle.consumed')}</Text>
297
369
  </View>)}
298
370
  </View>
299
371
  <Text style={bundleStyles.filePath} numberOfLines={1}>
@@ -303,14 +375,14 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
303
375
 
304
376
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
305
377
  <View style={bundleStyles.fileSizeBox}>
306
- <Text style={[bundleStyles.fileSizeKb, isUnused && { color: '#B45309' }]}>
378
+ <Text style={[bundleStyles.fileSizeKb, isUnused && { color: AppColors.amber800Warm }]}>
307
379
  {file.sizeKb >= 1024
308
- ? `${(file.sizeKb / 1024).toFixed(2)} MB`
309
- : `${file.sizeKb} KB`}
380
+ ? t('bundle.fileSizeMb', { size: (file.sizeKb / 1024).toFixed(2) })
381
+ : t('bundle.fileSizeKb', { size: file.sizeKb })}
310
382
  </Text>
311
383
  <Text style={bundleStyles.filePercent}>{pctOfTotal}%</Text>
312
384
  </View>
313
- <CopyButton value={() => file} label="File Details JSON"/>
385
+ <CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
314
386
  </View>
315
387
  </View>
316
388
 
@@ -321,429 +393,62 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
321
393
  isUnused && bundleStyles.adviceUnused,
322
394
  { marginLeft: 26, marginTop: 4 },
323
395
  ]}>
324
- <Text style={[
396
+ <View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
397
+ {isUnused ? (<TrashIcon color={AppColors.red600} size={12}/>) : file.status === 'warning' ? (<LightbulbIcon color={AppColors.amber800Warm} size={12}/>) : (<SparkleIcon color={AppColors.emerald700} size={12}/>)}
398
+ <Text style={[
325
399
  bundleStyles.adviceText,
326
- file.status === 'warning' && { color: '#B45309' },
327
- file.status === 'optimal' && { color: '#047857' },
328
- isUnused && { color: '#DC2626' },
400
+ file.status === 'warning' && { color: AppColors.amber800Warm },
401
+ file.status === 'optimal' && { color: AppColors.emerald700 },
402
+ isUnused && { color: AppColors.red600 },
329
403
  ]}>
330
- {isUnused ? '🗑️ ' : file.status === 'warning' ? '💡 ' : '✨ '}
331
- {file.advice}
332
- </Text>
404
+ {file.advice}
405
+ </Text>
406
+ </View>
333
407
  </View>)}
334
408
  </View>);
335
409
  };
336
- // ─── Sample Bundle Files Data ────────────────────────────────────────────────
337
- const BUNDLE_FILES_DATA = [
338
- // ── Images & Media ──
339
- {
340
- id: 'img-1',
341
- name: 'banner_dark.png',
342
- path: 'assets/images/banner_dark.png',
343
- ext: 'PNG',
344
- category: 'image',
345
- sizeKb: 460,
346
- meta: '1200×630px @2x • Raster Image',
347
- color: '#EC4899',
348
- status: 'warning',
349
- advice: 'Convert to .webp to save ~65% (160 KB)',
350
- previewUri: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=200&auto=format&fit=crop&q=80',
351
- },
352
- {
353
- id: 'img-2',
354
- name: 'banner_light.svg',
355
- path: 'assets/images/banner_light.svg',
356
- ext: 'SVG',
357
- category: 'image',
358
- sizeKb: 34,
359
- meta: 'Scalable Vector Graphic',
360
- color: '#F43F5E',
361
- status: 'optimal',
362
- advice: 'Optimal vector format for sharp rendering',
363
- previewUri: 'https://images.unsplash.com/photo-1579783902614-a3fb3927b675?w=200&auto=format&fit=crop&q=80',
364
- },
365
- {
366
- id: 'img-3',
367
- name: 'logo_hero@3x.png',
368
- path: 'assets/brand/logo_hero@3x.png',
369
- ext: 'PNG',
370
- category: 'image',
371
- sizeKb: 285,
372
- meta: '768×768px @3x Retina Asset',
373
- color: '#EC4899',
374
- status: 'warning',
375
- advice: 'High density PNG. Consider tintable SVG vector',
376
- previewUri: 'https://images.unsplash.com/photo-1634017839464-5c339ebe3cb4?w=200&auto=format&fit=crop&q=80',
377
- },
378
- {
379
- id: 'img-4',
380
- name: 'avatar_placeholder.webp',
381
- path: 'assets/placeholders/avatar_placeholder.webp',
382
- ext: 'WEBP',
383
- category: 'image',
384
- sizeKb: 28,
385
- meta: '256×256px Lossy WebP',
386
- color: '#F472B6',
387
- status: 'warning',
388
- advice: 'Unused in bundle: 0 active imports detected (28 KB unused)',
389
- previewUri: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200&auto=format&fit=crop&q=80',
390
- isConsumed: false,
391
- },
392
- {
393
- id: 'img-5',
394
- name: 'walkthrough_thumb.jpg',
395
- path: 'assets/previews/walkthrough_thumb.jpg',
396
- ext: 'JPG',
397
- category: 'image',
398
- sizeKb: 175,
399
- meta: '1080×600px Progressive JPEG',
400
- color: '#FB7185',
401
- status: 'warning',
402
- advice: 'Dead asset: 0 component references (175 KB unused)',
403
- previewUri: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=200&auto=format&fit=crop&q=80',
404
- isConsumed: false,
405
- },
406
- // ── TypeScript & JSX ──
407
- {
408
- id: 'ts-1',
409
- name: 'LogDetail.tsx',
410
- path: 'src/components/Inspector/LogDetail.tsx',
411
- ext: 'TSX',
412
- category: 'typescript',
413
- sizeKb: 44,
414
- meta: '1,238 lines • React Component',
415
- color: '#38BDF8',
416
- status: 'optimal',
417
- advice: 'Clean React.memo and useCallback optimization',
418
- },
419
- {
420
- id: 'ts-2',
421
- name: 'ReduxDetail.tsx',
422
- path: 'src/components/Inspector/ReduxDetail.tsx',
423
- ext: 'TSX',
424
- category: 'typescript',
425
- sizeKb: 38,
426
- meta: '680 lines • Slice state tree & action timeline',
427
- color: '#38BDF8',
428
- status: 'optimal',
429
- advice: 'Multi-tab subview with live state diffing',
430
- },
431
- {
432
- id: 'ts-3',
433
- name: 'NetworkTab.tsx',
434
- path: 'src/components/Inspector/NetworkTab.tsx',
435
- ext: 'TSX',
436
- category: 'typescript',
437
- sizeKb: 15,
438
- meta: '486 lines • Network request virtualization list',
439
- color: '#0EA5E9',
440
- status: 'optimal',
441
- advice: 'FlatList windowSize=7 tuned for 60fps',
442
- },
443
- {
444
- id: 'ts-4',
445
- name: 'SettingsPanel.tsx',
446
- path: 'src/components/Inspector/SettingsPanel.tsx',
447
- ext: 'TSX',
448
- category: 'typescript',
449
- sizeKb: 65,
450
- meta: '1,845 lines • Preferences & storage manager',
451
- color: '#38BDF8',
452
- status: 'info',
453
- advice: 'Self-contained settings subviews',
454
- },
455
- {
456
- id: 'ts-5',
457
- name: 'consoleLogger.ts',
458
- path: 'src/customHooks/consoleLogger.ts',
459
- ext: 'TS',
460
- category: 'typescript',
461
- sizeKb: 12,
462
- meta: '360 lines • Metro symbolication bridge',
463
- color: '#0284C7',
464
- status: 'optimal',
465
- advice: 'Zero-overhead in-memory stack capture',
466
- },
467
- {
468
- id: 'ts-6',
469
- name: 'App.tsx (Playground)',
470
- path: 'example/App.tsx',
471
- ext: 'TSX',
472
- category: 'typescript',
473
- sizeKb: 25,
474
- meta: '820 lines • Interactive test scenarios',
475
- color: '#38BDF8',
476
- status: 'optimal',
477
- advice: 'Sample harness for logs, APIs, and Redux',
478
- },
479
- // ── JavaScript & Node Modules ──
480
- {
481
- id: 'js-1',
482
- name: 'react-native/Libraries/Renderer',
483
- path: 'node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-prod.js',
484
- ext: 'JS',
485
- category: 'javascript',
486
- sizeKb: 480,
487
- meta: 'Fabric & Paper UI reconciler bundle',
488
- color: '#6366F1',
489
- status: 'optimal',
490
- advice: 'AOT compiled with Hermes Bytecode',
491
- },
492
- {
493
- id: 'js-2',
494
- name: '@react-navigation/native-stack',
495
- path: 'node_modules/@react-navigation/native-stack/lib/module/index.js',
496
- ext: 'JS',
497
- category: 'javascript',
498
- sizeKb: 195,
499
- meta: 'Native screen animation & transition engine',
500
- color: '#818CF8',
501
- status: 'optimal',
502
- advice: 'Optimized modular ES bundle',
503
- },
504
- {
505
- id: 'js-3',
506
- name: 'axios/dist/esm/axios.js',
507
- path: 'node_modules/axios/dist/esm/axios.js',
508
- ext: 'JS',
509
- category: 'javascript',
510
- sizeKb: 88,
511
- meta: 'HTTP client core with interceptors',
512
- color: '#F59E0B',
513
- status: 'optimal',
514
- advice: 'Standard lightweight network adapter',
515
- },
516
- {
517
- id: 'js-4',
518
- name: 'react-native-svg',
519
- path: 'node_modules/react-native-svg/lib/module/index.js',
520
- ext: 'JS',
521
- category: 'javascript',
522
- sizeKb: 140,
523
- meta: 'Native SVG drawing and vector paths',
524
- color: '#4F46E5',
525
- status: 'optimal',
526
- advice: 'Native Fabric/Paper vector bridge',
527
- },
528
- // ── Fonts & Typography ──
529
- {
530
- id: 'font-1',
531
- name: 'Inter-Bold.ttf',
532
- path: 'fonts/Inter-Bold.ttf',
533
- ext: 'TTF',
534
- category: 'font',
535
- sizeKb: 165,
536
- meta: 'Custom UI Font • Full Latin Charset (480 glyphs)',
537
- color: '#8B5CF6',
538
- status: 'info',
539
- advice: 'High readability modern typography',
540
- },
541
- {
542
- id: 'font-2',
543
- name: 'Inter-Regular.ttf',
544
- path: 'fonts/Inter-Regular.ttf',
545
- ext: 'TTF',
546
- category: 'font',
547
- sizeKb: 160,
548
- meta: 'Custom Body Font • 400 Weight (480 glyphs)',
549
- color: '#8B5CF6',
550
- status: 'info',
551
- advice: 'Applied across inspector UI cards and text',
552
- },
553
- {
554
- id: 'font-3',
555
- name: 'JetBrainsMono-Regular.ttf',
556
- path: 'fonts/JetBrainsMono-Regular.ttf',
557
- ext: 'TTF',
558
- category: 'font',
559
- sizeKb: 95,
560
- meta: 'Monospace Code Font • Raw / Table / Stack Traces',
561
- color: '#A855F7',
562
- status: 'optimal',
563
- advice: 'Crisp font for code snippets and stack traces',
564
- },
565
- // ── JSON & Localizations ──
566
- {
567
- id: 'json-1',
568
- name: 'en.json (i18n Translations)',
569
- path: 'src/i18n/locales/en.json',
570
- ext: 'JSON',
571
- category: 'json',
572
- sizeKb: 18,
573
- meta: '124 localization translation keys',
574
- color: '#10B981',
575
- status: 'optimal',
576
- advice: 'Static JSON locale dictionary',
577
- },
578
- {
579
- id: 'json-2',
580
- name: 'package.json',
581
- path: 'package.json',
582
- ext: 'JSON',
583
- category: 'json',
584
- sizeKb: 3,
585
- meta: 'Manifest & peer dependencies config',
586
- color: '#059669',
587
- status: 'optimal',
588
- advice: 'Stripped during production bundle export',
589
- },
590
- ];
591
- // ─── Packages / Node Modules Breakdown Data ──────────────────────────────────
592
- const BUNDLE_PACKAGES_DATA = [
593
- {
594
- id: 'pkg-1',
595
- name: 'react-native',
596
- version: '0.85.3',
597
- latestVersion: '0.85.3',
598
- sizeKb: 680,
599
- percentage: 38.5,
600
- type: 'direct',
601
- category: 'core',
602
- color: '#38BDF8',
603
- description: 'React Native runtime, Fabric UI engine, Native Bridge, and Turbomodules.',
604
- logoUri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/512px-React-icon.svg.png',
605
- lastActive: '3 days ago',
606
- npmUrl: 'https://www.npmjs.com/package/react-native',
607
- isDeprecated: false,
608
- },
609
- {
610
- id: 'pkg-2',
611
- name: '@react-navigation/native-stack',
612
- version: '^7.16.0',
613
- latestVersion: '7.16.0',
614
- sizeKb: 245,
615
- percentage: 13.9,
616
- type: 'direct',
617
- category: 'navigation',
618
- color: '#818CF8',
619
- description: 'Native screen router, UIViewController / Fragment animations and transitions.',
620
- logoUri: 'https://reactnavigation.org/img/spiro.svg',
621
- lastActive: '1 week ago',
622
- npmUrl: 'https://www.npmjs.com/package/@react-navigation/native-stack',
623
- isDeprecated: false,
624
- },
625
- {
626
- id: 'pkg-3',
627
- name: 'react',
628
- version: '19.2.3',
629
- latestVersion: '19.2.3',
630
- sizeKb: 185,
631
- percentage: 10.5,
632
- type: 'direct',
633
- category: 'core',
634
- color: '#06B6D4',
635
- description: 'React core virtual DOM reconciler, hooks engine, and JSX runtime.',
636
- logoUri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/512px-React-icon.svg.png',
637
- lastActive: '5 days ago',
638
- npmUrl: 'https://www.npmjs.com/package/react',
639
- isDeprecated: false,
640
- },
641
- {
642
- id: 'pkg-4',
643
- name: 'react-native-svg',
644
- version: '^15.15.5',
645
- latestVersion: '15.16.0',
646
- sizeKb: 140,
647
- percentage: 7.9,
648
- type: 'direct',
649
- category: 'ui',
650
- color: '#F472B6',
651
- description: 'Scalable Vector Graphics XML parser and native canvas path rasterizer.',
652
- logoUri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/SVG_logo.svg/512px-SVG_logo.svg.png',
653
- lastActive: '2 days ago',
654
- npmUrl: 'https://www.npmjs.com/package/react-native-svg',
655
- isDeprecated: false,
656
- },
657
- {
658
- id: 'pkg-5',
659
- name: 'react-native-screens',
660
- version: '^4.25.2',
661
- latestVersion: '4.25.2',
662
- sizeKb: 110,
663
- percentage: 6.2,
664
- type: 'transitive',
665
- category: 'navigation',
666
- color: '#A78BFA',
667
- description: 'Native navigation container primitives for iOS and Android memory management.',
668
- logoUri: 'https://reactnavigation.org/img/spiro.svg',
669
- lastActive: '2 weeks ago',
670
- npmUrl: 'https://www.npmjs.com/package/react-native-screens',
671
- isDeprecated: false,
672
- },
673
- {
674
- id: 'pkg-6',
675
- name: 'axios',
676
- version: '^1.17.0',
677
- latestVersion: '1.18.0',
678
- sizeKb: 88,
679
- percentage: 5.0,
680
- type: 'direct',
681
- category: 'network',
682
- color: '#F59E0B',
683
- description: 'HTTP client, request/response interceptors, and adapter pipelines.',
684
- logoUri: 'https://axios-http.com/assets/logo.svg',
685
- lastActive: '4 days ago',
686
- npmUrl: 'https://www.npmjs.com/package/axios',
687
- isDeprecated: false,
688
- },
689
- {
690
- id: 'pkg-7',
691
- name: 'react-native-linear-gradient',
692
- version: '^2.8.3',
693
- latestVersion: '2.8.3',
694
- sizeKb: 45,
695
- percentage: 2.5,
696
- type: 'direct',
697
- category: 'ui',
698
- color: '#FB7185',
699
- description: 'Hardware accelerated native gradient drawing shaders.',
700
- logoUri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/512px-React-icon.svg.png',
701
- lastActive: '2 years ago',
702
- npmUrl: 'https://www.npmjs.com/package/react-native-linear-gradient',
703
- isDeprecated: true,
704
- deprecationReason: 'Package maintenance is in transition. Consider expo-linear-gradient or @react-native-community/linear-gradient for Fabric support.',
705
- },
706
- {
707
- id: 'pkg-8',
708
- name: 'i18next & react-i18next',
709
- version: '^26.3.6',
710
- latestVersion: '26.3.6',
711
- sizeKb: 62,
712
- percentage: 3.5,
713
- type: 'direct',
714
- category: 'utils',
715
- color: '#10B981',
716
- description: 'Internationalization framework, language interpolator, and locale switcher.',
717
- logoUri: 'https://raw.githubusercontent.com/i18next/i18next/master/assets/i18next-logo.png',
718
- lastActive: '6 days ago',
719
- npmUrl: 'https://www.npmjs.com/package/i18next',
720
- isDeprecated: false,
721
- },
722
- ];
723
410
  const CATEGORY_TABS = [
724
- { key: 'ALL', label: 'All Files', icon: '📁' },
725
- { key: 'UNUSED', label: 'Not Consumed (Dead)', icon: '🚫' },
726
- { key: 'CONSUMED', label: 'In-Use / Active', icon: '' },
727
- { key: 'image', label: 'Images & Media', icon: '🖼️' },
728
- { key: 'typescript', label: 'TypeScript / TSX', icon: '📜' },
729
- { key: 'javascript', label: 'JS & Node Modules', icon: '⚙️' },
730
- { key: 'font', label: 'Fonts & Glyphs', icon: '🔤' },
731
- { key: 'json', label: 'JSON & Data', icon: '📄' },
411
+ { key: 'ALL', labelKey: 'bundle.catAll', iconType: 'folder' },
412
+ { key: 'UNUSED', labelKey: 'bundle.catUnused', iconType: 'ban' },
413
+ { key: 'CONSUMED', labelKey: 'bundle.catConsumed', iconType: 'bolt' },
414
+ { key: 'image', labelKey: 'bundle.catImages', iconType: 'image' },
415
+ { key: 'typescript', labelKey: 'bundle.catTypescript', iconType: 'typescript' },
416
+ { key: 'javascript', labelKey: 'bundle.catJavascript', iconType: 'javascript' },
417
+ { key: 'font', labelKey: 'bundle.catFonts', iconType: 'font' },
418
+ { key: 'json', labelKey: 'bundle.catJson', iconType: 'json' },
732
419
  ];
733
420
  const CATEGORY_COLORS = {
734
- image: { label: 'Images & Media', color: '#EC4899', bg: '#FCE7F3' },
735
- typescript: { label: 'TypeScript', color: '#0EA5E9', bg: '#E0F2FE' },
736
- javascript: { label: 'JavaScript', color: '#6366F1', bg: '#EEF2FF' },
737
- font: { label: 'Fonts', color: '#8B5CF6', bg: '#F3E8FF' },
738
- json: { label: 'JSON / Data', color: '#10B981', bg: '#D1FAE5' },
421
+ image: { label: 'Images & Media', color: AppColors.pink500, bg: AppColors.pink100 },
422
+ typescript: { label: 'TypeScript', color: AppColors.sky500, bg: AppColors.sky100 },
423
+ javascript: { label: 'JavaScript', color: AppColors.indigo500, bg: AppColors.indigo50 },
424
+ font: { label: 'Fonts', color: AppColors.purple500, bg: AppColors.purple100 },
425
+ json: { label: 'JSON / Data', color: AppColors.emerald500, bg: AppColors.emerald100 },
739
426
  };
740
427
  const BundleTab = React.memo(() => {
741
428
  const { t } = useTranslation();
742
429
  const [activeSubTab, setActiveSubTab] = useState('overview');
743
430
  const [search, setSearch] = useState('');
744
431
  const [activeCategory, setActiveCategory] = useState('ALL');
432
+ // ─── Live Host App Bundle Analysis (fetched from Metro / runtime) ─────────
433
+ const [analysis, setAnalysis] = useState(() => getCachedBundleAnalysis());
434
+ const [isAnalyzing, setIsAnalyzing] = useState(!getCachedBundleAnalysis());
435
+ useEffect(() => {
436
+ let mounted = true;
437
+ analyzeHostAppBundle().then(result => {
438
+ if (mounted) {
439
+ setAnalysis(result);
440
+ setIsAnalyzing(false);
441
+ }
442
+ });
443
+ return () => {
444
+ mounted = false;
445
+ };
446
+ }, []);
745
447
  // Check if Hermes is running
746
448
  const isHermes = Boolean(global.HermesInternal);
449
+ // Dynamic files & packages derived from the analyzed host bundle
450
+ const bundleFiles = useMemo(() => (analysis ? analysis.files : []), [analysis]);
451
+ const bundlePackages = useMemo(() => (analysis ? analysis.packages : []), [analysis]);
747
452
  // Group summary calculations
748
453
  const summary = useMemo(() => {
749
454
  let imagesKb = 0;
@@ -756,7 +461,7 @@ const BundleTab = React.memo(() => {
756
461
  let jsCount = 0;
757
462
  let fontsCount = 0;
758
463
  let jsonCount = 0;
759
- BUNDLE_FILES_DATA.forEach(f => {
464
+ bundleFiles.forEach(f => {
760
465
  if (f.category === 'image') {
761
466
  imagesKb += f.sizeKb;
762
467
  imagesCount++;
@@ -782,37 +487,37 @@ const BundleTab = React.memo(() => {
782
487
  return {
783
488
  totalKb,
784
489
  totalMb: (totalKb / 1024).toFixed(2),
785
- totalCount: BUNDLE_FILES_DATA.length,
490
+ totalCount: bundleFiles.length,
786
491
  images: {
787
492
  count: imagesCount,
788
493
  kb: imagesKb,
789
- pct: Number(((imagesKb / totalKb) * 100).toFixed(1)),
494
+ pct: totalKb ? Number(((imagesKb / totalKb) * 100).toFixed(1)) : 0,
790
495
  },
791
496
  ts: {
792
497
  count: tsCount,
793
498
  kb: tsKb,
794
- pct: Number(((tsKb / totalKb) * 100).toFixed(1)),
499
+ pct: totalKb ? Number(((tsKb / totalKb) * 100).toFixed(1)) : 0,
795
500
  },
796
501
  js: {
797
502
  count: jsCount,
798
503
  kb: jsKb,
799
- pct: Number(((jsKb / totalKb) * 100).toFixed(1)),
504
+ pct: totalKb ? Number(((jsKb / totalKb) * 100).toFixed(1)) : 0,
800
505
  },
801
506
  fonts: {
802
507
  count: fontsCount,
803
508
  kb: fontsKb,
804
- pct: Number(((fontsKb / totalKb) * 100).toFixed(1)),
509
+ pct: totalKb ? Number(((fontsKb / totalKb) * 100).toFixed(1)) : 0,
805
510
  },
806
511
  json: {
807
512
  count: jsonCount,
808
513
  kb: jsonKb,
809
- pct: Number(((jsonKb / totalKb) * 100).toFixed(1)),
514
+ pct: totalKb ? Number(((jsonKb / totalKb) * 100).toFixed(1)) : 0,
810
515
  },
811
516
  };
812
- }, []);
517
+ }, [bundleFiles]);
813
518
  // Filtered files
814
519
  const filteredFiles = useMemo(() => {
815
- return BUNDLE_FILES_DATA.filter(file => {
520
+ return bundleFiles.filter(file => {
816
521
  const matchSearch = !search ||
817
522
  file.name.toLowerCase().includes(search.toLowerCase()) ||
818
523
  file.path.toLowerCase().includes(search.toLowerCase()) ||
@@ -827,7 +532,7 @@ const BundleTab = React.memo(() => {
827
532
  : file.category === activeCategory;
828
533
  return matchSearch && matchCategory;
829
534
  });
830
- }, [search, activeCategory]);
535
+ }, [search, activeCategory, bundleFiles]);
831
536
  const filteredTotalKb = useMemo(() => {
832
537
  return filteredFiles.reduce((acc, f) => acc + f.sizeKb, 0);
833
538
  }, [filteredFiles]);
@@ -862,46 +567,46 @@ const BundleTab = React.memo(() => {
862
567
  // Filtered Packages
863
568
  const filteredPackages = useMemo(() => {
864
569
  if (!search)
865
- return BUNDLE_PACKAGES_DATA;
570
+ return bundlePackages;
866
571
  const q = search.toLowerCase();
867
- return BUNDLE_PACKAGES_DATA.filter(pkg => pkg.name.toLowerCase().includes(q) ||
572
+ return bundlePackages.filter(pkg => pkg.name.toLowerCase().includes(q) ||
868
573
  pkg.description.toLowerCase().includes(q) ||
869
574
  pkg.category.toLowerCase().includes(q));
870
- }, [search]);
575
+ }, [search, bundlePackages]);
871
576
  const packagesTotalKb = useMemo(() => {
872
577
  return filteredPackages.reduce((acc, p) => acc + p.sizeKb, 0);
873
578
  }, [filteredPackages]);
874
579
  // Media files (Images + Fonts)
875
580
  const mediaFiles = useMemo(() => {
876
- return BUNDLE_FILES_DATA.filter(f => f.category === 'image' || f.category === 'font');
877
- }, []);
581
+ return bundleFiles.filter(f => f.category === 'image' || f.category === 'font');
582
+ }, [bundleFiles]);
878
583
  const mediaTotalKb = useMemo(() => {
879
584
  return mediaFiles.reduce((acc, m) => acc + m.sizeKb, 0);
880
585
  }, [mediaFiles]);
881
586
  const subTabs = [
882
587
  {
883
588
  key: 'overview',
884
- label: 'Overview',
589
+ label: t('bundle.tabOverview'),
885
590
  icon: (isActive) => (<LiveStateIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
886
591
  },
887
592
  {
888
593
  key: 'files',
889
- label: `Files (${BUNDLE_FILES_DATA.length})`,
594
+ label: t('bundle.tabFiles', { count: bundleFiles.length }),
890
595
  icon: (isActive) => (<LayersIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
891
596
  },
892
597
  {
893
598
  key: 'packages',
894
- label: `Packages (${BUNDLE_PACKAGES_DATA.length})`,
599
+ label: t('bundle.tabPackages', { count: bundlePackages.length }),
895
600
  icon: (isActive) => (<PackageIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
896
601
  },
897
602
  {
898
603
  key: 'media',
899
- label: `Media (${mediaFiles.length})`,
604
+ label: t('bundle.tabMedia', { count: mediaFiles.length }),
900
605
  icon: (isActive) => (<StorageIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
901
606
  },
902
607
  {
903
608
  key: 'optimizer',
904
- label: 'Optimizer',
609
+ label: t('bundle.tabOptimizer'),
905
610
  icon: (isActive) => (<MetadataIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
906
611
  },
907
612
  ];
@@ -927,10 +632,21 @@ const BundleTab = React.memo(() => {
927
632
  </ScrollView>
928
633
  </View>
929
634
 
635
+ {/* ─── Live Bundle Analysis Loading State ─── */}
636
+ {isAnalyzing && (<View style={bundleStyles.analyzingContainer}>
637
+ <ActivityIndicator size="small" color={AppColors.brandPurple}/>
638
+ <Text style={bundleStyles.analyzingText}>
639
+ {t('bundle.analyzingTitle')}
640
+ </Text>
641
+ <Text style={bundleStyles.analyzingHint}>
642
+ {t('bundle.analyzingHint')}
643
+ </Text>
644
+ </View>)}
645
+
930
646
  {/* ══════════════════════════════════════════════════════════════════════ */}
931
647
  {/* ── 1. TAB: OVERVIEW & TREEMAP ───────────────────────────────────────── */}
932
648
  {/* ══════════════════════════════════════════════════════════════════════ */}
933
- {activeSubTab === 'overview' && (<ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
649
+ {activeSubTab === 'overview' && !isAnalyzing && (<ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
934
650
 
935
651
  {/* Hero Overview Card */}
936
652
  <View style={bundleStyles.heroCard}>
@@ -939,42 +655,59 @@ const BundleTab = React.memo(() => {
939
655
  <PackageIcon color={AppColors.brandPurple} size={24}/>
940
656
  </View>
941
657
  <View style={{ flex: 1 }}>
942
- <Text style={bundleStyles.heroTitle}>Bundle & Asset Architecture</Text>
658
+ <Text style={bundleStyles.heroTitle}>{t('bundle.heroTitle')}</Text>
943
659
  <Text style={bundleStyles.heroSubtitle}>
944
- Real-time size breakdown across all assets, code, and dependencies
660
+ {analysis?.isLive
661
+ ? t('bundle.heroSubtitleLive', { scriptUrl: analysis.scriptURL })
662
+ : t('bundle.heroSubtitle')}
945
663
  </Text>
946
664
  </View>
947
- <CopyButton value={() => summary} label="Bundle Overview JSON"/>
665
+ <CopyButton value={() => ({ ...summary, bundle: analysis })} label={t('bundle.bundleOverviewJson')}/>
948
666
  </View>
949
667
 
950
668
  {/* 4-Stat Metric Grid */}
951
669
  <View style={bundleStyles.metricsGrid}>
952
670
  <View style={bundleStyles.metricBox}>
953
- <Text style={bundleStyles.metricLabel}>TOTAL BUNDLE ASSETS</Text>
954
- <Text style={bundleStyles.metricValue}>~{summary.totalMb} MB</Text>
955
- <Text style={bundleStyles.metricHint}>{BUNDLE_FILES_DATA.length} Tracked files</Text>
671
+ <Text style={bundleStyles.metricLabel}>{t('bundle.devBundleSize')}</Text>
672
+ <Text style={bundleStyles.metricValue}>
673
+ {t('bundle.mbValue', {
674
+ size: analysis ? analysis.totalDevMb : summary.totalMb,
675
+ })}
676
+ </Text>
677
+ <Text style={bundleStyles.metricHint}>
678
+ {analysis
679
+ ? t('bundle.devBundleHint', {
680
+ kb: analysis.totalDevKb,
681
+ modules: analysis.moduleCount,
682
+ })
683
+ : t('bundle.trackedFilesHint', { count: bundleFiles.length })}
684
+ </Text>
956
685
  </View>
957
686
 
958
687
  <View style={bundleStyles.metricBox}>
959
- <Text style={bundleStyles.metricLabel}>IMAGES & MEDIA</Text>
960
- <Text style={[bundleStyles.metricValue, { color: '#EC4899' }]}>
961
- {summary.images.kb} KB
688
+ <Text style={bundleStyles.metricLabel}>{t('bundle.imagesMedia')}</Text>
689
+ <Text style={[bundleStyles.metricValue, { color: AppColors.pink500 }]}>
690
+ {t('bundle.fileSizeKb', { size: summary.images.kb })}
691
+ </Text>
692
+ <Text style={bundleStyles.metricHint}>
693
+ {t('bundle.pctOfTrackedAssets', { pct: summary.images.pct })}
962
694
  </Text>
963
- <Text style={bundleStyles.metricHint}>{summary.images.pct}% of package</Text>
964
695
  </View>
965
696
 
966
697
  <View style={bundleStyles.metricBox}>
967
- <Text style={bundleStyles.metricLabel}>TS / JS SOURCE</Text>
968
- <Text style={[bundleStyles.metricValue, { color: '#0EA5E9' }]}>
969
- {summary.ts.kb + summary.js.kb} KB
698
+ <Text style={bundleStyles.metricLabel}>{t('bundle.tsJsSource')}</Text>
699
+ <Text style={[bundleStyles.metricValue, { color: AppColors.sky500 }]}>
700
+ {t('bundle.fileSizeKb', { size: summary.ts.kb + summary.js.kb })}
970
701
  </Text>
971
702
  <Text style={bundleStyles.metricHint}>
972
- {(summary.ts.pct + summary.js.pct).toFixed(1)}% of package
703
+ {t('bundle.pctOfTrackedAssets', {
704
+ pct: (summary.ts.pct + summary.js.pct).toFixed(1),
705
+ })}
973
706
  </Text>
974
707
  </View>
975
708
 
976
709
  <View style={bundleStyles.metricBox}>
977
- <Text style={bundleStyles.metricLabel}>JS ENGINE</Text>
710
+ <Text style={bundleStyles.metricLabel}>{t('bundle.jsEngine')}</Text>
978
711
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5, marginTop: 2 }}>
979
712
  <View style={[
980
713
  bundleStyles.statusIndicator,
@@ -984,17 +717,109 @@ const BundleTab = React.memo(() => {
984
717
  {isHermes ? 'Hermes' : 'JSC'}
985
718
  </Text>
986
719
  </View>
987
- <Text style={bundleStyles.metricHint}>{isHermes ? 'Bytecode AOT' : 'Standard'}</Text>
720
+ <Text style={bundleStyles.metricHint}>
721
+ {isHermes ? t('bundle.bytecodeAot') : t('bundle.standard')}
722
+ </Text>
988
723
  </View>
989
724
  </View>
990
725
  </View>
991
726
 
727
+ {/* Development Bundle Split-Up (derived from the real running bundle) */}
728
+ {analysis && (<View style={bundleStyles.sectionCard}>
729
+ <View style={bundleStyles.sectionHeaderRow}>
730
+ <View>
731
+ <Text style={bundleStyles.sectionTitle}>{t('bundle.splitUpTitle')}</Text>
732
+ <Text style={bundleStyles.sectionSub}>
733
+ {t('bundle.splitUpSub', {
734
+ mb: analysis.totalDevMb,
735
+ files: analysis.filesCount,
736
+ packages: analysis.packageCount,
737
+ })}
738
+ </Text>
739
+ </View>
740
+ <CopyButton value={() => analysis.splitUp} label={t('bundle.splitUpJson')}/>
741
+ </View>
742
+
743
+ {[
744
+ {
745
+ iconType: 'source',
746
+ label: t('bundle.splitAppSource'),
747
+ color: AppColors.sky500,
748
+ ...analysis.splitUp.appSource,
749
+ },
750
+ {
751
+ iconType: 'deps',
752
+ label: t('bundle.splitNodeModules'),
753
+ color: AppColors.indigo500,
754
+ ...analysis.splitUp.nodeModules,
755
+ },
756
+ {
757
+ iconType: 'media',
758
+ label: t('bundle.splitAssetsMedia'),
759
+ color: AppColors.pink500,
760
+ ...analysis.splitUp.assetsMedia,
761
+ },
762
+ {
763
+ iconType: 'overhead',
764
+ label: t('bundle.splitMetroOverhead'),
765
+ color: AppColors.amber500,
766
+ ...analysis.splitUp.metroDevOverhead,
767
+ },
768
+ ].map((part, idx) => (<View key={idx} style={bundleStyles.catRowCard}>
769
+ <View style={bundleStyles.catRowTop}>
770
+ <BundleCategoryIcon type={part.iconType} size={18} color={part.color}/>
771
+ <View style={{ flex: 1 }}>
772
+ <Text style={bundleStyles.catRowTitle}>{part.label}</Text>
773
+ <Text style={bundleStyles.catRowDesc}>
774
+ {t('bundle.splitSize', { kb: part.kb, mb: part.mb })}
775
+ </Text>
776
+ </View>
777
+ <View style={{ alignItems: 'flex-end' }}>
778
+ <Text style={bundleStyles.catRowSize}>{part.pct}%</Text>
779
+ </View>
780
+ </View>
781
+ <View style={bundleStyles.catProgressTrack}>
782
+ <View style={[
783
+ bundleStyles.catProgressBar,
784
+ { width: `${part.pct}%`, backgroundColor: part.color },
785
+ ]}/>
786
+ </View>
787
+ </View>))}
788
+
789
+ {/* Production Binary Footprint */}
790
+ <View style={bundleStyles.totalSummaryCard}>
791
+ <View style={bundleStyles.totalSummaryRow}>
792
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
793
+ <PackageIcon color={AppColors.purple} size={16}/>
794
+ <Text style={bundleStyles.totalSummaryLabel}>
795
+ {t('bundle.productionFootprint')}
796
+ </Text>
797
+ </View>
798
+ <Text style={bundleStyles.totalSummaryValue}>
799
+ {t('bundle.productionValue', {
800
+ ios: analysis.production.ios.totalInstallMb,
801
+ android: analysis.production.android.totalInstallMb,
802
+ })}
803
+ </Text>
804
+ </View>
805
+ <Text style={bundleStyles.totalSummaryFormula}>
806
+ {t('bundle.productionDownload', {
807
+ ios: analysis.production.ios.totalDownloadMb,
808
+ aab: analysis.production.androidAab.totalDownloadMb,
809
+ apk: analysis.production.androidApk.totalDownloadMb,
810
+ })}
811
+ </Text>
812
+ </View>
813
+ </View>)}
814
+
992
815
  {/* Visual Type Distribution Ratio Bar */}
993
816
  <View style={bundleStyles.sectionCard}>
994
817
  <View style={bundleStyles.sectionHeaderRow}>
995
818
  <View>
996
- <Text style={bundleStyles.sectionTitle}>Asset & File Type Ratio Treemap</Text>
997
- <Text style={bundleStyles.sectionSub}>Total ~{summary.totalMb} MB</Text>
819
+ <Text style={bundleStyles.sectionTitle}>{t('bundle.treemapTitle')}</Text>
820
+ <Text style={bundleStyles.sectionSub}>
821
+ {t('bundle.treemapSub', { mb: summary.totalMb })}
822
+ </Text>
998
823
  </View>
999
824
  <CopyButton value={() => ({
1000
825
  totalMb: summary.totalMb,
@@ -1004,48 +829,63 @@ const BundleTab = React.memo(() => {
1004
829
  js: summary.js,
1005
830
  fonts: summary.fonts,
1006
831
  json: summary.json,
1007
- })} label="Ratio Treemap JSON"/>
832
+ })} label={t('bundle.treemapJson')}/>
1008
833
  </View>
1009
834
 
1010
835
  {/* Stacked Colored Bar */}
1011
836
  <View style={bundleStyles.treemapBar}>
1012
- <View style={{ flex: summary.images.pct, backgroundColor: '#EC4899', height: 16 }}/>
1013
- <View style={{ flex: summary.ts.pct, backgroundColor: '#0EA5E9', height: 16 }}/>
1014
- <View style={{ flex: summary.js.pct, backgroundColor: '#6366F1', height: 16 }}/>
1015
- <View style={{ flex: summary.fonts.pct, backgroundColor: '#8B5CF6', height: 16 }}/>
1016
- <View style={{ flex: summary.json.pct, backgroundColor: '#10B981', height: 16 }}/>
837
+ <View style={{ flex: summary.images.pct, backgroundColor: AppColors.pink500, height: 16 }}/>
838
+ <View style={{ flex: summary.ts.pct, backgroundColor: AppColors.sky500, height: 16 }}/>
839
+ <View style={{ flex: summary.js.pct, backgroundColor: AppColors.indigo500, height: 16 }}/>
840
+ <View style={{ flex: summary.fonts.pct, backgroundColor: AppColors.purple500, height: 16 }}/>
841
+ <View style={{ flex: summary.json.pct, backgroundColor: AppColors.emerald500, height: 16 }}/>
1017
842
  </View>
1018
843
 
1019
844
  {/* Legend Grid */}
1020
845
  <View style={bundleStyles.legendGrid}>
1021
846
  <View style={bundleStyles.legendItem}>
1022
- <View style={[bundleStyles.legendDot, { backgroundColor: '#EC4899' }]}/>
847
+ <BundleCategoryIcon type="image" size={13} color={AppColors.pink500}/>
1023
848
  <Text style={bundleStyles.legendText}>
1024
- 🖼️ Images: <Text style={bundleStyles.legendVal}>{summary.images.kb} KB ({summary.images.pct}%)</Text>
849
+ {t('bundle.legendImages')}{' '}
850
+ <Text style={bundleStyles.legendVal}>
851
+ {t('bundle.legendValue', { kb: summary.images.kb, pct: summary.images.pct })}
852
+ </Text>
1025
853
  </Text>
1026
854
  </View>
1027
855
  <View style={bundleStyles.legendItem}>
1028
- <View style={[bundleStyles.legendDot, { backgroundColor: '#0EA5E9' }]}/>
856
+ <BundleCategoryIcon type="typescript" size={13} color={AppColors.sky500}/>
1029
857
  <Text style={bundleStyles.legendText}>
1030
- 📜 TS/TSX: <Text style={bundleStyles.legendVal}>{summary.ts.kb} KB ({summary.ts.pct}%)</Text>
858
+ {t('bundle.legendTs')}{' '}
859
+ <Text style={bundleStyles.legendVal}>
860
+ {t('bundle.legendValue', { kb: summary.ts.kb, pct: summary.ts.pct })}
861
+ </Text>
1031
862
  </Text>
1032
863
  </View>
1033
864
  <View style={bundleStyles.legendItem}>
1034
- <View style={[bundleStyles.legendDot, { backgroundColor: '#6366F1' }]}/>
865
+ <BundleCategoryIcon type="javascript" size={13} color={AppColors.indigo500}/>
1035
866
  <Text style={bundleStyles.legendText}>
1036
- ⚙️ JS Libs: <Text style={bundleStyles.legendVal}>{summary.js.kb} KB ({summary.js.pct}%)</Text>
867
+ {t('bundle.legendJs')}{' '}
868
+ <Text style={bundleStyles.legendVal}>
869
+ {t('bundle.legendValue', { kb: summary.js.kb, pct: summary.js.pct })}
870
+ </Text>
1037
871
  </Text>
1038
872
  </View>
1039
873
  <View style={bundleStyles.legendItem}>
1040
- <View style={[bundleStyles.legendDot, { backgroundColor: '#8B5CF6' }]}/>
874
+ <BundleCategoryIcon type="font" size={13} color={AppColors.purple500}/>
1041
875
  <Text style={bundleStyles.legendText}>
1042
- 🔤 Fonts: <Text style={bundleStyles.legendVal}>{summary.fonts.kb} KB ({summary.fonts.pct}%)</Text>
876
+ {t('bundle.legendFonts')}{' '}
877
+ <Text style={bundleStyles.legendVal}>
878
+ {t('bundle.legendValue', { kb: summary.fonts.kb, pct: summary.fonts.pct })}
879
+ </Text>
1043
880
  </Text>
1044
881
  </View>
1045
882
  <View style={bundleStyles.legendItem}>
1046
- <View style={[bundleStyles.legendDot, { backgroundColor: '#10B981' }]}/>
883
+ <BundleCategoryIcon type="json" size={13} color={AppColors.emerald500}/>
1047
884
  <Text style={bundleStyles.legendText}>
1048
- 📄 JSON: <Text style={bundleStyles.legendVal}>{summary.json.kb} KB ({summary.json.pct}%)</Text>
885
+ {t('bundle.legendJson')}{' '}
886
+ <Text style={bundleStyles.legendVal}>
887
+ {t('bundle.legendValue', { kb: summary.json.kb, pct: summary.json.pct })}
888
+ </Text>
1049
889
  </Text>
1050
890
  </View>
1051
891
  </View>
@@ -1054,7 +894,7 @@ const BundleTab = React.memo(() => {
1054
894
  {/* Detailed Category Footprint Cards */}
1055
895
  <View style={bundleStyles.sectionCard}>
1056
896
  <View style={bundleStyles.sectionHeaderRow}>
1057
- <Text style={bundleStyles.sectionTitle}>Category Breakdown</Text>
897
+ <Text style={bundleStyles.sectionTitle}>{t('bundle.categoryBreakdown')}</Text>
1058
898
  <CopyButton value={() => ({
1059
899
  totalAssets: summary.totalCount,
1060
900
  totalSizeKb: summary.totalKb,
@@ -1065,61 +905,66 @@ const BundleTab = React.memo(() => {
1065
905
  typescript: summary.ts,
1066
906
  json: summary.json,
1067
907
  },
1068
- })} label="Categories Breakdown JSON"/>
908
+ })} label={t('bundle.categoriesJson')}/>
1069
909
  </View>
1070
910
 
1071
911
  {[
1072
912
  {
1073
- icon: '🖼️',
1074
- title: 'Images & Media Assets',
913
+ iconType: 'image',
914
+ title: t('bundle.catImagesTitle'),
1075
915
  count: summary.images.count,
1076
- size: `${summary.images.kb} KB`,
916
+ size: t('bundle.fileSizeKb', { size: summary.images.kb }),
1077
917
  pct: summary.images.pct,
1078
- color: '#EC4899',
1079
- desc: 'PNG, WebP, SVG, and JPG assets in bundle',
918
+ color: AppColors.pink500,
919
+ desc: t('bundle.catImagesDesc'),
1080
920
  },
1081
921
  {
1082
- icon: '⚙️',
1083
- title: 'Compiled Node Modules & JS',
922
+ iconType: 'javascript',
923
+ title: t('bundle.catJsTitle'),
1084
924
  count: summary.js.count,
1085
- size: `${summary.js.kb} KB`,
925
+ size: t('bundle.fileSizeKb', { size: summary.js.kb }),
1086
926
  pct: summary.js.pct,
1087
- color: '#6366F1',
1088
- desc: 'Third-party dependencies and native bridges',
927
+ color: AppColors.indigo500,
928
+ desc: t('bundle.catJsDesc'),
1089
929
  },
1090
930
  {
1091
- icon: '🔤',
1092
- title: 'Custom Fonts & Vector Glyphs',
931
+ iconType: 'font',
932
+ title: t('bundle.catFontsTitle'),
1093
933
  count: summary.fonts.count,
1094
- size: `${summary.fonts.kb} KB`,
934
+ size: t('bundle.fileSizeKb', { size: summary.fonts.kb }),
1095
935
  pct: summary.fonts.pct,
1096
- color: '#8B5CF6',
1097
- desc: 'Inter, JetBrainsMono, and UI typography fonts',
936
+ color: AppColors.purple500,
937
+ desc: bundleFiles
938
+ .filter(f => f.category === 'font')
939
+ .map(f => f.name)
940
+ .join(', ') || t('bundle.catFontsDesc'),
1098
941
  },
1099
942
  {
1100
- icon: '📜',
1101
- title: 'TypeScript & JSX Components',
943
+ iconType: 'typescript',
944
+ title: t('bundle.catTsTitle'),
1102
945
  count: summary.ts.count,
1103
- size: `${summary.ts.kb} KB`,
946
+ size: t('bundle.fileSizeKb', { size: summary.ts.kb }),
1104
947
  pct: summary.ts.pct,
1105
- color: '#0EA5E9',
1106
- desc: 'App screen components, hooks, and business logic',
948
+ color: AppColors.sky500,
949
+ desc: t('bundle.catTsDesc'),
1107
950
  },
1108
951
  {
1109
- icon: '📄',
1110
- title: 'JSON Data & Localizations',
952
+ iconType: 'json',
953
+ title: t('bundle.catJsonTitle'),
1111
954
  count: summary.json.count,
1112
- size: `${summary.json.kb} KB`,
955
+ size: t('bundle.fileSizeKb', { size: summary.json.kb }),
1113
956
  pct: summary.json.pct,
1114
- color: '#10B981',
1115
- desc: 'i18n translation dictionaries and static configs',
957
+ color: AppColors.emerald500,
958
+ desc: t('bundle.catJsonDesc'),
1116
959
  },
1117
960
  ].map((cat, cIdx) => (<View key={cIdx} style={bundleStyles.catRowCard}>
1118
961
  <View style={bundleStyles.catRowTop}>
1119
- <Text style={bundleStyles.catRowIcon}>{cat.icon}</Text>
962
+ <BundleCategoryIcon type={cat.iconType} size={18} color={cat.color}/>
1120
963
  <View style={{ flex: 1 }}>
1121
964
  <Text style={bundleStyles.catRowTitle}>{cat.title}</Text>
1122
- <Text style={bundleStyles.catRowDesc}>{cat.count} items • {cat.desc}</Text>
965
+ <Text style={bundleStyles.catRowDesc}>
966
+ {t('bundle.catRowSub', { count: cat.count, desc: cat.desc })}
967
+ </Text>
1123
968
  </View>
1124
969
  <View style={{ alignItems: 'flex-end' }}>
1125
970
  <Text style={bundleStyles.catRowSize}>{cat.size}</Text>
@@ -1139,14 +984,24 @@ const BundleTab = React.memo(() => {
1139
984
  <View style={bundleStyles.totalSummaryRow}>
1140
985
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1141
986
  <PackageIcon color={AppColors.purple} size={16}/>
1142
- <Text style={bundleStyles.totalSummaryLabel}>TOTAL BUNDLE ASSETS</Text>
987
+ <Text style={bundleStyles.totalSummaryLabel}>
988
+ {t('bundle.totalBundleAssets')}
989
+ </Text>
1143
990
  </View>
1144
991
  <Text style={bundleStyles.totalSummaryValue}>
1145
- {summary.totalKb} KB (~{summary.totalMb} MB)
992
+ {t('bundle.totalBundleValue', { kb: summary.totalKb, mb: summary.totalMb })}
1146
993
  </Text>
1147
994
  </View>
1148
995
  <Text style={bundleStyles.totalSummaryFormula}>
1149
- Sum: {summary.images.kb} KB + {summary.js.kb} KB + {summary.fonts.kb} KB + {summary.ts.kb} KB + {summary.json.kb} KB = {summary.totalKb} KB ({summary.totalCount} items • 100%)
996
+ {t('bundle.totalFormula', {
997
+ images: summary.images.kb,
998
+ js: summary.js.kb,
999
+ fonts: summary.fonts.kb,
1000
+ ts: summary.ts.kb,
1001
+ json: summary.json.kb,
1002
+ total: summary.totalKb,
1003
+ count: summary.totalCount,
1004
+ })}
1150
1005
  </Text>
1151
1006
  </View>
1152
1007
  </View>
@@ -1155,16 +1010,16 @@ const BundleTab = React.memo(() => {
1155
1010
  {/* ══════════════════════════════════════════════════════════════════════ */}
1156
1011
  {/* ── 2. TAB: FILES BREAKDOWN ─────────────────────────────────────────── */}
1157
1012
  {/* ══════════════════════════════════════════════════════════════════════ */}
1158
- {activeSubTab === 'files' && (<View style={{ flex: 1 }}>
1013
+ {activeSubTab === 'files' && !isAnalyzing && (<View style={{ flex: 1 }}>
1159
1014
  {/* Search & Category Filter */}
1160
1015
  <View style={bundleStyles.filterContainer}>
1161
1016
  <View style={bundleStyles.searchRow}>
1162
1017
  <SearchIcon color={AppColors.grayTextWeak} size={15}/>
1163
- <TextInput placeholder="Search by file name (.png, .tsx, .ttf, path)..." placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
1018
+ <TextInput placeholder={t('bundle.searchFilesPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
1164
1019
  {search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10}>
1165
1020
  <ClearIcon color={AppColors.grayTextWeak} size={14}/>
1166
1021
  </Pressable>)}
1167
- <CopyButton value={() => filteredFiles} label="Filtered Files JSON"/>
1022
+ <CopyButton value={() => filteredFiles} label={t('bundle.filteredFilesJson')}/>
1168
1023
  </View>
1169
1024
 
1170
1025
  {/* Category Filter Chips */}
@@ -1172,24 +1027,24 @@ const BundleTab = React.memo(() => {
1172
1027
  {CATEGORY_TABS.map(tab => {
1173
1028
  const isActive = activeCategory === tab.key;
1174
1029
  const count = tab.key === 'ALL'
1175
- ? BUNDLE_FILES_DATA.length
1030
+ ? bundleFiles.length
1176
1031
  : tab.key === 'UNUSED'
1177
- ? BUNDLE_FILES_DATA.filter(f => f.isConsumed === false).length
1032
+ ? bundleFiles.filter(f => f.isConsumed === false).length
1178
1033
  : tab.key === 'CONSUMED'
1179
- ? BUNDLE_FILES_DATA.filter(f => f.isConsumed !== false).length
1180
- : BUNDLE_FILES_DATA.filter(f => f.category === tab.key).length;
1034
+ ? bundleFiles.filter(f => f.isConsumed !== false).length
1035
+ : bundleFiles.filter(f => f.category === tab.key).length;
1181
1036
  return (<TouchableScale key={tab.key} onPress={() => setActiveCategory(tab.key)} style={[
1182
1037
  bundleStyles.catPill,
1183
1038
  isActive && bundleStyles.catPillActive,
1184
- tab.key === 'UNUSED' && isActive && { backgroundColor: '#EF4444', borderColor: '#DC2626' },
1185
- tab.key === 'CONSUMED' && isActive && { backgroundColor: '#059669', borderColor: '#047857' },
1039
+ tab.key === 'UNUSED' && isActive && { backgroundColor: AppColors.red500, borderColor: AppColors.red600 },
1040
+ tab.key === 'CONSUMED' && isActive && { backgroundColor: AppColors.emerald600, borderColor: AppColors.emerald700 },
1186
1041
  ]}>
1187
- <Text style={bundleStyles.catPillIcon}>{tab.icon}</Text>
1042
+ <BundleCategoryIcon type={tab.iconType} size={12} color={isActive ? AppColors.white : AppColors.grayTextStrong}/>
1188
1043
  <Text style={[
1189
1044
  bundleStyles.catPillText,
1190
1045
  isActive && bundleStyles.catPillTextActive,
1191
1046
  ]}>
1192
- {tab.label} ({count})
1047
+ {t(tab.labelKey)} ({count})
1193
1048
  </Text>
1194
1049
  </TouchableScale>);
1195
1050
  })}
@@ -1210,7 +1065,7 @@ const BundleTab = React.memo(() => {
1210
1065
  bundleStyles.viewModeBtnText,
1211
1066
  filesViewMode === 'tree' && bundleStyles.viewModeBtnTextActive,
1212
1067
  ]}>
1213
- 🌳 Tree View
1068
+ {t('bundle.treeView')}
1214
1069
  </Text>
1215
1070
  </TouchableScale>
1216
1071
  <TouchableScale onPress={() => setFilesViewMode('list')} style={[
@@ -1221,21 +1076,24 @@ const BundleTab = React.memo(() => {
1221
1076
  bundleStyles.viewModeBtnText,
1222
1077
  filesViewMode === 'list' && bundleStyles.viewModeBtnTextActive,
1223
1078
  ]}>
1224
- 📄 Flat List
1079
+ {t('bundle.flatList')}
1225
1080
  </Text>
1226
1081
  </TouchableScale>
1227
1082
  </View>
1228
1083
 
1229
1084
  {filesViewMode === 'tree' ? (<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1230
1085
  <TouchableScale onPress={expandAllFolders}>
1231
- <Text style={bundleStyles.treeActionLink}>Expand</Text>
1086
+ <Text style={bundleStyles.treeActionLink}>{t('bundle.expand')}</Text>
1232
1087
  </TouchableScale>
1233
1088
  <Text style={{ color: AppColors.grayTextWeak, fontSize: 11 }}>•</Text>
1234
1089
  <TouchableScale onPress={collapseAllFolders}>
1235
- <Text style={bundleStyles.treeActionLink}>Collapse</Text>
1090
+ <Text style={bundleStyles.treeActionLink}>{t('bundle.collapse')}</Text>
1236
1091
  </TouchableScale>
1237
1092
  </View>) : (<Text style={bundleStyles.listHeaderCount}>
1238
- Showing {filteredFiles.length} of {BUNDLE_FILES_DATA.length} files
1093
+ {t('bundle.showingFilesOf', {
1094
+ shown: filteredFiles.length,
1095
+ total: bundleFiles.length,
1096
+ })}
1239
1097
  </Text>)}
1240
1098
  </View>
1241
1099
 
@@ -1260,9 +1118,9 @@ const BundleTab = React.memo(() => {
1260
1118
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
1261
1119
  <HighlightText text={file.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
1262
1120
  {isUnused ? (<View style={bundleStyles.unusedBadge}>
1263
- <Text style={bundleStyles.unusedBadgeText}>🚫 Not Consumed</Text>
1121
+ <Text style={bundleStyles.unusedBadgeText}>{t('bundle.notConsumed')}</Text>
1264
1122
  </View>) : (<View style={bundleStyles.consumedBadge}>
1265
- <Text style={bundleStyles.consumedBadgeText}>⚡ Consumed</Text>
1123
+ <Text style={bundleStyles.consumedBadgeText}>{t('bundle.consumed')}</Text>
1266
1124
  </View>)}
1267
1125
  </View>
1268
1126
  <HighlightText text={file.path} search={search} style={bundleStyles.filePath} highlightStyle={bundleStyles.searchHighlight} numberOfLines={1}/>
@@ -1270,14 +1128,14 @@ const BundleTab = React.memo(() => {
1270
1128
 
1271
1129
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1272
1130
  <View style={bundleStyles.fileSizeBox}>
1273
- <Text style={[bundleStyles.fileSizeKb, isUnused && { color: '#B45309' }]}>
1131
+ <Text style={[bundleStyles.fileSizeKb, isUnused && { color: AppColors.amber800Warm }]}>
1274
1132
  {file.sizeKb >= 1024
1275
- ? `${(file.sizeKb / 1024).toFixed(2)} MB`
1276
- : `${file.sizeKb} KB`}
1133
+ ? t('bundle.fileSizeMb', { size: (file.sizeKb / 1024).toFixed(2) })
1134
+ : t('bundle.fileSizeKb', { size: file.sizeKb })}
1277
1135
  </Text>
1278
1136
  <Text style={bundleStyles.filePercent}>{pctOfTotal}%</Text>
1279
1137
  </View>
1280
- <CopyButton value={() => file} label="File Details JSON"/>
1138
+ <CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
1281
1139
  </View>
1282
1140
  </View>
1283
1141
 
@@ -1286,7 +1144,7 @@ const BundleTab = React.memo(() => {
1286
1144
  bundleStyles.fileProgressBar,
1287
1145
  {
1288
1146
  width: `${Math.min(100, Math.max(4, Number(pctOfTotal) * 4))}%`,
1289
- backgroundColor: isUnused ? '#EF4444' : categoryMeta.color,
1147
+ backgroundColor: isUnused ? AppColors.red500 : categoryMeta.color,
1290
1148
  },
1291
1149
  ]}/>
1292
1150
  </View>
@@ -1299,15 +1157,17 @@ const BundleTab = React.memo(() => {
1299
1157
  file.status === 'optimal' && bundleStyles.adviceOptimal,
1300
1158
  isUnused && bundleStyles.adviceUnused,
1301
1159
  ]}>
1160
+ <View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
1161
+ {isUnused ? (<TrashIcon color={AppColors.red600} size={12}/>) : file.status === 'warning' ? (<LightbulbIcon color={AppColors.amber800Warm} size={12}/>) : (<SparkleIcon color={AppColors.emerald700} size={12}/>)}
1302
1162
  <Text style={[
1303
1163
  bundleStyles.adviceText,
1304
- file.status === 'warning' && { color: '#B45309' },
1305
- file.status === 'optimal' && { color: '#047857' },
1306
- isUnused && { color: '#DC2626' },
1164
+ file.status === 'warning' && { color: AppColors.amber800Warm },
1165
+ file.status === 'optimal' && { color: AppColors.emerald700 },
1166
+ isUnused && { color: AppColors.red600 },
1307
1167
  ]}>
1308
- {isUnused ? '🗑️ ' : file.status === 'warning' ? '💡 ' : '✨ '}
1309
1168
  {file.advice}
1310
1169
  </Text>
1170
+ </View>
1311
1171
  </View>)}
1312
1172
  </View>
1313
1173
  </View>);
@@ -1319,11 +1179,17 @@ const BundleTab = React.memo(() => {
1319
1179
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1320
1180
  <LayersIcon color={AppColors.purple} size={14}/>
1321
1181
  <Text style={bundleStyles.footerTitle}>
1322
- {filteredFiles.length} of {summary.totalCount} Files
1182
+ {t('bundle.filesOf', {
1183
+ count: filteredFiles.length,
1184
+ total: summary.totalCount,
1185
+ })}
1323
1186
  </Text>
1324
1187
  </View>
1325
1188
  <Text style={bundleStyles.footerSizeVal}>
1326
- {filteredTotalKb} KB ({(filteredTotalKb / 1024).toFixed(2)} MB)
1189
+ {t('bundle.kbMbValue', {
1190
+ kb: filteredTotalKb,
1191
+ mb: (filteredTotalKb / 1024).toFixed(2),
1192
+ })}
1327
1193
  </Text>
1328
1194
  </View>
1329
1195
  </View>)}
@@ -1331,28 +1197,29 @@ const BundleTab = React.memo(() => {
1331
1197
  {/* ══════════════════════════════════════════════════════════════════════ */}
1332
1198
  {/* ── 3. TAB: PACKAGES & NODE_MODULES ─────────────────────────────────── */}
1333
1199
  {/* ══════════════════════════════════════════════════════════════════════ */}
1334
- {activeSubTab === 'packages' && (<View style={{ flex: 1 }}>
1200
+ {activeSubTab === 'packages' && !isAnalyzing && (<View style={{ flex: 1 }}>
1335
1201
  <ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
1336
1202
 
1337
1203
  <View style={bundleStyles.filterContainer}>
1338
1204
  <View style={bundleStyles.searchRow}>
1339
1205
  <SearchIcon color={AppColors.grayTextWeak} size={15}/>
1340
- <TextInput placeholder="Search package (react-native, axios, navigation)..." placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
1206
+ <TextInput placeholder={t('bundle.searchPackagesPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
1341
1207
  {search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10}>
1342
1208
  <ClearIcon color={AppColors.grayTextWeak} size={14}/>
1343
1209
  </Pressable>)}
1344
- <CopyButton value={() => filteredPackages} label="Dependencies JSON"/>
1210
+ <CopyButton value={() => filteredPackages} label={t('bundle.dependenciesJson')}/>
1345
1211
  </View>
1346
1212
  </View>
1347
1213
 
1348
1214
  <Text style={bundleStyles.listHeaderCount}>
1349
- Showing {filteredPackages.length} dependencies
1215
+ {t('bundle.showingDependencies', { count: filteredPackages.length })}
1350
1216
  </Text>
1351
1217
 
1352
1218
  {filteredPackages.map((pkg, index) => {
1353
- const cleanInstalledVersion = pkg.version.replace(/^\^/, '');
1354
- const hasUpdate = pkg.latestVersion && pkg.latestVersion !== cleanInstalledVersion;
1355
- return (<View key={pkg.id} style={[bundleStyles.fileCard, pkg.isDeprecated && { borderColor: '#FECDD3', backgroundColor: '#FFFDFD' }]}>
1219
+ const cleanInstalledVersion = pkg.version ? pkg.version.replace(/^\^/, '') : '';
1220
+ const hasUpdate = !!pkg.latestVersion && !!cleanInstalledVersion &&
1221
+ pkg.latestVersion !== cleanInstalledVersion;
1222
+ return (<View key={pkg.id} style={[bundleStyles.fileCard, pkg.isDeprecated && { borderColor: AppColors.errorBorder, backgroundColor: '#FFFDFD' }]}>
1356
1223
  {/* Header Row: #s.no + Logo + Package Name + Version Pill + Update/Deprecated Badges + Size & Copy */}
1357
1224
  <View style={bundleStyles.fileCardTop}>
1358
1225
  <View style={bundleStyles.sNoBadge}>
@@ -1366,26 +1233,40 @@ const BundleTab = React.memo(() => {
1366
1233
  <HighlightText text={pkg.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
1367
1234
  <View style={bundleStyles.versionPill}>
1368
1235
  <Text style={bundleStyles.versionPillText}>
1369
- v{cleanInstalledVersion}
1236
+ {cleanInstalledVersion
1237
+ ? t('bundle.versionPrefix', { version: cleanInstalledVersion })
1238
+ : t('bundle.bundled')}
1370
1239
  </Text>
1371
1240
  </View>
1372
1241
 
1373
1242
  {pkg.isDeprecated ? (<View style={bundleStyles.deprecatedBadge}>
1374
- <Text style={bundleStyles.deprecatedBadgeText}>⚠️ DEPRECATED</Text>
1243
+ <Text style={bundleStyles.deprecatedBadgeText}>
1244
+ {t('bundle.deprecated')}
1245
+ </Text>
1375
1246
  </View>) : hasUpdate ? (<View style={bundleStyles.updateBadge}>
1376
- <Text style={bundleStyles.updateBadgeText}>⚡ Update: v{pkg.latestVersion}</Text>
1377
- </View>) : (<View style={[bundleStyles.updateBadge, { backgroundColor: '#F3F4F6', borderColor: '#E5E7EB' }]}>
1378
- <Text style={[bundleStyles.updateBadgeText, { color: '#4B5563' }]}>✨ Up to date</Text>
1247
+ <Text style={bundleStyles.updateBadgeText}>
1248
+ {t('bundle.updateAvailable', { version: pkg.latestVersion })}
1249
+ </Text>
1250
+ </View>) : cleanInstalledVersion ? (<View style={[bundleStyles.updateBadge, { backgroundColor: AppColors.gray100, borderColor: AppColors.gray200 }]}>
1251
+ <Text style={[bundleStyles.updateBadgeText, { color: AppColors.gray600 }]}>
1252
+ {t('bundle.upToDate')}
1253
+ </Text>
1254
+ </View>) : (<View style={[bundleStyles.updateBadge, { backgroundColor: AppColors.gray100, borderColor: AppColors.gray200 }]}>
1255
+ <Text style={[bundleStyles.updateBadgeText, { color: AppColors.gray600 }]}>
1256
+ {t('bundle.bundledBadge')}
1257
+ </Text>
1379
1258
  </View>)}
1380
1259
  </View>
1381
1260
  </View>
1382
1261
 
1383
1262
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1384
1263
  <View style={bundleStyles.fileSizeBox}>
1385
- <Text style={bundleStyles.fileSizeKb}>{pkg.sizeKb} KB</Text>
1264
+ <Text style={bundleStyles.fileSizeKb}>
1265
+ {t('bundle.fileSizeKb', { size: pkg.sizeKb })}
1266
+ </Text>
1386
1267
  <Text style={bundleStyles.filePercent}>{pkg.percentage}%</Text>
1387
1268
  </View>
1388
- <CopyButton value={() => pkg} label="Package Details JSON"/>
1269
+ <CopyButton value={() => pkg} label={t('bundle.packageDetailsJson')}/>
1389
1270
  </View>
1390
1271
  </View>
1391
1272
 
@@ -1403,15 +1284,18 @@ const BundleTab = React.memo(() => {
1403
1284
 
1404
1285
  {/* Deprecation Warning Banner if deprecated */}
1405
1286
  {pkg.isDeprecated && pkg.deprecationReason && (<View style={bundleStyles.deprecationBox}>
1406
- <Text style={bundleStyles.deprecationText}>
1407
- ⚠️ {pkg.deprecationReason}
1408
- </Text>
1287
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
1288
+ <CircleAlertIcon color={AppColors.errorColor} size={12}/>
1289
+ <Text style={bundleStyles.deprecationText}>
1290
+ {pkg.deprecationReason}
1291
+ </Text>
1292
+ </View>
1409
1293
  </View>)}
1410
1294
 
1411
1295
  <View style={bundleStyles.fileProgressTrack}>
1412
1296
  <View style={[
1413
1297
  bundleStyles.fileProgressBar,
1414
- { width: `${pkg.percentage * 2}%`, backgroundColor: pkg.isDeprecated ? '#EF4444' : pkg.color },
1298
+ { width: `${pkg.percentage * 2}%`, backgroundColor: pkg.isDeprecated ? AppColors.red500 : pkg.color },
1415
1299
  ]}/>
1416
1300
  </View>
1417
1301
 
@@ -1420,23 +1304,26 @@ const BundleTab = React.memo(() => {
1420
1304
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1421
1305
  <View style={bundleStyles.typeTag}>
1422
1306
  <Text style={bundleStyles.typeTagText}>
1423
- {pkg.type === 'direct' ? '📦 Direct' : '🔗 Transitive'}
1307
+ {pkg.type === 'direct' ? t('bundle.direct') : t('bundle.transitive')}
1424
1308
  </Text>
1425
1309
  </View>
1426
- {pkg.lastActive && (<Text style={bundleStyles.lastActiveText}>
1427
- 🕒 {pkg.lastActive}
1428
- </Text>)}
1310
+ {pkg.lastActive && (<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
1311
+ <ClockIcon color={AppColors.grayTextWeak} size={11}/>
1312
+ <Text style={bundleStyles.lastActiveText}>
1313
+ {pkg.lastActive}
1314
+ </Text>
1315
+ </View>)}
1429
1316
  </View>
1430
1317
 
1431
1318
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1432
1319
  <Text style={bundleStyles.fileMetaText}>
1433
- ~{pkg.sizeKb} KB minified
1320
+ {t('bundle.minified', { size: pkg.sizeKb })}
1434
1321
  </Text>
1435
1322
  <TouchableScale onPress={() => {
1436
1323
  const url = pkg.npmUrl || `https://www.npmjs.com/package/${pkg.name}`;
1437
1324
  Linking.openURL(url).catch(() => { });
1438
1325
  }} style={bundleStyles.npmLinkBtn}>
1439
- <Text style={bundleStyles.npmLinkText}>npm ↗</Text>
1326
+ <Text style={bundleStyles.npmLinkText}>{t('bundle.npmLink')}</Text>
1440
1327
  </TouchableScale>
1441
1328
  </View>
1442
1329
  </View>
@@ -1449,11 +1336,14 @@ const BundleTab = React.memo(() => {
1449
1336
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1450
1337
  <PackageIcon color={AppColors.purple} size={14}/>
1451
1338
  <Text style={bundleStyles.footerTitle}>
1452
- {filteredPackages.length} Dependencies
1339
+ {t('bundle.dependenciesCount', { count: filteredPackages.length })}
1453
1340
  </Text>
1454
1341
  </View>
1455
1342
  <Text style={bundleStyles.footerSizeVal}>
1456
- {packagesTotalKb} KB ({(packagesTotalKb / 1024).toFixed(2)} MB)
1343
+ {t('bundle.kbMbValue', {
1344
+ kb: packagesTotalKb,
1345
+ mb: (packagesTotalKb / 1024).toFixed(2),
1346
+ })}
1457
1347
  </Text>
1458
1348
  </View>
1459
1349
  </View>)}
@@ -1461,24 +1351,32 @@ const BundleTab = React.memo(() => {
1461
1351
  {/* ══════════════════════════════════════════════════════════════════════ */}
1462
1352
  {/* ── 4. TAB: MEDIA & ASSET AUDITOR ───────────────────────────────────── */}
1463
1353
  {/* ══════════════════════════════════════════════════════════════════════ */}
1464
- {activeSubTab === 'media' && (<View style={{ flex: 1 }}>
1354
+ {activeSubTab === 'media' && !isAnalyzing && (<View style={{ flex: 1 }}>
1465
1355
  <ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
1466
1356
 
1467
1357
  <View style={bundleStyles.tipsCard}>
1468
1358
  <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
1469
1359
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1470
1360
  <StorageIcon color={AppColors.purple} size={18}/>
1471
- <Text style={bundleStyles.tipsHeading}>Media Compression Auditor</Text>
1361
+ <Text style={bundleStyles.tipsHeading}>{t('bundle.mediaAuditorTitle')}</Text>
1472
1362
  </View>
1473
- <CopyButton value={() => mediaFiles} label="Media Assets JSON"/>
1363
+ <CopyButton value={() => mediaFiles} label={t('bundle.mediaAssetsJson')}/>
1474
1364
  </View>
1475
1365
  <Text style={bundleStyles.tipDesc}>
1476
- Images & Fonts constitute <Text style={{ fontFamily: AppFonts.interBold, color: AppColors.purple }}>{summary.images.pct + summary.fonts.pct}%</Text> of total assets (~{summary.images.kb + summary.fonts.kb} KB). Converting PNGs to WebP and font subsetting can reduce size by up to <Text style={{ fontFamily: AppFonts.interBold, color: '#047857' }}>540 KB</Text>.
1366
+ {t('bundle.mediaAuditorPrefix')}{' '}
1367
+ <Text style={{ fontFamily: AppFonts.interBold, color: AppColors.purple }}>
1368
+ {summary.images.pct + summary.fonts.pct}%
1369
+ </Text>{' '}
1370
+ {t('bundle.mediaAuditorMid', { kb: summary.images.kb + summary.fonts.kb })}{' '}
1371
+ <Text style={{ fontFamily: AppFonts.interBold, color: AppColors.emerald700 }}>
1372
+ {t('bundle.mediaSavings', { size: 540 })}
1373
+ </Text>{' '}
1374
+ {t('bundle.mediaAuditorSuffix')}
1477
1375
  </Text>
1478
1376
  </View>
1479
1377
 
1480
1378
  <Text style={bundleStyles.listHeaderCount}>
1481
- Media Assets & Fonts ({mediaFiles.length} items)
1379
+ {t('bundle.mediaAssetsList', { count: mediaFiles.length })}
1482
1380
  </Text>
1483
1381
 
1484
1382
  {mediaFiles.map((file, index) => (<View key={file.id} style={bundleStyles.fileCard}>
@@ -1496,9 +1394,11 @@ const BundleTab = React.memo(() => {
1496
1394
 
1497
1395
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1498
1396
  <View style={bundleStyles.fileSizeBox}>
1499
- <Text style={bundleStyles.fileSizeKb}>{file.sizeKb} KB</Text>
1397
+ <Text style={bundleStyles.fileSizeKb}>
1398
+ {t('bundle.fileSizeKb', { size: file.sizeKb })}
1399
+ </Text>
1500
1400
  </View>
1501
- <CopyButton value={() => file} label="Media Item JSON"/>
1401
+ <CopyButton value={() => file} label={t('bundle.mediaItemJson')}/>
1502
1402
  </View>
1503
1403
  </View>
1504
1404
 
@@ -1509,14 +1409,16 @@ const BundleTab = React.memo(() => {
1509
1409
  file.status === 'warning' && bundleStyles.adviceWarning,
1510
1410
  file.status === 'optimal' && bundleStyles.adviceOptimal,
1511
1411
  ]}>
1512
- <Text style={[
1412
+ <View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
1413
+ {file.status === 'warning' ? (<LightbulbIcon color={AppColors.amber800Warm} size={12}/>) : (<SparkleIcon color={AppColors.emerald700} size={12}/>)}
1414
+ <Text style={[
1513
1415
  bundleStyles.adviceText,
1514
- file.status === 'warning' && { color: '#B45309' },
1515
- file.status === 'optimal' && { color: '#047857' },
1416
+ file.status === 'warning' && { color: AppColors.amber800Warm },
1417
+ file.status === 'optimal' && { color: AppColors.emerald700 },
1516
1418
  ]}>
1517
- {file.status === 'warning' ? '💡 ' : '✨ '}
1518
- {file.advice}
1519
- </Text>
1419
+ {file.advice}
1420
+ </Text>
1421
+ </View>
1520
1422
  </View>)}
1521
1423
  </View>
1522
1424
  </View>))}
@@ -1527,11 +1429,14 @@ const BundleTab = React.memo(() => {
1527
1429
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1528
1430
  <StorageIcon color={AppColors.purple} size={14}/>
1529
1431
  <Text style={bundleStyles.footerTitle}>
1530
- {mediaFiles.length} Media & Fonts
1432
+ {t('bundle.mediaAndFonts', { count: mediaFiles.length })}
1531
1433
  </Text>
1532
1434
  </View>
1533
1435
  <Text style={bundleStyles.footerSizeVal}>
1534
- {mediaTotalKb} KB ({(mediaTotalKb / 1024).toFixed(2)} MB)
1436
+ {t('bundle.kbMbValue', {
1437
+ kb: mediaTotalKb,
1438
+ mb: (mediaTotalKb / 1024).toFixed(2),
1439
+ })}
1535
1440
  </Text>
1536
1441
  </View>
1537
1442
  </View>)}
@@ -1545,49 +1450,59 @@ const BundleTab = React.memo(() => {
1545
1450
  <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
1546
1451
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1547
1452
  <CircleAlertIcon color={AppColors.purple} size={18}/>
1548
- <Text style={bundleStyles.tipsHeading}>React Native Bundle Optimization Checklist</Text>
1453
+ <Text style={bundleStyles.tipsHeading}>{t('bundle.optimizerTitle')}</Text>
1549
1454
  </View>
1550
1455
  <CopyButton value={() => [
1551
- 'Convert Heavy PNGs to WebP / SVGs (High Impact)',
1552
- `Enable Hermes Bytecode Engine (${isHermes ? 'Active' : 'Action Required'})`,
1553
- 'Font Subsetting & Weight Pruning (Medium Impact)',
1554
- 'Selective Path Imports Tree-shaking (Best Practice)',
1555
- 'Screen Lazy-Loading (Best Practice)',
1556
- ]} label="Optimization Checklist"/>
1456
+ t('bundle.optTip1Title'),
1457
+ `${t('bundle.optTip2Title')} (${isHermes ? t('bundle.active') : t('bundle.actionRequired')})`,
1458
+ t('bundle.optTip3Title'),
1459
+ t('bundle.optTip4Title'),
1460
+ t('bundle.optTip5Title'),
1461
+ ]} label={t('bundle.optimizationChecklist')}/>
1557
1462
  </View>
1558
1463
 
1559
1464
  {[
1560
1465
  {
1561
- title: '🖼️ Convert Heavy PNGs to WebP / SVGs',
1562
- desc: 'Images account for >40% of final app download size. Converting 2x/3x raster PNGs to WebP saves 60-70% size with no visible fidelity loss.',
1563
- badge: 'High Impact',
1564
- badgeColor: '#EC4899',
1466
+ iconType: 'image',
1467
+ iconColor: AppColors.pink500,
1468
+ title: t('bundle.optTip1Title'),
1469
+ desc: t('bundle.optTip1Desc'),
1470
+ badge: t('bundle.highImpact'),
1471
+ badgeColor: AppColors.pink500,
1565
1472
  },
1566
1473
  {
1567
- title: '📜 Enable Hermes Bytecode Engine',
1474
+ iconType: 'bolt',
1475
+ iconColor: isHermes ? AppColors.emerald500 : AppColors.amber500,
1476
+ title: t('bundle.optTip2Title'),
1568
1477
  desc: isHermes
1569
- ? '✅ Hermes is active! JavaScript is compiled into optimized bytecode Ahead-Of-Time.'
1570
- : '⚠️ Hermes is disabled. Enable hermes in your app config for ~40% smaller payload and instant TTI startup.',
1571
- badge: isHermes ? 'Active' : 'Action Required',
1572
- badgeColor: isHermes ? '#10B981' : '#F59E0B',
1478
+ ? t('bundle.optTip2DescActive')
1479
+ : t('bundle.optTip2DescInactive'),
1480
+ badge: isHermes ? t('bundle.active') : t('bundle.actionRequired'),
1481
+ badgeColor: isHermes ? AppColors.emerald500 : AppColors.amber500,
1573
1482
  },
1574
1483
  {
1575
- title: '🔤 Font Subsetting & Weight Pruning',
1576
- desc: 'Include only the font weights you actively use (e.g. Regular & Bold). Remove unused glyph ranges to save 100KB+ per font file.',
1577
- badge: 'Medium Impact',
1578
- badgeColor: '#8B5CF6',
1484
+ iconType: 'font',
1485
+ iconColor: AppColors.purple500,
1486
+ title: t('bundle.optTip3Title'),
1487
+ desc: t('bundle.optTip3Desc'),
1488
+ badge: t('bundle.mediumImpact'),
1489
+ badgeColor: AppColors.purple500,
1579
1490
  },
1580
1491
  {
1581
- title: '🌲 Selective Path Imports (Tree-shaking)',
1582
- desc: 'Import from specific subpaths (e.g. lodash/get or specific vector icon sets) instead of importing large monolithic packages.',
1583
- badge: 'Best Practice',
1584
- badgeColor: '#0EA5E9',
1492
+ iconType: 'source',
1493
+ iconColor: AppColors.sky500,
1494
+ title: t('bundle.optTip4Title'),
1495
+ desc: t('bundle.optTip4Desc'),
1496
+ badge: t('bundle.bestPractice'),
1497
+ badgeColor: AppColors.sky500,
1585
1498
  },
1586
1499
  {
1587
- title: '⚡ Screen Lazy-Loading',
1588
- desc: 'Lazy load secondary screens and heavy modal sheets using dynamic imports and InteractionManager to reduce initial bundle evaluation.',
1589
- badge: 'Best Practice',
1590
- badgeColor: '#6366F1',
1500
+ iconType: 'overhead',
1501
+ iconColor: AppColors.indigo500,
1502
+ title: t('bundle.optTip5Title'),
1503
+ desc: t('bundle.optTip5Desc'),
1504
+ badge: t('bundle.bestPractice'),
1505
+ badgeColor: AppColors.indigo500,
1591
1506
  },
1592
1507
  ].map((tip, tIdx) => (<View key={tIdx} style={bundleStyles.tipItem}>
1593
1508
  <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 2 }}>
@@ -1595,6 +1510,7 @@ const BundleTab = React.memo(() => {
1595
1510
  <View style={bundleStyles.sNoBadge}>
1596
1511
  <Text style={bundleStyles.sNoText}>#{tIdx + 1}</Text>
1597
1512
  </View>
1513
+ <BundleCategoryIcon type={tip.iconType} size={14} color={tip.iconColor}/>
1598
1514
  <Text style={bundleStyles.tipTitle}>{tip.title}</Text>
1599
1515
  </View>
1600
1516
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
@@ -1603,7 +1519,7 @@ const BundleTab = React.memo(() => {
1603
1519
  {tip.badge}
1604
1520
  </Text>
1605
1521
  </View>
1606
- <CopyButton value={() => tip} label="Tip Details"/>
1522
+ <CopyButton value={() => tip} label={t('bundle.tipDetails')}/>
1607
1523
  </View>
1608
1524
  </View>
1609
1525
  <Text style={bundleStyles.tipDesc}>{tip.desc}</Text>
@@ -1619,6 +1535,23 @@ const bundleStyles = StyleSheet.create({
1619
1535
  borderBottomWidth: 1,
1620
1536
  borderBottomColor: AppColors.dividerColor,
1621
1537
  },
1538
+ analyzingContainer: {
1539
+ flex: 1,
1540
+ alignItems: 'center',
1541
+ justifyContent: 'center',
1542
+ gap: 8,
1543
+ padding: 24,
1544
+ },
1545
+ analyzingText: {
1546
+ fontFamily: AppFonts.interBold,
1547
+ fontSize: 13,
1548
+ color: AppColors.primaryBlack,
1549
+ },
1550
+ analyzingHint: {
1551
+ fontFamily: AppFonts.interRegular,
1552
+ fontSize: 11,
1553
+ color: AppColors.grayTextWeak,
1554
+ },
1622
1555
  subTabsScroll: {
1623
1556
  paddingHorizontal: 12,
1624
1557
  gap: 8,
@@ -1777,11 +1710,6 @@ const bundleStyles = StyleSheet.create({
1777
1710
  gap: 5,
1778
1711
  minWidth: '45%',
1779
1712
  },
1780
- legendDot: {
1781
- width: 8,
1782
- height: 8,
1783
- borderRadius: 4,
1784
- },
1785
1713
  legendText: {
1786
1714
  fontFamily: AppFonts.interMedium,
1787
1715
  fontSize: 11,
@@ -1805,9 +1733,6 @@ const bundleStyles = StyleSheet.create({
1805
1733
  gap: 8,
1806
1734
  marginBottom: 8,
1807
1735
  },
1808
- catRowIcon: {
1809
- fontSize: 16,
1810
- },
1811
1736
  catRowTitle: {
1812
1737
  fontFamily: AppFonts.interBold,
1813
1738
  fontSize: 12,
@@ -1883,9 +1808,6 @@ const bundleStyles = StyleSheet.create({
1883
1808
  backgroundColor: AppColors.brandPurple,
1884
1809
  borderColor: AppColors.brandPurple,
1885
1810
  },
1886
- catPillIcon: {
1887
- fontSize: 11,
1888
- },
1889
1811
  catPillText: {
1890
1812
  fontFamily: AppFonts.interMedium,
1891
1813
  fontSize: 11,
@@ -2174,41 +2096,41 @@ const bundleStyles = StyleSheet.create({
2174
2096
  paddingHorizontal: 7,
2175
2097
  paddingVertical: 2,
2176
2098
  borderRadius: 5,
2177
- backgroundColor: '#CB383714',
2099
+ backgroundColor: AppColors.npmRedTint,
2178
2100
  borderWidth: 1,
2179
- borderColor: '#CB383733',
2101
+ borderColor: AppColors.npmRedBorder,
2180
2102
  },
2181
2103
  npmLinkText: {
2182
2104
  fontFamily: AppFonts.interBold,
2183
2105
  fontSize: 9.5,
2184
- color: '#CB3837',
2106
+ color: AppColors.npmRed,
2185
2107
  },
2186
2108
  deprecatedBadge: {
2187
2109
  paddingHorizontal: 6,
2188
2110
  paddingVertical: 1.5,
2189
2111
  borderRadius: 4,
2190
- backgroundColor: '#FEE2E2',
2112
+ backgroundColor: AppColors.red100,
2191
2113
  borderWidth: 1,
2192
- borderColor: '#FECACA',
2114
+ borderColor: AppColors.errorBorder,
2193
2115
  },
2194
2116
  deprecatedBadgeText: {
2195
2117
  fontFamily: AppFonts.interBold,
2196
2118
  fontSize: 9,
2197
- color: '#DC2626',
2119
+ color: AppColors.red600,
2198
2120
  letterSpacing: 0.3,
2199
2121
  },
2200
2122
  updateBadge: {
2201
2123
  paddingHorizontal: 6,
2202
2124
  paddingVertical: 1.5,
2203
2125
  borderRadius: 4,
2204
- backgroundColor: '#DCFCE7',
2126
+ backgroundColor: AppColors.green100,
2205
2127
  borderWidth: 1,
2206
- borderColor: '#BBF7D0',
2128
+ borderColor: AppColors.greenBorder,
2207
2129
  },
2208
2130
  updateBadgeText: {
2209
2131
  fontFamily: AppFonts.interBold,
2210
2132
  fontSize: 9,
2211
- color: '#047857',
2133
+ color: AppColors.emerald700,
2212
2134
  },
2213
2135
  lastActiveText: {
2214
2136
  fontFamily: AppFonts.interRegular,
@@ -2216,45 +2138,45 @@ const bundleStyles = StyleSheet.create({
2216
2138
  color: AppColors.grayTextWeak,
2217
2139
  },
2218
2140
  deprecationBox: {
2219
- backgroundColor: '#FFF1F2',
2141
+ backgroundColor: AppColors.errorCardBg,
2220
2142
  borderRadius: 6,
2221
2143
  paddingHorizontal: 8,
2222
2144
  paddingVertical: 5,
2223
2145
  borderWidth: 1,
2224
- borderColor: '#FECDD3',
2146
+ borderColor: AppColors.errorBorder,
2225
2147
  marginTop: 3,
2226
2148
  },
2227
2149
  deprecationText: {
2228
2150
  fontFamily: AppFonts.interMedium,
2229
2151
  fontSize: 10.5,
2230
- color: '#BE123C',
2152
+ color: AppColors.rose700,
2231
2153
  lineHeight: 14,
2232
2154
  },
2233
2155
  unusedBadge: {
2234
2156
  paddingHorizontal: 6,
2235
2157
  paddingVertical: 1.5,
2236
2158
  borderRadius: 4,
2237
- backgroundColor: '#FEE2E2',
2159
+ backgroundColor: AppColors.red100,
2238
2160
  borderWidth: 1,
2239
- borderColor: '#FECACA',
2161
+ borderColor: AppColors.errorBorder,
2240
2162
  },
2241
2163
  unusedBadgeText: {
2242
2164
  fontFamily: AppFonts.interBold,
2243
2165
  fontSize: 9.5,
2244
- color: '#DC2626',
2166
+ color: AppColors.red600,
2245
2167
  },
2246
2168
  consumedBadge: {
2247
2169
  paddingHorizontal: 6,
2248
2170
  paddingVertical: 1.5,
2249
2171
  borderRadius: 4,
2250
- backgroundColor: '#DCFCE7',
2172
+ backgroundColor: AppColors.green100,
2251
2173
  borderWidth: 1,
2252
- borderColor: '#BBF7D0',
2174
+ borderColor: AppColors.greenBorder,
2253
2175
  },
2254
2176
  consumedBadgeText: {
2255
2177
  fontFamily: AppFonts.interBold,
2256
2178
  fontSize: 9.5,
2257
- color: '#059669',
2179
+ color: AppColors.emerald600,
2258
2180
  },
2259
2181
  treeFolderChevronWrap: {
2260
2182
  width: 18,
@@ -2265,13 +2187,13 @@ const bundleStyles = StyleSheet.create({
2265
2187
  justifyContent: 'center',
2266
2188
  },
2267
2189
  treeFileCardUnused: {
2268
- borderColor: '#FECACA',
2269
- backgroundColor: '#FFFBFB',
2190
+ borderColor: AppColors.errorBorder,
2191
+ backgroundColor: AppColors.red50,
2270
2192
  },
2271
2193
  adviceUnused: {
2272
- backgroundColor: '#FEF2F2',
2194
+ backgroundColor: AppColors.errorCardBg,
2273
2195
  borderWidth: 1,
2274
- borderColor: '#FECACA',
2196
+ borderColor: AppColors.errorBorder,
2275
2197
  },
2276
2198
  adviceBadge: {
2277
2199
  paddingHorizontal: 8,
@@ -2280,14 +2202,14 @@ const bundleStyles = StyleSheet.create({
2280
2202
  marginTop: 2,
2281
2203
  },
2282
2204
  adviceWarning: {
2283
- backgroundColor: '#FEF3C7',
2205
+ backgroundColor: AppColors.amber100,
2284
2206
  borderWidth: 1,
2285
- borderColor: '#FDE68A',
2207
+ borderColor: AppColors.amber200,
2286
2208
  },
2287
2209
  adviceOptimal: {
2288
- backgroundColor: '#DCFCE7',
2210
+ backgroundColor: AppColors.green100,
2289
2211
  borderWidth: 1,
2290
- borderColor: '#BBF7D0',
2212
+ borderColor: AppColors.greenBorder,
2291
2213
  },
2292
2214
  adviceText: {
2293
2215
  fontFamily: AppFonts.interMedium,
@@ -2305,21 +2227,21 @@ const bundleStyles = StyleSheet.create({
2305
2227
  fontSize: 9,
2306
2228
  },
2307
2229
  searchHighlight: {
2308
- backgroundColor: '#FEF08A',
2309
- color: '#854D0E',
2230
+ backgroundColor: AppColors.yellow200,
2231
+ color: AppColors.yellow800,
2310
2232
  fontFamily: AppFonts.interBold,
2311
2233
  },
2312
2234
  tipsCard: {
2313
- backgroundColor: '#FAF5FF',
2235
+ backgroundColor: AppColors.purple50,
2314
2236
  borderRadius: 14,
2315
2237
  padding: 14,
2316
2238
  borderWidth: 1,
2317
- borderColor: '#E9D5FF',
2239
+ borderColor: AppColors.purple200,
2318
2240
  },
2319
2241
  tipsHeading: {
2320
2242
  fontFamily: AppFonts.interBold,
2321
2243
  fontSize: 13.5,
2322
- color: '#6B21A8',
2244
+ color: AppColors.purple800,
2323
2245
  },
2324
2246
  totalSummaryCard: {
2325
2247
  marginTop: 10,
@@ -2383,17 +2305,17 @@ const bundleStyles = StyleSheet.create({
2383
2305
  padding: 10,
2384
2306
  borderRadius: 8,
2385
2307
  borderWidth: 1,
2386
- borderColor: '#E9D5FF',
2308
+ borderColor: AppColors.purple200,
2387
2309
  },
2388
2310
  tipTitle: {
2389
2311
  fontFamily: AppFonts.interBold,
2390
2312
  fontSize: 12,
2391
- color: '#581C87',
2313
+ color: AppColors.purple900,
2392
2314
  },
2393
2315
  tipDesc: {
2394
2316
  fontFamily: AppFonts.interRegular,
2395
2317
  fontSize: 11,
2396
- color: '#6B7280',
2318
+ color: AppColors.gray500,
2397
2319
  lineHeight: 16,
2398
2320
  marginTop: 4,
2399
2321
  },