@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
@@ -17,6 +17,7 @@ var _useThemeStyles = require("../hooks/useThemeStyles.js");
17
17
  var _useColorScheme = require("../hooks/useColorScheme.js");
18
18
  var _themeUtils = require("../utils/themeUtils.js");
19
19
  var _OxyContext = require("../context/OxyContext.js");
20
+ var _useI18n = require("../hooks/useI18n.js");
20
21
  var _useAccountMutations = require("../hooks/mutations/useAccountMutations.js");
21
22
  var _fileManagement = require("../utils/fileManagement.js");
22
23
  var _FileViewer = require("../components/fileManagement/FileViewer.js");
@@ -41,6 +42,9 @@ const loadDocumentPicker = async () => {
41
42
 
42
43
  // @ts-ignore - MaterialCommunityIcons is available at runtime
43
44
 
45
+ /** Extract error message from unknown error type */
46
+ const getErrorMessage = error => error instanceof Error ? getErrorMessage(error) : typeof error === 'string' ? error : undefined;
47
+
44
48
  // Animated button component for smooth transitions
45
49
  const AnimatedButton = ({
46
50
  isSelected,
@@ -109,6 +113,9 @@ const FileManagementScreen = ({
109
113
  user,
110
114
  oxyServices
111
115
  } = (0, _OxyContext.useOxy)();
116
+ const {
117
+ t
118
+ } = (0, _useI18n.useI18n)();
112
119
  const uploadFileMutation = (0, _useAccountMutations.useUploadFile)();
113
120
  const files = (0, _fileStore.useFiles)();
114
121
  // Ensure containerWidth is a number (TypeScript guard)
@@ -197,7 +204,7 @@ const FileManagementScreen = ({
197
204
  if (disabledMimeTypes.length) {
198
205
  const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : `${mt}/`));
199
206
  if (blocked) {
200
- _sonner.toast.error('This file type cannot be selected');
207
+ _sonner.toast.error(t('fileManagement.toasts.fileTypeBlocked'));
201
208
  return;
202
209
  }
203
210
  }
@@ -237,7 +244,9 @@ const FileManagementScreen = ({
237
244
  const already = next.has(file.id);
238
245
  if (!already) {
239
246
  if (maxSelection && next.size >= maxSelection) {
240
- _sonner.toast.error(`You can select up to ${maxSelection}`);
247
+ _sonner.toast.error(t('fileManagement.toasts.maxSelection', {
248
+ max: maxSelection
249
+ }));
241
250
  return prev;
242
251
  }
243
252
  next.add(file.id);
@@ -371,7 +380,7 @@ const FileManagementScreen = ({
371
380
  }));
372
381
  }
373
382
  } catch (error) {
374
- _sonner.toast.error(error.message || 'Failed to load files');
383
+ _sonner.toast.error(getErrorMessage(error) || t('fileManagement.toasts.loadFailed'));
375
384
  } finally {
376
385
  setLoading(false);
377
386
  setRefreshing(false);
@@ -473,7 +482,9 @@ const FileManagementScreen = ({
473
482
  const oversizedFiles = selectedFiles.filter(file => file.size > maxSize);
474
483
  if (oversizedFiles.length > 0) {
475
484
  const fileList = oversizedFiles.map(f => f.name).join(', ');
476
- _sonner.toast.error(`The following files are too large (max 50MB): ${fileList}`);
485
+ _sonner.toast.error(t('fileManagement.toasts.filesTooLarge', {
486
+ files: fileList
487
+ }));
477
488
  return [];
478
489
  }
479
490
  let successCount = 0;
@@ -562,7 +573,7 @@ const FileManagementScreen = ({
562
573
  }
563
574
  } catch (error) {
564
575
  failureCount++;
565
- const errorMessage = error.message || error.toString() || 'Upload failed';
576
+ const errorMessage = getErrorMessage(error) || String(error) || 'Upload failed';
566
577
  const fullError = `${fileName}: ${errorMessage}`;
567
578
  errors.push(fullError);
568
579
  if (__DEV__) {
@@ -571,7 +582,7 @@ const FileManagementScreen = ({
571
582
  fileSize: raw.size,
572
583
  fileType: raw.type,
573
584
  error: errorMessage,
574
- stack: error.stack
585
+ stack: error instanceof Error ? error.stack : undefined
575
586
  });
576
587
  }
577
588
 
@@ -582,24 +593,30 @@ const FileManagementScreen = ({
582
593
 
583
594
  // Show success/error messages
584
595
  if (successCount > 0) {
585
- _sonner.toast.success(`${successCount} file(s) uploaded successfully`);
596
+ _sonner.toast.success(t('fileManagement.toasts.uploadSuccess', {
597
+ count: successCount
598
+ }));
586
599
  }
587
600
  if (failureCount > 0) {
588
601
  // Show detailed error message with first few errors
589
602
  const errorDetails = errors.length > 0 ? `\n${errors.slice(0, 3).join('\n')}${errors.length > 3 ? `\n...and ${errors.length - 3} more` : ''}` : '';
590
- _sonner.toast.error(`${failureCount} file(s) failed to upload${errorDetails}`);
603
+ _sonner.toast.error(`${t('fileManagement.toasts.uploadFailed', {
604
+ count: failureCount
605
+ })}${errorDetails}`);
591
606
  }
592
607
  // Silent background refresh to ensure metadata/variants updated
593
608
  setTimeout(() => {
594
609
  loadFiles('silent');
595
610
  }, 1200);
596
611
  } catch (error) {
597
- _sonner.toast.error(error.message || 'Failed to upload files');
612
+ _sonner.toast.error(getErrorMessage(error) || t('fileManagement.toasts.uploadError'));
598
613
  } finally {
599
614
  storeSetUploadProgress(null);
600
615
  }
601
616
  return uploadedFiles;
602
617
  };
618
+
619
+ // biome-ignore lint/suspicious/noExplicitAny: Files from document picker may have extra properties like uri
603
620
  const handleFileSelection = (0, _react.useCallback)(async selectedFiles => {
604
621
  const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
605
622
  const processedFiles = [];
@@ -609,34 +626,41 @@ const FileManagementScreen = ({
609
626
  if (__DEV__) {
610
627
  console.error('Invalid file: file is null or undefined');
611
628
  }
612
- _sonner.toast.error('Invalid file: file is missing');
629
+ _sonner.toast.error(t('fileManagement.toasts.invalidFileMissing'));
613
630
  continue;
614
631
  }
615
632
  if (!file.name || typeof file.name !== 'string') {
616
633
  if (__DEV__) {
617
634
  console.error('Invalid file: missing or invalid name property', file);
618
635
  }
619
- _sonner.toast.error('Invalid file: missing file name');
636
+ _sonner.toast.error(t('fileManagement.toasts.invalidFileName'));
620
637
  continue;
621
638
  }
622
639
  if (file.size === undefined || file.size === null || Number.isNaN(file.size)) {
623
640
  if (__DEV__) {
624
641
  console.error('Invalid file: missing or invalid size property', file);
625
642
  }
626
- _sonner.toast.error(`Invalid file "${file.name || 'unknown'}": missing file size`);
643
+ _sonner.toast.error(t('fileManagement.toasts.invalidFileSize', {
644
+ name: file.name || 'unknown'
645
+ }));
627
646
  continue;
628
647
  }
629
648
  if (file.size <= 0) {
630
649
  if (__DEV__) {
631
650
  console.error('Invalid file: file size is zero or negative', file);
632
651
  }
633
- _sonner.toast.error(`File "${file.name}" is empty`);
652
+ _sonner.toast.error(t('fileManagement.toasts.fileEmpty', {
653
+ name: file.name
654
+ }));
634
655
  continue;
635
656
  }
636
657
 
637
658
  // Validate file size
638
659
  if (file.size > MAX_FILE_SIZE) {
639
- _sonner.toast.error(`"${file.name}" is too large. Maximum file size is ${(0, _fileManagement.formatFileSize)(MAX_FILE_SIZE)}`);
660
+ _sonner.toast.error(t('fileManagement.toasts.fileTooLarge', {
661
+ name: file.name,
662
+ maxSize: (0, _fileManagement.formatFileSize)(MAX_FILE_SIZE)
663
+ }));
640
664
  continue;
641
665
  }
642
666
 
@@ -673,7 +697,7 @@ const FileManagementScreen = ({
673
697
  });
674
698
  }
675
699
  if (processedFiles.length === 0) {
676
- _sonner.toast.error('No valid files to upload');
700
+ _sonner.toast.error(t('fileManagement.toasts.noValidFiles'));
677
701
  return;
678
702
  }
679
703
 
@@ -728,7 +752,7 @@ const FileManagementScreen = ({
728
752
  }
729
753
  endUpload();
730
754
  } catch (error) {
731
- _sonner.toast.error(error.message || 'Failed to upload files');
755
+ _sonner.toast.error(getErrorMessage(error) || t('fileManagement.toasts.uploadError'));
732
756
  endUpload();
733
757
  }
734
758
  };
@@ -761,7 +785,7 @@ const FileManagementScreen = ({
761
785
  const handleFileUpload = async () => {
762
786
  // Prevent concurrent document picker calls
763
787
  if (isPickingDocument) {
764
- _sonner.toast.error('Please wait for the current file selection to complete');
788
+ _sonner.toast.error(t('fileManagement.toasts.waitForSelection'));
765
789
  return;
766
790
  }
767
791
  try {
@@ -783,7 +807,7 @@ const FileManagementScreen = ({
783
807
  }
784
808
  if (!result.assets || result.assets.length === 0) {
785
809
  setIsPickingDocument(false);
786
- _sonner.toast.error('No files were selected');
810
+ _sonner.toast.error(t('fileManagement.toasts.noFilesSelected'));
787
811
  return;
788
812
  }
789
813
 
@@ -810,7 +834,7 @@ const FileManagementScreen = ({
810
834
  }
811
835
  return null;
812
836
  }).catch(error => {
813
- errors.push(`File "${doc.name || 'file'}": ${error.message || 'Failed to process'}`);
837
+ errors.push(`File "${doc.name || 'file'}": ${getErrorMessage(error) || 'Failed to process'}`);
814
838
  return null;
815
839
  }));
816
840
  const convertedFiles = await Promise.all(conversionPromises);
@@ -825,7 +849,9 @@ const FileManagementScreen = ({
825
849
  // Show errors if any
826
850
  if (errors.length > 0) {
827
851
  const errorMessage = errors.slice(0, 3).join('\n') + (errors.length > 3 ? `\n...and ${errors.length - 3} more` : '');
828
- _sonner.toast.error(`Failed to load some files:\n${errorMessage}`);
852
+ _sonner.toast.error(t('fileManagement.toasts.loadSomeFailed', {
853
+ errors: errorMessage
854
+ }));
829
855
  }
830
856
 
831
857
  // Process successfully converted files
@@ -833,20 +859,20 @@ const FileManagementScreen = ({
833
859
  await handleFileSelection(files);
834
860
  } else {
835
861
  // Files were selected but none could be converted
836
- _sonner.toast.error('No files could be processed. Please try selecting files again.');
862
+ _sonner.toast.error(t('fileManagement.toasts.noFilesProcessed'));
837
863
  }
838
864
  } catch (error) {
839
865
  if (__DEV__) {
840
866
  console.error('File upload error:', error);
841
867
  }
842
- if (error.message?.includes('expo-document-picker') || error.message?.includes('Different document picking in progress')) {
843
- if (error.message?.includes('Different document picking in progress')) {
844
- _sonner.toast.error('Please wait for the current file selection to complete');
868
+ if (getErrorMessage(error)?.includes('expo-document-picker') || getErrorMessage(error)?.includes('Different document picking in progress')) {
869
+ if (getErrorMessage(error)?.includes('Different document picking in progress')) {
870
+ _sonner.toast.error(t('fileManagement.toasts.waitForSelection'));
845
871
  } else {
846
- _sonner.toast.error('File picker not available. Please install expo-document-picker');
872
+ _sonner.toast.error(t('fileManagement.toasts.filePickerNotAvailable'));
847
873
  }
848
874
  } else {
849
- _sonner.toast.error(error.message || 'Failed to select files');
875
+ _sonner.toast.error(getErrorMessage(error) || t('fileManagement.toasts.selectFilesFailed'));
850
876
  }
851
877
  } finally {
852
878
  // Always reset the picking state, even if there was an error
@@ -855,14 +881,16 @@ const FileManagementScreen = ({
855
881
  };
856
882
  const handleFileDelete = async (fileId, filename) => {
857
883
  // Use platform-aware confirmation dialog
858
- const confirmed = await (0, _fileManagement.confirmAction)(`Are you sure you want to delete "${filename}"? This action cannot be undone.`, 'Delete File', 'Delete', 'Cancel');
884
+ const confirmed = await (0, _fileManagement.confirmAction)(t('fileManagement.confirms.deleteFile', {
885
+ filename
886
+ }), t('fileManagement.deleteFile'), t('fileManagement.confirm'), t('common.cancel'));
859
887
  if (!confirmed) {
860
888
  return;
861
889
  }
862
890
  try {
863
891
  storeSetDeleting(fileId);
864
892
  await oxyServices.deleteFile(fileId);
865
- _sonner.toast.success('File deleted successfully');
893
+ _sonner.toast.success(t('fileManagement.toasts.deleteSuccess'));
866
894
 
867
895
  // Reload files after successful deletion
868
896
  // Optimistic remove
@@ -871,14 +899,14 @@ const FileManagementScreen = ({
871
899
  setTimeout(() => loadFiles('silent'), 800);
872
900
  } catch (error) {
873
901
  // Provide specific error messages
874
- if (error.message?.includes('File not found') || error.message?.includes('404')) {
875
- _sonner.toast.error('File not found. It may have already been deleted.');
902
+ if (getErrorMessage(error)?.includes('File not found') || getErrorMessage(error)?.includes('404')) {
903
+ _sonner.toast.error(t('fileManagement.toasts.fileNotFound'));
876
904
  // Still reload files to refresh the list
877
905
  setTimeout(() => loadFiles('silent'), 800);
878
- } else if (error.message?.includes('permission') || error.message?.includes('403')) {
879
- _sonner.toast.error('You do not have permission to delete this file.');
906
+ } else if (getErrorMessage(error)?.includes('permission') || getErrorMessage(error)?.includes('403')) {
907
+ _sonner.toast.error(t('fileManagement.toasts.noPermission'));
880
908
  } else {
881
- _sonner.toast.error(error.message || 'Failed to delete file');
909
+ _sonner.toast.error(getErrorMessage(error) || t('fileManagement.toasts.deleteFailed'));
882
910
  }
883
911
  } finally {
884
912
  storeSetDeleting(null);
@@ -891,7 +919,9 @@ const FileManagementScreen = ({
891
919
  fileMap[f.id] = f;
892
920
  });
893
921
  const selectedFiles = Array.from(selectedIds).map(id => fileMap[id]).filter(Boolean);
894
- const confirmed = await (0, _fileManagement.confirmAction)(`Are you sure you want to delete ${selectedFiles.length} file(s)? This action cannot be undone.`, 'Delete Files', 'Delete', 'Cancel');
922
+ const confirmed = await (0, _fileManagement.confirmAction)(t('fileManagement.confirms.deleteFiles', {
923
+ count: selectedFiles.length
924
+ }), t('fileManagement.deleteFiles'), t('fileManagement.confirm'), t('common.cancel'));
895
925
  if (!confirmed) return;
896
926
  try {
897
927
  const deletePromises = Array.from(selectedIds).map(async fileId => {
@@ -914,15 +944,19 @@ const FileManagementScreen = ({
914
944
  const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length;
915
945
  const failed = results.length - successful;
916
946
  if (successful > 0) {
917
- _sonner.toast.success(`${successful} file(s) deleted successfully`);
947
+ _sonner.toast.success(t('fileManagement.toasts.bulkDeleteSuccess', {
948
+ count: successful
949
+ }));
918
950
  }
919
951
  if (failed > 0) {
920
- _sonner.toast.error(`${failed} file(s) failed to delete`);
952
+ _sonner.toast.error(t('fileManagement.toasts.bulkDeleteFailed', {
953
+ count: failed
954
+ }));
921
955
  }
922
956
  setSelectedIds(new Set());
923
957
  setTimeout(() => loadFiles('silent'), 800);
924
958
  } catch (error) {
925
- _sonner.toast.error(error.message || 'Failed to delete files');
959
+ _sonner.toast.error(getErrorMessage(error) || t('fileManagement.toasts.bulkDeleteError'));
926
960
  }
927
961
  }, [selectedIds, files, oxyServices, loadFiles]);
928
962
  const handleBulkVisibilityChange = (0, _react.useCallback)(async visibility => {
@@ -947,7 +981,10 @@ const FileManagementScreen = ({
947
981
  const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length;
948
982
  const failed = results.length - successful;
949
983
  if (successful > 0) {
950
- _sonner.toast.success(`${successful} file(s) visibility updated to ${visibility}`);
984
+ _sonner.toast.success(t('fileManagement.toasts.visibilitySuccess', {
985
+ count: successful,
986
+ visibility
987
+ }));
951
988
  // Update file metadata in store
952
989
  Array.from(selectedIds).forEach(fileId => {
953
990
  _fileStore.useFileStore.getState().updateFile(fileId, {
@@ -959,11 +996,13 @@ const FileManagementScreen = ({
959
996
  });
960
997
  }
961
998
  if (failed > 0) {
962
- _sonner.toast.error(`${failed} file(s) failed to update visibility`);
999
+ _sonner.toast.error(t('fileManagement.toasts.visibilityFailed', {
1000
+ count: failed
1001
+ }));
963
1002
  }
964
1003
  setTimeout(() => loadFiles('silent'), 800);
965
1004
  } catch (error) {
966
- _sonner.toast.error(error.message || 'Failed to update visibility');
1005
+ _sonner.toast.error(getErrorMessage(error) || t('fileManagement.toasts.visibilityError'));
967
1006
  }
968
1007
  }, [selectedIds, oxyServices, files, loadFiles]);
969
1008
 
@@ -985,7 +1024,7 @@ const FileManagementScreen = ({
985
1024
  document.body.appendChild(link);
986
1025
  link.click();
987
1026
  document.body.removeChild(link);
988
- _sonner.toast.success('File download started');
1027
+ _sonner.toast.success(t('fileManagement.toasts.downloadStarted'));
989
1028
  } catch (linkError) {
990
1029
  // Fallback to authenticated download
991
1030
  const blob = await oxyServices.getFileContentAsBlob(fileId);
@@ -999,16 +1038,16 @@ const FileManagementScreen = ({
999
1038
 
1000
1039
  // Clean up the blob URL
1001
1040
  URL.revokeObjectURL(url);
1002
- _sonner.toast.success('File downloaded successfully');
1041
+ _sonner.toast.success(t('fileManagement.toasts.downloadSuccess'));
1003
1042
  }
1004
1043
  } else {
1005
1044
  // For mobile, open the URL (user can save from browser)
1006
1045
  // Note: This is a simplified approach - for full mobile support,
1007
1046
  // consider using expo-file-system or react-native-fs
1008
- _sonner.toast.info('Please use your browser to download the file');
1047
+ _sonner.toast.info(t('fileManagement.toasts.downloadMobile'));
1009
1048
  }
1010
1049
  } catch (error) {
1011
- _sonner.toast.error(error.message || 'Failed to download file');
1050
+ _sonner.toast.error(getErrorMessage(error) || t('fileManagement.toasts.downloadFailed'));
1012
1051
  }
1013
1052
  };
1014
1053
  const handleFileOpen = async file => {
@@ -1033,10 +1072,10 @@ const FileManagementScreen = ({
1033
1072
  setFileContent(content);
1034
1073
  }
1035
1074
  } catch (error) {
1036
- if (error.message?.includes('404') || error.message?.includes('not found')) {
1037
- _sonner.toast.error('File not found. It may have been deleted.');
1075
+ if (getErrorMessage(error)?.includes('404') || getErrorMessage(error)?.includes('not found')) {
1076
+ _sonner.toast.error(t('fileManagement.toasts.fileNotFoundContent'));
1038
1077
  } else {
1039
- _sonner.toast.error('Failed to load file content');
1078
+ _sonner.toast.error(t('fileManagement.toasts.loadContentFailed'));
1040
1079
  }
1041
1080
  setFileContent(null);
1042
1081
  }
@@ -1045,7 +1084,7 @@ const FileManagementScreen = ({
1045
1084
  setFileContent(null);
1046
1085
  }
1047
1086
  } catch (error) {
1048
- _sonner.toast.error(error.message || 'Failed to open file');
1087
+ _sonner.toast.error(getErrorMessage(error) || t('fileManagement.toasts.openFailed'));
1049
1088
  } finally {
1050
1089
  setLoadingFileContent(false);
1051
1090
  }
@@ -1320,6 +1359,7 @@ const FileManagementScreen = ({
1320
1359
  };
1321
1360
 
1322
1361
  // GroupedSection-based file items (for 'all' view) replacing legacy flat list look
1362
+ // biome-ignore lint/suspicious/noExplicitAny: GroupedSection items have dynamic props
1323
1363
  const groupedFileItems = (0, _react.useMemo)(() => {
1324
1364
  // filteredFiles is already sorted, so just use it directly
1325
1365
  const sortedFiles = filteredFiles;
@@ -1499,6 +1539,7 @@ const FileManagementScreen = ({
1499
1539
  numberOfLines: 2,
1500
1540
  children: file.metadata.description
1501
1541
  }) : undefined
1542
+ // biome-ignore lint/suspicious/noExplicitAny: GroupedSectionItem has dynamic properties
1502
1543
  };
1503
1544
  });
1504
1545
  }, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrlCallback, selectMode, selectedIds]);
@@ -1635,12 +1676,12 @@ const FileManagementScreen = ({
1635
1676
  style: [_styles.fileManagementStyles.emptyStateTitle, {
1636
1677
  color: themeStyles.textColor
1637
1678
  }],
1638
- children: "No Photos Yet"
1679
+ children: t('fileManagement.emptyPhotos.title')
1639
1680
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
1640
1681
  style: [_styles.fileManagementStyles.emptyStateDescription, {
1641
1682
  color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666'
1642
1683
  }],
1643
- children: [" ", user?.id === targetUserId ? "Upload photos to get started. You can select multiple photos at once." : "This user hasn't uploaded any photos yet", " "]
1684
+ children: [" ", user?.id === targetUserId ? t('fileManagement.emptyPhotos.ownDescription') : t('fileManagement.emptyPhotos.otherDescription'), " "]
1644
1685
  }), user?.id === targetUserId && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1645
1686
  style: [_styles.fileManagementStyles.emptyStateButton, {
1646
1687
  backgroundColor: themeStyles.primaryColor
@@ -1660,7 +1701,7 @@ const FileManagementScreen = ({
1660
1701
  color: "#FFFFFF"
1661
1702
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1662
1703
  style: _styles.fileManagementStyles.emptyStateButtonText,
1663
- children: "Upload Photos"
1704
+ children: t('fileManagement.uploadPhotos')
1664
1705
  })]
1665
1706
  })
1666
1707
  })]
@@ -1699,7 +1740,7 @@ const FileManagementScreen = ({
1699
1740
  style: [_styles.fileManagementStyles.dimensionsLoadingText, {
1700
1741
  color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666'
1701
1742
  }],
1702
- children: "Loading photo layout..."
1743
+ children: t('fileManagement.loadingPhotoLayout')
1703
1744
  })]
1704
1745
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_JustifiedPhotoGrid.default, {
1705
1746
  photos: photos,
@@ -1726,12 +1767,12 @@ const FileManagementScreen = ({
1726
1767
  style: [_styles.fileManagementStyles.emptyStateTitle, {
1727
1768
  color: themeStyles.textColor
1728
1769
  }],
1729
- children: "No Files Yet"
1770
+ children: t('fileManagement.emptyFiles.title')
1730
1771
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1731
1772
  style: [_styles.fileManagementStyles.emptyStateDescription, {
1732
1773
  color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666'
1733
1774
  }],
1734
- children: user?.id === targetUserId ? "Upload files to get started. You can select multiple files at once." : "This user hasn't uploaded any files yet"
1775
+ children: user?.id === targetUserId ? t('fileManagement.emptyFiles.ownDescription') : t('fileManagement.emptyFiles.otherDescription')
1735
1776
  }), user?.id === targetUserId && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1736
1777
  style: [_styles.fileManagementStyles.emptyStateButton, {
1737
1778
  backgroundColor: themeStyles.primaryColor
@@ -1751,7 +1792,7 @@ const FileManagementScreen = ({
1751
1792
  color: "#FFFFFF"
1752
1793
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1753
1794
  style: _styles.fileManagementStyles.emptyStateButtonText,
1754
- children: "Upload Files"
1795
+ children: t('fileManagement.uploadFiles')
1755
1796
  })]
1756
1797
  })
1757
1798
  })]
@@ -1997,8 +2038,10 @@ const FileManagementScreen = ({
1997
2038
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1998
2039
  style: _styles.fileManagementStyles.container,
1999
2040
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Header.default, {
2000
- title: "Review Files",
2001
- subtitle: `${pendingFiles.length} file${pendingFiles.length !== 1 ? 's' : ''} ready to upload`,
2041
+ title: t('fileManagement.reviewFiles'),
2042
+ subtitle: t('fileManagement.readyToUpload', {
2043
+ count: pendingFiles.length
2044
+ }),
2002
2045
  onBack: handleCancelUpload,
2003
2046
  showBackButton: true,
2004
2047
  variant: "minimal",
@@ -2018,43 +2061,58 @@ const FileManagementScreen = ({
2018
2061
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2019
2062
  style: _styles.fileManagementStyles.container,
2020
2063
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Header.default, {
2021
- title: selectMode ? multiSelect ? `${selectedIds.size}${maxSelection ? `/${maxSelection}` : ''} Selected` : 'Select a File' : viewMode === 'photos' ? 'Photos' : 'File Management',
2022
- subtitle: selectMode ? multiSelect ? `${filteredFiles.length} available` : 'Tap to select' : `${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`,
2064
+ title: selectMode ? multiSelect ? maxSelection ? t('fileManagement.selectedWithMax', {
2065
+ count: selectedIds.size,
2066
+ max: maxSelection
2067
+ }) : t('fileManagement.selected', {
2068
+ count: selectedIds.size
2069
+ }) : t('fileManagement.selectFile') : viewMode === 'photos' ? t('fileManagement.photos') : t('fileManagement.title'),
2070
+ subtitle: selectMode ? multiSelect ? t('fileManagement.available', {
2071
+ count: filteredFiles.length
2072
+ }) : t('fileManagement.tapToSelect') : filteredFiles.length === 1 ? t('fileManagement.itemCount', {
2073
+ count: filteredFiles.length
2074
+ }) : t('fileManagement.itemCount_plural', {
2075
+ count: filteredFiles.length
2076
+ }),
2023
2077
  rightActions: selectMode && multiSelect ? [{
2024
2078
  key: 'clear',
2025
- text: 'Clear',
2079
+ text: t('fileManagement.clear'),
2026
2080
  onPress: () => setSelectedIds(new Set()),
2027
2081
  disabled: selectedIds.size === 0
2028
2082
  }, {
2029
2083
  key: 'confirm',
2030
- text: 'Confirm',
2084
+ text: t('fileManagement.confirm'),
2031
2085
  onPress: confirmMultiSelection,
2032
2086
  disabled: selectedIds.size === 0
2033
2087
  }] : !selectMode && selectedIds.size > 0 ? [{
2034
2088
  key: 'clear',
2035
- text: 'Clear',
2089
+ text: t('fileManagement.clear'),
2036
2090
  onPress: () => setSelectedIds(new Set())
2037
2091
  }, {
2038
2092
  key: 'delete',
2039
- text: `Delete (${selectedIds.size})`,
2093
+ text: t('fileManagement.delete', {
2094
+ count: selectedIds.size
2095
+ }),
2040
2096
  onPress: handleBulkDelete,
2041
2097
  icon: 'delete'
2042
2098
  }, {
2043
2099
  key: 'visibility',
2044
- text: 'Visibility',
2100
+ text: t('fileManagement.visibility'),
2045
2101
  onPress: () => {
2046
2102
  // Show visibility options menu
2047
- _reactNative.Alert.alert('Change Visibility', `Change visibility for ${selectedIds.size} file(s)?`, [{
2048
- text: 'Cancel',
2103
+ _reactNative.Alert.alert(t('fileManagement.changeVisibility'), t('fileManagement.changeVisibilityConfirm', {
2104
+ count: selectedIds.size
2105
+ }), [{
2106
+ text: t('common.cancel'),
2049
2107
  style: 'cancel'
2050
2108
  }, {
2051
- text: 'Private',
2109
+ text: t('fileManagement.private'),
2052
2110
  onPress: () => handleBulkVisibilityChange('private')
2053
2111
  }, {
2054
- text: 'Public',
2112
+ text: t('fileManagement.public'),
2055
2113
  onPress: () => handleBulkVisibilityChange('public')
2056
2114
  }, {
2057
- text: 'Unlisted',
2115
+ text: t('fileManagement.unlisted'),
2058
2116
  onPress: () => handleBulkVisibilityChange('unlisted')
2059
2117
  }]);
2060
2118
  },
@@ -2172,7 +2230,7 @@ const FileManagementScreen = ({
2172
2230
  style: [_styles.fileManagementStyles.searchInput, {
2173
2231
  color: themeStyles.textColor
2174
2232
  }],
2175
- placeholder: viewMode === 'photos' ? 'Search photos...' : 'Search files...',
2233
+ placeholder: viewMode === 'photos' ? t('fileManagement.searchPhotos') : t('fileManagement.searchFiles'),
2176
2234
  placeholderTextColor: themeStyles.colors.secondaryText,
2177
2235
  value: searchQuery,
2178
2236
  onChangeText: setSearchQuery
@@ -2200,7 +2258,7 @@ const FileManagementScreen = ({
2200
2258
  style: [_styles.fileManagementStyles.statLabel, {
2201
2259
  color: themeStyles.colors.secondaryText
2202
2260
  }],
2203
- children: searchQuery.length > 0 ? 'Found' : filteredFiles.length === 1 ? viewMode === 'photos' ? 'Photo' : 'File' : viewMode === 'photos' ? 'Photos' : 'Files'
2261
+ children: 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')
2204
2262
  })]
2205
2263
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2206
2264
  style: _styles.fileManagementStyles.statItem,
@@ -2213,7 +2271,7 @@ const FileManagementScreen = ({
2213
2271
  style: [_styles.fileManagementStyles.statLabel, {
2214
2272
  color: themeStyles.colors.secondaryText
2215
2273
  }],
2216
- children: searchQuery.length > 0 ? 'Size' : 'Total Size'
2274
+ children: searchQuery.length > 0 ? t('fileManagement.size') : t('fileManagement.totalSize')
2217
2275
  })]
2218
2276
  }), searchQuery.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2219
2277
  style: _styles.fileManagementStyles.statItem,
@@ -2226,7 +2284,7 @@ const FileManagementScreen = ({
2226
2284
  style: [_styles.fileManagementStyles.statLabel, {
2227
2285
  color: themeStyles.colors.secondaryText
2228
2286
  }],
2229
- children: "Total"
2287
+ children: t('fileManagement.total')
2230
2288
  })]
2231
2289
  })]
2232
2290
  }), viewMode === 'photos' ? renderPhotoGrid() : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
@@ -2262,12 +2320,14 @@ const FileManagementScreen = ({
2262
2320
  style: [_styles.fileManagementStyles.emptyStateTitle, {
2263
2321
  color: themeStyles.textColor
2264
2322
  }],
2265
- children: "No Results Found"
2266
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
2323
+ children: t('fileManagement.noResults.title')
2324
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
2267
2325
  style: [_styles.fileManagementStyles.emptyStateDescription, {
2268
2326
  color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666'
2269
2327
  }],
2270
- children: ["No files match your search for \"", searchQuery, "\""]
2328
+ children: t('fileManagement.noResults.description', {
2329
+ query: searchQuery
2330
+ })
2271
2331
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
2272
2332
  style: [_styles.fileManagementStyles.emptyStateButton, {
2273
2333
  backgroundColor: themeStyles.primaryColor
@@ -2279,7 +2339,7 @@ const FileManagementScreen = ({
2279
2339
  color: "#FFFFFF"
2280
2340
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
2281
2341
  style: _styles.fileManagementStyles.emptyStateButtonText,
2282
- children: "Clear Search"
2342
+ children: t('fileManagement.clearSearch')
2283
2343
  })]
2284
2344
  })]
2285
2345
  }) : filteredFiles.length === 0 ? renderEmptyState() : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
@@ -2294,7 +2354,7 @@ const FileManagementScreen = ({
2294
2354
  style: [_styles.fileManagementStyles.loadingMoreText, {
2295
2355
  color: themeStyles.textColor
2296
2356
  }],
2297
- children: "Loading more..."
2357
+ children: t('fileManagement.loadingMore')
2298
2358
  })]
2299
2359
  })]
2300
2360
  })
@@ -2325,7 +2385,7 @@ const FileManagementScreen = ({
2325
2385
  style: [_styles.fileManagementStyles.uploadBannerText, {
2326
2386
  color: themeStyles.textColor
2327
2387
  }],
2328
- children: ["Uploading", uploadProgress ? ` ${uploadProgress.current}/${uploadProgress.total}` : '...']
2388
+ children: [t('fileManagement.uploading'), uploadProgress ? ` ${uploadProgress.current}/${uploadProgress.total}` : '...']
2329
2389
  }), uploadProgress && uploadProgress.total > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2330
2390
  style: [_styles.fileManagementStyles.uploadProgressBarContainer, {
2331
2391
  backgroundColor: themeStyles.isDarkTheme ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)'