@oxyhq/services 6.9.4 → 6.9.5

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 (71) hide show
  1. package/lib/commonjs/ui/client.js +0 -7
  2. package/lib/commonjs/ui/client.js.map +1 -1
  3. package/lib/commonjs/ui/components/feedback/FormInput.js.map +1 -1
  4. package/lib/commonjs/ui/components/icon/OxyIcon.js.map +1 -1
  5. package/lib/commonjs/ui/components/types.js +4 -0
  6. package/lib/commonjs/ui/screens/AppInfoScreen.js +66 -60
  7. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  8. package/lib/commonjs/ui/screens/FileManagementScreen.js +139 -79
  9. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  10. package/lib/commonjs/ui/screens/SessionManagementScreen.js +39 -29
  11. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  12. package/lib/module/ui/client.js +0 -1
  13. package/lib/module/ui/client.js.map +1 -1
  14. package/lib/module/ui/components/feedback/FormInput.js.map +1 -1
  15. package/lib/module/ui/components/icon/OxyIcon.js.map +1 -1
  16. package/lib/module/ui/components/types.js +2 -0
  17. package/lib/module/ui/screens/AppInfoScreen.js +66 -60
  18. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  19. package/lib/module/ui/screens/FileManagementScreen.js +139 -79
  20. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  21. package/lib/module/ui/screens/SessionManagementScreen.js +39 -29
  22. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  23. package/lib/typescript/commonjs/ui/client.d.ts +0 -1
  24. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  25. package/lib/typescript/commonjs/ui/components/types.d.ts +18 -17
  26. package/lib/typescript/commonjs/ui/components/types.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/ui/screens/AppInfoScreen.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  30. package/lib/typescript/module/ui/client.d.ts +0 -1
  31. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  32. package/lib/typescript/module/ui/components/types.d.ts +18 -17
  33. package/lib/typescript/module/ui/components/types.d.ts.map +1 -1
  34. package/lib/typescript/module/ui/screens/AppInfoScreen.d.ts.map +1 -1
  35. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  36. package/lib/typescript/module/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/ui/client.ts +0 -1
  39. package/src/ui/components/feedback/FormInput.tsx +1 -1
  40. package/src/ui/components/icon/OxyIcon.tsx +1 -1
  41. package/src/ui/components/types.tsx +19 -17
  42. package/src/ui/screens/AppInfoScreen.tsx +63 -61
  43. package/src/ui/screens/FileManagementScreen.tsx +130 -121
  44. package/src/ui/screens/SessionManagementScreen.tsx +30 -28
  45. package/lib/commonjs/ui/components/AnimationExample.js +0 -213
  46. package/lib/commonjs/ui/components/AnimationExample.js.map +0 -1
  47. package/lib/commonjs/ui/components/ErrorBoundary.js +0 -145
  48. package/lib/commonjs/ui/components/ErrorBoundary.js.map +0 -1
  49. package/lib/commonjs/ui/components/WebOxyProvider.js +0 -106
  50. package/lib/commonjs/ui/components/WebOxyProvider.js.map +0 -1
  51. package/lib/module/ui/components/AnimationExample.js +0 -209
  52. package/lib/module/ui/components/AnimationExample.js.map +0 -1
  53. package/lib/module/ui/components/ErrorBoundary.js +0 -139
  54. package/lib/module/ui/components/ErrorBoundary.js.map +0 -1
  55. package/lib/module/ui/components/WebOxyProvider.js +0 -102
  56. package/lib/module/ui/components/WebOxyProvider.js.map +0 -1
  57. package/lib/typescript/commonjs/ui/components/AnimationExample.d.ts +0 -4
  58. package/lib/typescript/commonjs/ui/components/AnimationExample.d.ts.map +0 -1
  59. package/lib/typescript/commonjs/ui/components/ErrorBoundary.d.ts +0 -31
  60. package/lib/typescript/commonjs/ui/components/ErrorBoundary.d.ts.map +0 -1
  61. package/lib/typescript/commonjs/ui/components/WebOxyProvider.d.ts +0 -52
  62. package/lib/typescript/commonjs/ui/components/WebOxyProvider.d.ts.map +0 -1
  63. package/lib/typescript/module/ui/components/AnimationExample.d.ts +0 -4
  64. package/lib/typescript/module/ui/components/AnimationExample.d.ts.map +0 -1
  65. package/lib/typescript/module/ui/components/ErrorBoundary.d.ts +0 -31
  66. package/lib/typescript/module/ui/components/ErrorBoundary.d.ts.map +0 -1
  67. package/lib/typescript/module/ui/components/WebOxyProvider.d.ts +0 -52
  68. package/lib/typescript/module/ui/components/WebOxyProvider.d.ts.map +0 -1
  69. package/src/ui/components/AnimationExample.tsx +0 -195
  70. package/src/ui/components/ErrorBoundary.tsx +0 -154
  71. package/src/ui/components/WebOxyProvider.tsx +0 -117
@@ -41,6 +41,7 @@ import { useThemeStyles } from '../hooks/useThemeStyles';
41
41
  import { useColorScheme } from '../hooks/useColorScheme';
42
42
  import { normalizeTheme } from '../utils/themeUtils';
43
43
  import { useOxy } from '../context/OxyContext';
44
+ import { useI18n } from '../hooks/useI18n';
44
45
  import { useUploadFile } from '../hooks/mutations/useAccountMutations';
45
46
  import {
46
47
  confirmAction,
@@ -55,6 +56,10 @@ import { UploadPreview } from '../components/fileManagement/UploadPreview';
55
56
  import { fileManagementStyles } from '../components/fileManagement/styles';
56
57
  import type { OnConfirmFileSelection } from '../types/fileManagement';
57
58
 
59
+ /** Extract error message from unknown error type */
60
+ const getErrorMessage = (error: unknown): string | undefined =>
61
+ error instanceof Error ? getErrorMessage(error) : typeof error === 'string' ? error : undefined;
62
+
58
63
  // Animated button component for smooth transitions
59
64
  const AnimatedButton: React.FC<{
60
65
  isSelected: boolean;
@@ -62,7 +67,7 @@ const AnimatedButton: React.FC<{
62
67
  icon: string;
63
68
  primaryColor: string;
64
69
  textColor: string;
65
- style: any;
70
+ style: Record<string, unknown>;
66
71
  }> = ({ isSelected, onPress, icon, primaryColor, textColor, style }) => {
67
72
  const animatedValue = useRef(new Animated.Value(isSelected ? 1 : 0)).current;
68
73
 
@@ -97,7 +102,7 @@ const AnimatedButton: React.FC<{
97
102
  >
98
103
  <Animated.View>
99
104
  <MaterialCommunityIcons
100
- name={icon as any}
105
+ name={icon as React.ComponentProps<typeof MaterialCommunityIcons>['name']}
101
106
  size={16}
102
107
  color={isSelected ? '#FFFFFF' : textColor}
103
108
  />
@@ -128,6 +133,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
128
133
  }) => {
129
134
  // Use useOxy() hook for OxyContext values
130
135
  const { user, oxyServices } = useOxy();
136
+ const { t } = useI18n();
131
137
  const uploadFileMutation = useUploadFile();
132
138
  const files = useFiles();
133
139
  // Ensure containerWidth is a number (TypeScript guard)
@@ -226,13 +232,13 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
226
232
  if (disabledMimeTypes.length) {
227
233
  const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : `${mt}/`));
228
234
  if (blocked) {
229
- toast.error('This file type cannot be selected');
235
+ toast.error(t('fileManagement.toasts.fileTypeBlocked'));
230
236
  return;
231
237
  }
232
238
  }
233
239
 
234
240
  // Update file visibility if it differs from defaultVisibility
235
- const fileVisibility = (file.metadata as any)?.visibility || 'private';
241
+ const fileVisibility = (file.metadata as Record<string, unknown> | undefined)?.visibility || 'private';
236
242
  if (fileVisibility !== defaultVisibility) {
237
243
  try {
238
244
  await oxyServices.assetUpdateVisibility(file.id, defaultVisibility);
@@ -253,7 +259,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
253
259
  linkContext.entityType,
254
260
  linkContext.entityId,
255
261
  defaultVisibility,
256
- (linkContext as any).webhookUrl
262
+ (linkContext as Record<string, unknown>).webhookUrl
257
263
  );
258
264
  } catch (error) {
259
265
  // Continue anyway - selection shouldn't fail if linking fails
@@ -274,7 +280,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
274
280
  const already = next.has(file.id);
275
281
  if (!already) {
276
282
  if (maxSelection && next.size >= maxSelection) {
277
- toast.error(`You can select up to ${maxSelection}`);
283
+ toast.error(t('fileManagement.toasts.maxSelection', { max: maxSelection }));
278
284
  return prev;
279
285
  }
280
286
  next.add(file.id);
@@ -294,7 +300,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
294
300
  // Update visibility and link files if needed
295
301
  const updatePromises = chosen.map(async (file) => {
296
302
  // Update visibility if needed
297
- const fileVisibility = (file.metadata as any)?.visibility || 'private';
303
+ const fileVisibility = (file.metadata as Record<string, unknown> | undefined)?.visibility || 'private';
298
304
  if (fileVisibility !== defaultVisibility) {
299
305
  try {
300
306
  await oxyServices.assetUpdateVisibility(file.id, defaultVisibility);
@@ -312,7 +318,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
312
318
  linkContext.entityType,
313
319
  linkContext.entityId,
314
320
  defaultVisibility,
315
- (linkContext as any).webhookUrl
321
+ (linkContext as Record<string, unknown>).webhookUrl
316
322
  );
317
323
  } catch (error) {
318
324
  // File linking failed, continue with selection
@@ -381,7 +387,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
381
387
  const currentPaging = mode === 'more' ? (prevPagingRef.current ?? paging) : paging;
382
388
  const effectiveOffset = mode === 'more' ? currentPaging.offset + currentPaging.limit : 0;
383
389
  const response = await oxyServices.listUserFiles(currentPaging.limit, effectiveOffset);
384
- const assets: FileMetadata[] = (response.files || []).map((f: any) => ({
390
+ const assets: FileMetadata[] = (response.files || []).map((f: Record<string, unknown>) => ({
385
391
  id: f.id,
386
392
  filename: f.originalName || f.sha256,
387
393
  contentType: f.mime,
@@ -411,8 +417,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
411
417
  loadingMore: false,
412
418
  }));
413
419
  }
414
- } catch (error: any) {
415
- toast.error(error.message || 'Failed to load files');
420
+ } catch (error: unknown) {
421
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.loadFailed'));
416
422
  } finally {
417
423
  setLoading(false);
418
424
  setRefreshing(false);
@@ -508,7 +514,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
508
514
  const oversizedFiles = selectedFiles.filter(file => file.size > maxSize);
509
515
  if (oversizedFiles.length > 0) {
510
516
  const fileList = oversizedFiles.map(f => f.name).join(', ');
511
- toast.error(`The following files are too large (max 50MB): ${fileList}`);
517
+ toast.error(t('fileManagement.toasts.filesTooLarge', { files: fileList }));
512
518
  return [];
513
519
  }
514
520
  let successCount = 0;
@@ -570,16 +576,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
570
576
  successCount++;
571
577
  } else {
572
578
  // Fallback: will reconcile on later list refresh
573
- useFileStore.getState().updateFile(optimisticId, { metadata: { uploading: false } as any });
579
+ useFileStore.getState().updateFile(optimisticId, { metadata: { uploading: false } as Partial<FileMetadata>['metadata'] });
574
580
  if (__DEV__) {
575
581
  console.warn('Upload completed but no file data returned:', { fileName, result });
576
582
  }
577
583
  // Still count as success if upload didn't throw
578
584
  successCount++;
579
585
  }
580
- } catch (error: any) {
586
+ } catch (error: unknown) {
581
587
  failureCount++;
582
- const errorMessage = error.message || error.toString() || 'Upload failed';
588
+ const errorMessage = getErrorMessage(error) || String(error) || 'Upload failed';
583
589
  const fullError = `${fileName}: ${errorMessage}`;
584
590
  errors.push(fullError);
585
591
  if (__DEV__) {
@@ -588,7 +594,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
588
594
  fileSize: raw.size,
589
595
  fileType: raw.type,
590
596
  error: errorMessage,
591
- stack: error.stack
597
+ stack: (error instanceof Error) ? error.stack : undefined
592
598
  });
593
599
  }
594
600
 
@@ -599,26 +605,27 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
599
605
 
600
606
  // Show success/error messages
601
607
  if (successCount > 0) {
602
- toast.success(`${successCount} file(s) uploaded successfully`);
608
+ toast.success(t('fileManagement.toasts.uploadSuccess', { count: successCount }));
603
609
  }
604
610
  if (failureCount > 0) {
605
611
  // Show detailed error message with first few errors
606
612
  const errorDetails = errors.length > 0
607
613
  ? `\n${errors.slice(0, 3).join('\n')}${errors.length > 3 ? `\n...and ${errors.length - 3} more` : ''}`
608
614
  : '';
609
- toast.error(`${failureCount} file(s) failed to upload${errorDetails}`);
615
+ toast.error(`${t('fileManagement.toasts.uploadFailed', { count: failureCount })}${errorDetails}`);
610
616
  }
611
617
  // Silent background refresh to ensure metadata/variants updated
612
618
  setTimeout(() => { loadFiles('silent'); }, 1200);
613
- } catch (error: any) {
614
- toast.error(error.message || 'Failed to upload files');
619
+ } catch (error: unknown) {
620
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.uploadError'));
615
621
  } finally {
616
622
  storeSetUploadProgress(null);
617
623
  }
618
624
  return uploadedFiles;
619
625
  };
620
626
 
621
- const handleFileSelection = useCallback(async (selectedFiles: File[] | any[]) => {
627
+ // biome-ignore lint/suspicious/noExplicitAny: Files from document picker may have extra properties like uri
628
+ const handleFileSelection = useCallback(async (selectedFiles: Array<File | any>) => {
622
629
  const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
623
630
  const processedFiles: Array<{ file: File | Blob; preview?: string; size: number; name: string; type: string }> = [];
624
631
 
@@ -628,7 +635,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
628
635
  if (__DEV__) {
629
636
  console.error('Invalid file: file is null or undefined');
630
637
  }
631
- toast.error('Invalid file: file is missing');
638
+ toast.error(t('fileManagement.toasts.invalidFileMissing'));
632
639
  continue;
633
640
  }
634
641
 
@@ -636,7 +643,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
636
643
  if (__DEV__) {
637
644
  console.error('Invalid file: missing or invalid name property', file);
638
645
  }
639
- toast.error('Invalid file: missing file name');
646
+ toast.error(t('fileManagement.toasts.invalidFileName'));
640
647
  continue;
641
648
  }
642
649
 
@@ -644,7 +651,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
644
651
  if (__DEV__) {
645
652
  console.error('Invalid file: missing or invalid size property', file);
646
653
  }
647
- toast.error(`Invalid file "${file.name || 'unknown'}": missing file size`);
654
+ toast.error(t('fileManagement.toasts.invalidFileSize', { name: file.name || 'unknown' }));
648
655
  continue;
649
656
  }
650
657
 
@@ -652,13 +659,13 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
652
659
  if (__DEV__) {
653
660
  console.error('Invalid file: file size is zero or negative', file);
654
661
  }
655
- toast.error(`File "${file.name}" is empty`);
662
+ toast.error(t('fileManagement.toasts.fileEmpty', { name: file.name }));
656
663
  continue;
657
664
  }
658
665
 
659
666
  // Validate file size
660
667
  if (file.size > MAX_FILE_SIZE) {
661
- toast.error(`"${file.name}" is too large. Maximum file size is ${formatFileSize(MAX_FILE_SIZE)}`);
668
+ toast.error(t('fileManagement.toasts.fileTooLarge', { name: file.name, maxSize: formatFileSize(MAX_FILE_SIZE) }));
662
669
  continue;
663
670
  }
664
671
 
@@ -669,7 +676,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
669
676
  let preview: string | undefined;
670
677
  if (fileType.startsWith('image/')) {
671
678
  // Try to use file URI from expo-document-picker if available (works on all platforms)
672
- const fileUri = (file as any).uri;
679
+ const fileUri = (file as File & { uri?: string }).uri;
673
680
  if (fileUri && typeof fileUri === 'string' &&
674
681
  (fileUri.startsWith('file://') || fileUri.startsWith('content://') ||
675
682
  fileUri.startsWith('http://') || fileUri.startsWith('https://') ||
@@ -678,10 +685,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
678
685
  } else {
679
686
  // Fallback: create blob URL if possible (works on web)
680
687
  try {
681
- if (file instanceof File || file instanceof Blob) {
688
+ if ((file as object) instanceof File || (file as object) instanceof Blob) {
682
689
  preview = URL.createObjectURL(file);
683
690
  }
684
- } catch (error: any) {
691
+ } catch (error: unknown) {
685
692
  if (__DEV__) {
686
693
  console.warn('Failed to create preview URL:', error);
687
694
  }
@@ -700,7 +707,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
700
707
  }
701
708
 
702
709
  if (processedFiles.length === 0) {
703
- toast.error('No valid files to upload');
710
+ toast.error(t('fileManagement.toasts.noValidFiles'));
704
711
  return;
705
712
  }
706
713
 
@@ -755,8 +762,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
755
762
  }
756
763
 
757
764
  endUpload();
758
- } catch (error: any) {
759
- toast.error(error.message || 'Failed to upload files');
765
+ } catch (error: unknown) {
766
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.uploadError'));
760
767
  endUpload();
761
768
  }
762
769
  };
@@ -791,7 +798,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
791
798
  const handleFileUpload = async () => {
792
799
  // Prevent concurrent document picker calls
793
800
  if (isPickingDocument) {
794
- toast.error('Please wait for the current file selection to complete');
801
+ toast.error(t('fileManagement.toasts.waitForSelection'));
795
802
  return;
796
803
  }
797
804
 
@@ -816,7 +823,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
816
823
 
817
824
  if (!result.assets || result.assets.length === 0) {
818
825
  setIsPickingDocument(false);
819
- toast.error('No files were selected');
826
+ toast.error(t('fileManagement.toasts.noFilesSelected'));
820
827
  return;
821
828
  }
822
829
 
@@ -845,8 +852,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
845
852
  }
846
853
  return null;
847
854
  })
848
- .catch((error: any) => {
849
- errors.push(`File "${doc.name || 'file'}": ${error.message || 'Failed to process'}`);
855
+ .catch((error: unknown) => {
856
+ errors.push(`File "${doc.name || 'file'}": ${getErrorMessage(error) || 'Failed to process'}`);
850
857
  return null;
851
858
  })
852
859
  );
@@ -863,7 +870,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
863
870
  // Show errors if any
864
871
  if (errors.length > 0) {
865
872
  const errorMessage = errors.slice(0, 3).join('\n') + (errors.length > 3 ? `\n...and ${errors.length - 3} more` : '');
866
- toast.error(`Failed to load some files:\n${errorMessage}`);
873
+ toast.error(t('fileManagement.toasts.loadSomeFailed', { errors: errorMessage }));
867
874
  }
868
875
 
869
876
  // Process successfully converted files
@@ -871,20 +878,20 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
871
878
  await handleFileSelection(files);
872
879
  } else {
873
880
  // Files were selected but none could be converted
874
- toast.error('No files could be processed. Please try selecting files again.');
881
+ toast.error(t('fileManagement.toasts.noFilesProcessed'));
875
882
  }
876
- } catch (error: any) {
883
+ } catch (error: unknown) {
877
884
  if (__DEV__) {
878
885
  console.error('File upload error:', error);
879
886
  }
880
- if (error.message?.includes('expo-document-picker') || error.message?.includes('Different document picking in progress')) {
881
- if (error.message?.includes('Different document picking in progress')) {
882
- toast.error('Please wait for the current file selection to complete');
887
+ if (getErrorMessage(error)?.includes('expo-document-picker') || getErrorMessage(error)?.includes('Different document picking in progress')) {
888
+ if (getErrorMessage(error)?.includes('Different document picking in progress')) {
889
+ toast.error(t('fileManagement.toasts.waitForSelection'));
883
890
  } else {
884
- toast.error('File picker not available. Please install expo-document-picker');
891
+ toast.error(t('fileManagement.toasts.filePickerNotAvailable'));
885
892
  }
886
893
  } else {
887
- toast.error(error.message || 'Failed to select files');
894
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.selectFilesFailed'));
888
895
  }
889
896
  } finally {
890
897
  // Always reset the picking state, even if there was an error
@@ -895,10 +902,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
895
902
  const handleFileDelete = async (fileId: string, filename: string) => {
896
903
  // Use platform-aware confirmation dialog
897
904
  const confirmed = await confirmAction(
898
- `Are you sure you want to delete "${filename}"? This action cannot be undone.`,
899
- 'Delete File',
900
- 'Delete',
901
- 'Cancel'
905
+ t('fileManagement.confirms.deleteFile', { filename }),
906
+ t('fileManagement.deleteFile'),
907
+ t('fileManagement.confirm'),
908
+ t('common.cancel')
902
909
  );
903
910
 
904
911
  if (!confirmed) {
@@ -909,24 +916,24 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
909
916
  storeSetDeleting(fileId);
910
917
  await oxyServices.deleteFile(fileId);
911
918
 
912
- toast.success('File deleted successfully');
919
+ toast.success(t('fileManagement.toasts.deleteSuccess'));
913
920
 
914
921
  // Reload files after successful deletion
915
922
  // Optimistic remove
916
923
  useFileStore.getState().removeFile(fileId);
917
924
  // Silent background reconcile
918
925
  setTimeout(() => loadFiles('silent'), 800);
919
- } catch (error: any) {
926
+ } catch (error: unknown) {
920
927
 
921
928
  // Provide specific error messages
922
- if (error.message?.includes('File not found') || error.message?.includes('404')) {
923
- toast.error('File not found. It may have already been deleted.');
929
+ if (getErrorMessage(error)?.includes('File not found') || getErrorMessage(error)?.includes('404')) {
930
+ toast.error(t('fileManagement.toasts.fileNotFound'));
924
931
  // Still reload files to refresh the list
925
932
  setTimeout(() => loadFiles('silent'), 800);
926
- } else if (error.message?.includes('permission') || error.message?.includes('403')) {
927
- toast.error('You do not have permission to delete this file.');
933
+ } else if (getErrorMessage(error)?.includes('permission') || getErrorMessage(error)?.includes('403')) {
934
+ toast.error(t('fileManagement.toasts.noPermission'));
928
935
  } else {
929
- toast.error(error.message || 'Failed to delete file');
936
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.deleteFailed'));
930
937
  }
931
938
  } finally {
932
939
  storeSetDeleting(null);
@@ -941,10 +948,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
941
948
  const selectedFiles = Array.from(selectedIds).map(id => fileMap[id]).filter(Boolean);
942
949
 
943
950
  const confirmed = await confirmAction(
944
- `Are you sure you want to delete ${selectedFiles.length} file(s)? This action cannot be undone.`,
945
- 'Delete Files',
946
- 'Delete',
947
- 'Cancel'
951
+ t('fileManagement.confirms.deleteFiles', { count: selectedFiles.length }),
952
+ t('fileManagement.deleteFiles'),
953
+ t('fileManagement.confirm'),
954
+ t('common.cancel')
948
955
  );
949
956
 
950
957
  if (!confirmed) return;
@@ -955,7 +962,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
955
962
  await oxyServices.deleteFile(fileId);
956
963
  useFileStore.getState().removeFile(fileId);
957
964
  return { success: true, fileId };
958
- } catch (error: any) {
965
+ } catch (error: unknown) {
959
966
  return { success: false, fileId, error };
960
967
  }
961
968
  });
@@ -965,16 +972,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
965
972
  const failed = results.length - successful;
966
973
 
967
974
  if (successful > 0) {
968
- toast.success(`${successful} file(s) deleted successfully`);
975
+ toast.success(t('fileManagement.toasts.bulkDeleteSuccess', { count: successful }));
969
976
  }
970
977
  if (failed > 0) {
971
- toast.error(`${failed} file(s) failed to delete`);
978
+ toast.error(t('fileManagement.toasts.bulkDeleteFailed', { count: failed }));
972
979
  }
973
980
 
974
981
  setSelectedIds(new Set());
975
982
  setTimeout(() => loadFiles('silent'), 800);
976
- } catch (error: any) {
977
- toast.error(error.message || 'Failed to delete files');
983
+ } catch (error: unknown) {
984
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.bulkDeleteError'));
978
985
  }
979
986
  }, [selectedIds, files, oxyServices, loadFiles]);
980
987
 
@@ -986,7 +993,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
986
993
  try {
987
994
  await oxyServices.assetUpdateVisibility(fileId, visibility);
988
995
  return { success: true, fileId };
989
- } catch (error: any) {
996
+ } catch (error: unknown) {
990
997
  return { success: false, fileId, error };
991
998
  }
992
999
  });
@@ -996,21 +1003,21 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
996
1003
  const failed = results.length - successful;
997
1004
 
998
1005
  if (successful > 0) {
999
- toast.success(`${successful} file(s) visibility updated to ${visibility}`);
1006
+ toast.success(t('fileManagement.toasts.visibilitySuccess', { count: successful, visibility }));
1000
1007
  // Update file metadata in store
1001
1008
  Array.from(selectedIds).forEach(fileId => {
1002
1009
  useFileStore.getState().updateFile(fileId, {
1003
- metadata: { ...files.find(f => f.id === fileId)?.metadata, visibility }
1004
- } as any);
1010
+ metadata: { ...files.find(f => f.id === fileId)?.metadata, visibility } as Partial<FileMetadata>['metadata']
1011
+ });
1005
1012
  });
1006
1013
  }
1007
1014
  if (failed > 0) {
1008
- toast.error(`${failed} file(s) failed to update visibility`);
1015
+ toast.error(t('fileManagement.toasts.visibilityFailed', { count: failed }));
1009
1016
  }
1010
1017
 
1011
1018
  setTimeout(() => loadFiles('silent'), 800);
1012
- } catch (error: any) {
1013
- toast.error(error.message || 'Failed to update visibility');
1019
+ } catch (error: unknown) {
1020
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.visibilityError'));
1014
1021
  }
1015
1022
  }, [selectedIds, oxyServices, files, loadFiles]);
1016
1023
 
@@ -1032,7 +1039,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1032
1039
  document.body.appendChild(link);
1033
1040
  link.click();
1034
1041
  document.body.removeChild(link);
1035
- toast.success('File download started');
1042
+ toast.success(t('fileManagement.toasts.downloadStarted'));
1036
1043
  } catch (linkError) {
1037
1044
  // Fallback to authenticated download
1038
1045
  const blob = await oxyServices.getFileContentAsBlob(fileId);
@@ -1047,16 +1054,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1047
1054
 
1048
1055
  // Clean up the blob URL
1049
1056
  URL.revokeObjectURL(url);
1050
- toast.success('File downloaded successfully');
1057
+ toast.success(t('fileManagement.toasts.downloadSuccess'));
1051
1058
  }
1052
1059
  } else {
1053
1060
  // For mobile, open the URL (user can save from browser)
1054
1061
  // Note: This is a simplified approach - for full mobile support,
1055
1062
  // consider using expo-file-system or react-native-fs
1056
- toast.info('Please use your browser to download the file');
1063
+ toast.info(t('fileManagement.toasts.downloadMobile'));
1057
1064
  }
1058
- } catch (error: any) {
1059
- toast.error(error.message || 'Failed to download file');
1065
+ } catch (error: unknown) {
1066
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.downloadFailed'));
1060
1067
  }
1061
1068
  };
1062
1069
 
@@ -1094,11 +1101,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1094
1101
  const content = await oxyServices.getFileContentAsText(file.id);
1095
1102
  setFileContent(content);
1096
1103
  }
1097
- } catch (error: any) {
1098
- if (error.message?.includes('404') || error.message?.includes('not found')) {
1099
- toast.error('File not found. It may have been deleted.');
1104
+ } catch (error: unknown) {
1105
+ if (getErrorMessage(error)?.includes('404') || getErrorMessage(error)?.includes('not found')) {
1106
+ toast.error(t('fileManagement.toasts.fileNotFoundContent'));
1100
1107
  } else {
1101
- toast.error('Failed to load file content');
1108
+ toast.error(t('fileManagement.toasts.loadContentFailed'));
1102
1109
  }
1103
1110
  setFileContent(null);
1104
1111
  }
@@ -1106,8 +1113,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1106
1113
  // For non-viewable files, don't load content
1107
1114
  setFileContent(null);
1108
1115
  }
1109
- } catch (error: any) {
1110
- toast.error(error.message || 'Failed to open file');
1116
+ } catch (error: unknown) {
1117
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.openFailed'));
1111
1118
  } finally {
1112
1119
  setLoadingFileContent(false);
1113
1120
  }
@@ -1275,7 +1282,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1275
1282
  contentFit="cover"
1276
1283
  transition={120}
1277
1284
  cachePolicy="memory-disk"
1278
- onError={(_: any) => {
1285
+ onError={(_: unknown) => {
1279
1286
  // If thumbnail not available, we still show icon overlay
1280
1287
  }}
1281
1288
  accessibilityLabel={`${file.filename} video thumbnail`}
@@ -1290,7 +1297,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1290
1297
  style={[fileManagementStyles.fallbackIcon, { display: isImage ? 'none' : 'flex' }]}
1291
1298
  >
1292
1299
  <Ionicons
1293
- name={getFileIcon(file.contentType) as any}
1300
+ name={getFileIcon(file.contentType) as React.ComponentProps<typeof Ionicons>['name']}
1294
1301
  size={32}
1295
1302
  color={themeStyles.primaryColor}
1296
1303
  />
@@ -1305,7 +1312,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1305
1312
  ) : (
1306
1313
  <View style={fileManagementStyles.fileIconContainer}>
1307
1314
  <Ionicons
1308
- name={getFileIcon(file.contentType) as any}
1315
+ name={getFileIcon(file.contentType) as React.ComponentProps<typeof Ionicons>['name']}
1309
1316
  size={32}
1310
1317
  color={themeStyles.primaryColor}
1311
1318
  />
@@ -1371,7 +1378,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1371
1378
  };
1372
1379
 
1373
1380
  // GroupedSection-based file items (for 'all' view) replacing legacy flat list look
1374
- const groupedFileItems = useMemo(() => {
1381
+ // biome-ignore lint/suspicious/noExplicitAny: GroupedSection items have dynamic props
1382
+ const groupedFileItems: any[] = useMemo(() => {
1375
1383
  // filteredFiles is already sorted, so just use it directly
1376
1384
  const sortedFiles = filteredFiles;
1377
1385
 
@@ -1415,7 +1423,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1415
1423
  contentFit="cover"
1416
1424
  transition={120}
1417
1425
  cachePolicy="memory-disk"
1418
- onError={(_: any) => {
1426
+ onError={(_: unknown) => {
1419
1427
  // If thumbnail not available, we still show icon overlay
1420
1428
  }}
1421
1429
  accessibilityLabel={`${file.filename} video thumbnail`}
@@ -1498,6 +1506,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1498
1506
  {file.metadata.description}
1499
1507
  </Text>
1500
1508
  ) : undefined,
1509
+ // biome-ignore lint/suspicious/noExplicitAny: GroupedSectionItem has dynamic properties
1501
1510
  } as any;
1502
1511
  });
1503
1512
  }, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrlCallback, selectMode, selectedIds]);
@@ -1639,11 +1648,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1639
1648
  return (
1640
1649
  <View style={fileManagementStyles.emptyState}>
1641
1650
  <Ionicons name="images-outline" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
1642
- <Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>No Photos Yet</Text>
1651
+ <Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>{t('fileManagement.emptyPhotos.title')}</Text>
1643
1652
  <Text style={[fileManagementStyles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}> {
1644
1653
  user?.id === targetUserId
1645
- ? "Upload photos to get started. You can select multiple photos at once."
1646
- : "This user hasn't uploaded any photos yet"
1654
+ ? t('fileManagement.emptyPhotos.ownDescription')
1655
+ : t('fileManagement.emptyPhotos.otherDescription')
1647
1656
  } </Text>
1648
1657
  {user?.id === targetUserId && (
1649
1658
  <TouchableOpacity
@@ -1658,7 +1667,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1658
1667
  ) : (
1659
1668
  <>
1660
1669
  <Ionicons name="cloud-upload" size={20} color="#FFFFFF" />
1661
- <Text style={fileManagementStyles.emptyStateButtonText}>Upload Photos</Text>
1670
+ <Text style={fileManagementStyles.emptyStateButtonText}>{t('fileManagement.uploadPhotos')}</Text>
1662
1671
  </>
1663
1672
  )}
1664
1673
  </TouchableOpacity>
@@ -1692,7 +1701,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1692
1701
  {loadingDimensions && (
1693
1702
  <View style={fileManagementStyles.dimensionsLoadingIndicator}>
1694
1703
  <ActivityIndicator size="small" color={themeStyles.primaryColor} />
1695
- <Text style={[fileManagementStyles.dimensionsLoadingText, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>Loading photo layout...</Text>
1704
+ <Text style={[fileManagementStyles.dimensionsLoadingText, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>{t('fileManagement.loadingPhotoLayout')}</Text>
1696
1705
  </View>
1697
1706
  )}
1698
1707
 
@@ -1733,11 +1742,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1733
1742
  const renderEmptyState = () => (
1734
1743
  <View style={fileManagementStyles.emptyState}>
1735
1744
  <Ionicons name="folder-open-outline" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
1736
- <Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>No Files Yet</Text>
1745
+ <Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>{t('fileManagement.emptyFiles.title')}</Text>
1737
1746
  <Text style={[fileManagementStyles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1738
1747
  {user?.id === targetUserId
1739
- ? "Upload files to get started. You can select multiple files at once."
1740
- : "This user hasn't uploaded any files yet"
1748
+ ? t('fileManagement.emptyFiles.ownDescription')
1749
+ : t('fileManagement.emptyFiles.otherDescription')
1741
1750
  }
1742
1751
  </Text>
1743
1752
  {user?.id === targetUserId && (
@@ -1753,7 +1762,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1753
1762
  ) : (
1754
1763
  <>
1755
1764
  <Ionicons name="cloud-upload" size={20} color="#FFFFFF" />
1756
- <Text style={fileManagementStyles.emptyStateButtonText}>Upload Files</Text>
1765
+ <Text style={fileManagementStyles.emptyStateButtonText}>{t('fileManagement.uploadFiles')}</Text>
1757
1766
  </>
1758
1767
  )}
1759
1768
  </TouchableOpacity>
@@ -1785,7 +1794,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1785
1794
  outputRange: [-skeletonContainerWidth * 2, skeletonContainerWidth * 2],
1786
1795
  });
1787
1796
 
1788
- const SkeletonBox = ({ width, height, borderRadius = 8, style, delay = 0 }: { width: number | string; height: number; borderRadius?: number; style?: any; delay?: number }) => {
1797
+ const SkeletonBox = ({ width, height, borderRadius = 8, style, delay = 0 }: { width: number | string; height: number; borderRadius?: number; style?: Record<string, unknown>; delay?: number }) => {
1789
1798
  const delayedTranslateX = shimmerAnim.interpolate({
1790
1799
  inputRange: [0, 1],
1791
1800
  outputRange: [-skeletonContainerWidth * 2 + delay, skeletonContainerWidth * 2 + delay],
@@ -1976,8 +1985,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1976
1985
  return (
1977
1986
  <View style={fileManagementStyles.container}>
1978
1987
  <Header
1979
- title="Review Files"
1980
- subtitle={`${pendingFiles.length} file${pendingFiles.length !== 1 ? 's' : ''} ready to upload`}
1988
+ title={t('fileManagement.reviewFiles')}
1989
+ subtitle={t('fileManagement.readyToUpload', { count: pendingFiles.length })}
1981
1990
  onBack={handleCancelUpload}
1982
1991
  showBackButton
1983
1992
  variant="minimal"
@@ -2000,46 +2009,46 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
2000
2009
  return (
2001
2010
  <View style={fileManagementStyles.container}>
2002
2011
  <Header
2003
- title={selectMode ? (multiSelect ? `${selectedIds.size}${maxSelection ? `/${maxSelection}` : ''} Selected` : 'Select a File') : (viewMode === 'photos' ? 'Photos' : 'File Management')}
2004
- subtitle={selectMode ? (multiSelect ? `${filteredFiles.length} available` : 'Tap to select') : `${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`}
2012
+ title={selectMode ? (multiSelect ? (maxSelection ? t('fileManagement.selectedWithMax', { count: selectedIds.size, max: maxSelection }) : t('fileManagement.selected', { count: selectedIds.size })) : t('fileManagement.selectFile')) : (viewMode === 'photos' ? t('fileManagement.photos') : t('fileManagement.title'))}
2013
+ subtitle={selectMode ? (multiSelect ? t('fileManagement.available', { count: filteredFiles.length }) : t('fileManagement.tapToSelect')) : (filteredFiles.length === 1 ? t('fileManagement.itemCount', { count: filteredFiles.length }) : t('fileManagement.itemCount_plural', { count: filteredFiles.length }))}
2005
2014
  rightActions={selectMode && multiSelect ? [
2006
2015
  {
2007
2016
  key: 'clear',
2008
- text: 'Clear',
2017
+ text: t('fileManagement.clear'),
2009
2018
  onPress: () => setSelectedIds(new Set()),
2010
2019
  disabled: selectedIds.size === 0,
2011
2020
  },
2012
2021
  {
2013
2022
  key: 'confirm',
2014
- text: 'Confirm',
2023
+ text: t('fileManagement.confirm'),
2015
2024
  onPress: confirmMultiSelection,
2016
2025
  disabled: selectedIds.size === 0,
2017
2026
  }
2018
2027
  ] : !selectMode && selectedIds.size > 0 ? [
2019
2028
  {
2020
2029
  key: 'clear',
2021
- text: 'Clear',
2030
+ text: t('fileManagement.clear'),
2022
2031
  onPress: () => setSelectedIds(new Set()),
2023
2032
  },
2024
2033
  {
2025
2034
  key: 'delete',
2026
- text: `Delete (${selectedIds.size})`,
2035
+ text: t('fileManagement.delete', { count: selectedIds.size }),
2027
2036
  onPress: handleBulkDelete,
2028
2037
  icon: 'delete',
2029
2038
  },
2030
2039
  {
2031
2040
  key: 'visibility',
2032
- text: 'Visibility',
2041
+ text: t('fileManagement.visibility'),
2033
2042
  onPress: () => {
2034
2043
  // Show visibility options menu
2035
2044
  Alert.alert(
2036
- 'Change Visibility',
2037
- `Change visibility for ${selectedIds.size} file(s)?`,
2045
+ t('fileManagement.changeVisibility'),
2046
+ t('fileManagement.changeVisibilityConfirm', { count: selectedIds.size }),
2038
2047
  [
2039
- { text: 'Cancel', style: 'cancel' },
2040
- { text: 'Private', onPress: () => handleBulkVisibilityChange('private') },
2041
- { text: 'Public', onPress: () => handleBulkVisibilityChange('public') },
2042
- { text: 'Unlisted', onPress: () => handleBulkVisibilityChange('unlisted') },
2048
+ { text: t('common.cancel'), style: 'cancel' },
2049
+ { text: t('fileManagement.private'), onPress: () => handleBulkVisibilityChange('private') },
2050
+ { text: t('fileManagement.public'), onPress: () => handleBulkVisibilityChange('public') },
2051
+ { text: t('fileManagement.unlisted'), onPress: () => handleBulkVisibilityChange('unlisted') },
2043
2052
  ]
2044
2053
  );
2045
2054
  },
@@ -2174,7 +2183,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
2174
2183
  <Ionicons name="search" size={22} color={themeStyles.colors.icon} />
2175
2184
  <TextInput
2176
2185
  style={[fileManagementStyles.searchInput, { color: themeStyles.textColor }]}
2177
- placeholder={viewMode === 'photos' ? 'Search photos...' : 'Search files...'}
2186
+ placeholder={viewMode === 'photos' ? t('fileManagement.searchPhotos') : t('fileManagement.searchFiles')}
2178
2187
  placeholderTextColor={themeStyles.colors.secondaryText}
2179
2188
  value={searchQuery}
2180
2189
  onChangeText={setSearchQuery}
@@ -2201,7 +2210,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
2201
2210
  <View style={fileManagementStyles.statItem}>
2202
2211
  <Text style={[fileManagementStyles.statValue, { color: themeStyles.textColor }]}>{filteredFiles.length}</Text>
2203
2212
  <Text style={[fileManagementStyles.statLabel, { color: themeStyles.colors.secondaryText }]}>
2204
- {searchQuery.length > 0 ? 'Found' : (filteredFiles.length === 1 ? (viewMode === 'photos' ? 'Photo' : 'File') : (viewMode === 'photos' ? 'Photos' : 'Files'))}
2213
+ {searchQuery.length > 0 ? t('fileManagement.found') : (filteredFiles.length === 1 ? (viewMode === 'photos' ? t('fileManagement.photo') : t('fileManagement.file')) : (viewMode === 'photos' ? t('fileManagement.photos_stat') : t('fileManagement.files')))}
2205
2214
  </Text>
2206
2215
  </View>
2207
2216
  <View style={fileManagementStyles.statItem}>
@@ -2209,14 +2218,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
2209
2218
  {formatFileSize(filteredFiles.reduce((total, file) => total + file.length, 0))}
2210
2219
  </Text>
2211
2220
  <Text style={[fileManagementStyles.statLabel, { color: themeStyles.colors.secondaryText }]}>
2212
- {searchQuery.length > 0 ? 'Size' : 'Total Size'}
2221
+ {searchQuery.length > 0 ? t('fileManagement.size') : t('fileManagement.totalSize')}
2213
2222
  </Text>
2214
2223
  </View>
2215
2224
  {searchQuery.length > 0 && (
2216
2225
  <View style={fileManagementStyles.statItem}>
2217
2226
  <Text style={[fileManagementStyles.statValue, { color: themeStyles.textColor }]}>{files.length}</Text>
2218
2227
  <Text style={[fileManagementStyles.statLabel, { color: themeStyles.colors.secondaryText }]}>
2219
- Total
2228
+ {t('fileManagement.total')}
2220
2229
  </Text>
2221
2230
  </View>
2222
2231
  )}
@@ -2250,16 +2259,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
2250
2259
  {filteredFiles.length === 0 && searchQuery.length > 0 ? (
2251
2260
  <View style={fileManagementStyles.emptyState}>
2252
2261
  <Ionicons name="search" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
2253
- <Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>No Results Found</Text>
2262
+ <Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>{t('fileManagement.noResults.title')}</Text>
2254
2263
  <Text style={[fileManagementStyles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
2255
- No files match your search for "{searchQuery}"
2264
+ {t('fileManagement.noResults.description', { query: searchQuery })}
2256
2265
  </Text>
2257
2266
  <TouchableOpacity
2258
2267
  style={[fileManagementStyles.emptyStateButton, { backgroundColor: themeStyles.primaryColor }]}
2259
2268
  onPress={() => setSearchQuery('')}
2260
2269
  >
2261
2270
  <Ionicons name="refresh" size={20} color="#FFFFFF" />
2262
- <Text style={fileManagementStyles.emptyStateButtonText}>Clear Search</Text>
2271
+ <Text style={fileManagementStyles.emptyStateButtonText}>{t('fileManagement.clearSearch')}</Text>
2263
2272
  </TouchableOpacity>
2264
2273
  </View>
2265
2274
  ) : filteredFiles.length === 0 ? renderEmptyState() : (
@@ -2268,7 +2277,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
2268
2277
  {paging.loadingMore && (
2269
2278
  <View style={fileManagementStyles.loadingMoreBar}>
2270
2279
  <ActivityIndicator size="small" color={themeStyles.primaryColor} />
2271
- <Text style={[fileManagementStyles.loadingMoreText, { color: themeStyles.textColor }]}>Loading more...</Text>
2280
+ <Text style={[fileManagementStyles.loadingMoreText, { color: themeStyles.textColor }]}>{t('fileManagement.loadingMore')}</Text>
2272
2281
  </View>
2273
2282
  )}
2274
2283
  </>
@@ -2295,7 +2304,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
2295
2304
  <Ionicons name="cloud-upload" size={18} color={themeStyles.primaryColor} />
2296
2305
  <View style={fileManagementStyles.uploadBannerContent}>
2297
2306
  <Text style={[fileManagementStyles.uploadBannerText, { color: themeStyles.textColor }]}>
2298
- Uploading{uploadProgress ? ` ${uploadProgress.current}/${uploadProgress.total}` : '...'}
2307
+ {t('fileManagement.uploading')}{uploadProgress ? ` ${uploadProgress.current}/${uploadProgress.total}` : '...'}
2299
2308
  </Text>
2300
2309
  {uploadProgress && uploadProgress.total > 0 && (
2301
2310
  <View style={[fileManagementStyles.uploadProgressBarContainer, { backgroundColor: themeStyles.isDarkTheme ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)' }]}>