diff --git a/README.md b/README.md index 8a8947ee..963820ff 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ | **Translate with DeepL and Yandex** | From a single sentence to the entire book—translate instantly. | ✅ | | **Text-to-Speech (TTS) Support** | Enjoy smooth, multilingual narration—even within a single book. | ✅ | | **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ | +| **OPDS/Calibre Integration** | Integrate OPDS/Calibre to access online libraries and catalogs. | ✅ | | **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ | ## Planned Features @@ -72,7 +73,6 @@ | ------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------ | | [**Sync with Koreader**][link-kosync-wiki] | Synchronize reading progress, notes, and bookmarks with [Koreader][link-koreader] devices. | 🛠 | | **AI-Powered Summarization** | Generate summaries of books or chapters using AI for quick insights. | 🛠 | -| **Support OPDS/Calibre** | Integrate OPDS/Calibre to access online libraries and catalogs. | 🔄 | | **Audiobook Support** | Extend functionality to play and manage audiobooks. | 🔄 | | **Handwriting Annotations** | Add support for handwriting annotations using a pen on compatible devices. | 🔄 | | **Advanced Reading Stats** | Track reading time, pages read, and more for detailed insights. | 🔄 | diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 84cb4af7..5aaa4da2 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -741,5 +741,13 @@ "Last": "الأخير", "Cannot Load Page": "تعذر تحميل الصفحة", "An error occurred": "حدث خطأ ما", - "Online Library": "المكتبة عبر الإنترنت" + "Online Library": "المكتبة عبر الإنترنت", + "URL must start with http:// or https://": "يجب أن يبدأ عنوان URL بـ http:// أو https://", + "Title, Author, Tag, etc...": "العنوان، المؤلف، العلامة، إلخ...", + "Query": "استعلام", + "Subject": "موضوع", + "Enter {{terms}}": "أدخل {{terms}}", + "No search results found": "لم يتم العثور على نتائج بحث", + "Failed to load OPDS feed: {{status}} {{statusText}}": "فشل في تحميل تغذية OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "البحث في {{title}}" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index 2beb25b5..02e4e31b 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -721,5 +721,13 @@ "Last": "শেষ", "Cannot Load Page": "পৃষ্ঠা লোড করা যায়নি", "An error occurred": "একটি ত্রুটি ঘটেছে", - "Online Library": "অনলাইন লাইব্রেরি" + "Online Library": "অনলাইন লাইব্রেরি", + "URL must start with http:// or https://": "URL অবশ্যই http:// বা https:// দিয়ে শুরু হতে হবে", + "Title, Author, Tag, etc...": "শিরোনাম, লেখক, ট্যাগ, ইত্যাদি...", + "Query": "কোয়েরি", + "Subject": "বিষয়", + "Enter {{terms}}": "{{terms}} লিখুন", + "No search results found": "কোনও অনুসন্ধান ফলাফল পাওয়া যায়নি", + "Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS ফিড লোড করতে ব্যর্থ: {{status}} {{statusText}}", + "Search in {{title}}": "{{title}} এ অনুসন্ধান করুন" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index 1cece03f..4e9fa41a 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -716,5 +716,13 @@ "Last": "མཐའ་མ།", "Cannot Load Page": "ཤོག་ངོས་འགུལ་སྐྱོང་བྱེད་ཐུབ་མེད།", "An error occurred": "ནོར་འཁྲུལ་ཞིག་བྱུང་སོང་།", - "Online Library": "དྲ་རྒྱུན་དེབ་མཛོད།" + "Online Library": "དྲ་རྒྱུན་དེབ་མཛོད།", + "URL must start with http:// or https://": "URL ནི་ http:// ཡང་ https:// ནས་འགོ་བཙུགས་དགོ།", + "Title, Author, Tag, etc...": "མིང་།, རྩོམ་པ།, མཚོན་འགྲེལ།, དེ་ལས་སྐུགས་...", + "Query": "འཚོལ་ཞིབ་", + "Subject": "དོན་ཚན་", + "Enter {{terms}}": "{{terms}} ལ་འགྲོ།", + "No search results found": "འཚོལ་ཞིབ་རྫོགས་མ་ཐུབ།", + "Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS འཕྲིན་འདེམས་བྱས་མ་ཐུབ།: {{status}} {{statusText}}", + "Search in {{title}}": "{{title}} ནང་འཚོལ།" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 427e1069..b92acf57 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -721,5 +721,13 @@ "Last": "Letzte", "Cannot Load Page": "Seite kann nicht geladen werden", "An error occurred": "Ein Fehler ist aufgetreten", - "Online Library": "Online-Bibliothek" + "Online Library": "Online-Bibliothek", + "URL must start with http:// or https://": "Die URL muss mit http:// oder https:// beginnen", + "Title, Author, Tag, etc...": "Titel, Autor, Tag, etc...", + "Query": "Suchbegriff", + "Subject": "Thema", + "Enter {{terms}}": "Gib {{terms}} ein", + "No search results found": "Keine Suchergebnisse gefunden", + "Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS-Feed konnte nicht geladen werden: {{status}} {{statusText}}", + "Search in {{title}}": "Suche in {{title}}" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 03b84d69..58162e26 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -721,5 +721,13 @@ "Last": "Τελευταίο", "Cannot Load Page": "Δεν είναι δυνατή η φόρτωση της σελίδας", "An error occurred": "Προέκυψε σφάλμα", - "Online Library": "Online Βιβλιοθήκη" + "Online Library": "Online Βιβλιοθήκη", + "URL must start with http:// or https://": "Το URL πρέπει να ξεκινά με http:// ή https://", + "Title, Author, Tag, etc...": "Τίτλος, Συγγραφέας, Ετικέτα, κ.λπ...", + "Query": "Ερώτημα", + "Subject": "Θέμα", + "Enter {{terms}}": "Εισαγάγετε {{terms}}", + "No search results found": "Δεν βρέθηκαν αποτελέσματα αναζήτησης", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Αποτυχία φόρτωσης ροής OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "Αναζήτηση στο {{title}}" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 5e5a7c32..3929577c 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -726,5 +726,13 @@ "Last": "Último", "Cannot Load Page": "No se puede cargar la página", "An error occurred": "Ocurrió un error", - "Online Library": "Biblioteca en línea" + "Online Library": "Biblioteca en línea", + "URL must start with http:// or https://": "URL debe comenzar con http:// o https://", + "Title, Author, Tag, etc...": "Titulo, Autor, Etiqueta, etc...", + "Query": "Consulta", + "Subject": "Asunto", + "Enter {{terms}}": "Ingrese {{terms}}", + "No search results found": "No se encontraron resultados de búsqueda", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Error al cargar el feed OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "Buscar en {{title}}" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 7321a667..36b59730 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -721,5 +721,13 @@ "Last": "آخرین", "Cannot Load Page": "بارگذاری صفحه امکان‌پذیر نیست", "An error occurred": "خطایی رخ داد", - "Online Library": "کتابخانه آنلاین" + "Online Library": "کتابخانه آنلاین", + "URL must start with http:// or https://": "آدرس باید با http:// یا https:// شروع شود", + "Title, Author, Tag, etc...": "عنوان، نویسنده، برچسب و غیره...", + "Query": "پرس‌وجو", + "Subject": "موضوع", + "Enter {{terms}}": "وارد کردن {{terms}}", + "No search results found": "هیچ نتیجه‌ای یافت نشد", + "Failed to load OPDS feed: {{status}} {{statusText}}": "بارگذاری فید OPDS ناموفق بود: {{status}} {{statusText}}", + "Search in {{title}}": "جستجو در {{title}}" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index cba8f351..fd46a5df 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -726,5 +726,13 @@ "Last": "Dernier", "Cannot Load Page": "Impossible de charger la page", "An error occurred": "Une erreur est survenue", - "Online Library": "Bibliothèque en ligne" + "Online Library": "Bibliothèque en ligne", + "URL must start with http:// or https://": "URL doit commencer par http:// ou https://", + "Title, Author, Tag, etc...": "Titre, Auteur, Tag, etc...", + "Query": "Requête", + "Subject": "Sujet", + "Enter {{terms}}": "Entrez {{terms}}", + "No search results found": "Aucun résultat trouvé", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Échec du chargement du flux OPDS : {{status}} {{statusText}}", + "Search in {{title}}": "Rechercher dans {{title}}" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 366441a0..449a2e35 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -721,5 +721,13 @@ "Last": "अंतिम", "Cannot Load Page": "पृष्ठ लोड नहीं किया जा सका", "An error occurred": "एक त्रुटि हुई", - "Online Library": "ऑनलाइन लाइब्रेरी" + "Online Library": "ऑनलाइन लाइब्रेरी", + "URL must start with http:// or https://": "URL http:// या https:// से शुरू होना चाहिए", + "Title, Author, Tag, etc...": "शीर्षक, लेखक, टैग, आदि...", + "Query": "प्रश्न", + "Subject": "विषय", + "Enter {{terms}}": "{{terms}} दर्ज करें", + "No search results found": "कोई खोज परिणाम नहीं मिला", + "Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS फ़ीड लोड करने में विफल: {{status}} {{statusText}}", + "Search in {{title}}": "{{title}} में खोजें" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index a419367d..9ff9e457 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -716,5 +716,13 @@ "Last": "Terakhir", "Cannot Load Page": "Tidak dapat memuat halaman", "An error occurred": "Terjadi kesalahan", - "Online Library": "Perpustakaan Online" + "Online Library": "Perpustakaan Online", + "URL must start with http:// or https://": "URL harus diawali dengan http:// atau https://", + "Title, Author, Tag, etc...": "Judul, Penulis, Tag, dll...", + "Query": "Kueri", + "Subject": "Subjek", + "Enter {{terms}}": "Masukkan {{terms}}", + "No search results found": "Tidak ada hasil pencarian ditemukan", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Gagal memuat feed OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "Cari di {{title}}" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 625f7419..1797d525 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -726,5 +726,13 @@ "Last": "Ultimo", "Cannot Load Page": "Impossibile caricare la pagina", "An error occurred": "Si è verificato un errore", - "Online Library": "Libreria online" + "Online Library": "Libreria online", + "URL must start with http:// or https://": "URL deve iniziare con http:// o https://", + "Title, Author, Tag, etc...": "Titolo, Autore, Tag, ecc...", + "Query": "Query", + "Subject": "Soggetto", + "Enter {{terms}}": "Inserisci {{terms}}", + "No search results found": "Nessun risultato di ricerca trovato", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Impossibile caricare il feed OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "Cerca in {{title}}" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index dfd3c9db..a2962b4b 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -716,5 +716,13 @@ "Last": "最後", "Cannot Load Page": "ページを読み込めません", "An error occurred": "エラーが発生しました", - "Online Library": "オンラインライブラリ" + "Online Library": "オンラインライブラリ", + "URL must start with http:// or https://": "URLはhttp://またはhttps://で始まる必要があります", + "Title, Author, Tag, etc...": "タイトル、著者、タグなど...", + "Query": "クエリ", + "Subject": "件名", + "Enter {{terms}}": "{{terms}}を入力してください", + "No search results found": "検索結果が見つかりません", + "Failed to load OPDS feed: {{status}} {{statusText}}": "OPDSフィードの読み込みに失敗しました: {{status}} {{statusText}}", + "Search in {{title}}": "{{title}}内を検索" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 0b1d356b..e4c41b7f 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -716,5 +716,13 @@ "Last": "마지막", "Cannot Load Page": "페이지를 불러올 수 없습니다", "An error occurred": "오류가 발생했습니다", - "Online Library": "온라인 라이브러리" + "Online Library": "온라인 라이브러리", + "URL must start with http:// or https://": "URL은 http:// 또는 https://로 시작해야 합니다", + "Title, Author, Tag, etc...": "제목, 저자, 태그 등...", + "Query": "쿼리", + "Subject": "주제", + "Enter {{terms}}": "{{terms}} 입력", + "No search results found": "검색 결과가 없습니다", + "Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS 피드를 불러오지 못했습니다: {{status}} {{statusText}}", + "Search in {{title}}": "{{title}}에서 검색" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index c37f191a..ed2f0b80 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -716,5 +716,13 @@ "Last": "Terakhir", "Cannot Load Page": "Tidak dapat memuatkan halaman", "An error occurred": "Ralat telah berlaku", - "Online Library": "Perpustakaan Dalam Talian" + "Online Library": "Perpustakaan Dalam Talian", + "URL must start with http:// or https://": "URL mesti bermula dengan http:// atau https://", + "Title, Author, Tag, etc...": "Tajuk, Pengarang, Tag, dll...", + "Query": "Pertanyaan", + "Subject": "Subjek", + "Enter {{terms}}": "Masukkan {{terms}}", + "No search results found": "Tiada hasil carian ditemui", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Gagal memuatkan suapan OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "Cari dalam {{title}}" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 271c087f..3fe1e70e 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -721,5 +721,13 @@ "Last": "Laatste", "Cannot Load Page": "Pagina kan niet worden geladen", "An error occurred": "Er is een fout opgetreden", - "Online Library": "Online Bibliotheek" + "Online Library": "Online Bibliotheek", + "URL must start with http:// or https://": "URL moet beginnen met http:// of https://", + "Title, Author, Tag, etc...": "Titel, Auteur, Tag, enzovoort...", + "Query": "Zoekopdracht", + "Subject": "Onderwerp", + "Enter {{terms}}": "Voer {{terms}} in", + "No search results found": "Geen zoekresultaten gevonden", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Het laden van de OPDS-feed is mislukt: {{status}} {{statusText}}", + "Search in {{title}}": "Zoeken in {{title}}" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 278792c3..4bb105a5 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -731,5 +731,13 @@ "Last": "Ostatnia", "Cannot Load Page": "Nie można załadować strony", "An error occurred": "Wystąpił błąd", - "Online Library": "Biblioteka online" + "Online Library": "Biblioteka online", + "URL must start with http:// or https://": "URL musi zaczynać się od http:// lub https://", + "Title, Author, Tag, etc...": "Tytuł, Autor, Tag, itp...", + "Query": "Zapytanie", + "Subject": "Temat", + "Enter {{terms}}": "Wprowadź {{terms}}", + "No search results found": "Nie znaleziono wyników wyszukiwania", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Nie udało się załadować kanału OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "Szukaj w {{title}}" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index c7ba44cc..f82ea184 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -726,5 +726,13 @@ "Last": "Ostatnia", "Cannot Load Page": "Nie można załadować strony", "An error occurred": "Wystąpił błąd", - "Online Library": "Biblioteka online" + "Online Library": "Biblioteca online", + "URL must start with http:// or https://": "URL deve começar com http:// ou https://", + "Title, Author, Tag, etc...": "Título, Autor, Etiqueta, etc...", + "Query": "Consulta", + "Subject": "Assunto", + "Enter {{terms}}": "Insira {{terms}}", + "No search results found": "Nenhum resultado encontrado", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Falha ao carregar o feed OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "Pesquisar em {{title}}" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index d0195c9b..a4ec2873 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -731,5 +731,13 @@ "Last": "Последняя", "Cannot Load Page": "Не удалось загрузить страницу", "An error occurred": "Произошла ошибка", - "Online Library": "Онлайн библиотека" + "Online Library": "Онлайн библиотека", + "URL must start with http:// or https://": "URL должен начинаться с http:// или https://", + "Title, Author, Tag, etc...": "Название, Автор, Тег и т.д...", + "Query": "Запрос", + "Subject": "Тема", + "Enter {{terms}}": "Введите {{terms}}", + "No search results found": "Результаты поиска не найдены", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Не удалось загрузить ленту OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "Поиск в {{title}}" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index c953cd75..90f2d64e 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -721,5 +721,13 @@ "Last": "අවසාන", "Cannot Load Page": "පිටුවට ප්‍රවේශ විය නොහැක", "An error occurred": "දෝෂයක් සිදු විය", - "Online Library": "ඔන්ලයින් පුස්තකාලය" + "Online Library": "ඔන්ලයින් පුස්තකාලය", + "URL must start with http:// or https://": "URL එක http:// හෝ https:// සමඟ ආරම්භ විය යුතුය", + "Title, Author, Tag, etc...": "ශීර්ෂය, කතුවරයා, ටැග්, ආදිය...", + "Query": "විමසුම", + "Subject": "විෂය", + "Enter {{terms}}": "{{terms}} ඇතුළත් කරන්න", + "No search results found": "සෙවුම් ප්‍රතිඵල නොමැත", + "Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS ආහාරය පූරණය කිරීමට අසමත් විය: {{status}} {{statusText}}", + "Search in {{title}}": "{{title}} තුළ සෙවීම" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index ba27ce8a..5a7b053e 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -721,5 +721,13 @@ "Last": "Sista", "Cannot Load Page": "Kan inte ladda sidan", "An error occurred": "Ett fel uppstod", - "Online Library": "Onlinebibliotek" + "Online Library": "Onlinebibliotek", + "URL must start with http:// or https://": "URL måste börja med http:// eller https://", + "Title, Author, Tag, etc...": "Titel, författare, tagg, etc...", + "Query": "Fråga", + "Subject": "Ämne", + "Enter {{terms}}": "Ange {{terms}}", + "No search results found": "Inga sökresultat hittades", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Misslyckades med att ladda OPDS-flöde: {{status}} {{statusText}}", + "Search in {{title}}": "Sök i {{title}}" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index bdcf44c5..87ad8f5a 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -721,5 +721,13 @@ "Last": "இறுதி", "Cannot Load Page": "பக்கம் ஏற்ற முடியவில்லை", "An error occurred": "ஒரு பிழை ஏற்பட்டது", - "Online Library": "ஆன்லைன் நூலகம்" + "Online Library": "ஆன்லைன் நூலகம்", + "URL must start with http:// or https://": "URL http:// அல்லது https:// கொண்டு தொடங்க வேண்டும்", + "Title, Author, Tag, etc...": "தலைப்பு, ஆசிரியர், குறிச்சொல், மற்றும் பல...", + "Query": "கேள்வி", + "Subject": "பொருள்", + "Enter {{terms}}": "{{terms}} உள்ளிடவும்", + "No search results found": "தேடல் முடிவுகள் இல்லை", + "Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS ஊட்டத்தை ஏற்ற முடியவில்லை: {{status}} {{statusText}}", + "Search in {{title}}": "{{title}} இல் தேடவும்" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 2f89972a..83e5c8f5 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -716,5 +716,13 @@ "Last": "สุดท้าย", "Cannot Load Page": "ไม่สามารถโหลดหน้าหน้าได้", "An error occurred": "เกิดข้อผิดพลาด", - "Online Library": "ห้องสมุดออนไลน์" + "Online Library": "ห้องสมุดออนไลน์", + "URL must start with http:// or https://": "URL ต้องเริ่มต้นด้วย http:// หรือ https://", + "Title, Author, Tag, etc...": "ชื่อเรื่อง ผู้แต่ง แท็ก ฯลฯ...", + "Query": "แบบสอบถาม", + "Subject": "หัวข้อ", + "Enter {{terms}}": "ป้อน {{terms}}", + "No search results found": "ไม่พบผลการค้นหา", + "Failed to load OPDS feed: {{status}} {{statusText}}": "ไม่สามารถโหลดฟีด OPDS ได้: {{status}} {{statusText}}", + "Search in {{title}}": "ค้นหาใน {{title}}" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 69899018..70039dab 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -721,5 +721,13 @@ "Last": "Son", "Cannot Load Page": "Sayfa yüklenemiyor", "An error occurred": "Bir hata oluştu", - "Online Library": "Çevrimiçi Kütüphane" + "Online Library": "Çevrimiçi Kütüphane", + "URL must start with http:// or https://": "URL http:// veya https:// ile başlamalıdır", + "Title, Author, Tag, etc...": "Başlık, Yazar, Etiket, vb...", + "Query": "Sorgu", + "Subject": "Konu", + "Enter {{terms}}": "{{terms}} girin", + "No search results found": "Arama sonucu bulunamadı", + "Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS beslemesi yüklenemedi: {{status}} {{statusText}}", + "Search in {{title}}": "{{title}} içinde ara" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index ec4ce745..a7099cea 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -731,5 +731,13 @@ "Last": "Остання", "Cannot Load Page": "Не вдалося завантажити сторінку", "An error occurred": "Сталася помилка", - "Online Library": "Онлайн бібліотека" + "Online Library": "Онлайн бібліотека", + "URL must start with http:// or https://": "URL повинен починатися з http:// або https://", + "Title, Author, Tag, etc...": "Назва, автор, тег тощо...", + "Query": "Запит", + "Subject": "Тема", + "Enter {{terms}}": "Введіть {{terms}}", + "No search results found": "Результатів пошуку не знайдено", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Не вдалося завантажити стрічку OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "Пошук у {{title}}" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 3b254454..e10ab42c 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -716,5 +716,13 @@ "Last": "Cuối cùng", "Cannot Load Page": "Không thể tải trang", "An error occurred": "Đã xảy ra lỗi", - "Online Library": "Thư viện trực tuyến" + "Online Library": "Thư viện trực tuyến", + "URL must start with http:// or https://": "URL phải bắt đầu bằng http:// hoặc https://", + "Title, Author, Tag, etc...": "Tiêu đề, Tác giả, Thẻ, v.v...", + "Query": "Truy vấn", + "Subject": "Chủ đề", + "Enter {{terms}}": "Nhập {{terms}}", + "No search results found": "Không tìm thấy kết quả tìm kiếm", + "Failed to load OPDS feed: {{status}} {{statusText}}": "Không tải được nguồn cấp OPDS: {{status}} {{statusText}}", + "Search in {{title}}": "Tìm kiếm trong {{title}}" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 85061089..ea5ada5b 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -716,5 +716,13 @@ "Last": "末页", "Cannot Load Page": "无法加载页面", "An error occurred": "发生错误", - "Online Library": "在线书库" + "Online Library": "在线书库", + "URL must start with http:// or https://": "URL 必须以 http:// 或 https:// 开头", + "Title, Author, Tag, etc...": "标题,作者,标签等...", + "Query": "查询", + "Subject": "主题", + "Enter {{terms}}": "输入 {{terms}}", + "No search results found": "未找到搜索结果", + "Failed to load OPDS feed: {{status}} {{statusText}}": "加载 OPDS 源失败:{{status}} {{statusText}}", + "Search in {{title}}": "在 {{title}} 中搜索" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 9e2dc1fd..b2974dc8 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -716,5 +716,13 @@ "Last": "最後一頁", "Cannot Load Page": "無法載入頁面", "An error occurred": "發生錯誤", - "Online Library": "線上書庫" + "Online Library": "線上書庫", + "URL must start with http:// or https://": "URL 必須以 http:// 或 https:// 開頭", + "Title, Author, Tag, etc...": "標題、作者、標籤等...", + "Query": "查詢", + "Subject": "主題", + "Enter {{terms}}": "輸入 {{terms}}", + "No search results found": "未找到搜尋結果", + "Failed to load OPDS feed: {{status}} {{statusText}}": "載入 OPDS 資料源失敗:{{status}} {{statusText}}", + "Search in {{title}}": "在 {{title}} 中搜尋" } diff --git a/apps/readest-app/src/app/api/opds/proxy/route.ts b/apps/readest-app/src/app/api/opds/proxy/route.ts index ebb34ff7..f6e2035b 100644 --- a/apps/readest-app/src/app/api/opds/proxy/route.ts +++ b/apps/readest-app/src/app/api/opds/proxy/route.ts @@ -33,7 +33,7 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') { console.log(`[OPDS Proxy] ${method}: ${url}`); const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 10000); + const timeout = setTimeout(() => controller.abort(), 20000); const headers: HeadersInit = { 'User-Agent': 'Readest/1.0 (OPDS Browser)', Accept: 'application/atom+xml, application/xml, text/xml, application/json, */*', @@ -82,7 +82,16 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') { }, }); } - throw new Error(`HTTP ${response.status}: ${response.statusText}`); + return new NextResponse(data, { + status: response.status, + headers: { + ...Object.fromEntries(response.headers.entries()), + 'Cache-Control': 'public, max-age=300', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + }, + }); } const contentType = response.headers.get('Content-Type') || 'text/xml'; diff --git a/apps/readest-app/src/app/opds/components/CatelogManager.tsx b/apps/readest-app/src/app/opds/components/CatelogManager.tsx index 2262b8ee..447154f4 100644 --- a/apps/readest-app/src/app/opds/components/CatelogManager.tsx +++ b/apps/readest-app/src/app/opds/components/CatelogManager.tsx @@ -28,6 +28,21 @@ const POPULAR_CATALOGS: OPDSCatalog[] = [ description: 'Over 50,000 free ebooks', icon: '📖', }, + { + id: 'standardebooks', + name: 'Standard Ebooks', + url: 'https://standardebooks.org/feeds/opds', + description: 'Carefully formatted and lovingly produced free ebooks', + icon: '📚', + disabled: true, + }, + { + id: 'unglue.it', + name: 'Unglue.it', + url: 'https://unglue.it/api/opds/', + description: 'Free ebooks from authors who have "unglued" their books', + icon: '📚', + }, ]; async function validateOPDSCatalog( @@ -65,6 +80,12 @@ export function CatalogManager() { const handleAddCatalog = async () => { if (!newCatalog.name || !newCatalog.url) return; + const urlLower = newCatalog.url.trim().toLowerCase(); + if (!urlLower.startsWith('http://') && !urlLower.startsWith('https://')) { + setUrlError(_('URL must start with http:// or https://')); + return; + } + if ( process.env['NODE_ENV'] === 'production' && isWebAppPlatform() && @@ -216,7 +237,7 @@ export function CatalogManager() {

{_('Popular Catalogs')}

- {POPULAR_CATALOGS.map((catalog) => { + {POPULAR_CATALOGS.filter((catalog) => !catalog.disabled).map((catalog) => { const isAdded = catalogs.some((c) => c.url === catalog.url); return (
void; onBack?: () => void; onForward?: () => void; + onSearch: () => void; canGoBack: boolean; canGoForward: boolean; + hasSearch: boolean; } export function Navigation({ @@ -25,8 +27,10 @@ export function Navigation({ onNavigate, onBack, onForward, + onSearch, canGoBack, canGoForward, + hasSearch = false, }: NavigationProps) { const _ = useTranslation(); const router = useRouter(); @@ -44,6 +48,10 @@ export function Navigation({ navigateToLibrary(router, '', {}, true); }, [router]); + const handleSearch = useCallback(() => { + onSearch(); + }, [onSearch]); + return (
+ {hasSearch && ( + + )} diff --git a/apps/readest-app/src/app/opds/components/PublicationView.tsx b/apps/readest-app/src/app/opds/components/PublicationView.tsx index 9d87611a..b2020220 100644 --- a/apps/readest-app/src/app/opds/components/PublicationView.tsx +++ b/apps/readest-app/src/app/opds/components/PublicationView.tsx @@ -7,6 +7,7 @@ import { IoPricetag } from 'react-icons/io5'; import { Book } from '@/types/book'; import { OPDSLink, OPDSPublication, REL, SYMBOL } from '@/types/opds'; import { useTranslation } from '@/hooks/useTranslation'; +import { getFileExtFromMimeType } from '@/libs/document'; import { formatDate, formatLanguage } from '@/utils/book'; import { eventDispatcher } from '@/utils/event'; import { navigateToReader } from '@/utils/nav'; @@ -190,7 +191,11 @@ export function PublicationView({ key={idx} noIcon transient - label={link.title || link.type || ''} + label={ + link.title || + getFileExtFromMimeType(link.type || '').toUpperCase() || + idx.toString() + } onClick={() => handleActionButton(link.href, link.type)} /> ))} diff --git a/apps/readest-app/src/app/opds/components/SearchView.tsx b/apps/readest-app/src/app/opds/components/SearchView.tsx index c76b9139..32fe495e 100644 --- a/apps/readest-app/src/app/opds/components/SearchView.tsx +++ b/apps/readest-app/src/app/opds/components/SearchView.tsx @@ -2,16 +2,18 @@ import { useState, FormEvent } from 'react'; import { IoSearch } from 'react-icons/io5'; +import { useTranslation } from '@/hooks/useTranslation'; import { OPDSSearch } from '@/types/opds'; interface SearchViewProps { search: OPDSSearch; baseURL: string; - onNavigate: (url: string) => void; + onNavigate: (url: string, isSearch?: boolean) => void; resolveURL: (url: string, base: string) => string; } export function SearchView({ search, baseURL, onNavigate, resolveURL }: SearchViewProps) { + const _ = useTranslation(); const [formData, setFormData] = useState>(() => { const initial: Record = {}; search.params?.forEach((param) => { @@ -38,7 +40,7 @@ export function SearchView({ search, baseURL, onNavigate, resolveURL }: SearchVi const searchURL = search.search(map); const resolvedURL = resolveURL(searchURL, baseURL); - onNavigate(resolvedURL); + onNavigate(resolvedURL, true); }; const handleInputChange = (name: string, value: string) => { @@ -47,21 +49,21 @@ export function SearchView({ search, baseURL, onNavigate, resolveURL }: SearchVi const getParamLabel = (name: string): string => { const labels: Record = { - searchTerms: 'Search', - query: 'Query', - title: 'Title', - author: 'Author', - publisher: 'Publisher', - language: 'Language', - subject: 'Subject', + searchTerms: _('Title, Author, Tag, etc...'), + query: _('Query'), + title: _('Title'), + author: _('Author'), + publisher: _('Publisher'), + language: _('Language'), + subject: _('Subject'), }; return labels[name] || name; }; return ( -
+
-

{search.metadata?.title || 'Search'}

+

{search.metadata?.title || _('Search')}

{search.metadata?.description && (

{search.metadata.description}

)} @@ -81,7 +83,7 @@ export function SearchView({ search, baseURL, onNavigate, resolveURL }: SearchVi value={formData[param.name] || ''} onChange={(e) => handleInputChange(param.name, e.target.value)} required={param.required} - placeholder={`Enter ${getParamLabel(param.name).toLowerCase()}`} + placeholder={`${_('Enter {{terms}}', { terms: getParamLabel(param.name).toLowerCase() })}`} className='input input-bordered w-full' // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus={ @@ -96,7 +98,7 @@ export function SearchView({ search, baseURL, onNavigate, resolveURL }: SearchVi
diff --git a/apps/readest-app/src/app/opds/page.tsx b/apps/readest-app/src/app/opds/page.tsx index 00bcc9da..dab820a5 100644 --- a/apps/readest-app/src/app/opds/page.tsx +++ b/apps/readest-app/src/app/opds/page.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import { md5 } from 'js-md5'; -import { useEffect, useState, useCallback, useRef } from 'react'; +import { useEffect, useState, useCallback, useRef, useMemo } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { isOPDSCatalog, getPublication, getFeed, getOpenSearch } from 'foliate-js/opds.js'; import { openUrl } from '@tauri-apps/plugin-opener'; @@ -19,7 +19,7 @@ import { useLibrary } from '@/hooks/useLibrary'; import { eventDispatcher } from '@/utils/event'; import { getFileExtFromMimeType } from '@/libs/document'; import { OPDSFeed, OPDSPublication, OPDSSearch } from '@/types/opds'; -import { MIME, parseMediaType, resolveURL } from './utils/opdsUtils'; +import { isSearchLink, MIME, parseMediaType, resolveURL } from './utils/opdsUtils'; import { getProxiedURL, fetchWithAuth, probeAuth, needsProxy } from './utils/opdsReq'; import { FeedView } from './components/FeedView'; import { PublicationView } from './components/PublicationView'; @@ -75,7 +75,6 @@ export default function BrowserPage() { useTheme({ systemUIVisible: false }); - // Keep refs in sync with state useEffect(() => { startURLRef.current = state.startURL; }, [state.startURL]); @@ -104,7 +103,9 @@ export default function BrowserPage() { ); const loadOPDS = useCallback( - async (url: string, skipHistory = false) => { + async (url: string, options: { skipHistory?: boolean; isSearch?: boolean } = {}) => { + const { skipHistory = false, isSearch = false } = options; + if (loadingOPDSRef.current) return; loadingOPDSRef.current = true; @@ -118,15 +119,30 @@ export default function BrowserPage() { const res = await fetchWithAuth(url, username, password, useProxy); if (!res.ok) { - eventDispatcher.dispatch('toast', { - message: `Failed to load OPDS feed: ${res.status} ${res.statusText}`, - timeout: 5000, - type: 'error', - }); - setTimeout(() => { - router.back(); - }, 5000); - throw new Error(`Failed to load OPDS feed: ${res.status} ${res.statusText}`); + if (isSearch && res.status === 404) { + const warnMessage = _('No search results found'); + eventDispatcher.dispatch('toast', { + message: warnMessage, + timeout: 2000, + type: 'warning', + }); + setViewMode('search'); + return; + } else { + const errorMessage = _('Failed to load OPDS feed: {{status}} {{statusText}}', { + status: res.status, + statusText: res.statusText, + }); + eventDispatcher.dispatch('toast', { + message: errorMessage, + timeout: 5000, + type: 'error', + }); + setTimeout(() => { + router.back(); + }, 5000); + throw new Error(errorMessage); + } } const currentStartURL = startURLRef.current || url; @@ -232,7 +248,7 @@ export default function BrowserPage() { loadingOPDSRef.current = false; } }, - [router, addToHistory], + [_, router, addToHistory], ); useEffect(() => { @@ -260,15 +276,64 @@ export default function BrowserPage() { }, [searchParams, settings, libraryLoaded, loadOPDS]); const handleNavigate = useCallback( - (url: string) => { + (url: string, isSearch = false) => { const newURL = new URL(window.location.href); newURL.searchParams.set('url', url); window.history.pushState({}, '', newURL.toString()); - loadOPDS(url); + loadOPDS(url, { isSearch }); }, [loadOPDS], ); + const hasSearch = useMemo(() => { + return !!state.feed?.links?.find(isSearchLink); + }, [state.feed]); + + const handleSearch = useCallback(() => { + if (!state.feed) return; + + const searchLink = state.feed.links?.find(isSearchLink); + + if (searchLink && searchLink.href) { + const searchURL = resolveURL(searchLink.href, state.baseURL); + if (searchLink.type === MIME.OPENSEARCH) { + handleNavigate(searchURL, true); + } else if (searchLink.type === MIME.ATOM) { + const search: OPDSSearch = { + metadata: { + title: _('Search'), + description: state.feed.metadata?.title + ? _('Search in {{title}}', { title: state.feed.metadata.title }) + : undefined, + }, + params: [ + { + name: 'searchTerms', + required: true, + }, + ], + search: (map: Map>) => { + const defaultParams = map.get(null); + const searchTerms = defaultParams?.get('searchTerms') || ''; + const decodedURL = decodeURIComponent(searchURL); + return decodedURL.replace('{searchTerms}', encodeURIComponent(searchTerms)); + }, + }; + const newState: OPDSState = { + feed: state.feed, + search, + baseURL: state.baseURL, + currentURL: state.currentURL, + startURL: state.startURL, + }; + setState(newState); + setViewMode('search'); + setSelectedPublication(null); + addToHistory(state.currentURL, newState, 'search', null); + } + } + }, [_, state, handleNavigate, addToHistory]); + const handleDownload = useCallback( async ( href: string, @@ -457,8 +522,10 @@ export default function BrowserPage() { onNavigate={handleNavigate} onBack={handleBack} onForward={handleForward} + onSearch={handleSearch} canGoBack={canGoBack} canGoForward={canGoForward} + hasSearch={hasSearch} />
diff --git a/apps/readest-app/src/app/opds/utils/opdsUtils.ts b/apps/readest-app/src/app/opds/utils/opdsUtils.ts index 3fabb2bd..17a828fa 100644 --- a/apps/readest-app/src/app/opds/utils/opdsUtils.ts +++ b/apps/readest-app/src/app/opds/utils/opdsUtils.ts @@ -1,4 +1,5 @@ import { isOPDSCatalog } from 'foliate-js/opds.js'; +import { OPDSLink } from '@/types/opds'; import { fetchWithAuth } from './opdsReq'; export const groupByArray = (arr: T[] | undefined, f: (el: T) => K | K[]): Map => { @@ -66,6 +67,11 @@ export const parseMediaType = (str?: string) => { }; }; +export const isSearchLink = (link: OPDSLink): boolean => { + const rels = Array.isArray(link.rel) ? link.rel : [link.rel || '']; + return rels.includes('search') && (link.type === MIME.OPENSEARCH || link.type === MIME.ATOM); +}; + export const resolveURL = (url: string, relativeTo: string): string => { if (!url) return ''; if (relativeTo.includes('/api/opds/proxy?url=')) { diff --git a/apps/readest-app/src/types/opds.ts b/apps/readest-app/src/types/opds.ts index daacca03..b7e067be 100644 --- a/apps/readest-app/src/types/opds.ts +++ b/apps/readest-app/src/types/opds.ts @@ -19,6 +19,7 @@ export interface OPDSCatalog { name: string; url: string; description?: string; + disabled?: boolean; icon?: string; username?: string; password?: string; @@ -65,7 +66,7 @@ export interface OPDSSearch { } export interface OPDSLink { - rel?: string; + rel?: string | string[]; href: string; type?: string; title?: string; diff --git a/packages/foliate-js b/packages/foliate-js index 7ff1afba..771c2bcf 160000 --- a/packages/foliate-js +++ b/packages/foliate-js @@ -1 +1 @@ -Subproject commit 7ff1afba8e434beaafe2d902da014d7584030fb4 +Subproject commit 771c2bcf16ff387f8b8f6c6438b9c0a26334098f