From 3d7e7951a23868db09dfbd417e6f7bcc59a5601e Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 17 Jan 2026 22:31:51 +0800 Subject: [PATCH] :lipstick: Localizable file list --- assets/i18n/en-US.json | 26 ++++++ lib/screens/files/file_list.dart | 41 +++++----- lib/widgets/file_list_view.dart | 136 ++++++++++++++++++------------- 3 files changed, 125 insertions(+), 78 deletions(-) diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 9ce24e46..5ae34fde 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -1360,6 +1360,32 @@ "orCreateWith": "Or\ncreate with", "unindexedFiles": "Unindexed files", "folder": "Folder", + "rootDirectory": "Root Directory", + "pathSeparator": " / ", + "selectAll": "Select All", + "deselectAll": "Deselect All", + "createDirectory": "Create Directory", + "thisDirectoryIsEmpty": "This directory is empty", + "emptyDirectoryHint": "Upload files or create subdirectories to populate this path.\nDirectories are created implicitly when you upload files to them.", + "noUnindexedFiles": "No unindexed files", + "noUnindexedFilesHint": "All files have been assigned to paths.\nFiles without paths will appear here.", + "clearAllRecycledFiles": "Clear All Recycled Files", + "clearRecycledFilesDescription": "Permanently delete all marked recycled files to free up space.", + "allFiles": "All files", + "confirmDeleteSelectedFiles": "Are you sure you want to delete the selected files?", + "deleteSelectedFiles": "Delete Selected Files", + "confirmClearRecycledFiles": "Are you sure you want to clear all recycled files?", + "clearRecycledFiles": "Clear Recycled Files", + "failedToUploadFile": "Failed to upload file: {}", + "deletedFilesCount": "Deleted {} files.", + "failedToDeleteSelectedFiles": "Failed to delete selected files.", + "clearedRecycledFilesCount": "Cleared {} recycled files.", + "failedToClearRecycledFiles": "Failed to clear recycled files.", + "root": "Root", + "searchFiles": "Search files...", + "selectedCount": "{} selected", + "filesSelected": "{} files selected", + "fileSelected": "{} file selected", "clearCompleted": "Clear Completed", "uploadSuccess": "Upload successful!", "wouldYouLikeToViewFile": "Would you like to view the file?", diff --git a/lib/screens/files/file_list.dart b/lib/screens/files/file_list.dart index 12f54631..68013839 100644 --- a/lib/screens/files/file_list.dart +++ b/lib/screens/files/file_list.dart @@ -1,4 +1,5 @@ import 'package:cross_file/cross_file.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -41,7 +42,7 @@ class FileListScreen extends HookConsumerWidget { appBar: AppBar( title: SearchBar( constraints: const BoxConstraints(maxWidth: 400, minHeight: 32), - hintText: 'Search files...', + hintText: 'searchFiles'.tr(), hintStyle: WidgetStatePropertyAll(TextStyle(fontSize: 14)), textStyle: WidgetStatePropertyAll(TextStyle(fontSize: 14)), onChanged: (value) { @@ -105,26 +106,26 @@ class FileListScreen extends HookConsumerWidget { ) : null, body: usageAsync.when( - data: (usage) => quotaAsync.when( - data: (quota) => FileListView( - usage: usage, - quota: quota, - currentPath: currentPath, - selectedPool: selectedPool, - onPickAndUpload: () => _pickAndUploadFile( - ref, - currentPath.value, - selectedPool.value?.id, - ), - onShowCreateDirectory: _showCreateDirectoryDialog, - mode: mode, - viewMode: viewMode, - isSelectionMode: isSelectionMode, - query: query, + data: (usage) => quotaAsync.when( + data: (quota) => FileListView( + usage: usage, + quota: quota, + currentPath: currentPath, + selectedPool: selectedPool, + onPickAndUpload: () => _pickAndUploadFile( + ref, + currentPath.value, + selectedPool.value?.id, ), - loading: () => const Center(child: CircularProgressIndicator()), - error: (e, _) => Center(child: Text('Error loading quota')), + onShowCreateDirectory: _showCreateDirectoryDialog, + mode: mode, + viewMode: viewMode, + isSelectionMode: isSelectionMode, + query: query, ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, _) => Center(child: Text('Error loading quota')), + ), loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center(child: Text('Error loading usage')), ), @@ -307,4 +308,4 @@ class FileListScreen extends HookConsumerWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/file_list_view.dart b/lib/widgets/file_list_view.dart index 0455d115..1b7d66b1 100644 --- a/lib/widgets/file_list_view.dart +++ b/lib/widgets/file_list_view.dart @@ -175,7 +175,7 @@ class FileListView extends HookConsumerWidget { children: [ Icon(Symbols.folder), const Gap(12), - Text('Root Directory'), + Text('rootDirectory').tr(), ], ), ), @@ -185,7 +185,7 @@ class FileListView extends HookConsumerWidget { children: [ Icon(Symbols.inventory_2), const Gap(12), - Text('Unindexed Files'), + Text('unindexedFiles').tr(), ], ), ), @@ -202,10 +202,10 @@ class FileListView extends HookConsumerWidget { children: [ const Icon(Symbols.inventory_2, size: 20), const Gap(8), - const Text( - 'Unindexed Files', + Text( + 'unindexedFiles', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), - ), + ).tr(), ], ), ); @@ -222,7 +222,7 @@ class FileListView extends HookConsumerWidget { children: [ Icon(Symbols.inventory_2), const Gap(12), - Text('Unindexed Files'), + Text('unindexedFiles').tr(), ], ), ), @@ -232,7 +232,7 @@ class FileListView extends HookConsumerWidget { children: [ Icon(Symbols.folder), const Gap(12), - Text('Root Directory'), + Text('rootDirectory').tr(), ], ), ), @@ -248,10 +248,10 @@ class FileListView extends HookConsumerWidget { children: [ const Icon(Symbols.folder, size: 20), const Gap(8), - const Text( - 'Root Directory', + Text( + 'rootDirectory', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), - ), + ).tr(), ], ), ); @@ -286,7 +286,7 @@ class FileListView extends HookConsumerWidget { currentPathBuilder += '/${pathParts[i]}'; final path = currentPathBuilder; - breadcrumbs.add(const Text(' / ')); + breadcrumbs.add(Text('pathSeparator').tr()); if (i == pathParts.length - 1) { // Current directory breadcrumbs.add( @@ -346,7 +346,7 @@ class FileListView extends HookConsumerWidget { } }) .catchError((error) { - showSnackBar('Failed to upload file: $error'); + showSnackBar('failedToUploadFile'.tr(args: [error])); }); } }, @@ -378,16 +378,16 @@ class FileListView extends HookConsumerWidget { ), const Gap(12), SegmentedButton( - segments: const [ + segments: [ ButtonSegment( value: FileListViewMode.list, icon: Icon(Symbols.list), - tooltip: 'List View', + tooltip: 'listView'.tr(), ), ButtonSegment( value: FileListViewMode.waterfall, icon: Icon(Symbols.view_module), - tooltip: 'Waterfall View', + tooltip: 'waterfallView'.tr(), ), ], selected: {viewMode.value}, @@ -451,7 +451,7 @@ class FileListView extends HookConsumerWidget { isSelectionMode.value = false; selectedFileIds.value.clear(); }, - child: const Text('Cancel'), + child: Text('cancel').tr(), ), const Gap(12), OutlinedButton( @@ -478,7 +478,7 @@ class FileListView extends HookConsumerWidget { }, child: Text( currentVisibleItems.value.isEmpty - ? 'Select All' + ? 'selectAll'.tr() : currentVisibleItems.value .expand( (item) => item.maybeMap( @@ -490,21 +490,29 @@ class FileListView extends HookConsumerWidget { .toSet() .difference(selectedFileIds.value) .isEmpty - ? 'Deselect All' - : 'Select All', + ? 'deselectAll'.tr() + : 'selectAll'.tr(), ), ), const Spacer(), - Text('${selectedFileIds.value.length} selected'), + Text( + selectedFileIds.value.length == 1 + ? 'fileSelected'.tr( + args: [selectedFileIds.value.length.toString()], + ) + : 'filesSelected'.tr( + args: [selectedFileIds.value.length.toString()], + ), + ), const Spacer(), ElevatedButton.icon( icon: const Icon(Symbols.delete), - label: const Text('Delete'), + label: Text('delete').tr(), onPressed: selectedFileIds.value.isNotEmpty ? () async { final confirmed = await showConfirmAlert( - 'Are you sure you want to delete the selected files?', - 'Delete Selected Files', + 'confirmDeleteSelectedFiles'.tr(), + 'deleteSelectedFiles'.tr(), isDanger: true, ); if (!confirmed) return; @@ -528,10 +536,14 @@ class FileListView extends HookConsumerWidget { ? indexedCloudFileListProvider : unindexedFileListProvider, ); - showSnackBar('Deleted $count files.'); + showSnackBar( + 'deletedFilesCount'.tr( + args: [count.toString()], + ), + ); } catch (e) { showSnackBar( - 'Failed to delete selected files.', + 'failedToDeleteSelectedFiles'.tr(), ); } finally { if (context.mounted) { @@ -645,7 +657,7 @@ class FileListView extends HookConsumerWidget { maxLines: 1, overflow: TextOverflow.ellipsis, ), - subtitle: const Text('folder').tr(), + subtitle: Text('folder').tr(), onTap: () { final newPath = currentPath.value == '/' ? '/${folderItem.folderName}' @@ -679,24 +691,23 @@ class FileListView extends HookConsumerWidget { const Icon(Symbols.folder_off, size: 64, color: Colors.grey), const Gap(16), Text( - 'This directory is empty', + 'thisDirectoryIsEmpty', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Theme.of(ref.context).textTheme.bodyLarge?.color, ), - ), + ).tr(), const Gap(8), Text( - 'Upload files or create subdirectories to populate this path.\n' - 'Directories are created implicitly when you upload files to them.', + 'emptyDirectoryHint', textAlign: TextAlign.center, style: TextStyle( color: Theme.of( ref.context, ).textTheme.bodyMedium?.color?.withOpacity(0.7), ), - ), + ).tr(), const Gap(16), SingleChildScrollView( scrollDirection: Axis.horizontal, @@ -706,14 +717,14 @@ class FileListView extends HookConsumerWidget { ElevatedButton.icon( onPressed: onPickAndUpload, icon: const Icon(Symbols.upload_file), - label: const Text('Upload Files'), + label: Text('uploadFiles').tr(), ), const Gap(12), OutlinedButton.icon( onPressed: () => onShowCreateDirectory(ref.context, currentPath), icon: const Icon(Symbols.create_new_folder), - label: const Text('Create Directory'), + label: Text('createDirectory').tr(), ), ], ), @@ -1255,24 +1266,23 @@ class FileListView extends HookConsumerWidget { const Icon(Symbols.inventory_2, size: 64, color: Colors.grey), const Gap(16), Text( - 'No unindexed files', + 'thisDirectoryIsEmpty', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Theme.of(ref.context).textTheme.bodyLarge?.color, ), - ), + ).tr(), const Gap(8), Text( - 'All files have been assigned to paths.\n' - 'Files without paths will appear here.', + 'emptyDirectoryHint', textAlign: TextAlign.center, style: TextStyle( color: Theme.of( ref.context, ).textTheme.bodyMedium?.color?.withOpacity(0.7), ), - ), + ).tr(), ], ), ), @@ -1291,20 +1301,18 @@ class FileListView extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Clear All Recycled Files').bold(), - const Text( - 'Permanently delete all marked recycled files to free up space.', - ).fontSize(13), + Text('clearAllRecycledFiles').tr().bold(), + Text('clearRecycledFilesDescription').tr().fontSize(13), ], ), ), ElevatedButton.icon( icon: const Icon(Symbols.delete_forever), - label: const Text('Clear'), + label: Text('clear').tr(), onPressed: () async { final confirmed = await showConfirmAlert( - 'Are you sure you want to clear all recycled files?', - 'Clear Recycled Files', + 'confirmClearRecycledFiles'.tr(), + 'clearRecycledFiles'.tr(), ); if (!confirmed) return; @@ -1317,10 +1325,12 @@ class FileListView extends HookConsumerWidget { '/drive/files/me/recycle', ); final count = response.data['count'] as int? ?? 0; - showSnackBar('Cleared $count recycled files.'); + showSnackBar( + 'clearedRecycledFilesCount'.tr(args: [count.toString()]), + ); ref.invalidate(unindexedFileListProvider); } catch (e) { - showSnackBar('Failed to clear recycled files.'); + showSnackBar('failedToClearRecycledFiles'.tr()); } finally { if (ref.context.mounted) { hideLoadingModal(ref.context); @@ -1359,7 +1369,9 @@ class FileListView extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( border: Border.all( - color: Theme.of(ref.context).colorScheme.outline.withOpacity(0.5), + color: Theme.of( + ref.context, + ).colorScheme.outline.withOpacity(0.5), ), borderRadius: BorderRadius.circular(8), ), @@ -1367,14 +1379,14 @@ class FileListView extends HookConsumerWidget { child: DropdownButton( value: selectedPool.value, items: [ - const DropdownMenuItem( + DropdownMenuItem( value: null, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Symbols.database, size: 16), Gap(6), - Text('All files', style: TextStyle(fontSize: 12)), + Text('allFiles', style: TextStyle(fontSize: 12)).tr(), ], ), ), @@ -1422,7 +1434,9 @@ class FileListView extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( border: Border.all( - color: Theme.of(ref.context).colorScheme.outline.withOpacity(0.5), + color: Theme.of( + ref.context, + ).colorScheme.outline.withOpacity(0.5), ), borderRadius: BorderRadius.circular(8), ), @@ -1437,10 +1451,12 @@ class FileListView extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ Icon(Symbols.schedule, size: 16), - Text('Date', style: const TextStyle(fontSize: 12)), + Text('date', style: const TextStyle(fontSize: 12)).tr(), if (order.value == 'date') Icon( - orderDesc.value ? Symbols.arrow_downward : Symbols.arrow_upward, + orderDesc.value + ? Symbols.arrow_downward + : Symbols.arrow_upward, size: 14, ), ], @@ -1459,7 +1475,9 @@ class FileListView extends HookConsumerWidget { ), if (order.value == 'size') Icon( - orderDesc.value ? Symbols.arrow_downward : Symbols.arrow_upward, + orderDesc.value + ? Symbols.arrow_downward + : Symbols.arrow_upward, size: 16, ), ], @@ -1478,7 +1496,9 @@ class FileListView extends HookConsumerWidget { ), if (order.value == 'name') Icon( - orderDesc.value ? Symbols.arrow_downward : Symbols.arrow_upward, + orderDesc.value + ? Symbols.arrow_downward + : Symbols.arrow_upward, size: 16, ), ], @@ -1515,12 +1535,12 @@ class FileListView extends HookConsumerWidget { // Refresh chip FilterChip( - label: const Row( + label: Row( mainAxisSize: MainAxisSize.min, spacing: 6, children: [ Icon(Symbols.refresh, size: 16), - Text('Refresh', style: TextStyle(fontSize: 12)), + Text('refresh', style: TextStyle(fontSize: 12)).tr(), ], ), selected: false, @@ -1538,4 +1558,4 @@ class FileListView extends HookConsumerWidget { ), ); } -} \ No newline at end of file +}