diff --git a/apps/readest-app/public/locales/en/translation.json b/apps/readest-app/public/locales/en/translation.json index 7b27fa39..a95976d4 100644 --- a/apps/readest-app/public/locales/en/translation.json +++ b/apps/readest-app/public/locales/en/translation.json @@ -69,6 +69,7 @@ "Imported {{count}} annotations_one": "Imported {{count}} annotation", "Imported {{count}} annotations_other": "Imported {{count}} annotations", "Recently read": "Recently read", + "Bilingual": "Bilingual", "Show recently read": "Show recently read", "Your books will appear here": "Your books will appear here", "{{count}} results_one": "{{count}} result", diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 870adbab..ad49bd70 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -9,6 +9,7 @@ "Apply": "应用", "Auto Mode": "自动主题", "Behavior": "行为", + "Bilingual": "双语拆分", "Book": "书籍", "Bookmark": "书签", "Cancel": "取消", diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 74c2ee54..624a2146 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -9,6 +9,7 @@ "Apply": "應用", "Auto Mode": "自動主題", "Behavior": "行為", + "Bilingual": "雙語拆分", "Book": "書籍", "Bookmark": "書籤", "Cancel": "取消", diff --git a/apps/readest-app/src/app/library/components/Bookshelf.tsx b/apps/readest-app/src/app/library/components/Bookshelf.tsx index f93d617c..4179e51e 100644 --- a/apps/readest-app/src/app/library/components/Bookshelf.tsx +++ b/apps/readest-app/src/app/library/components/Bookshelf.tsx @@ -205,6 +205,7 @@ const Bookshelf: React.FC = ({ const [showStatusAlert, setShowStatusAlert] = useState(false); const [showGroupingModal, setShowGroupingModal] = useState(false); const [showBilingualFilterAlert, setShowBilingualFilterAlert] = useState(false); + const [bilingualFilterBook, setBilingualFilterBook] = useState(null); const [bilingualFilterProcessing, setBilingualFilterProcessing] = useState(false); const [importBookUrl] = useState(searchParams?.get('url') || ''); @@ -468,18 +469,37 @@ const Bookshelf: React.FC = ({ const showBilingualFilterSelection = () => { const book = getSingleSelectedBook(); if (!book || book.format !== 'EPUB') return; + setBilingualFilterBook(null); setShowSelectModeActions(false); setShowBilingualFilterAlert(true); }; + const showBilingualFilterBook = useCallback( + (book: Book) => { + if (book.format !== 'EPUB') { + eventDispatcher.dispatch('toast', { + type: 'warning', + message: _('Only EPUB books can be filtered'), + timeout: 2500, + }); + return; + } + setBilingualFilterBook(book); + setShowSelectModeActions(false); + setShowBilingualFilterAlert(true); + }, + [_], + ); + const closeBilingualFilterSelection = () => { if (bilingualFilterProcessing) return; setShowBilingualFilterAlert(false); - setShowSelectModeActions(true); + setBilingualFilterBook(null); + if (isSelectMode) setShowSelectModeActions(true); }; const runBilingualFilter = async (options: BilingualFilterOptions) => { - const book = getSingleSelectedBook(); + const book = bilingualFilterBook ?? getSingleSelectedBook(); if (!book || !appService) return; if (book.format !== 'EPUB') { eventDispatcher.dispatch('toast', { @@ -515,6 +535,7 @@ const Bookshelf: React.FC = ({ await updateBooks(envConfig, [importedBook]); handlePushLibrary(); setSelectedBooks([]); + setBilingualFilterBook(null); setShowBilingualFilterAlert(false); handleSetSelectMode(false); eventDispatcher.dispatch('toast', { @@ -536,8 +557,9 @@ const Bookshelf: React.FC = ({ message: _('Failed to filter bilingual EPUB'), timeout: 3000, }); + setBilingualFilterBook(null); setShowBilingualFilterAlert(false); - setShowSelectModeActions(true); + if (isSelectMode) setShowSelectModeActions(true); } finally { setLoading(false); setBilingualFilterProcessing(false); @@ -888,6 +910,7 @@ const Bookshelf: React.FC = ({ handleBookDelete={handleBookDelete} handleSetSelectMode={handleSetSelectMode} handleShowDetailsBook={handleShowDetailsBook} + handleBilingualFilterBook={showBilingualFilterBook} handleLibraryNavigation={handleLibraryNavigation} handleUpdateReadingStatus={handleUpdateReadingStatus} transferProgress={ @@ -913,6 +936,7 @@ const Bookshelf: React.FC = ({ handleBookDelete, handleSetSelectMode, handleShowDetailsBook, + showBilingualFilterBook, handleLibraryNavigation, handleUpdateReadingStatus, ], @@ -993,9 +1017,9 @@ const Bookshelf: React.FC = ({ bilingualFilterEnabled={bilingualFilterEnabled} /> )} - {showBilingualFilterAlert && selectedBilingualBook && ( + {showBilingualFilterAlert && (bilingualFilterBook || selectedBilingualBook) && ( Promise; handleSetSelectMode: (selectMode: boolean) => void; handleShowDetailsBook: (book: Book) => void; + handleBilingualFilterBook: (book: Book) => void; handleLibraryNavigation: (targetGroup: string) => void; handleUpdateReadingStatus: (book: Book, status: ReadingStatus | undefined) => void; } @@ -135,6 +136,7 @@ const BookshelfItem: React.FC = ({ handleBookDownload, handleSetSelectMode, handleShowDetailsBook, + handleBilingualFilterBook, handleLibraryNavigation, handleUpdateReadingStatus, }) => { @@ -228,6 +230,12 @@ const BookshelfItem: React.FC = ({ showBookDetailsModal(book); }, }, + bilingual: { + text: _('Bilingual'), + action: async () => { + handleBilingualFilterBook(book); + }, + }, showInFinder: { text: _(fileRevealLabel), action: async () => { @@ -366,6 +374,7 @@ const BookshelfItem: React.FC = ({ appService?.isMobileApp, handleBookDownload, handleBookUpload, + handleBilingualFilterBook, handleGroupBooks, handleSetSelectMode, handleUpdateReadingStatus, diff --git a/apps/readest-app/src/app/library/utils/libraryUtils.ts b/apps/readest-app/src/app/library/utils/libraryUtils.ts index 03446f56..8b598f6b 100644 --- a/apps/readest-app/src/app/library/utils/libraryUtils.ts +++ b/apps/readest-app/src/app/library/utils/libraryUtils.ts @@ -651,6 +651,7 @@ export type BookContextMenuItemId = | 'markAbandoned' | 'clearStatus' | 'showDetails' + | 'bilingual' | 'showInFinder' | 'searchGoodreads' | 'download' @@ -750,7 +751,9 @@ export const getBookContextMenuItemIds = (book: Book): BookContextMenuItemId[] = ) { ids.push('clearStatus'); } - ids.push('showDetails', 'showInFinder', 'searchGoodreads'); + ids.push('showDetails'); + if (book.format === 'EPUB') ids.push('bilingual'); + ids.push('showInFinder', 'searchGoodreads'); if (book.uploadedAt && !book.downloadedAt) ids.push('download'); if (!book.uploadedAt && book.downloadedAt) ids.push('upload'); // Share is offered for any local-or-uploaded book; the dialog uploads first