react-native-inapp-inspector 1.1.21 → 1.1.22

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 (37) 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 +85 -96
  4. package/dist/commonjs/components/Inspector/BundleTab.js +486 -651
  5. package/dist/commonjs/components/Inspector/PerformanceTab.js +185 -244
  6. package/dist/commonjs/components/Inspector/ReduxDetail.js +2 -2
  7. package/dist/commonjs/components/Inspector/ReduxTab.js +3 -3
  8. package/dist/commonjs/constants/version.d.ts +1 -1
  9. package/dist/commonjs/constants/version.js +1 -1
  10. package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +115 -0
  11. package/dist/commonjs/customHooks/bundleAnalyzer.js +561 -0
  12. package/dist/commonjs/customHooks/performanceTracker.d.ts +84 -0
  13. package/dist/commonjs/customHooks/performanceTracker.js +541 -0
  14. package/dist/commonjs/helpers/index.d.ts +15 -0
  15. package/dist/commonjs/helpers/index.js +112 -1
  16. package/dist/commonjs/i18n/locales/en.json +225 -2
  17. package/dist/commonjs/styles/AppColors.d.ts +110 -0
  18. package/dist/commonjs/styles/AppColors.js +114 -0
  19. package/dist/esm/components/AnalyticsDetail.js +5 -3
  20. package/dist/esm/components/AnalyticsEventCard.d.ts +0 -1
  21. package/dist/esm/components/AnalyticsEventCard.js +85 -95
  22. package/dist/esm/components/Inspector/BundleTab.js +489 -654
  23. package/dist/esm/components/Inspector/PerformanceTab.js +185 -244
  24. package/dist/esm/components/Inspector/ReduxDetail.js +2 -2
  25. package/dist/esm/components/Inspector/ReduxTab.js +3 -3
  26. package/dist/esm/constants/version.d.ts +1 -1
  27. package/dist/esm/constants/version.js +1 -1
  28. package/dist/esm/customHooks/bundleAnalyzer.d.ts +115 -0
  29. package/dist/esm/customHooks/bundleAnalyzer.js +554 -0
  30. package/dist/esm/customHooks/performanceTracker.d.ts +84 -0
  31. package/dist/esm/customHooks/performanceTracker.js +537 -0
  32. package/dist/esm/helpers/index.d.ts +15 -0
  33. package/dist/esm/helpers/index.js +107 -0
  34. package/dist/esm/i18n/locales/en.json +225 -2
  35. package/dist/esm/styles/AppColors.d.ts +110 -0
  36. package/dist/esm/styles/AppColors.js +114 -0
  37. package/package.json +1 -1
@@ -1,6 +1,6 @@
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';
@@ -8,6 +8,7 @@ import { AppColors } from '../../styles/AppColors';
8
8
  import { AppFonts } from '../../styles/AppFonts';
9
9
  import { PackageIcon, SearchIcon, ClearIcon, CircleAlertIcon, LayersIcon, LiveStateIcon, StorageIcon, MetadataIcon, } from '../NetworkIcons';
10
10
  import Svg, { Path, Rect, Circle, Ellipse, 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,23 +104,23 @@ 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
  };
@@ -131,25 +132,25 @@ const MediaPreviewThumbnail = ({ file }) => {
131
132
  return (<View style={[
132
133
  bundleStyles.mediaThumbWrap,
133
134
  {
134
- backgroundColor: '#FFF1F2',
135
+ backgroundColor: AppColors.errorCardBg,
135
136
  alignItems: 'center',
136
137
  justifyContent: 'center',
137
- borderColor: '#FECDD3',
138
+ borderColor: AppColors.errorBorder,
138
139
  },
139
140
  ]}>
140
141
  <Svg width={22} height={22} viewBox="0 0 100 100" fill="none">
141
142
  <Defs>
142
143
  <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"/>
144
+ <Stop offset="0" stopColor={AppColors.rose500}/>
145
+ <Stop offset="1" stopColor={AppColors.rose600}/>
145
146
  </LinearGradient>
146
147
  </Defs>
147
148
  <Circle cx="50" cy="50" r="44" fill="url(#svgGradThumb)"/>
148
149
  {/* 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"/>
150
+ <Path d="M25 70 C35 30, 65 30, 75 70" stroke={AppColors.white} strokeWidth="8" strokeLinecap="round"/>
151
+ <Circle cx="25" cy="70" r="7" fill={AppColors.white}/>
152
+ <Circle cx="75" cy="70" r="7" fill={AppColors.white}/>
153
+ <Circle cx="50" cy="40" r="6" fill="#FFE4E6" stroke={AppColors.white} strokeWidth="2"/>
153
154
  </Svg>
154
155
  </View>);
155
156
  }
@@ -247,7 +248,7 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
247
248
  <HighlightText text={node.name + '/'} search={search} style={bundleStyles.treeFolderName} highlightStyle={bundleStyles.searchHighlight}/>
248
249
  <View style={bundleStyles.treeCountBadge}>
249
250
  <Text style={bundleStyles.treeCountBadgeText}>
250
- {node.fileCount} {node.fileCount === 1 ? 'file' : 'files'}
251
+ {node.fileCount} {node.fileCount === 1 ? t('bundle.file') : t('bundle.files')}
251
252
  </Text>
252
253
  </View>
253
254
  </View>
@@ -255,14 +256,14 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
255
256
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
256
257
  <Text style={bundleStyles.treeFolderSize}>
257
258
  {node.sizeKb >= 1024
258
- ? `${(node.sizeKb / 1024).toFixed(2)} MB`
259
- : `${node.sizeKb} KB`}
259
+ ? t('bundle.fileSizeMb', { size: (node.sizeKb / 1024).toFixed(2) })
260
+ : t('bundle.fileSizeKb', { size: node.sizeKb })}
260
261
  </Text>
261
262
  <CopyButton value={() => ({
262
263
  folder: node.fullPath,
263
264
  sizeKb: node.sizeKb,
264
265
  fileCount: node.fileCount,
265
- })} label={`Copy ${node.name} Info`}/>
266
+ })} label={t('bundle.copyFileInfo', { name: node.name })}/>
266
267
  </View>
267
268
  </TouchableScale>
268
269
 
@@ -291,9 +292,9 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
291
292
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
292
293
  <HighlightText text={file.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
293
294
  {isUnused ? (<View style={bundleStyles.unusedBadge}>
294
- <Text style={bundleStyles.unusedBadgeText}>🚫 Not Consumed</Text>
295
+ <Text style={bundleStyles.unusedBadgeText}>{t('bundle.notConsumed')}</Text>
295
296
  </View>) : (<View style={bundleStyles.consumedBadge}>
296
- <Text style={bundleStyles.consumedBadgeText}>⚡ Consumed</Text>
297
+ <Text style={bundleStyles.consumedBadgeText}>{t('bundle.consumed')}</Text>
297
298
  </View>)}
298
299
  </View>
299
300
  <Text style={bundleStyles.filePath} numberOfLines={1}>
@@ -303,14 +304,14 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
303
304
 
304
305
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
305
306
  <View style={bundleStyles.fileSizeBox}>
306
- <Text style={[bundleStyles.fileSizeKb, isUnused && { color: '#B45309' }]}>
307
+ <Text style={[bundleStyles.fileSizeKb, isUnused && { color: AppColors.amber800Warm }]}>
307
308
  {file.sizeKb >= 1024
308
- ? `${(file.sizeKb / 1024).toFixed(2)} MB`
309
- : `${file.sizeKb} KB`}
309
+ ? t('bundle.fileSizeMb', { size: (file.sizeKb / 1024).toFixed(2) })
310
+ : t('bundle.fileSizeKb', { size: file.sizeKb })}
310
311
  </Text>
311
312
  <Text style={bundleStyles.filePercent}>{pctOfTotal}%</Text>
312
313
  </View>
313
- <CopyButton value={() => file} label="File Details JSON"/>
314
+ <CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
314
315
  </View>
315
316
  </View>
316
317
 
@@ -323,9 +324,9 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
323
324
  ]}>
324
325
  <Text style={[
325
326
  bundleStyles.adviceText,
326
- file.status === 'warning' && { color: '#B45309' },
327
- file.status === 'optimal' && { color: '#047857' },
328
- isUnused && { color: '#DC2626' },
327
+ file.status === 'warning' && { color: AppColors.amber800Warm },
328
+ file.status === 'optimal' && { color: AppColors.emerald700 },
329
+ isUnused && { color: AppColors.red600 },
329
330
  ]}>
330
331
  {isUnused ? '🗑️ ' : file.status === 'warning' ? '💡 ' : '✨ '}
331
332
  {file.advice}
@@ -333,417 +334,48 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
333
334
  </View>)}
334
335
  </View>);
335
336
  };
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
337
  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: '📄' },
338
+ { key: 'ALL', labelKey: 'bundle.catAll', icon: '📁' },
339
+ { key: 'UNUSED', labelKey: 'bundle.catUnused', icon: '🚫' },
340
+ { key: 'CONSUMED', labelKey: 'bundle.catConsumed', icon: '⚡' },
341
+ { key: 'image', labelKey: 'bundle.catImages', icon: '🖼️' },
342
+ { key: 'typescript', labelKey: 'bundle.catTypescript', icon: '📜' },
343
+ { key: 'javascript', labelKey: 'bundle.catJavascript', icon: '⚙️' },
344
+ { key: 'font', labelKey: 'bundle.catFonts', icon: '🔤' },
345
+ { key: 'json', labelKey: 'bundle.catJson', icon: '📄' },
732
346
  ];
733
347
  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' },
348
+ image: { label: 'Images & Media', color: AppColors.pink500, bg: AppColors.pink100 },
349
+ typescript: { label: 'TypeScript', color: AppColors.sky500, bg: AppColors.sky100 },
350
+ javascript: { label: 'JavaScript', color: AppColors.indigo500, bg: AppColors.indigo50 },
351
+ font: { label: 'Fonts', color: AppColors.purple500, bg: AppColors.purple100 },
352
+ json: { label: 'JSON / Data', color: AppColors.emerald500, bg: AppColors.emerald100 },
739
353
  };
740
354
  const BundleTab = React.memo(() => {
741
355
  const { t } = useTranslation();
742
356
  const [activeSubTab, setActiveSubTab] = useState('overview');
743
357
  const [search, setSearch] = useState('');
744
358
  const [activeCategory, setActiveCategory] = useState('ALL');
359
+ // ─── Live Host App Bundle Analysis (fetched from Metro / runtime) ─────────
360
+ const [analysis, setAnalysis] = useState(() => getCachedBundleAnalysis());
361
+ const [isAnalyzing, setIsAnalyzing] = useState(!getCachedBundleAnalysis());
362
+ useEffect(() => {
363
+ let mounted = true;
364
+ analyzeHostAppBundle().then(result => {
365
+ if (mounted) {
366
+ setAnalysis(result);
367
+ setIsAnalyzing(false);
368
+ }
369
+ });
370
+ return () => {
371
+ mounted = false;
372
+ };
373
+ }, []);
745
374
  // Check if Hermes is running
746
375
  const isHermes = Boolean(global.HermesInternal);
376
+ // Dynamic files & packages derived from the analyzed host bundle
377
+ const bundleFiles = useMemo(() => (analysis ? analysis.files : []), [analysis]);
378
+ const bundlePackages = useMemo(() => (analysis ? analysis.packages : []), [analysis]);
747
379
  // Group summary calculations
748
380
  const summary = useMemo(() => {
749
381
  let imagesKb = 0;
@@ -756,7 +388,7 @@ const BundleTab = React.memo(() => {
756
388
  let jsCount = 0;
757
389
  let fontsCount = 0;
758
390
  let jsonCount = 0;
759
- BUNDLE_FILES_DATA.forEach(f => {
391
+ bundleFiles.forEach(f => {
760
392
  if (f.category === 'image') {
761
393
  imagesKb += f.sizeKb;
762
394
  imagesCount++;
@@ -782,37 +414,37 @@ const BundleTab = React.memo(() => {
782
414
  return {
783
415
  totalKb,
784
416
  totalMb: (totalKb / 1024).toFixed(2),
785
- totalCount: BUNDLE_FILES_DATA.length,
417
+ totalCount: bundleFiles.length,
786
418
  images: {
787
419
  count: imagesCount,
788
420
  kb: imagesKb,
789
- pct: Number(((imagesKb / totalKb) * 100).toFixed(1)),
421
+ pct: totalKb ? Number(((imagesKb / totalKb) * 100).toFixed(1)) : 0,
790
422
  },
791
423
  ts: {
792
424
  count: tsCount,
793
425
  kb: tsKb,
794
- pct: Number(((tsKb / totalKb) * 100).toFixed(1)),
426
+ pct: totalKb ? Number(((tsKb / totalKb) * 100).toFixed(1)) : 0,
795
427
  },
796
428
  js: {
797
429
  count: jsCount,
798
430
  kb: jsKb,
799
- pct: Number(((jsKb / totalKb) * 100).toFixed(1)),
431
+ pct: totalKb ? Number(((jsKb / totalKb) * 100).toFixed(1)) : 0,
800
432
  },
801
433
  fonts: {
802
434
  count: fontsCount,
803
435
  kb: fontsKb,
804
- pct: Number(((fontsKb / totalKb) * 100).toFixed(1)),
436
+ pct: totalKb ? Number(((fontsKb / totalKb) * 100).toFixed(1)) : 0,
805
437
  },
806
438
  json: {
807
439
  count: jsonCount,
808
440
  kb: jsonKb,
809
- pct: Number(((jsonKb / totalKb) * 100).toFixed(1)),
441
+ pct: totalKb ? Number(((jsonKb / totalKb) * 100).toFixed(1)) : 0,
810
442
  },
811
443
  };
812
- }, []);
444
+ }, [bundleFiles]);
813
445
  // Filtered files
814
446
  const filteredFiles = useMemo(() => {
815
- return BUNDLE_FILES_DATA.filter(file => {
447
+ return bundleFiles.filter(file => {
816
448
  const matchSearch = !search ||
817
449
  file.name.toLowerCase().includes(search.toLowerCase()) ||
818
450
  file.path.toLowerCase().includes(search.toLowerCase()) ||
@@ -827,7 +459,7 @@ const BundleTab = React.memo(() => {
827
459
  : file.category === activeCategory;
828
460
  return matchSearch && matchCategory;
829
461
  });
830
- }, [search, activeCategory]);
462
+ }, [search, activeCategory, bundleFiles]);
831
463
  const filteredTotalKb = useMemo(() => {
832
464
  return filteredFiles.reduce((acc, f) => acc + f.sizeKb, 0);
833
465
  }, [filteredFiles]);
@@ -862,46 +494,46 @@ const BundleTab = React.memo(() => {
862
494
  // Filtered Packages
863
495
  const filteredPackages = useMemo(() => {
864
496
  if (!search)
865
- return BUNDLE_PACKAGES_DATA;
497
+ return bundlePackages;
866
498
  const q = search.toLowerCase();
867
- return BUNDLE_PACKAGES_DATA.filter(pkg => pkg.name.toLowerCase().includes(q) ||
499
+ return bundlePackages.filter(pkg => pkg.name.toLowerCase().includes(q) ||
868
500
  pkg.description.toLowerCase().includes(q) ||
869
501
  pkg.category.toLowerCase().includes(q));
870
- }, [search]);
502
+ }, [search, bundlePackages]);
871
503
  const packagesTotalKb = useMemo(() => {
872
504
  return filteredPackages.reduce((acc, p) => acc + p.sizeKb, 0);
873
505
  }, [filteredPackages]);
874
506
  // Media files (Images + Fonts)
875
507
  const mediaFiles = useMemo(() => {
876
- return BUNDLE_FILES_DATA.filter(f => f.category === 'image' || f.category === 'font');
877
- }, []);
508
+ return bundleFiles.filter(f => f.category === 'image' || f.category === 'font');
509
+ }, [bundleFiles]);
878
510
  const mediaTotalKb = useMemo(() => {
879
511
  return mediaFiles.reduce((acc, m) => acc + m.sizeKb, 0);
880
512
  }, [mediaFiles]);
881
513
  const subTabs = [
882
514
  {
883
515
  key: 'overview',
884
- label: 'Overview',
516
+ label: t('bundle.tabOverview'),
885
517
  icon: (isActive) => (<LiveStateIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
886
518
  },
887
519
  {
888
520
  key: 'files',
889
- label: `Files (${BUNDLE_FILES_DATA.length})`,
521
+ label: t('bundle.tabFiles', { count: bundleFiles.length }),
890
522
  icon: (isActive) => (<LayersIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
891
523
  },
892
524
  {
893
525
  key: 'packages',
894
- label: `Packages (${BUNDLE_PACKAGES_DATA.length})`,
526
+ label: t('bundle.tabPackages', { count: bundlePackages.length }),
895
527
  icon: (isActive) => (<PackageIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
896
528
  },
897
529
  {
898
530
  key: 'media',
899
- label: `Media (${mediaFiles.length})`,
531
+ label: t('bundle.tabMedia', { count: mediaFiles.length }),
900
532
  icon: (isActive) => (<StorageIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
901
533
  },
902
534
  {
903
535
  key: 'optimizer',
904
- label: 'Optimizer',
536
+ label: t('bundle.tabOptimizer'),
905
537
  icon: (isActive) => (<MetadataIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
906
538
  },
907
539
  ];
@@ -927,10 +559,21 @@ const BundleTab = React.memo(() => {
927
559
  </ScrollView>
928
560
  </View>
929
561
 
562
+ {/* ─── Live Bundle Analysis Loading State ─── */}
563
+ {isAnalyzing && (<View style={bundleStyles.analyzingContainer}>
564
+ <ActivityIndicator size="small" color={AppColors.brandPurple}/>
565
+ <Text style={bundleStyles.analyzingText}>
566
+ {t('bundle.analyzingTitle')}
567
+ </Text>
568
+ <Text style={bundleStyles.analyzingHint}>
569
+ {t('bundle.analyzingHint')}
570
+ </Text>
571
+ </View>)}
572
+
930
573
  {/* ══════════════════════════════════════════════════════════════════════ */}
931
574
  {/* ── 1. TAB: OVERVIEW & TREEMAP ───────────────────────────────────────── */}
932
575
  {/* ══════════════════════════════════════════════════════════════════════ */}
933
- {activeSubTab === 'overview' && (<ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
576
+ {activeSubTab === 'overview' && !isAnalyzing && (<ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
934
577
 
935
578
  {/* Hero Overview Card */}
936
579
  <View style={bundleStyles.heroCard}>
@@ -939,42 +582,59 @@ const BundleTab = React.memo(() => {
939
582
  <PackageIcon color={AppColors.brandPurple} size={24}/>
940
583
  </View>
941
584
  <View style={{ flex: 1 }}>
942
- <Text style={bundleStyles.heroTitle}>Bundle & Asset Architecture</Text>
585
+ <Text style={bundleStyles.heroTitle}>{t('bundle.heroTitle')}</Text>
943
586
  <Text style={bundleStyles.heroSubtitle}>
944
- Real-time size breakdown across all assets, code, and dependencies
587
+ {analysis?.isLive
588
+ ? t('bundle.heroSubtitleLive', { scriptUrl: analysis.scriptURL })
589
+ : t('bundle.heroSubtitle')}
945
590
  </Text>
946
591
  </View>
947
- <CopyButton value={() => summary} label="Bundle Overview JSON"/>
592
+ <CopyButton value={() => ({ ...summary, bundle: analysis })} label={t('bundle.bundleOverviewJson')}/>
948
593
  </View>
949
594
 
950
595
  {/* 4-Stat Metric Grid */}
951
596
  <View style={bundleStyles.metricsGrid}>
952
597
  <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>
598
+ <Text style={bundleStyles.metricLabel}>{t('bundle.devBundleSize')}</Text>
599
+ <Text style={bundleStyles.metricValue}>
600
+ {t('bundle.mbValue', {
601
+ size: analysis ? analysis.totalDevMb : summary.totalMb,
602
+ })}
603
+ </Text>
604
+ <Text style={bundleStyles.metricHint}>
605
+ {analysis
606
+ ? t('bundle.devBundleHint', {
607
+ kb: analysis.totalDevKb,
608
+ modules: analysis.moduleCount,
609
+ })
610
+ : t('bundle.trackedFilesHint', { count: bundleFiles.length })}
611
+ </Text>
956
612
  </View>
957
613
 
958
614
  <View style={bundleStyles.metricBox}>
959
- <Text style={bundleStyles.metricLabel}>IMAGES & MEDIA</Text>
960
- <Text style={[bundleStyles.metricValue, { color: '#EC4899' }]}>
961
- {summary.images.kb} KB
615
+ <Text style={bundleStyles.metricLabel}>{t('bundle.imagesMedia')}</Text>
616
+ <Text style={[bundleStyles.metricValue, { color: AppColors.pink500 }]}>
617
+ {t('bundle.fileSizeKb', { size: summary.images.kb })}
618
+ </Text>
619
+ <Text style={bundleStyles.metricHint}>
620
+ {t('bundle.pctOfTrackedAssets', { pct: summary.images.pct })}
962
621
  </Text>
963
- <Text style={bundleStyles.metricHint}>{summary.images.pct}% of package</Text>
964
622
  </View>
965
623
 
966
624
  <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
625
+ <Text style={bundleStyles.metricLabel}>{t('bundle.tsJsSource')}</Text>
626
+ <Text style={[bundleStyles.metricValue, { color: AppColors.sky500 }]}>
627
+ {t('bundle.fileSizeKb', { size: summary.ts.kb + summary.js.kb })}
970
628
  </Text>
971
629
  <Text style={bundleStyles.metricHint}>
972
- {(summary.ts.pct + summary.js.pct).toFixed(1)}% of package
630
+ {t('bundle.pctOfTrackedAssets', {
631
+ pct: (summary.ts.pct + summary.js.pct).toFixed(1),
632
+ })}
973
633
  </Text>
974
634
  </View>
975
635
 
976
636
  <View style={bundleStyles.metricBox}>
977
- <Text style={bundleStyles.metricLabel}>JS ENGINE</Text>
637
+ <Text style={bundleStyles.metricLabel}>{t('bundle.jsEngine')}</Text>
978
638
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5, marginTop: 2 }}>
979
639
  <View style={[
980
640
  bundleStyles.statusIndicator,
@@ -984,17 +644,105 @@ const BundleTab = React.memo(() => {
984
644
  {isHermes ? 'Hermes' : 'JSC'}
985
645
  </Text>
986
646
  </View>
987
- <Text style={bundleStyles.metricHint}>{isHermes ? 'Bytecode AOT' : 'Standard'}</Text>
647
+ <Text style={bundleStyles.metricHint}>
648
+ {isHermes ? t('bundle.bytecodeAot') : t('bundle.standard')}
649
+ </Text>
988
650
  </View>
989
651
  </View>
990
652
  </View>
991
653
 
654
+ {/* Development Bundle Split-Up (derived from the real running bundle) */}
655
+ {analysis && (<View style={bundleStyles.sectionCard}>
656
+ <View style={bundleStyles.sectionHeaderRow}>
657
+ <View>
658
+ <Text style={bundleStyles.sectionTitle}>{t('bundle.splitUpTitle')}</Text>
659
+ <Text style={bundleStyles.sectionSub}>
660
+ {t('bundle.splitUpSub', {
661
+ mb: analysis.totalDevMb,
662
+ files: analysis.filesCount,
663
+ packages: analysis.packageCount,
664
+ })}
665
+ </Text>
666
+ </View>
667
+ <CopyButton value={() => analysis.splitUp} label={t('bundle.splitUpJson')}/>
668
+ </View>
669
+
670
+ {[
671
+ {
672
+ label: t('bundle.splitAppSource'),
673
+ color: AppColors.sky500,
674
+ ...analysis.splitUp.appSource,
675
+ },
676
+ {
677
+ label: t('bundle.splitNodeModules'),
678
+ color: AppColors.indigo500,
679
+ ...analysis.splitUp.nodeModules,
680
+ },
681
+ {
682
+ label: t('bundle.splitAssetsMedia'),
683
+ color: AppColors.pink500,
684
+ ...analysis.splitUp.assetsMedia,
685
+ },
686
+ {
687
+ label: t('bundle.splitMetroOverhead'),
688
+ color: AppColors.amber500,
689
+ ...analysis.splitUp.metroDevOverhead,
690
+ },
691
+ ].map((part, idx) => (<View key={idx} style={bundleStyles.catRowCard}>
692
+ <View style={bundleStyles.catRowTop}>
693
+ <Text style={bundleStyles.catRowIcon}>{['📦', '🧩', '🖼️', '⚙️'][idx]}</Text>
694
+ <View style={{ flex: 1 }}>
695
+ <Text style={bundleStyles.catRowTitle}>{part.label}</Text>
696
+ <Text style={bundleStyles.catRowDesc}>
697
+ {t('bundle.splitSize', { kb: part.kb, mb: part.mb })}
698
+ </Text>
699
+ </View>
700
+ <View style={{ alignItems: 'flex-end' }}>
701
+ <Text style={bundleStyles.catRowSize}>{part.pct}%</Text>
702
+ </View>
703
+ </View>
704
+ <View style={bundleStyles.catProgressTrack}>
705
+ <View style={[
706
+ bundleStyles.catProgressBar,
707
+ { width: `${part.pct}%`, backgroundColor: part.color },
708
+ ]}/>
709
+ </View>
710
+ </View>))}
711
+
712
+ {/* Production Binary Footprint */}
713
+ <View style={bundleStyles.totalSummaryCard}>
714
+ <View style={bundleStyles.totalSummaryRow}>
715
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
716
+ <PackageIcon color={AppColors.purple} size={16}/>
717
+ <Text style={bundleStyles.totalSummaryLabel}>
718
+ {t('bundle.productionFootprint')}
719
+ </Text>
720
+ </View>
721
+ <Text style={bundleStyles.totalSummaryValue}>
722
+ {t('bundle.productionValue', {
723
+ ios: analysis.production.ios.totalInstallMb,
724
+ android: analysis.production.android.totalInstallMb,
725
+ })}
726
+ </Text>
727
+ </View>
728
+ <Text style={bundleStyles.totalSummaryFormula}>
729
+ {t('bundle.productionDownload', {
730
+ ios: analysis.production.ios.totalDownloadMb,
731
+ aab: analysis.production.androidAab.totalDownloadMb,
732
+ apk: analysis.production.androidApk.totalDownloadMb,
733
+ })}
734
+ </Text>
735
+ </View>
736
+ </View>)}
737
+
992
738
  {/* Visual Type Distribution Ratio Bar */}
993
739
  <View style={bundleStyles.sectionCard}>
994
740
  <View style={bundleStyles.sectionHeaderRow}>
995
741
  <View>
996
- <Text style={bundleStyles.sectionTitle}>Asset & File Type Ratio Treemap</Text>
997
- <Text style={bundleStyles.sectionSub}>Total ~{summary.totalMb} MB</Text>
742
+ <Text style={bundleStyles.sectionTitle}>{t('bundle.treemapTitle')}</Text>
743
+ <Text style={bundleStyles.sectionSub}>
744
+ {t('bundle.treemapSub', { mb: summary.totalMb })}
745
+ </Text>
998
746
  </View>
999
747
  <CopyButton value={() => ({
1000
748
  totalMb: summary.totalMb,
@@ -1004,48 +752,63 @@ const BundleTab = React.memo(() => {
1004
752
  js: summary.js,
1005
753
  fonts: summary.fonts,
1006
754
  json: summary.json,
1007
- })} label="Ratio Treemap JSON"/>
755
+ })} label={t('bundle.treemapJson')}/>
1008
756
  </View>
1009
757
 
1010
758
  {/* Stacked Colored Bar */}
1011
759
  <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 }}/>
760
+ <View style={{ flex: summary.images.pct, backgroundColor: AppColors.pink500, height: 16 }}/>
761
+ <View style={{ flex: summary.ts.pct, backgroundColor: AppColors.sky500, height: 16 }}/>
762
+ <View style={{ flex: summary.js.pct, backgroundColor: AppColors.indigo500, height: 16 }}/>
763
+ <View style={{ flex: summary.fonts.pct, backgroundColor: AppColors.purple500, height: 16 }}/>
764
+ <View style={{ flex: summary.json.pct, backgroundColor: AppColors.emerald500, height: 16 }}/>
1017
765
  </View>
1018
766
 
1019
767
  {/* Legend Grid */}
1020
768
  <View style={bundleStyles.legendGrid}>
1021
769
  <View style={bundleStyles.legendItem}>
1022
- <View style={[bundleStyles.legendDot, { backgroundColor: '#EC4899' }]}/>
770
+ <View style={[bundleStyles.legendDot, { backgroundColor: AppColors.pink500 }]}/>
1023
771
  <Text style={bundleStyles.legendText}>
1024
- 🖼️ Images: <Text style={bundleStyles.legendVal}>{summary.images.kb} KB ({summary.images.pct}%)</Text>
772
+ {t('bundle.legendImages')}{' '}
773
+ <Text style={bundleStyles.legendVal}>
774
+ {t('bundle.legendValue', { kb: summary.images.kb, pct: summary.images.pct })}
775
+ </Text>
1025
776
  </Text>
1026
777
  </View>
1027
778
  <View style={bundleStyles.legendItem}>
1028
- <View style={[bundleStyles.legendDot, { backgroundColor: '#0EA5E9' }]}/>
779
+ <View style={[bundleStyles.legendDot, { backgroundColor: AppColors.sky500 }]}/>
1029
780
  <Text style={bundleStyles.legendText}>
1030
- 📜 TS/TSX: <Text style={bundleStyles.legendVal}>{summary.ts.kb} KB ({summary.ts.pct}%)</Text>
781
+ {t('bundle.legendTs')}{' '}
782
+ <Text style={bundleStyles.legendVal}>
783
+ {t('bundle.legendValue', { kb: summary.ts.kb, pct: summary.ts.pct })}
784
+ </Text>
1031
785
  </Text>
1032
786
  </View>
1033
787
  <View style={bundleStyles.legendItem}>
1034
- <View style={[bundleStyles.legendDot, { backgroundColor: '#6366F1' }]}/>
788
+ <View style={[bundleStyles.legendDot, { backgroundColor: AppColors.indigo500 }]}/>
1035
789
  <Text style={bundleStyles.legendText}>
1036
- ⚙️ JS Libs: <Text style={bundleStyles.legendVal}>{summary.js.kb} KB ({summary.js.pct}%)</Text>
790
+ {t('bundle.legendJs')}{' '}
791
+ <Text style={bundleStyles.legendVal}>
792
+ {t('bundle.legendValue', { kb: summary.js.kb, pct: summary.js.pct })}
793
+ </Text>
1037
794
  </Text>
1038
795
  </View>
1039
796
  <View style={bundleStyles.legendItem}>
1040
- <View style={[bundleStyles.legendDot, { backgroundColor: '#8B5CF6' }]}/>
797
+ <View style={[bundleStyles.legendDot, { backgroundColor: AppColors.purple500 }]}/>
1041
798
  <Text style={bundleStyles.legendText}>
1042
- 🔤 Fonts: <Text style={bundleStyles.legendVal}>{summary.fonts.kb} KB ({summary.fonts.pct}%)</Text>
799
+ {t('bundle.legendFonts')}{' '}
800
+ <Text style={bundleStyles.legendVal}>
801
+ {t('bundle.legendValue', { kb: summary.fonts.kb, pct: summary.fonts.pct })}
802
+ </Text>
1043
803
  </Text>
1044
804
  </View>
1045
805
  <View style={bundleStyles.legendItem}>
1046
- <View style={[bundleStyles.legendDot, { backgroundColor: '#10B981' }]}/>
806
+ <View style={[bundleStyles.legendDot, { backgroundColor: AppColors.emerald500 }]}/>
1047
807
  <Text style={bundleStyles.legendText}>
1048
- 📄 JSON: <Text style={bundleStyles.legendVal}>{summary.json.kb} KB ({summary.json.pct}%)</Text>
808
+ {t('bundle.legendJson')}{' '}
809
+ <Text style={bundleStyles.legendVal}>
810
+ {t('bundle.legendValue', { kb: summary.json.kb, pct: summary.json.pct })}
811
+ </Text>
1049
812
  </Text>
1050
813
  </View>
1051
814
  </View>
@@ -1054,7 +817,7 @@ const BundleTab = React.memo(() => {
1054
817
  {/* Detailed Category Footprint Cards */}
1055
818
  <View style={bundleStyles.sectionCard}>
1056
819
  <View style={bundleStyles.sectionHeaderRow}>
1057
- <Text style={bundleStyles.sectionTitle}>Category Breakdown</Text>
820
+ <Text style={bundleStyles.sectionTitle}>{t('bundle.categoryBreakdown')}</Text>
1058
821
  <CopyButton value={() => ({
1059
822
  totalAssets: summary.totalCount,
1060
823
  totalSizeKb: summary.totalKb,
@@ -1065,61 +828,66 @@ const BundleTab = React.memo(() => {
1065
828
  typescript: summary.ts,
1066
829
  json: summary.json,
1067
830
  },
1068
- })} label="Categories Breakdown JSON"/>
831
+ })} label={t('bundle.categoriesJson')}/>
1069
832
  </View>
1070
833
 
1071
834
  {[
1072
835
  {
1073
836
  icon: '🖼️',
1074
- title: 'Images & Media Assets',
837
+ title: t('bundle.catImagesTitle'),
1075
838
  count: summary.images.count,
1076
- size: `${summary.images.kb} KB`,
839
+ size: t('bundle.fileSizeKb', { size: summary.images.kb }),
1077
840
  pct: summary.images.pct,
1078
- color: '#EC4899',
1079
- desc: 'PNG, WebP, SVG, and JPG assets in bundle',
841
+ color: AppColors.pink500,
842
+ desc: t('bundle.catImagesDesc'),
1080
843
  },
1081
844
  {
1082
845
  icon: '⚙️',
1083
- title: 'Compiled Node Modules & JS',
846
+ title: t('bundle.catJsTitle'),
1084
847
  count: summary.js.count,
1085
- size: `${summary.js.kb} KB`,
848
+ size: t('bundle.fileSizeKb', { size: summary.js.kb }),
1086
849
  pct: summary.js.pct,
1087
- color: '#6366F1',
1088
- desc: 'Third-party dependencies and native bridges',
850
+ color: AppColors.indigo500,
851
+ desc: t('bundle.catJsDesc'),
1089
852
  },
1090
853
  {
1091
854
  icon: '🔤',
1092
- title: 'Custom Fonts & Vector Glyphs',
855
+ title: t('bundle.catFontsTitle'),
1093
856
  count: summary.fonts.count,
1094
- size: `${summary.fonts.kb} KB`,
857
+ size: t('bundle.fileSizeKb', { size: summary.fonts.kb }),
1095
858
  pct: summary.fonts.pct,
1096
- color: '#8B5CF6',
1097
- desc: 'Inter, JetBrainsMono, and UI typography fonts',
859
+ color: AppColors.purple500,
860
+ desc: bundleFiles
861
+ .filter(f => f.category === 'font')
862
+ .map(f => f.name)
863
+ .join(', ') || t('bundle.catFontsDesc'),
1098
864
  },
1099
865
  {
1100
866
  icon: '📜',
1101
- title: 'TypeScript & JSX Components',
867
+ title: t('bundle.catTsTitle'),
1102
868
  count: summary.ts.count,
1103
- size: `${summary.ts.kb} KB`,
869
+ size: t('bundle.fileSizeKb', { size: summary.ts.kb }),
1104
870
  pct: summary.ts.pct,
1105
- color: '#0EA5E9',
1106
- desc: 'App screen components, hooks, and business logic',
871
+ color: AppColors.sky500,
872
+ desc: t('bundle.catTsDesc'),
1107
873
  },
1108
874
  {
1109
875
  icon: '📄',
1110
- title: 'JSON Data & Localizations',
876
+ title: t('bundle.catJsonTitle'),
1111
877
  count: summary.json.count,
1112
- size: `${summary.json.kb} KB`,
878
+ size: t('bundle.fileSizeKb', { size: summary.json.kb }),
1113
879
  pct: summary.json.pct,
1114
- color: '#10B981',
1115
- desc: 'i18n translation dictionaries and static configs',
880
+ color: AppColors.emerald500,
881
+ desc: t('bundle.catJsonDesc'),
1116
882
  },
1117
883
  ].map((cat, cIdx) => (<View key={cIdx} style={bundleStyles.catRowCard}>
1118
884
  <View style={bundleStyles.catRowTop}>
1119
885
  <Text style={bundleStyles.catRowIcon}>{cat.icon}</Text>
1120
886
  <View style={{ flex: 1 }}>
1121
887
  <Text style={bundleStyles.catRowTitle}>{cat.title}</Text>
1122
- <Text style={bundleStyles.catRowDesc}>{cat.count} items • {cat.desc}</Text>
888
+ <Text style={bundleStyles.catRowDesc}>
889
+ {t('bundle.catRowSub', { count: cat.count, desc: cat.desc })}
890
+ </Text>
1123
891
  </View>
1124
892
  <View style={{ alignItems: 'flex-end' }}>
1125
893
  <Text style={bundleStyles.catRowSize}>{cat.size}</Text>
@@ -1139,14 +907,24 @@ const BundleTab = React.memo(() => {
1139
907
  <View style={bundleStyles.totalSummaryRow}>
1140
908
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1141
909
  <PackageIcon color={AppColors.purple} size={16}/>
1142
- <Text style={bundleStyles.totalSummaryLabel}>TOTAL BUNDLE ASSETS</Text>
910
+ <Text style={bundleStyles.totalSummaryLabel}>
911
+ {t('bundle.totalBundleAssets')}
912
+ </Text>
1143
913
  </View>
1144
914
  <Text style={bundleStyles.totalSummaryValue}>
1145
- {summary.totalKb} KB (~{summary.totalMb} MB)
915
+ {t('bundle.totalBundleValue', { kb: summary.totalKb, mb: summary.totalMb })}
1146
916
  </Text>
1147
917
  </View>
1148
918
  <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%)
919
+ {t('bundle.totalFormula', {
920
+ images: summary.images.kb,
921
+ js: summary.js.kb,
922
+ fonts: summary.fonts.kb,
923
+ ts: summary.ts.kb,
924
+ json: summary.json.kb,
925
+ total: summary.totalKb,
926
+ count: summary.totalCount,
927
+ })}
1150
928
  </Text>
1151
929
  </View>
1152
930
  </View>
@@ -1155,16 +933,16 @@ const BundleTab = React.memo(() => {
1155
933
  {/* ══════════════════════════════════════════════════════════════════════ */}
1156
934
  {/* ── 2. TAB: FILES BREAKDOWN ─────────────────────────────────────────── */}
1157
935
  {/* ══════════════════════════════════════════════════════════════════════ */}
1158
- {activeSubTab === 'files' && (<View style={{ flex: 1 }}>
936
+ {activeSubTab === 'files' && !isAnalyzing && (<View style={{ flex: 1 }}>
1159
937
  {/* Search & Category Filter */}
1160
938
  <View style={bundleStyles.filterContainer}>
1161
939
  <View style={bundleStyles.searchRow}>
1162
940
  <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"/>
941
+ <TextInput placeholder={t('bundle.searchFilesPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
1164
942
  {search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10}>
1165
943
  <ClearIcon color={AppColors.grayTextWeak} size={14}/>
1166
944
  </Pressable>)}
1167
- <CopyButton value={() => filteredFiles} label="Filtered Files JSON"/>
945
+ <CopyButton value={() => filteredFiles} label={t('bundle.filteredFilesJson')}/>
1168
946
  </View>
1169
947
 
1170
948
  {/* Category Filter Chips */}
@@ -1172,24 +950,24 @@ const BundleTab = React.memo(() => {
1172
950
  {CATEGORY_TABS.map(tab => {
1173
951
  const isActive = activeCategory === tab.key;
1174
952
  const count = tab.key === 'ALL'
1175
- ? BUNDLE_FILES_DATA.length
953
+ ? bundleFiles.length
1176
954
  : tab.key === 'UNUSED'
1177
- ? BUNDLE_FILES_DATA.filter(f => f.isConsumed === false).length
955
+ ? bundleFiles.filter(f => f.isConsumed === false).length
1178
956
  : tab.key === 'CONSUMED'
1179
- ? BUNDLE_FILES_DATA.filter(f => f.isConsumed !== false).length
1180
- : BUNDLE_FILES_DATA.filter(f => f.category === tab.key).length;
957
+ ? bundleFiles.filter(f => f.isConsumed !== false).length
958
+ : bundleFiles.filter(f => f.category === tab.key).length;
1181
959
  return (<TouchableScale key={tab.key} onPress={() => setActiveCategory(tab.key)} style={[
1182
960
  bundleStyles.catPill,
1183
961
  isActive && bundleStyles.catPillActive,
1184
- tab.key === 'UNUSED' && isActive && { backgroundColor: '#EF4444', borderColor: '#DC2626' },
1185
- tab.key === 'CONSUMED' && isActive && { backgroundColor: '#059669', borderColor: '#047857' },
962
+ tab.key === 'UNUSED' && isActive && { backgroundColor: AppColors.red500, borderColor: AppColors.red600 },
963
+ tab.key === 'CONSUMED' && isActive && { backgroundColor: AppColors.emerald600, borderColor: AppColors.emerald700 },
1186
964
  ]}>
1187
965
  <Text style={bundleStyles.catPillIcon}>{tab.icon}</Text>
1188
966
  <Text style={[
1189
967
  bundleStyles.catPillText,
1190
968
  isActive && bundleStyles.catPillTextActive,
1191
969
  ]}>
1192
- {tab.label} ({count})
970
+ {t(tab.labelKey)} ({count})
1193
971
  </Text>
1194
972
  </TouchableScale>);
1195
973
  })}
@@ -1210,7 +988,7 @@ const BundleTab = React.memo(() => {
1210
988
  bundleStyles.viewModeBtnText,
1211
989
  filesViewMode === 'tree' && bundleStyles.viewModeBtnTextActive,
1212
990
  ]}>
1213
- 🌳 Tree View
991
+ {t('bundle.treeView')}
1214
992
  </Text>
1215
993
  </TouchableScale>
1216
994
  <TouchableScale onPress={() => setFilesViewMode('list')} style={[
@@ -1221,21 +999,24 @@ const BundleTab = React.memo(() => {
1221
999
  bundleStyles.viewModeBtnText,
1222
1000
  filesViewMode === 'list' && bundleStyles.viewModeBtnTextActive,
1223
1001
  ]}>
1224
- 📄 Flat List
1002
+ {t('bundle.flatList')}
1225
1003
  </Text>
1226
1004
  </TouchableScale>
1227
1005
  </View>
1228
1006
 
1229
1007
  {filesViewMode === 'tree' ? (<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1230
1008
  <TouchableScale onPress={expandAllFolders}>
1231
- <Text style={bundleStyles.treeActionLink}>Expand</Text>
1009
+ <Text style={bundleStyles.treeActionLink}>{t('bundle.expand')}</Text>
1232
1010
  </TouchableScale>
1233
1011
  <Text style={{ color: AppColors.grayTextWeak, fontSize: 11 }}>•</Text>
1234
1012
  <TouchableScale onPress={collapseAllFolders}>
1235
- <Text style={bundleStyles.treeActionLink}>Collapse</Text>
1013
+ <Text style={bundleStyles.treeActionLink}>{t('bundle.collapse')}</Text>
1236
1014
  </TouchableScale>
1237
1015
  </View>) : (<Text style={bundleStyles.listHeaderCount}>
1238
- Showing {filteredFiles.length} of {BUNDLE_FILES_DATA.length} files
1016
+ {t('bundle.showingFilesOf', {
1017
+ shown: filteredFiles.length,
1018
+ total: bundleFiles.length,
1019
+ })}
1239
1020
  </Text>)}
1240
1021
  </View>
1241
1022
 
@@ -1260,9 +1041,9 @@ const BundleTab = React.memo(() => {
1260
1041
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
1261
1042
  <HighlightText text={file.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
1262
1043
  {isUnused ? (<View style={bundleStyles.unusedBadge}>
1263
- <Text style={bundleStyles.unusedBadgeText}>🚫 Not Consumed</Text>
1044
+ <Text style={bundleStyles.unusedBadgeText}>{t('bundle.notConsumed')}</Text>
1264
1045
  </View>) : (<View style={bundleStyles.consumedBadge}>
1265
- <Text style={bundleStyles.consumedBadgeText}>⚡ Consumed</Text>
1046
+ <Text style={bundleStyles.consumedBadgeText}>{t('bundle.consumed')}</Text>
1266
1047
  </View>)}
1267
1048
  </View>
1268
1049
  <HighlightText text={file.path} search={search} style={bundleStyles.filePath} highlightStyle={bundleStyles.searchHighlight} numberOfLines={1}/>
@@ -1270,14 +1051,14 @@ const BundleTab = React.memo(() => {
1270
1051
 
1271
1052
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1272
1053
  <View style={bundleStyles.fileSizeBox}>
1273
- <Text style={[bundleStyles.fileSizeKb, isUnused && { color: '#B45309' }]}>
1054
+ <Text style={[bundleStyles.fileSizeKb, isUnused && { color: AppColors.amber800Warm }]}>
1274
1055
  {file.sizeKb >= 1024
1275
- ? `${(file.sizeKb / 1024).toFixed(2)} MB`
1276
- : `${file.sizeKb} KB`}
1056
+ ? t('bundle.fileSizeMb', { size: (file.sizeKb / 1024).toFixed(2) })
1057
+ : t('bundle.fileSizeKb', { size: file.sizeKb })}
1277
1058
  </Text>
1278
1059
  <Text style={bundleStyles.filePercent}>{pctOfTotal}%</Text>
1279
1060
  </View>
1280
- <CopyButton value={() => file} label="File Details JSON"/>
1061
+ <CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
1281
1062
  </View>
1282
1063
  </View>
1283
1064
 
@@ -1286,7 +1067,7 @@ const BundleTab = React.memo(() => {
1286
1067
  bundleStyles.fileProgressBar,
1287
1068
  {
1288
1069
  width: `${Math.min(100, Math.max(4, Number(pctOfTotal) * 4))}%`,
1289
- backgroundColor: isUnused ? '#EF4444' : categoryMeta.color,
1070
+ backgroundColor: isUnused ? AppColors.red500 : categoryMeta.color,
1290
1071
  },
1291
1072
  ]}/>
1292
1073
  </View>
@@ -1301,9 +1082,9 @@ const BundleTab = React.memo(() => {
1301
1082
  ]}>
1302
1083
  <Text style={[
1303
1084
  bundleStyles.adviceText,
1304
- file.status === 'warning' && { color: '#B45309' },
1305
- file.status === 'optimal' && { color: '#047857' },
1306
- isUnused && { color: '#DC2626' },
1085
+ file.status === 'warning' && { color: AppColors.amber800Warm },
1086
+ file.status === 'optimal' && { color: AppColors.emerald700 },
1087
+ isUnused && { color: AppColors.red600 },
1307
1088
  ]}>
1308
1089
  {isUnused ? '🗑️ ' : file.status === 'warning' ? '💡 ' : '✨ '}
1309
1090
  {file.advice}
@@ -1319,11 +1100,17 @@ const BundleTab = React.memo(() => {
1319
1100
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1320
1101
  <LayersIcon color={AppColors.purple} size={14}/>
1321
1102
  <Text style={bundleStyles.footerTitle}>
1322
- {filteredFiles.length} of {summary.totalCount} Files
1103
+ {t('bundle.filesOf', {
1104
+ count: filteredFiles.length,
1105
+ total: summary.totalCount,
1106
+ })}
1323
1107
  </Text>
1324
1108
  </View>
1325
1109
  <Text style={bundleStyles.footerSizeVal}>
1326
- {filteredTotalKb} KB ({(filteredTotalKb / 1024).toFixed(2)} MB)
1110
+ {t('bundle.kbMbValue', {
1111
+ kb: filteredTotalKb,
1112
+ mb: (filteredTotalKb / 1024).toFixed(2),
1113
+ })}
1327
1114
  </Text>
1328
1115
  </View>
1329
1116
  </View>)}
@@ -1331,28 +1118,29 @@ const BundleTab = React.memo(() => {
1331
1118
  {/* ══════════════════════════════════════════════════════════════════════ */}
1332
1119
  {/* ── 3. TAB: PACKAGES & NODE_MODULES ─────────────────────────────────── */}
1333
1120
  {/* ══════════════════════════════════════════════════════════════════════ */}
1334
- {activeSubTab === 'packages' && (<View style={{ flex: 1 }}>
1121
+ {activeSubTab === 'packages' && !isAnalyzing && (<View style={{ flex: 1 }}>
1335
1122
  <ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
1336
1123
 
1337
1124
  <View style={bundleStyles.filterContainer}>
1338
1125
  <View style={bundleStyles.searchRow}>
1339
1126
  <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"/>
1127
+ <TextInput placeholder={t('bundle.searchPackagesPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
1341
1128
  {search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10}>
1342
1129
  <ClearIcon color={AppColors.grayTextWeak} size={14}/>
1343
1130
  </Pressable>)}
1344
- <CopyButton value={() => filteredPackages} label="Dependencies JSON"/>
1131
+ <CopyButton value={() => filteredPackages} label={t('bundle.dependenciesJson')}/>
1345
1132
  </View>
1346
1133
  </View>
1347
1134
 
1348
1135
  <Text style={bundleStyles.listHeaderCount}>
1349
- Showing {filteredPackages.length} dependencies
1136
+ {t('bundle.showingDependencies', { count: filteredPackages.length })}
1350
1137
  </Text>
1351
1138
 
1352
1139
  {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' }]}>
1140
+ const cleanInstalledVersion = pkg.version ? pkg.version.replace(/^\^/, '') : '';
1141
+ const hasUpdate = !!pkg.latestVersion && !!cleanInstalledVersion &&
1142
+ pkg.latestVersion !== cleanInstalledVersion;
1143
+ return (<View key={pkg.id} style={[bundleStyles.fileCard, pkg.isDeprecated && { borderColor: AppColors.errorBorder, backgroundColor: '#FFFDFD' }]}>
1356
1144
  {/* Header Row: #s.no + Logo + Package Name + Version Pill + Update/Deprecated Badges + Size & Copy */}
1357
1145
  <View style={bundleStyles.fileCardTop}>
1358
1146
  <View style={bundleStyles.sNoBadge}>
@@ -1366,26 +1154,40 @@ const BundleTab = React.memo(() => {
1366
1154
  <HighlightText text={pkg.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
1367
1155
  <View style={bundleStyles.versionPill}>
1368
1156
  <Text style={bundleStyles.versionPillText}>
1369
- v{cleanInstalledVersion}
1157
+ {cleanInstalledVersion
1158
+ ? t('bundle.versionPrefix', { version: cleanInstalledVersion })
1159
+ : t('bundle.bundled')}
1370
1160
  </Text>
1371
1161
  </View>
1372
1162
 
1373
1163
  {pkg.isDeprecated ? (<View style={bundleStyles.deprecatedBadge}>
1374
- <Text style={bundleStyles.deprecatedBadgeText}>⚠️ DEPRECATED</Text>
1164
+ <Text style={bundleStyles.deprecatedBadgeText}>
1165
+ {t('bundle.deprecated')}
1166
+ </Text>
1375
1167
  </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>
1168
+ <Text style={bundleStyles.updateBadgeText}>
1169
+ {t('bundle.updateAvailable', { version: pkg.latestVersion })}
1170
+ </Text>
1171
+ </View>) : cleanInstalledVersion ? (<View style={[bundleStyles.updateBadge, { backgroundColor: AppColors.gray100, borderColor: AppColors.gray200 }]}>
1172
+ <Text style={[bundleStyles.updateBadgeText, { color: AppColors.gray600 }]}>
1173
+ {t('bundle.upToDate')}
1174
+ </Text>
1175
+ </View>) : (<View style={[bundleStyles.updateBadge, { backgroundColor: AppColors.gray100, borderColor: AppColors.gray200 }]}>
1176
+ <Text style={[bundleStyles.updateBadgeText, { color: AppColors.gray600 }]}>
1177
+ {t('bundle.bundledBadge')}
1178
+ </Text>
1379
1179
  </View>)}
1380
1180
  </View>
1381
1181
  </View>
1382
1182
 
1383
1183
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1384
1184
  <View style={bundleStyles.fileSizeBox}>
1385
- <Text style={bundleStyles.fileSizeKb}>{pkg.sizeKb} KB</Text>
1185
+ <Text style={bundleStyles.fileSizeKb}>
1186
+ {t('bundle.fileSizeKb', { size: pkg.sizeKb })}
1187
+ </Text>
1386
1188
  <Text style={bundleStyles.filePercent}>{pkg.percentage}%</Text>
1387
1189
  </View>
1388
- <CopyButton value={() => pkg} label="Package Details JSON"/>
1190
+ <CopyButton value={() => pkg} label={t('bundle.packageDetailsJson')}/>
1389
1191
  </View>
1390
1192
  </View>
1391
1193
 
@@ -1411,7 +1213,7 @@ const BundleTab = React.memo(() => {
1411
1213
  <View style={bundleStyles.fileProgressTrack}>
1412
1214
  <View style={[
1413
1215
  bundleStyles.fileProgressBar,
1414
- { width: `${pkg.percentage * 2}%`, backgroundColor: pkg.isDeprecated ? '#EF4444' : pkg.color },
1216
+ { width: `${pkg.percentage * 2}%`, backgroundColor: pkg.isDeprecated ? AppColors.red500 : pkg.color },
1415
1217
  ]}/>
1416
1218
  </View>
1417
1219
 
@@ -1420,7 +1222,7 @@ const BundleTab = React.memo(() => {
1420
1222
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1421
1223
  <View style={bundleStyles.typeTag}>
1422
1224
  <Text style={bundleStyles.typeTagText}>
1423
- {pkg.type === 'direct' ? '📦 Direct' : '🔗 Transitive'}
1225
+ {pkg.type === 'direct' ? t('bundle.direct') : t('bundle.transitive')}
1424
1226
  </Text>
1425
1227
  </View>
1426
1228
  {pkg.lastActive && (<Text style={bundleStyles.lastActiveText}>
@@ -1430,13 +1232,13 @@ const BundleTab = React.memo(() => {
1430
1232
 
1431
1233
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1432
1234
  <Text style={bundleStyles.fileMetaText}>
1433
- ~{pkg.sizeKb} KB minified
1235
+ {t('bundle.minified', { size: pkg.sizeKb })}
1434
1236
  </Text>
1435
1237
  <TouchableScale onPress={() => {
1436
1238
  const url = pkg.npmUrl || `https://www.npmjs.com/package/${pkg.name}`;
1437
1239
  Linking.openURL(url).catch(() => { });
1438
1240
  }} style={bundleStyles.npmLinkBtn}>
1439
- <Text style={bundleStyles.npmLinkText}>npm ↗</Text>
1241
+ <Text style={bundleStyles.npmLinkText}>{t('bundle.npmLink')}</Text>
1440
1242
  </TouchableScale>
1441
1243
  </View>
1442
1244
  </View>
@@ -1449,11 +1251,14 @@ const BundleTab = React.memo(() => {
1449
1251
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1450
1252
  <PackageIcon color={AppColors.purple} size={14}/>
1451
1253
  <Text style={bundleStyles.footerTitle}>
1452
- {filteredPackages.length} Dependencies
1254
+ {t('bundle.dependenciesCount', { count: filteredPackages.length })}
1453
1255
  </Text>
1454
1256
  </View>
1455
1257
  <Text style={bundleStyles.footerSizeVal}>
1456
- {packagesTotalKb} KB ({(packagesTotalKb / 1024).toFixed(2)} MB)
1258
+ {t('bundle.kbMbValue', {
1259
+ kb: packagesTotalKb,
1260
+ mb: (packagesTotalKb / 1024).toFixed(2),
1261
+ })}
1457
1262
  </Text>
1458
1263
  </View>
1459
1264
  </View>)}
@@ -1461,24 +1266,32 @@ const BundleTab = React.memo(() => {
1461
1266
  {/* ══════════════════════════════════════════════════════════════════════ */}
1462
1267
  {/* ── 4. TAB: MEDIA & ASSET AUDITOR ───────────────────────────────────── */}
1463
1268
  {/* ══════════════════════════════════════════════════════════════════════ */}
1464
- {activeSubTab === 'media' && (<View style={{ flex: 1 }}>
1269
+ {activeSubTab === 'media' && !isAnalyzing && (<View style={{ flex: 1 }}>
1465
1270
  <ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
1466
1271
 
1467
1272
  <View style={bundleStyles.tipsCard}>
1468
1273
  <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
1469
1274
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1470
1275
  <StorageIcon color={AppColors.purple} size={18}/>
1471
- <Text style={bundleStyles.tipsHeading}>Media Compression Auditor</Text>
1276
+ <Text style={bundleStyles.tipsHeading}>{t('bundle.mediaAuditorTitle')}</Text>
1472
1277
  </View>
1473
- <CopyButton value={() => mediaFiles} label="Media Assets JSON"/>
1278
+ <CopyButton value={() => mediaFiles} label={t('bundle.mediaAssetsJson')}/>
1474
1279
  </View>
1475
1280
  <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>.
1281
+ {t('bundle.mediaAuditorPrefix')}{' '}
1282
+ <Text style={{ fontFamily: AppFonts.interBold, color: AppColors.purple }}>
1283
+ {summary.images.pct + summary.fonts.pct}%
1284
+ </Text>{' '}
1285
+ {t('bundle.mediaAuditorMid', { kb: summary.images.kb + summary.fonts.kb })}{' '}
1286
+ <Text style={{ fontFamily: AppFonts.interBold, color: AppColors.emerald700 }}>
1287
+ {t('bundle.mediaSavings', { size: 540 })}
1288
+ </Text>{' '}
1289
+ {t('bundle.mediaAuditorSuffix')}
1477
1290
  </Text>
1478
1291
  </View>
1479
1292
 
1480
1293
  <Text style={bundleStyles.listHeaderCount}>
1481
- Media Assets & Fonts ({mediaFiles.length} items)
1294
+ {t('bundle.mediaAssetsList', { count: mediaFiles.length })}
1482
1295
  </Text>
1483
1296
 
1484
1297
  {mediaFiles.map((file, index) => (<View key={file.id} style={bundleStyles.fileCard}>
@@ -1496,9 +1309,11 @@ const BundleTab = React.memo(() => {
1496
1309
 
1497
1310
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1498
1311
  <View style={bundleStyles.fileSizeBox}>
1499
- <Text style={bundleStyles.fileSizeKb}>{file.sizeKb} KB</Text>
1312
+ <Text style={bundleStyles.fileSizeKb}>
1313
+ {t('bundle.fileSizeKb', { size: file.sizeKb })}
1314
+ </Text>
1500
1315
  </View>
1501
- <CopyButton value={() => file} label="Media Item JSON"/>
1316
+ <CopyButton value={() => file} label={t('bundle.mediaItemJson')}/>
1502
1317
  </View>
1503
1318
  </View>
1504
1319
 
@@ -1511,8 +1326,8 @@ const BundleTab = React.memo(() => {
1511
1326
  ]}>
1512
1327
  <Text style={[
1513
1328
  bundleStyles.adviceText,
1514
- file.status === 'warning' && { color: '#B45309' },
1515
- file.status === 'optimal' && { color: '#047857' },
1329
+ file.status === 'warning' && { color: AppColors.amber800Warm },
1330
+ file.status === 'optimal' && { color: AppColors.emerald700 },
1516
1331
  ]}>
1517
1332
  {file.status === 'warning' ? '💡 ' : '✨ '}
1518
1333
  {file.advice}
@@ -1527,11 +1342,14 @@ const BundleTab = React.memo(() => {
1527
1342
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1528
1343
  <StorageIcon color={AppColors.purple} size={14}/>
1529
1344
  <Text style={bundleStyles.footerTitle}>
1530
- {mediaFiles.length} Media & Fonts
1345
+ {t('bundle.mediaAndFonts', { count: mediaFiles.length })}
1531
1346
  </Text>
1532
1347
  </View>
1533
1348
  <Text style={bundleStyles.footerSizeVal}>
1534
- {mediaTotalKb} KB ({(mediaTotalKb / 1024).toFixed(2)} MB)
1349
+ {t('bundle.kbMbValue', {
1350
+ kb: mediaTotalKb,
1351
+ mb: (mediaTotalKb / 1024).toFixed(2),
1352
+ })}
1535
1353
  </Text>
1536
1354
  </View>
1537
1355
  </View>)}
@@ -1545,49 +1363,49 @@ const BundleTab = React.memo(() => {
1545
1363
  <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
1546
1364
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1547
1365
  <CircleAlertIcon color={AppColors.purple} size={18}/>
1548
- <Text style={bundleStyles.tipsHeading}>React Native Bundle Optimization Checklist</Text>
1366
+ <Text style={bundleStyles.tipsHeading}>{t('bundle.optimizerTitle')}</Text>
1549
1367
  </View>
1550
1368
  <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"/>
1369
+ t('bundle.optTip1Title'),
1370
+ `${t('bundle.optTip2Title')} (${isHermes ? t('bundle.active') : t('bundle.actionRequired')})`,
1371
+ t('bundle.optTip3Title'),
1372
+ t('bundle.optTip4Title'),
1373
+ t('bundle.optTip5Title'),
1374
+ ]} label={t('bundle.optimizationChecklist')}/>
1557
1375
  </View>
1558
1376
 
1559
1377
  {[
1560
1378
  {
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',
1379
+ title: t('bundle.optTip1Title'),
1380
+ desc: t('bundle.optTip1Desc'),
1381
+ badge: t('bundle.highImpact'),
1382
+ badgeColor: AppColors.pink500,
1565
1383
  },
1566
1384
  {
1567
- title: '📜 Enable Hermes Bytecode Engine',
1385
+ title: t('bundle.optTip2Title'),
1568
1386
  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',
1387
+ ? t('bundle.optTip2DescActive')
1388
+ : t('bundle.optTip2DescInactive'),
1389
+ badge: isHermes ? t('bundle.active') : t('bundle.actionRequired'),
1390
+ badgeColor: isHermes ? AppColors.emerald500 : AppColors.amber500,
1573
1391
  },
1574
1392
  {
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',
1393
+ title: t('bundle.optTip3Title'),
1394
+ desc: t('bundle.optTip3Desc'),
1395
+ badge: t('bundle.mediumImpact'),
1396
+ badgeColor: AppColors.purple500,
1579
1397
  },
1580
1398
  {
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',
1399
+ title: t('bundle.optTip4Title'),
1400
+ desc: t('bundle.optTip4Desc'),
1401
+ badge: t('bundle.bestPractice'),
1402
+ badgeColor: AppColors.sky500,
1585
1403
  },
1586
1404
  {
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',
1405
+ title: t('bundle.optTip5Title'),
1406
+ desc: t('bundle.optTip5Desc'),
1407
+ badge: t('bundle.bestPractice'),
1408
+ badgeColor: AppColors.indigo500,
1591
1409
  },
1592
1410
  ].map((tip, tIdx) => (<View key={tIdx} style={bundleStyles.tipItem}>
1593
1411
  <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 2 }}>
@@ -1603,7 +1421,7 @@ const BundleTab = React.memo(() => {
1603
1421
  {tip.badge}
1604
1422
  </Text>
1605
1423
  </View>
1606
- <CopyButton value={() => tip} label="Tip Details"/>
1424
+ <CopyButton value={() => tip} label={t('bundle.tipDetails')}/>
1607
1425
  </View>
1608
1426
  </View>
1609
1427
  <Text style={bundleStyles.tipDesc}>{tip.desc}</Text>
@@ -1619,6 +1437,23 @@ const bundleStyles = StyleSheet.create({
1619
1437
  borderBottomWidth: 1,
1620
1438
  borderBottomColor: AppColors.dividerColor,
1621
1439
  },
1440
+ analyzingContainer: {
1441
+ flex: 1,
1442
+ alignItems: 'center',
1443
+ justifyContent: 'center',
1444
+ gap: 8,
1445
+ padding: 24,
1446
+ },
1447
+ analyzingText: {
1448
+ fontFamily: AppFonts.interBold,
1449
+ fontSize: 13,
1450
+ color: AppColors.primaryBlack,
1451
+ },
1452
+ analyzingHint: {
1453
+ fontFamily: AppFonts.interRegular,
1454
+ fontSize: 11,
1455
+ color: AppColors.grayTextWeak,
1456
+ },
1622
1457
  subTabsScroll: {
1623
1458
  paddingHorizontal: 12,
1624
1459
  gap: 8,
@@ -2174,41 +2009,41 @@ const bundleStyles = StyleSheet.create({
2174
2009
  paddingHorizontal: 7,
2175
2010
  paddingVertical: 2,
2176
2011
  borderRadius: 5,
2177
- backgroundColor: '#CB383714',
2012
+ backgroundColor: AppColors.npmRedTint,
2178
2013
  borderWidth: 1,
2179
- borderColor: '#CB383733',
2014
+ borderColor: AppColors.npmRedBorder,
2180
2015
  },
2181
2016
  npmLinkText: {
2182
2017
  fontFamily: AppFonts.interBold,
2183
2018
  fontSize: 9.5,
2184
- color: '#CB3837',
2019
+ color: AppColors.npmRed,
2185
2020
  },
2186
2021
  deprecatedBadge: {
2187
2022
  paddingHorizontal: 6,
2188
2023
  paddingVertical: 1.5,
2189
2024
  borderRadius: 4,
2190
- backgroundColor: '#FEE2E2',
2025
+ backgroundColor: AppColors.red100,
2191
2026
  borderWidth: 1,
2192
- borderColor: '#FECACA',
2027
+ borderColor: AppColors.errorBorder,
2193
2028
  },
2194
2029
  deprecatedBadgeText: {
2195
2030
  fontFamily: AppFonts.interBold,
2196
2031
  fontSize: 9,
2197
- color: '#DC2626',
2032
+ color: AppColors.red600,
2198
2033
  letterSpacing: 0.3,
2199
2034
  },
2200
2035
  updateBadge: {
2201
2036
  paddingHorizontal: 6,
2202
2037
  paddingVertical: 1.5,
2203
2038
  borderRadius: 4,
2204
- backgroundColor: '#DCFCE7',
2039
+ backgroundColor: AppColors.green100,
2205
2040
  borderWidth: 1,
2206
- borderColor: '#BBF7D0',
2041
+ borderColor: AppColors.greenBorder,
2207
2042
  },
2208
2043
  updateBadgeText: {
2209
2044
  fontFamily: AppFonts.interBold,
2210
2045
  fontSize: 9,
2211
- color: '#047857',
2046
+ color: AppColors.emerald700,
2212
2047
  },
2213
2048
  lastActiveText: {
2214
2049
  fontFamily: AppFonts.interRegular,
@@ -2216,45 +2051,45 @@ const bundleStyles = StyleSheet.create({
2216
2051
  color: AppColors.grayTextWeak,
2217
2052
  },
2218
2053
  deprecationBox: {
2219
- backgroundColor: '#FFF1F2',
2054
+ backgroundColor: AppColors.errorCardBg,
2220
2055
  borderRadius: 6,
2221
2056
  paddingHorizontal: 8,
2222
2057
  paddingVertical: 5,
2223
2058
  borderWidth: 1,
2224
- borderColor: '#FECDD3',
2059
+ borderColor: AppColors.errorBorder,
2225
2060
  marginTop: 3,
2226
2061
  },
2227
2062
  deprecationText: {
2228
2063
  fontFamily: AppFonts.interMedium,
2229
2064
  fontSize: 10.5,
2230
- color: '#BE123C',
2065
+ color: AppColors.rose700,
2231
2066
  lineHeight: 14,
2232
2067
  },
2233
2068
  unusedBadge: {
2234
2069
  paddingHorizontal: 6,
2235
2070
  paddingVertical: 1.5,
2236
2071
  borderRadius: 4,
2237
- backgroundColor: '#FEE2E2',
2072
+ backgroundColor: AppColors.red100,
2238
2073
  borderWidth: 1,
2239
- borderColor: '#FECACA',
2074
+ borderColor: AppColors.errorBorder,
2240
2075
  },
2241
2076
  unusedBadgeText: {
2242
2077
  fontFamily: AppFonts.interBold,
2243
2078
  fontSize: 9.5,
2244
- color: '#DC2626',
2079
+ color: AppColors.red600,
2245
2080
  },
2246
2081
  consumedBadge: {
2247
2082
  paddingHorizontal: 6,
2248
2083
  paddingVertical: 1.5,
2249
2084
  borderRadius: 4,
2250
- backgroundColor: '#DCFCE7',
2085
+ backgroundColor: AppColors.green100,
2251
2086
  borderWidth: 1,
2252
- borderColor: '#BBF7D0',
2087
+ borderColor: AppColors.greenBorder,
2253
2088
  },
2254
2089
  consumedBadgeText: {
2255
2090
  fontFamily: AppFonts.interBold,
2256
2091
  fontSize: 9.5,
2257
- color: '#059669',
2092
+ color: AppColors.emerald600,
2258
2093
  },
2259
2094
  treeFolderChevronWrap: {
2260
2095
  width: 18,
@@ -2265,13 +2100,13 @@ const bundleStyles = StyleSheet.create({
2265
2100
  justifyContent: 'center',
2266
2101
  },
2267
2102
  treeFileCardUnused: {
2268
- borderColor: '#FECACA',
2269
- backgroundColor: '#FFFBFB',
2103
+ borderColor: AppColors.errorBorder,
2104
+ backgroundColor: AppColors.red50,
2270
2105
  },
2271
2106
  adviceUnused: {
2272
- backgroundColor: '#FEF2F2',
2107
+ backgroundColor: AppColors.errorCardBg,
2273
2108
  borderWidth: 1,
2274
- borderColor: '#FECACA',
2109
+ borderColor: AppColors.errorBorder,
2275
2110
  },
2276
2111
  adviceBadge: {
2277
2112
  paddingHorizontal: 8,
@@ -2280,14 +2115,14 @@ const bundleStyles = StyleSheet.create({
2280
2115
  marginTop: 2,
2281
2116
  },
2282
2117
  adviceWarning: {
2283
- backgroundColor: '#FEF3C7',
2118
+ backgroundColor: AppColors.amber100,
2284
2119
  borderWidth: 1,
2285
- borderColor: '#FDE68A',
2120
+ borderColor: AppColors.amber200,
2286
2121
  },
2287
2122
  adviceOptimal: {
2288
- backgroundColor: '#DCFCE7',
2123
+ backgroundColor: AppColors.green100,
2289
2124
  borderWidth: 1,
2290
- borderColor: '#BBF7D0',
2125
+ borderColor: AppColors.greenBorder,
2291
2126
  },
2292
2127
  adviceText: {
2293
2128
  fontFamily: AppFonts.interMedium,
@@ -2305,21 +2140,21 @@ const bundleStyles = StyleSheet.create({
2305
2140
  fontSize: 9,
2306
2141
  },
2307
2142
  searchHighlight: {
2308
- backgroundColor: '#FEF08A',
2309
- color: '#854D0E',
2143
+ backgroundColor: AppColors.yellow200,
2144
+ color: AppColors.yellow800,
2310
2145
  fontFamily: AppFonts.interBold,
2311
2146
  },
2312
2147
  tipsCard: {
2313
- backgroundColor: '#FAF5FF',
2148
+ backgroundColor: AppColors.purple50,
2314
2149
  borderRadius: 14,
2315
2150
  padding: 14,
2316
2151
  borderWidth: 1,
2317
- borderColor: '#E9D5FF',
2152
+ borderColor: AppColors.purple200,
2318
2153
  },
2319
2154
  tipsHeading: {
2320
2155
  fontFamily: AppFonts.interBold,
2321
2156
  fontSize: 13.5,
2322
- color: '#6B21A8',
2157
+ color: AppColors.purple800,
2323
2158
  },
2324
2159
  totalSummaryCard: {
2325
2160
  marginTop: 10,
@@ -2383,17 +2218,17 @@ const bundleStyles = StyleSheet.create({
2383
2218
  padding: 10,
2384
2219
  borderRadius: 8,
2385
2220
  borderWidth: 1,
2386
- borderColor: '#E9D5FF',
2221
+ borderColor: AppColors.purple200,
2387
2222
  },
2388
2223
  tipTitle: {
2389
2224
  fontFamily: AppFonts.interBold,
2390
2225
  fontSize: 12,
2391
- color: '#581C87',
2226
+ color: AppColors.purple900,
2392
2227
  },
2393
2228
  tipDesc: {
2394
2229
  fontFamily: AppFonts.interRegular,
2395
2230
  fontSize: 11,
2396
- color: '#6B7280',
2231
+ color: AppColors.gray500,
2397
2232
  lineHeight: 16,
2398
2233
  marginTop: 4,
2399
2234
  },