From 2ca35610936f35a4e172206db8f59f1179ce0792 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 1 Dec 2025 04:22:54 +0800 Subject: [PATCH] feat(opds): support downloading ebooks from OPDS catalogs (#2571) --- apps/readest-app/eslint.config.mjs | 16 +- apps/readest-app/package.json | 2 + .../public/locales/ar/translation.json | 51 +- .../public/locales/bn/translation.json | 47 +- .../public/locales/bo/translation.json | 46 +- .../public/locales/de/translation.json | 47 +- .../public/locales/el/translation.json | 47 +- .../public/locales/es/translation.json | 48 +- .../public/locales/fa/translation.json | 47 +- .../public/locales/fr/translation.json | 48 +- .../public/locales/hi/translation.json | 47 +- .../public/locales/id/translation.json | 46 +- .../public/locales/it/translation.json | 48 +- .../public/locales/ja/translation.json | 46 +- .../public/locales/ko/translation.json | 46 +- .../public/locales/ms/translation.json | 46 +- .../public/locales/nl/translation.json | 47 +- .../public/locales/pl/translation.json | 49 +- .../public/locales/pt/translation.json | 48 +- .../public/locales/ru/translation.json | 49 +- .../public/locales/si/translation.json | 47 +- .../public/locales/sv/translation.json | 47 +- .../public/locales/ta/translation.json | 47 +- .../public/locales/th/translation.json | 46 +- .../public/locales/tr/translation.json | 47 +- .../public/locales/uk/translation.json | 49 +- .../public/locales/vi/translation.json | 46 +- .../public/locales/zh-CN/translation.json | 46 +- .../public/locales/zh-TW/translation.json | 46 +- .../src-tauri/src/transfer_file.rs | 65 ++- .../src/__tests__/helpers/supabase-mock.ts | 2 + .../src/app/api/opds/proxy/route.ts | 167 ++++++ apps/readest-app/src/app/auth/update/page.tsx | 4 +- .../app/library/components/BookshelfItem.tsx | 2 +- .../src/app/library/components/ImportMenu.tsx | 26 +- .../app/library/components/LibraryHeader.tsx | 33 +- .../src/app/library/components/OPDSDialog.tsx | 28 + apps/readest-app/src/app/library/page.tsx | 4 + .../src/app/opds/CatelogManager.tsx | 398 ++++++++++++++ apps/readest-app/src/app/opds/FeedView.tsx | 249 +++++++++ apps/readest-app/src/app/opds/Navigation.tsx | 92 ++++ .../src/app/opds/NavigationCard.tsx | 43 ++ .../src/app/opds/PublicationCard.tsx | 92 ++++ .../src/app/opds/PublicationView.tsx | 316 +++++++++++ apps/readest-app/src/app/opds/SearchView.tsx | 105 ++++ apps/readest-app/src/app/opds/page.tsx | 516 ++++++++++++++++++ .../readest-app/src/app/opds/utils/opdsReq.ts | 282 ++++++++++ .../src/app/opds/utils/opdsUtils.ts | 211 +++++++ .../src/app/reader/components/HeaderBar.tsx | 22 +- .../src/app/reader/components/Reader.tsx | 27 +- .../app/reader/components/sidebar/Header.tsx | 26 +- .../src/components/CachedImage.tsx | 112 ++++ apps/readest-app/src/components/Dropdown.tsx | 3 + apps/readest-app/src/hooks/useLibrary.ts | 29 + apps/readest-app/src/hooks/useTrafficLight.ts | 29 + apps/readest-app/src/libs/document.ts | 21 + apps/readest-app/src/libs/storage.ts | 19 +- apps/readest-app/src/pages/api/sync.ts | 2 +- apps/readest-app/src/services/appService.ts | 13 +- .../src/services/nativeAppService.ts | 5 +- .../src/services/transformers/sanitizer.ts | 4 +- .../src/services/tts/EdgeTTSClient.ts | 2 +- .../readest-app/src/services/webAppService.ts | 3 + apps/readest-app/src/store/customFontStore.ts | 1 + .../src/store/customTextureStore.ts | 1 + apps/readest-app/src/types/opds.ts | 125 +++++ apps/readest-app/src/types/settings.ts | 2 + apps/readest-app/src/types/system.ts | 3 + apps/readest-app/src/utils/cors.ts | 1 + apps/readest-app/src/utils/diff.ts | 4 +- apps/readest-app/src/utils/files.ts | 2 +- apps/readest-app/src/utils/path.ts | 10 +- apps/readest-app/src/utils/transfer.ts | 27 +- pnpm-lock.yaml | 211 ++++++- 74 files changed, 4450 insertions(+), 181 deletions(-) create mode 100644 apps/readest-app/src/app/api/opds/proxy/route.ts create mode 100644 apps/readest-app/src/app/library/components/OPDSDialog.tsx create mode 100644 apps/readest-app/src/app/opds/CatelogManager.tsx create mode 100644 apps/readest-app/src/app/opds/FeedView.tsx create mode 100644 apps/readest-app/src/app/opds/Navigation.tsx create mode 100644 apps/readest-app/src/app/opds/NavigationCard.tsx create mode 100644 apps/readest-app/src/app/opds/PublicationCard.tsx create mode 100644 apps/readest-app/src/app/opds/PublicationView.tsx create mode 100644 apps/readest-app/src/app/opds/SearchView.tsx create mode 100644 apps/readest-app/src/app/opds/page.tsx create mode 100644 apps/readest-app/src/app/opds/utils/opdsReq.ts create mode 100644 apps/readest-app/src/app/opds/utils/opdsUtils.ts create mode 100644 apps/readest-app/src/components/CachedImage.tsx create mode 100644 apps/readest-app/src/hooks/useLibrary.ts create mode 100644 apps/readest-app/src/hooks/useTrafficLight.ts create mode 100644 apps/readest-app/src/types/opds.ts diff --git a/apps/readest-app/eslint.config.mjs b/apps/readest-app/eslint.config.mjs index 05784df3..f04691a8 100644 --- a/apps/readest-app/eslint.config.mjs +++ b/apps/readest-app/eslint.config.mjs @@ -1,11 +1,25 @@ import { defineConfig, globalIgnores } from 'eslint/config'; +import next from 'eslint-config-next'; import nextVitals from 'eslint-config-next/core-web-vitals'; +import tseslint from 'eslint-config-next/typescript'; import jsxA11y from 'eslint-plugin-jsx-a11y'; const eslintConfig = defineConfig([ + ...tseslint, + ...next, ...nextVitals, { - rules: jsxA11y.configs.recommended.rules, + rules: { + ...jsxA11y.configs.recommended.rules, + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + }, }, globalIgnores([ 'node_modules/**', diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index 95e237fb..35dc101b 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -130,6 +130,8 @@ "@types/semver": "^7.7.0", "@types/tinycolor2": "^1.4.6", "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^8.48.0", + "@typescript-eslint/parser": "^8.48.0", "@vitejs/plugin-react": "^4.7.0", "autoprefixer": "^10.4.20", "caniuse-lite": "^1.0.30001746", diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 21e576ea..3544e4d0 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -691,5 +691,54 @@ "Download from Cloud": "تحميل من السحابة", "Upload to Cloud": "رفع إلى السحابة", "Clear Custom Fonts": "مسح الخطوط المخصصة", - "Columns": "الأعمدة" + "Columns": "الأعمدة", + "OPDS Catalogs": "كتالوجات OPDS", + "Adding LAN addresses is not supported in the web app version.": "لا يتم دعم إضافة عناوين LAN في إصدار تطبيق الويب.", + "Invalid OPDS catalog. Please check the URL.": "كتالوج OPDS غير صالح. يرجى التحقق من عنوان URL.", + "Browse and download books from online catalogs": "تصفح وتنزيل الكتب من الكتالوجات عبر الإنترنت", + "My Catalogs": "كتالوجاتي", + "Add Catalog": "إضافة كتالوج", + "No catalogs yet": "لا توجد كتالوجات بعد", + "Add your first OPDS catalog to start browsing books": "أضف كتالوج OPDS الأول لتبدأ في تصفح الكتب", + "Add Your First Catalog": "أضف كتالوجك الأول", + "Browse": "تصفح", + "Popular Catalogs": "الكتالوجات الشائعة", + "Add": "إضافة", + "Add OPDS Catalog": "إضافة كتالوج OPDS", + "Catalog Name": "اسم الكتالوج", + "My Calibre Library": "مكتبة Calibre الخاصة بي", + "OPDS URL": "رابط OPDS", + "Username (optional)": "اسم المستخدم (اختياري)", + "Password (optional)": "كلمة المرور (اختياري)", + "Description (optional)": "الوصف (اختياري)", + "A brief description of this catalog": "وصف موجز لهذا الكتالوج", + "Validating...": "جارٍ التحقق...", + "View All": "عرض الكل", + "Forward": "إلى الأمام", + "OPDS Catalog": "كتالوج OPDS", + "Home": "الصفحة الرئيسية", + "Library": "المكتبة", + "{{count}} items_zero": "{{count}} عناصر", + "{{count}} items_one": "{{count}} عنصر", + "{{count}} items_two": "{{count}} عنصران", + "{{count}} items_few": "{{count}} عناصر", + "{{count}} items_many": "{{count}} عنصرًا", + "{{count}} items_other": "{{count}} من العناصر", + "Download completed": "اكتمل التنزيل", + "Download failed": "فشل التنزيل", + "Open Access": "الوصول المفتوح", + "Borrow": "استعارة", + "Buy": "شراء", + "Subscribe": "الاشتراك", + "Sample": "عينة", + "Download": "تنزيل", + "Open & Read": "فتح وقراءة", + "Tags": "العلامات", + "Tag": "علامة", + "First": "الأول", + "Previous": "السابق", + "Next": "التالي", + "Last": "الأخير", + "Cannot Load Page": "تعذر تحميل الصفحة", + "An error occurred": "حدث خطأ ما" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index 2078f604..b79b27fc 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -675,5 +675,50 @@ "Download from Cloud": "ক্লাউড থেকে ডাউনলোড করুন", "Upload to Cloud": "ক্লাউডে আপলোড করুন", "Clear Custom Fonts": "কাস্টম ফন্টস মুছুন", - "Columns": "কলামস" + "Columns": "কলামস", + "OPDS Catalogs": "OPDS ক্যাটালগস", + "Adding LAN addresses is not supported in the web app version.": "ওয়েব অ্যাপ সংস্করণে LAN ঠিকানা যোগ করা সমর্থিত নয়।", + "Invalid OPDS catalog. Please check the URL.": "অবৈধ OPDS ক্যাটালগ। দয়া করে URL চেক করুন।", + "Browse and download books from online catalogs": "অনলাইন ক্যাটালগ থেকে বই ব্রাউজ এবং ডাউনলোড করুন", + "My Catalogs": "আমার ক্যাটালগস", + "Add Catalog": "ক্যাটালগ যোগ করুন", + "No catalogs yet": "এখনও কোন ক্যাটালগ নেই", + "Add your first OPDS catalog to start browsing books": "আপনার প্রথম OPDS ক্যাটালগ যোগ করুন বই ব্রাউজ শুরু করতে", + "Add Your First Catalog": "আপনার প্রথম ক্যাটালগ যোগ করুন", + "Browse": "ব্রাউজ করুন", + "Popular Catalogs": "জনপ্রিয় ক্যাটালগস", + "Add": "যোগ করুন", + "Add OPDS Catalog": "OPDS ক্যাটালগ যোগ করুন", + "Catalog Name": "ক্যাটালগের নাম", + "My Calibre Library": "আমার ক্যালিব্র লাইব্রেরি", + "OPDS URL": "OPDS URL", + "Username (optional)": "ব্যবহারকারীর নাম (ঐচ্ছিক)", + "Password (optional)": "পাসওয়ার্ড (ঐচ্ছিক)", + "Description (optional)": "বর্ণনা (ঐচ্ছিক)", + "A brief description of this catalog": "এই ক্যাটালগের সংক্ষিপ্ত বিবরণ", + "Validating...": "যাচাই করা হচ্ছে...", + "View All": "সব দেখুন", + "Forward": "ফরোয়ার্ড", + "OPDS Catalog": "OPDS ক্যাটালগ", + "Home": "হোম", + "Library": "লাইব্রেরি", + "{{count}} items_one": "{{count}} আইটেম", + "{{count}} items_other": "{{count}} আইটেম", + "Download completed": "ডাউনলোড সম্পন্ন হয়েছে", + "Download failed": "ডাউনলোড ব্যর্থ হয়েছে", + "Open Access": "ওপেন অ্যাক্সেস", + "Borrow": "ঋণ নিন", + "Buy": "কিনুন", + "Subscribe": "সাবস্ক্রাইব করুন", + "Sample": "নমুনা", + "Download": "ডাউনলোড", + "Open & Read": "খুলুন ও পড়ুন", + "Tags": "ট্যাগসমূহ", + "Tag": "ট্যাগ", + "First": "প্রথম", + "Previous": "পূর্ববর্তী", + "Next": "পরবর্তী", + "Last": "শেষ", + "Cannot Load Page": "পৃষ্ঠা লোড করা যায়নি", + "An error occurred": "একটি ত্রুটি ঘটেছে" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index 4933941a..67ba5fea 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -671,5 +671,49 @@ "Download from Cloud": "སྤྲིན་ནས་ཕབ་སྟེ་འབེབས་", "Upload to Cloud": "སྤྲིན་ལ་ཡར་སྤེལ་", "Clear Custom Fonts": "རང་བཞིན་ཡིག་གཟུགས་སུབ་པ།", - "Columns": "མཚན་ཉིད།" + "Columns": "མཚན་ཉིད།", + "OPDS Catalogs": "OPDS གནས་ཚུལ།", + "Adding LAN addresses is not supported in the web app version.": "གནས་ཚུལ་དང་ལུས་སྐོར་གྱི་གྲོང་ཁྱེར་གྱི་གློག་འཕྲིན་ལས་མཐུན་པ་མེད།", + "Invalid OPDS catalog. Please check the URL.": "མི་འབྱུང་བའི OPDS གནས་ཚུལ། ཨེ་ཨོ་ཨེར་ལུས་སྐོར་བཟོ་བྱས།", + "Browse and download books from online catalogs": "དེབ་གནས་བཅས་པའི་གྲོང་ཁྱེར་ལས་དེབ་འདེམས་དང་ཕབ་སྟེ་འབེབས།", + "My Catalogs": "ངའི་དཀར་ཆག", + "Add Catalog": "དཀར་ཆག་ཁ་སྣོན་", + "No catalogs yet": "དཀར་ཆག་མེད་པས།", + "Add your first OPDS catalog to start browsing books": "དེབ་ལྟ་བཤེར་འགོ་བཙུགས་ནས་ དང་པོའི་ OPDS དཀར་ཆག་ཁ་སྣོན་གནང་།", + "Add Your First Catalog": "དཀར་ཆག་དང་པོ་ཁ་སྣོན་", + "Browse": "ལྟ་བཤེར་", + "Popular Catalogs": "མི་མང་མཐོང་བའི་དཀར་ཆག", + "Add": "ཁ་སྣོན་", + "Add OPDS Catalog": "OPDS དཀར་ཆག་ཁ་སྣོན་", + "Catalog Name": "དཀར་ཆག་མིང་", + "My Calibre Library": "ངའི Calibre དེབ་མཛོད་", + "OPDS URL": "OPDS URL", + "Username (optional)": "མིང་སྒྲོམ་ (འདེམས་རུང་)", + "Password (optional)": "གསང་ཚིག (འདེམས་རུང་)", + "Description (optional)": "འགྲེལ་བཤད་ (འདེམས་རུང་)", + "A brief description of this catalog": "དཀར་ཆག་འདིའི་གསལ་བཤད་ཐུང་ཐུང་", + "Validating...": "བདེན་སྦྱོར་བཞིན་...", + "View All": "ཡོངས་ལྟ་བ་", + "Forward": "མདུན་དུ་", + "OPDS Catalog": "OPDS དཀར་ཆག", + "Home": "གཙོ་ངོས་", + "Library": "དེབ་མཛོད་", + "{{count}} items_other": "{{count}} རྣམ་གྲངས་", + "Download completed": "ཕབ་ལེན་རྫོགས་སོང་།", + "Download failed": "ཕབ་ལེན་ཕམ་པ་", + "Open Access": "ཁ་ཕྱབ་ལྟ་བཤེར་", + "Borrow": "ལེན་པ་", + "Buy": "ཉོ་", + "Subscribe": "མཁོ་མངགས་", + "Sample": "དཔེ་དབང་", + "Download": "ཕབ་ལེན་", + "Open & Read": "ཁ་ཕྱེས་ནས་ཀློག་", + "Tags": "མཚོན་འགྲེལ་", + "Tag": "མཚོན་འགྲེལ་", + "First": "དང་པོ།", + "Previous": "སྔོན་མ།", + "Next": "རྗེས་མ།", + "Last": "མཐའ་མ།", + "Cannot Load Page": "ཤོག་ངོས་འགུལ་སྐྱོང་བྱེད་ཐུབ་མེད།", + "An error occurred": "ནོར་འཁྲུལ་ཞིག་བྱུང་སོང་།" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 51cbc295..af177f98 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -675,5 +675,50 @@ "Download from Cloud": "Aus der Cloud herunterladen", "Upload to Cloud": "In die Cloud hochladen", "Clear Custom Fonts": "Benutzerdefinierte Schriftarten löschen", - "Columns": "Spalten" + "Columns": "Spalten", + "OPDS Catalogs": "OPDS-Kataloge", + "Adding LAN addresses is not supported in the web app version.": "Das Hinzufügen von LAN-Adressen wird in der Web-App nicht unterstützt.", + "Invalid OPDS catalog. Please check the URL.": "Ungültiger OPDS-Katalog. Bitte überprüfe die URL.", + "Browse and download books from online catalogs": "Bücher aus Online-Katalogen durchsuchen und herunterladen", + "My Catalogs": "Meine Kataloge", + "Add Catalog": "Katalog hinzufügen", + "No catalogs yet": "Noch keine Kataloge", + "Add your first OPDS catalog to start browsing books": "Füge deinen ersten OPDS-Katalog hinzu, um Bücher zu durchsuchen.", + "Add Your First Catalog": "Ersten Katalog hinzufügen", + "Browse": "Durchsuchen", + "Popular Catalogs": "Beliebte Kataloge", + "Add": "Hinzufügen", + "Add OPDS Catalog": "OPDS-Katalog hinzufügen", + "Catalog Name": "Katalogname", + "My Calibre Library": "Meine Calibre-Bibliothek", + "OPDS URL": "OPDS-URL", + "Username (optional)": "Benutzername (optional)", + "Password (optional)": "Passwort (optional)", + "Description (optional)": "Beschreibung (optional)", + "A brief description of this catalog": "Kurzbeschreibung dieses Katalogs", + "Validating...": "Wird überprüft...", + "View All": "Alle anzeigen", + "Forward": "Weiter", + "OPDS Catalog": "OPDS-Katalog", + "Home": "Start", + "Library": "Bibliothek", + "{{count}} items_one": "{{count}} Element", + "{{count}} items_other": "{{count}} Elemente", + "Download completed": "Download abgeschlossen", + "Download failed": "Download fehlgeschlagen", + "Open Access": "Offener Zugriff", + "Borrow": "Ausleihen", + "Buy": "Kaufen", + "Subscribe": "Abonnieren", + "Sample": "Leseprobe", + "Download": "Herunterladen", + "Open & Read": "Öffnen & Lesen", + "Tags": "Tags", + "Tag": "Tag", + "First": "Erste", + "Previous": "Vorherige", + "Next": "Nächste", + "Last": "Letzte", + "Cannot Load Page": "Seite kann nicht geladen werden", + "An error occurred": "Ein Fehler ist aufgetreten" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 8a6e4304..c8209699 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -675,5 +675,50 @@ "Download from Cloud": "Λήψη από το Cloud", "Upload to Cloud": "Ανέβασμα στο Cloud", "Clear Custom Fonts": "Καθαρισμός προσαρμοσμένων γραμματοσειρών", - "Columns": "Στήλες" + "Columns": "Στήλες", + "OPDS Catalogs": "Κατάλογοι OPDS", + "Adding LAN addresses is not supported in the web app version.": "Η προσθήκη διευθύνσεων LAN δεν υποστηρίζεται στην έκδοση web της εφαρμογής.", + "Invalid OPDS catalog. Please check the URL.": "Μη έγκυρος κατάλογος OPDS. Ελέγξτε το URL.", + "Browse and download books from online catalogs": "Περιηγηθείτε και κατεβάστε βιβλία από online καταλόγους", + "My Catalogs": "Οι Κατάλογοί μου", + "Add Catalog": "Προσθήκη Καταλόγου", + "No catalogs yet": "Δεν υπάρχουν ακόμα κατάλογοι", + "Add your first OPDS catalog to start browsing books": "Προσθέστε τον πρώτο σας κατάλογο OPDS για να αρχίσετε την περιήγηση.", + "Add Your First Catalog": "Προσθήκη Πρώτου Καταλόγου", + "Browse": "Περιήγηση", + "Popular Catalogs": "Δημοφιλείς Κατάλογοι", + "Add": "Προσθήκη", + "Add OPDS Catalog": "Προσθήκη Καταλόγου OPDS", + "Catalog Name": "Όνομα Καταλόγου", + "My Calibre Library": "Η Βιβλιοθήκη μου στο Calibre", + "OPDS URL": "OPDS URL", + "Username (optional)": "Όνομα χρήστη (προαιρετικό)", + "Password (optional)": "Κωδικός πρόσβασης (προαιρετικό)", + "Description (optional)": "Περιγραφή (προαιρετική)", + "A brief description of this catalog": "Μια σύντομη περιγραφή του καταλόγου", + "Validating...": "Γίνεται έλεγχος...", + "View All": "Προβολή όλων", + "Forward": "Μπροστά", + "OPDS Catalog": "Κατάλογος OPDS", + "Home": "Αρχική", + "Library": "Βιβλιοθήκη", + "{{count}} items_one": "{{count}} στοιχείο", + "{{count}} items_other": "{{count}} στοιχεία", + "Download completed": "Η λήψη ολοκληρώθηκε", + "Download failed": "Η λήψη απέτυχε", + "Open Access": "Ανοιχτή πρόσβαση", + "Borrow": "Δανεισμός", + "Buy": "Αγορά", + "Subscribe": "Συνδρομή", + "Sample": "Δείγμα", + "Download": "Λήψη", + "Open & Read": "Άνοιγμα & Ανάγνωση", + "Tags": "Ετικέτες", + "Tag": "Ετικέτα", + "First": "Πρώτο", + "Previous": "Προηγούμενο", + "Next": "Επόμενο", + "Last": "Τελευταίο", + "Cannot Load Page": "Δεν είναι δυνατή η φόρτωση της σελίδας", + "An error occurred": "Προέκυψε σφάλμα" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index a614a93c..98437a3c 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -679,5 +679,51 @@ "Download from Cloud": "Descargar desde la nube", "Upload to Cloud": "Subir a la nube", "Clear Custom Fonts": "Limpiar fuentes personalizadas", - "Columns": "Columnas" + "Columns": "Columnas", + "OPDS Catalogs": "Catálogos OPDS", + "Adding LAN addresses is not supported in the web app version.": "La versión web no permite añadir direcciones LAN.", + "Invalid OPDS catalog. Please check the URL.": "Catálogo OPDS no válido. Por favor, comprueba la URL.", + "Browse and download books from online catalogs": "Explora y descarga libros de catálogos en línea", + "My Catalogs": "Mis catálogos", + "Add Catalog": "Añadir catálogo", + "No catalogs yet": "Aún no hay catálogos", + "Add your first OPDS catalog to start browsing books": "Añade tu primer catálogo OPDS para empezar a explorar libros.", + "Add Your First Catalog": "Añadir primer catálogo", + "Browse": "Explorar", + "Popular Catalogs": "Catálogos populares", + "Add": "Añadir", + "Add OPDS Catalog": "Añadir catálogo OPDS", + "Catalog Name": "Nombre del catálogo", + "My Calibre Library": "Mi biblioteca Calibre", + "OPDS URL": "URL OPDS", + "Username (optional)": "Usuario (opcional)", + "Password (optional)": "Contraseña (opcional)", + "Description (optional)": "Descripción (opcional)", + "A brief description of this catalog": "Una breve descripción de este catálogo", + "Validating...": "Validando...", + "View All": "Ver todo", + "Forward": "Adelante", + "OPDS Catalog": "Catálogo OPDS", + "Home": "Inicio", + "Library": "Biblioteca", + "{{count}} items_one": "{{count}} elemento", + "{{count}} items_many": "{{count}} elementos", + "{{count}} items_other": "{{count}} elementos", + "Download completed": "Descarga completa", + "Download failed": "Error en la descarga", + "Open Access": "Acceso abierto", + "Borrow": "Prestar", + "Buy": "Comprar", + "Subscribe": "Suscribirse", + "Sample": "Muestra", + "Download": "Descargar", + "Open & Read": "Abrir y leer", + "Tags": "Etiquetas", + "Tag": "Etiqueta", + "First": "Primero", + "Previous": "Anterior", + "Next": "Siguiente", + "Last": "Último", + "Cannot Load Page": "No se puede cargar la página", + "An error occurred": "Ocurrió un error" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index bffd6174..5ed24f82 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -675,5 +675,50 @@ "Download from Cloud": "دانلود از فضای ابری", "Upload to Cloud": "آپلود به فضای ابری", "Clear Custom Fonts": "پاک‌کردن فونت‌های سفارشی", - "Columns": "ستون‌ها" + "Columns": "ستون‌ها", + "OPDS Catalogs": "فهرست‌های OPDS", + "Adding LAN addresses is not supported in the web app version.": "افزودن آدرس‌های LAN در نسخه وب پشتیبانی نمی‌شود.", + "Invalid OPDS catalog. Please check the URL.": "فهرست OPDS نامعتبر است. لطفاً آدرس را بررسی کنید.", + "Browse and download books from online catalogs": "مرور و دانلود کتاب‌ها از فهرست‌های آنلاین", + "My Catalogs": "فهرست‌های من", + "Add Catalog": "افزودن فهرست", + "No catalogs yet": "هنوز فهرستی وجود ندارد", + "Add your first OPDS catalog to start browsing books": "برای شروع مرور کتاب‌ها، اولین فهرست OPDS خود را اضافه کنید.", + "Add Your First Catalog": "افزودن اولین فهرست", + "Browse": "مرور", + "Popular Catalogs": "فهرست‌های محبوب", + "Add": "افزودن", + "Add OPDS Catalog": "افزودن فهرست OPDS", + "Catalog Name": "نام فهرست", + "My Calibre Library": "کتابخانه Calibre من", + "OPDS URL": "آدرس OPDS", + "Username (optional)": "نام کاربری (اختیاری)", + "Password (optional)": "رمز عبور (اختیاری)", + "Description (optional)": "توضیحات (اختیاری)", + "A brief description of this catalog": "توضیح کوتاهی درباره این فهرست", + "Validating...": "در حال بررسی...", + "View All": "مشاهده همه", + "Forward": "بعدی", + "OPDS Catalog": "فهرست OPDS", + "Home": "خانه", + "Library": "کتابخانه", + "{{count}} items_one": "{{count}} مورد", + "{{count}} items_other": "{{count}} مورد", + "Download completed": "دانلود کامل شد", + "Download failed": "دانلود ناموفق بود", + "Open Access": "دسترسی آزاد", + "Borrow": "امانت گرفتن", + "Buy": "خرید", + "Subscribe": "اشتراک", + "Sample": "نمونه", + "Download": "دانلود", + "Open & Read": "باز کردن و خواندن", + "Tags": "برچسب‌ها", + "Tag": "برچسب", + "First": "اولین", + "Previous": "قبلی", + "Next": "بعدی", + "Last": "آخرین", + "Cannot Load Page": "بارگذاری صفحه امکان‌پذیر نیست", + "An error occurred": "خطایی رخ داد" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 8cbb93ce..6af2de62 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -679,5 +679,51 @@ "Download from Cloud": "Télécharger depuis le Cloud", "Upload to Cloud": "Téléverser vers le Cloud", "Clear Custom Fonts": "Effacer les Polices Personnalisées", - "Columns": "Colonnes" + "Columns": "Colonnes", + "OPDS Catalogs": "Catalogues OPDS", + "Adding LAN addresses is not supported in the web app version.": "L’ajout d’adresses LAN n’est pas pris en charge dans la version web.", + "Invalid OPDS catalog. Please check the URL.": "Catalogue OPDS invalide. Veuillez vérifier l’URL.", + "Browse and download books from online catalogs": "Parcourez et téléchargez des livres depuis des catalogues en ligne", + "My Catalogs": "Mes catalogues", + "Add Catalog": "Ajouter un catalogue", + "No catalogs yet": "Aucun catalogue pour le moment", + "Add your first OPDS catalog to start browsing books": "Ajoutez votre premier catalogue OPDS pour commencer à parcourir des livres.", + "Add Your First Catalog": "Ajouter votre premier catalogue", + "Browse": "Parcourir", + "Popular Catalogs": "Catalogues populaires", + "Add": "Ajouter", + "Add OPDS Catalog": "Ajouter un catalogue OPDS", + "Catalog Name": "Nom du catalogue", + "My Calibre Library": "Ma bibliothèque Calibre", + "OPDS URL": "URL OPDS", + "Username (optional)": "Nom d’utilisateur (optionnel)", + "Password (optional)": "Mot de passe (optionnel)", + "Description (optional)": "Description (optionnelle)", + "A brief description of this catalog": "Brève description de ce catalogue", + "Validating...": "Validation...", + "View All": "Tout afficher", + "Forward": "Suivant", + "OPDS Catalog": "Catalogue OPDS", + "Home": "Accueil", + "Library": "Bibliothèque", + "{{count}} items_one": "{{count}} élément", + "{{count}} items_many": "{{count}} éléments", + "{{count}} items_other": "{{count}} éléments", + "Download completed": "Téléchargement terminé", + "Download failed": "Échec du téléchargement", + "Open Access": "Accès libre", + "Borrow": "Emprunter", + "Buy": "Acheter", + "Subscribe": "S’abonner", + "Sample": "Extrait", + "Download": "Télécharger", + "Open & Read": "Ouvrir et lire", + "Tags": "Tags", + "Tag": "Tag", + "First": "Premier", + "Previous": "Précédent", + "Next": "Suivant", + "Last": "Dernier", + "Cannot Load Page": "Impossible de charger la page", + "An error occurred": "Une erreur est survenue" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index df88fad1..9d31ea66 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -675,5 +675,50 @@ "Download from Cloud": "क्लाउड से डाउनलोड करें", "Upload to Cloud": "क्लाउड पर अपलोड करें", "Clear Custom Fonts": "कस्टम फ़ॉन्ट साफ़ करें", - "Columns": "कॉलम" + "Columns": "कॉलम", + "OPDS Catalogs": "OPDS कैटलॉग", + "Adding LAN addresses is not supported in the web app version.": "वेब ऐप संस्करण में LAN पता जोड़ना समर्थित नहीं है।", + "Invalid OPDS catalog. Please check the URL.": "अमान्य OPDS कैटलॉग। कृपया URL जांचें।", + "Browse and download books from online catalogs": "ऑनलाइन कैटलॉग से किताबें ब्राउज़ और डाउनलोड करें", + "My Catalogs": "मेरे कैटलॉग", + "Add Catalog": "कैटलॉग जोड़ें", + "No catalogs yet": "अभी कोई कैटलॉग नहीं", + "Add your first OPDS catalog to start browsing books": "किताबें ब्राउज़ करने के लिए अपना पहला OPDS कैटलॉग जोड़ें", + "Add Your First Catalog": "अपना पहला कैटलॉग जोड़ें", + "Browse": "ब्राउज़", + "Popular Catalogs": "लोकप्रिय कैटलॉग", + "Add": "जोड़ें", + "Add OPDS Catalog": "OPDS कैटलॉग जोड़ें", + "Catalog Name": "कैटलॉग नाम", + "My Calibre Library": "मेरी Calibre लाइब्रेरी", + "OPDS URL": "OPDS URL", + "Username (optional)": "यूज़रनेम (वैकल्पिक)", + "Password (optional)": "पासवर्ड (वैकल्पिक)", + "Description (optional)": "विवरण (वैकल्पिक)", + "A brief description of this catalog": "इस कैटलॉग का संक्षिप्त विवरण", + "Validating...": "मान्य किया जा रहा है...", + "View All": "सभी देखें", + "Forward": "आगे", + "OPDS Catalog": "OPDS कैटलॉग", + "Home": "होम", + "Library": "लाइब्रेरी", + "{{count}} items_one": "{{count}} आइटम", + "{{count}} items_other": "{{count}} आइटम", + "Download completed": "डाउनलोड पूरा हुआ", + "Download failed": "डाउनलोड विफल हुआ", + "Open Access": "ओपन एक्सेस", + "Borrow": "उधार लें", + "Buy": "खरीदें", + "Subscribe": "सदस्यता लें", + "Sample": "नमूना", + "Download": "डाउनलोड", + "Open & Read": "खोलें और पढ़ें", + "Tags": "टैग", + "Tag": "टैग", + "First": "पहला", + "Previous": "पिछला", + "Next": "अगला", + "Last": "अंतिम", + "Cannot Load Page": "पृष्ठ लोड नहीं किया जा सका", + "An error occurred": "एक त्रुटि हुई" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 61e17e9a..bf6eec8b 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -671,5 +671,49 @@ "Download from Cloud": "Unduh dari Cloud", "Upload to Cloud": "Unggah ke Cloud", "Clear Custom Fonts": "Bersihkan Font Kustom", - "Columns": "Kolom" + "Columns": "Kolom", + "OPDS Catalogs": "Katalog OPDS", + "Adding LAN addresses is not supported in the web app version.": "Menambahkan alamat LAN tidak didukung di versi web.", + "Invalid OPDS catalog. Please check the URL.": "Katalog OPDS tidak valid. Silakan periksa URL-nya.", + "Browse and download books from online catalogs": "Jelajahi dan unduh buku dari katalog online", + "My Catalogs": "Katalog Saya", + "Add Catalog": "Tambah Katalog", + "No catalogs yet": "Belum ada katalog", + "Add your first OPDS catalog to start browsing books": "Tambahkan katalog OPDS pertama Anda untuk mulai menjelajahi buku", + "Add Your First Catalog": "Tambah Katalog Pertama Anda", + "Browse": "Jelajahi", + "Popular Catalogs": "Katalog Populer", + "Add": "Tambah", + "Add OPDS Catalog": "Tambah Katalog OPDS", + "Catalog Name": "Nama Katalog", + "My Calibre Library": "Perpustakaan Calibre Saya", + "OPDS URL": "URL OPDS", + "Username (optional)": "Username (opsional)", + "Password (optional)": "Password (opsional)", + "Description (optional)": "Deskripsi (opsional)", + "A brief description of this catalog": "Deskripsi singkat untuk katalog ini", + "Validating...": "Memvalidasi...", + "View All": "Lihat Semua", + "Forward": "Maju", + "OPDS Catalog": "Katalog OPDS", + "Home": "Beranda", + "Library": "Perpustakaan", + "{{count}} items_other": "{{count}} item", + "Download completed": "Unduhan selesai", + "Download failed": "Unduhan gagal", + "Open Access": "Akses Terbuka", + "Borrow": "Pinjam", + "Buy": "Beli", + "Subscribe": "Berlangganan", + "Sample": "Contoh", + "Download": "Unduh", + "Open & Read": "Buka & Baca", + "Tags": "Tag", + "Tag": "Tag", + "First": "Pertama", + "Previous": "Sebelumnya", + "Next": "Berikutnya", + "Last": "Terakhir", + "Cannot Load Page": "Tidak dapat memuat halaman", + "An error occurred": "Terjadi kesalahan" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 9fb6b34a..fe314f0a 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -679,5 +679,51 @@ "Download from Cloud": "Scarica dal Cloud", "Upload to Cloud": "Carica sul Cloud", "Clear Custom Fonts": "Pulisci Font Personalizzati", - "Columns": "Colonne" + "Columns": "Colonne", + "OPDS Catalogs": "Cataloghi OPDS", + "Adding LAN addresses is not supported in the web app version.": "L'aggiunta di indirizzi LAN non è supportata nella versione web.", + "Invalid OPDS catalog. Please check the URL.": "Catalogo OPDS non valido. Controlla l'URL.", + "Browse and download books from online catalogs": "Sfoglia e scarica libri dai cataloghi online", + "My Catalogs": "I miei cataloghi", + "Add Catalog": "Aggiungi catalogo", + "No catalogs yet": "Nessun catalogo", + "Add your first OPDS catalog to start browsing books": "Aggiungi il tuo primo catalogo OPDS per iniziare a sfogliare i libri", + "Add Your First Catalog": "Aggiungi il tuo primo catalogo", + "Browse": "Sfoglia", + "Popular Catalogs": "Cataloghi popolari", + "Add": "Aggiungi", + "Add OPDS Catalog": "Aggiungi catalogo OPDS", + "Catalog Name": "Nome del catalogo", + "My Calibre Library": "La mia libreria Calibre", + "OPDS URL": "URL OPDS", + "Username (optional)": "Username (opzionale)", + "Password (optional)": "Password (opzionale)", + "Description (optional)": "Descrizione (opzionale)", + "A brief description of this catalog": "Breve descrizione del catalogo", + "Validating...": "Convalida in corso...", + "View All": "Vedi tutto", + "Forward": "Avanti", + "OPDS Catalog": "Catalogo OPDS", + "Home": "Home", + "Library": "Libreria", + "{{count}} items_one": "{{count}} elemento", + "{{count}} items_many": "{{count}} elementi", + "{{count}} items_other": "{{count}} elementi", + "Download completed": "Download completato", + "Download failed": "Download non riuscito", + "Open Access": "Accesso libero", + "Borrow": "Prendi in prestito", + "Buy": "Acquista", + "Subscribe": "Abbonati", + "Sample": "Anteprima", + "Download": "Scarica", + "Open & Read": "Apri e leggi", + "Tags": "Tag", + "Tag": "Tag", + "First": "Primo", + "Previous": "Precedente", + "Next": "Successivo", + "Last": "Ultimo", + "Cannot Load Page": "Impossibile caricare la pagina", + "An error occurred": "Si è verificato un errore" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 88f8efe8..0f474263 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -671,5 +671,49 @@ "Download from Cloud": "クラウドからダウンロード", "Upload to Cloud": "クラウドにアップロード", "Clear Custom Fonts": "カスタムフォントをクリア", - "Columns": "列" + "Columns": "列", + "OPDS Catalogs": "OPDSカタログ", + "Adding LAN addresses is not supported in the web app version.": "Webアプリ版ではLANアドレスの追加はサポートされていません。", + "Invalid OPDS catalog. Please check the URL.": "無効なOPDSカタログです。URLを確認してください。", + "Browse and download books from online catalogs": "オンラインカタログから本を閲覧・ダウンロード", + "My Catalogs": "マイカタログ", + "Add Catalog": "カタログを追加", + "No catalogs yet": "まだカタログはありません", + "Add your first OPDS catalog to start browsing books": "本を閲覧するには、最初のOPDSカタログを追加してください。", + "Add Your First Catalog": "最初のカタログを追加", + "Browse": "閲覧", + "Popular Catalogs": "人気カタログ", + "Add": "追加", + "Add OPDS Catalog": "OPDSカタログを追加", + "Catalog Name": "カタログ名", + "My Calibre Library": "私のCalibreライブラリ", + "OPDS URL": "OPDS URL", + "Username (optional)": "ユーザー名(任意)", + "Password (optional)": "パスワード(任意)", + "Description (optional)": "説明(任意)", + "A brief description of this catalog": "このカタログの簡単な説明", + "Validating...": "検証中...", + "View All": "すべて表示", + "Forward": "進む", + "OPDS Catalog": "OPDSカタログ", + "Home": "ホーム", + "Library": "ライブラリ", + "{{count}} items_other": "{{count}} 件", + "Download completed": "ダウンロード完了", + "Download failed": "ダウンロード失敗", + "Open Access": "オープンアクセス", + "Borrow": "借りる", + "Buy": "購入", + "Subscribe": "購読", + "Sample": "サンプル", + "Download": "ダウンロード", + "Open & Read": "開いて読む", + "Tags": "タグ", + "Tag": "タグ", + "First": "最初", + "Previous": "前へ", + "Next": "次へ", + "Last": "最後", + "Cannot Load Page": "ページを読み込めません", + "An error occurred": "エラーが発生しました" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index dbc9f633..0e0212f0 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -671,5 +671,49 @@ "Download from Cloud": "클라우드에서 다운로드", "Upload to Cloud": "클라우드에 업로드", "Clear Custom Fonts": "사용자 정의 글꼴 지우기", - "Columns": "열" + "Columns": "열", + "OPDS Catalogs": "OPDS 카탈로그", + "Adding LAN addresses is not supported in the web app version.": "웹 앱 버전에서는 LAN 주소 추가를 지원하지 않습니다.", + "Invalid OPDS catalog. Please check the URL.": "잘못된 OPDS 카탈로그입니다. URL을 확인해주세요.", + "Browse and download books from online catalogs": "온라인 카탈로그에서 책을 탐색하고 다운로드", + "My Catalogs": "내 카탈로그", + "Add Catalog": "카탈로그 추가", + "No catalogs yet": "아직 카탈로그가 없습니다", + "Add your first OPDS catalog to start browsing books": "책을 탐색하려면 첫 번째 OPDS 카탈로그를 추가하세요.", + "Add Your First Catalog": "첫 번째 카탈로그 추가", + "Browse": "탐색", + "Popular Catalogs": "인기 카탈로그", + "Add": "추가", + "Add OPDS Catalog": "OPDS 카탈로그 추가", + "Catalog Name": "카탈로그 이름", + "My Calibre Library": "내 Calibre 라이브러리", + "OPDS URL": "OPDS URL", + "Username (optional)": "사용자 이름 (선택 사항)", + "Password (optional)": "비밀번호 (선택 사항)", + "Description (optional)": "설명 (선택 사항)", + "A brief description of this catalog": "이 카탈로그의 간단한 설명", + "Validating...": "검증 중...", + "View All": "모두 보기", + "Forward": "다음", + "OPDS Catalog": "OPDS 카탈로그", + "Home": "홈", + "Library": "라이브러리", + "{{count}} items_other": "{{count}}개 항목", + "Download completed": "다운로드 완료", + "Download failed": "다운로드 실패", + "Open Access": "오픈 액세스", + "Borrow": "대출", + "Buy": "구매", + "Subscribe": "구독", + "Sample": "샘플", + "Download": "다운로드", + "Open & Read": "열기 및 읽기", + "Tags": "태그", + "Tag": "태그", + "First": "처음", + "Previous": "이전", + "Next": "다음", + "Last": "마지막", + "Cannot Load Page": "페이지를 불러올 수 없습니다", + "An error occurred": "오류가 발생했습니다" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index 7fdd8a7a..4cc4f0c5 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -671,5 +671,49 @@ "Reveal in Finder": "Tunjukkan dalam Finder", "Reveal in File Explorer": "Tunjukkan dalam File Explorer", "Reveal in Folder": "Tunjukkan dalam Folder", - "Columns": "Lajur" + "Columns": "Lajur", + "OPDS Catalogs": "Katalog OPDS", + "Adding LAN addresses is not supported in the web app version.": "Menambah alamat LAN tidak disokong dalam versi web.", + "Invalid OPDS catalog. Please check the URL.": "Katalog OPDS tidak sah. Sila semak URL.", + "Browse and download books from online catalogs": "Terokai dan muat turun buku dari katalog dalam talian", + "My Catalogs": "Katalog Saya", + "Add Catalog": "Tambah Katalog", + "No catalogs yet": "Tiada katalog lagi", + "Add your first OPDS catalog to start browsing books": "Tambah katalog OPDS pertama anda untuk mula meneroka buku", + "Add Your First Catalog": "Tambah Katalog Pertama Anda", + "Browse": "Terokai", + "Popular Catalogs": "Katalog Popular", + "Add": "Tambah", + "Add OPDS Catalog": "Tambah Katalog OPDS", + "Catalog Name": "Nama Katalog", + "My Calibre Library": "Perpustakaan Calibre Saya", + "OPDS URL": "URL OPDS", + "Username (optional)": "Nama Pengguna (pilihan)", + "Password (optional)": "Kata Laluan (pilihan)", + "Description (optional)": "Penerangan (pilihan)", + "A brief description of this catalog": "Penerangan ringkas katalog ini", + "Validating...": "Mengesahkan...", + "View All": "Lihat Semua", + "Forward": "Maju", + "OPDS Catalog": "Katalog OPDS", + "Home": "Laman Utama", + "Library": "Perpustakaan", + "{{count}} items_other": "{{count}} item", + "Download completed": "Muat turun selesai", + "Download failed": "Muat turun gagal", + "Open Access": "Akses Terbuka", + "Borrow": "Pinjam", + "Buy": "Beli", + "Subscribe": "Langgan", + "Sample": "Contoh", + "Download": "Muat turun", + "Open & Read": "Buka & Baca", + "Tags": "Tag", + "Tag": "Tag", + "First": "Pertama", + "Previous": "Sebelumnya", + "Next": "Seterusnya", + "Last": "Terakhir", + "Cannot Load Page": "Tidak dapat memuatkan halaman", + "An error occurred": "Ralat telah berlaku" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 40e82415..01bae6bb 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -675,5 +675,50 @@ "Download from Cloud": "Downloaden van Cloud", "Upload to Cloud": "Uploaden naar Cloud", "Clear Custom Fonts": "Aangepaste Lettertypen Wissen", - "Columns": "Kolommen" + "Columns": "Kolommen", + "OPDS Catalogs": "OPDS-catalogi", + "Adding LAN addresses is not supported in the web app version.": "Het toevoegen van LAN-adressen wordt niet ondersteund in de webversie.", + "Invalid OPDS catalog. Please check the URL.": "Ongeldige OPDS-catalogus. Controleer de URL.", + "Browse and download books from online catalogs": "Blader door en download boeken uit online catalogi", + "My Catalogs": "Mijn catalogi", + "Add Catalog": "Catalogus toevoegen", + "No catalogs yet": "Nog geen catalogi", + "Add your first OPDS catalog to start browsing books": "Voeg je eerste OPDS-catalogus toe om boeken te bekijken", + "Add Your First Catalog": "Voeg je eerste catalogus toe", + "Browse": "Bladeren", + "Popular Catalogs": "Populaire catalogi", + "Add": "Toevoegen", + "Add OPDS Catalog": "OPDS-catalogus toevoegen", + "Catalog Name": "Catalogusnaam", + "My Calibre Library": "Mijn Calibre-bibliotheek", + "OPDS URL": "OPDS-URL", + "Username (optional)": "Gebruikersnaam (optioneel)", + "Password (optional)": "Wachtwoord (optioneel)", + "Description (optional)": "Beschrijving (optioneel)", + "A brief description of this catalog": "Een korte beschrijving van deze catalogus", + "Validating...": "Valideren...", + "View All": "Alles bekijken", + "Forward": "Verder", + "OPDS Catalog": "OPDS-catalogus", + "Home": "Startpagina", + "Library": "Bibliotheek", + "{{count}} items_one": "{{count}} item", + "{{count}} items_other": "{{count}} items", + "Download completed": "Download voltooid", + "Download failed": "Download mislukt", + "Open Access": "Open toegang", + "Borrow": "Lenen", + "Buy": "Kopen", + "Subscribe": "Abonneren", + "Sample": "Voorbeeld", + "Download": "Downloaden", + "Open & Read": "Openen & Lezen", + "Tags": "Tags", + "Tag": "Tag", + "First": "Eerste", + "Previous": "Vorige", + "Next": "Volgende", + "Last": "Laatste", + "Cannot Load Page": "Pagina kan niet worden geladen", + "An error occurred": "Er is een fout opgetreden" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index bba7fd87..03e5479d 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -683,5 +683,52 @@ "Download from Cloud": "Pobierz z chmury", "Upload to Cloud": "Prześlij do chmury", "Clear Custom Fonts": "Wyczyść niestandardowe czcionki", - "Columns": "Kolumny" + "Columns": "Kolumny", + "OPDS Catalogs": "Katalogi OPDS", + "Adding LAN addresses is not supported in the web app version.": "Dodawanie adresów LAN nie jest obsługiwane w wersji webowej.", + "Invalid OPDS catalog. Please check the URL.": "Nieprawidłowy katalog OPDS. Sprawdź URL.", + "Browse and download books from online catalogs": "Przeglądaj i pobieraj książki z katalogów online", + "My Catalogs": "Moje katalogi", + "Add Catalog": "Dodaj katalog", + "No catalogs yet": "Brak katalogów", + "Add your first OPDS catalog to start browsing books": "Dodaj pierwszy katalog OPDS, aby rozpocząć przeglądanie książek", + "Add Your First Catalog": "Dodaj pierwszy katalog", + "Browse": "Przeglądaj", + "Popular Catalogs": "Popularne katalogi", + "Add": "Dodaj", + "Add OPDS Catalog": "Dodaj katalog OPDS", + "Catalog Name": "Nazwa katalogu", + "My Calibre Library": "Moja biblioteka Calibre", + "OPDS URL": "URL OPDS", + "Username (optional)": "Nazwa użytkownika (opcjonalnie)", + "Password (optional)": "Hasło (opcjonalnie)", + "Description (optional)": "Opis (opcjonalnie)", + "A brief description of this catalog": "Krótki opis katalogu", + "Validating...": "Weryfikacja...", + "View All": "Pokaż wszystkie", + "Forward": "Dalej", + "OPDS Catalog": "Katalog OPDS", + "Home": "Strona główna", + "Library": "Biblioteka", + "{{count}} items_one": "{{count}} element", + "{{count}} items_few": "{{count}} elementy", + "{{count}} items_many": "{{count}} elementów", + "{{count}} items_other": "{{count}} elementu", + "Download completed": "Pobieranie zakończone", + "Download failed": "Pobieranie nie powiodło się", + "Open Access": "Dostęp otwarty", + "Borrow": "Wypożycz", + "Buy": "Kup", + "Subscribe": "Subskrybuj", + "Sample": "Próbka", + "Download": "Pobierz", + "Open & Read": "Otwórz i czytaj", + "Tags": "Tagi", + "Tag": "Tag", + "First": "Pierwsza", + "Previous": "Poprzednia", + "Next": "Następna", + "Last": "Ostatnia", + "Cannot Load Page": "Nie można załadować strony", + "An error occurred": "Wystąpił błąd" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index ec894bd8..72d78d72 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -679,5 +679,51 @@ "Download from Cloud": "Baixar da Nuvem", "Upload to Cloud": "Enviar para a Nuvem", "Clear Custom Fonts": "Limpar Fontes Personalizadas", - "Columns": "Colunas" + "Columns": "Colunas", + "OPDS Catalogs": "Catálogos OPDS", + "Adding LAN addresses is not supported in the web app version.": "Adicionar endereços LAN não é suportado na versão web.", + "Invalid OPDS catalog. Please check the URL.": "Catálogo OPDS inválido. Verifique o URL.", + "Browse and download books from online catalogs": "Navegar e baixar livros de catálogos online", + "My Catalogs": "Meus catálogos", + "Add Catalog": "Adicionar catálogo", + "No catalogs yet": "Nenhum catálogo ainda", + "Add your first OPDS catalog to start browsing books": "Adicione seu primeiro catálogo OPDS para começar a navegar pelos livros", + "Add Your First Catalog": "Adicione seu primeiro catálogo", + "Browse": "Navegar", + "Popular Catalogs": "Catálogos populares", + "Add": "Adicionar", + "Add OPDS Catalog": "Adicionar catálogo OPDS", + "Catalog Name": "Nome do catálogo", + "My Calibre Library": "Minha biblioteca Calibre", + "OPDS URL": "URL do OPDS", + "Username (optional)": "Nome de usuário (opcional)", + "Password (optional)": "Senha (opcional)", + "Description (optional)": "Descrição (opcional)", + "A brief description of this catalog": "Uma breve descrição deste catálogo", + "Validating...": "Validando...", + "View All": "Ver todos", + "Forward": "Avançar", + "OPDS Catalog": "Catálogo OPDS", + "Home": "Início", + "Library": "Biblioteca", + "{{count}} items_one": "{{count}} item", + "{{count}} items_many": "{{count}} itens", + "{{count}} items_other": "{{count}} item", + "Download completed": "Download concluído", + "Download failed": "Falha no download", + "Open Access": "Acesso aberto", + "Borrow": "Emprestar", + "Buy": "Comprar", + "Subscribe": "Assinar", + "Sample": "Amostra", + "Download": "Baixar", + "Open & Read": "Abrir e ler", + "Tags": "Etiquetas", + "Tag": "Etiqueta", + "First": "Pierwsza", + "Previous": "Poprzednia", + "Next": "Następna", + "Last": "Ostatnia", + "Cannot Load Page": "Nie można załadować strony", + "An error occurred": "Wystąpił błąd" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index bafc0017..c68abd66 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -683,5 +683,52 @@ "Download from Cloud": "Скачать из облака", "Upload to Cloud": "Загрузить в облако", "Clear Custom Fonts": "Очистить пользовательские шрифты", - "Columns": "Колонки" + "Columns": "Колонки", + "OPDS Catalogs": "Каталоги OPDS", + "Adding LAN addresses is not supported in the web app version.": "Добавление LAN-адресов не поддерживается в веб-версии.", + "Invalid OPDS catalog. Please check the URL.": "Недействительный каталог OPDS. Пожалуйста, проверьте URL.", + "Browse and download books from online catalogs": "Просматривать и скачивать книги из онлайн-каталогов", + "My Catalogs": "Мои каталоги", + "Add Catalog": "Добавить каталог", + "No catalogs yet": "Каталоги отсутствуют", + "Add your first OPDS catalog to start browsing books": "Добавьте первый каталог OPDS, чтобы начать просмотр книг", + "Add Your First Catalog": "Добавьте первый каталог", + "Browse": "Просмотр", + "Popular Catalogs": "Популярные каталоги", + "Add": "Добавить", + "Add OPDS Catalog": "Добавить каталог OPDS", + "Catalog Name": "Название каталога", + "My Calibre Library": "Моя библиотека Calibre", + "OPDS URL": "URL OPDS", + "Username (optional)": "Имя пользователя (необязательно)", + "Password (optional)": "Пароль (необязательно)", + "Description (optional)": "Описание (необязательно)", + "A brief description of this catalog": "Краткое описание каталога", + "Validating...": "Проверка...", + "View All": "Посмотреть все", + "Forward": "Вперёд", + "OPDS Catalog": "Каталог OPDS", + "Home": "Главная", + "Library": "Библиотека", + "{{count}} items_one": "{{count}} элемент", + "{{count}} items_few": "{{count}} элемента", + "{{count}} items_many": "{{count}} элементов", + "{{count}} items_other": "{{count}} элемента", + "Download completed": "Загрузка завершена", + "Download failed": "Загрузка не удалась", + "Open Access": "Открытый доступ", + "Borrow": "Взять в аренду", + "Buy": "Купить", + "Subscribe": "Подписаться", + "Sample": "Образец", + "Download": "Скачать", + "Open & Read": "Открыть и читать", + "Tags": "Теги", + "Tag": "Тег", + "First": "Первая", + "Previous": "Предыдущая", + "Next": "Следующая", + "Last": "Последняя", + "Cannot Load Page": "Не удалось загрузить страницу", + "An error occurred": "Произошла ошибка" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 20f5d22a..58c9a9d0 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -675,5 +675,50 @@ "Download from Cloud": "කලාඬයෙන් බාගත කරන්න", "Upload to Cloud": "කලාඬයට උඩුගත කරන්න", "Clear Custom Fonts": "අභිරුචි අකුරු පැහැදිලි කරන්න", - "Columns": "තීරු" + "Columns": "තීරු", + "OPDS Catalogs": "OPDS දත්තසමුදා", + "Adding LAN addresses is not supported in the web app version.": "වෙබ් යෙදුම් අනුවාදයේ LAN ලිපින එක් කිරීම සහය නොදක්වයි.", + "Invalid OPDS catalog. Please check the URL.": "අවලංගු OPDS දත්තසමුදායකි. කරුණාකර URL පරීක්ෂා කරන්න.", + "Browse and download books from online catalogs": "ඔන්ලයින් දත්තසමුදා වලින් පොත් බ්‍රවුස් සහ බාගන්න", + "My Catalogs": "මගේ දත්තසමුදා", + "Add Catalog": "දත්තසමුදා එක් කරන්න", + "No catalogs yet": "තවම දත්තසමුදා නැත", + "Add your first OPDS catalog to start browsing books": "පොත් බ්‍රවුස් කිරීමට ඔබේ පළමු OPDS දත්තසමුදා එක් කරන්න", + "Add Your First Catalog": "ඔබේ පළමු දත්තසමුදා එක් කරන්න", + "Browse": "බ්‍රවුස් කරන්න", + "Popular Catalogs": "ප්‍රසිද්ධ දත්තසමුදා", + "Add": "එකතු කරන්න", + "Add OPDS Catalog": "OPDS දත්තසමුදා එක් කරන්න", + "Catalog Name": "දත්තසමුදා නාමය", + "My Calibre Library": "මගේ Calibre පුස්තකාලය", + "OPDS URL": "OPDS URL", + "Username (optional)": "පරිශීලක නාමය (අත්‍යාවශ්‍ය නොවේ)", + "Password (optional)": "මුරපදය (අත්‍යාවශ්‍ය නොවේ)", + "Description (optional)": "විස්තරය (අත්‍යාවශ්‍ය නොවේ)", + "A brief description of this catalog": "මෙම දත්තසමුදායේ කෙටි විස්තරයක්", + "Validating...": "තහවුරු කරමින්...", + "View All": "සියල්ල බලන්න", + "Forward": "ඉදිරියට", + "OPDS Catalog": "OPDS දත්තසමුදා", + "Home": "මුල් පිටුව", + "Library": "පුස්තකාලය", + "{{count}} items_one": "{{count}} අයිතමය", + "{{count}} items_other": "{{count}} අයිතම", + "Download completed": "බාගත කිරීම සම්පූර්ණයි", + "Download failed": "බාගත කිරීම අසාර්ථකයි", + "Open Access": "විවෘත ප්‍රවේශය", + "Borrow": "උधාර ගන්න", + "Buy": "ගන්න", + "Subscribe": "දායක වන්න", + "Sample": "නම්නය", + "Download": "බාගන්න", + "Open & Read": "විවෘත කර කියවන්න", + "Tags": "ටැග්", + "Tag": "ටැග්", + "First": "පළමුවා", + "Previous": "පෙර", + "Next": "ඊළඟ", + "Last": "අවසාන", + "Cannot Load Page": "පිටුවට ප්‍රවේශ විය නොහැක", + "An error occurred": "දෝෂයක් සිදු විය" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index b9b6374f..a546fdfa 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -675,5 +675,50 @@ "Download from Cloud": "Ladda ner från molnet", "Upload to Cloud": "Ladda upp till molnet", "Clear Custom Fonts": "Rensa anpassade typsnitt", - "Columns": "Kolumner" + "Columns": "Kolumner", + "OPDS Catalogs": "OPDS-kataloger", + "Adding LAN addresses is not supported in the web app version.": "Att lägga till LAN-adresser stöds inte i webbappen.", + "Invalid OPDS catalog. Please check the URL.": "Ogiltig OPDS-katalog. Kontrollera URL:en.", + "Browse and download books from online catalogs": "Bläddra och ladda ner böcker från onlinekataloger", + "My Catalogs": "Mina kataloger", + "Add Catalog": "Lägg till katalog", + "No catalogs yet": "Inga kataloger än", + "Add your first OPDS catalog to start browsing books": "Lägg till din första OPDS-katalog för att börja bläddra i böcker", + "Add Your First Catalog": "Lägg till din första katalog", + "Browse": "Bläddra", + "Popular Catalogs": "Populära kataloger", + "Add": "Lägg till", + "Add OPDS Catalog": "Lägg till OPDS-katalog", + "Catalog Name": "Katalognamn", + "My Calibre Library": "Mitt Calibre-bibliotek", + "OPDS URL": "OPDS-URL", + "Username (optional)": "Användarnamn (valfritt)", + "Password (optional)": "Lösenord (valfritt)", + "Description (optional)": "Beskrivning (valfritt)", + "A brief description of this catalog": "En kort beskrivning av denna katalog", + "Validating...": "Verifierar...", + "View All": "Visa alla", + "Forward": "Framåt", + "OPDS Catalog": "OPDS-katalog", + "Home": "Start", + "Library": "Bibliotek", + "{{count}} items_one": "{{count}} objekt", + "{{count}} items_other": "{{count}} objekt", + "Download completed": "Nedladdning klar", + "Download failed": "Nedladdning misslyckades", + "Open Access": "Öppen åtkomst", + "Borrow": "Låna", + "Buy": "Köp", + "Subscribe": "Prenumerera", + "Sample": "Prov", + "Download": "Ladda ner", + "Open & Read": "Öppna & Läs", + "Tags": "Taggar", + "Tag": "Tagg", + "First": "Första", + "Previous": "Föregående", + "Next": "Nästa", + "Last": "Sista", + "Cannot Load Page": "Kan inte ladda sidan", + "An error occurred": "Ett fel uppstod" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index 7bd749c2..2f56da5a 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -675,5 +675,50 @@ "Download from Cloud": "மேகத்திலிருந்து பதிவிறக்கு செய்யவும்", "Upload to Cloud": "மேகத்திற்கு பதிவேற்றவும்", "Clear Custom Fonts": "தனிப்பயன் எழுத்துருக்களை அழிக்கவும்", - "Columns": "நெடுவரிசைகள்" + "Columns": "நெடுவரிசைகள்", + "OPDS Catalogs": "OPDS பட்டியல்கள்", + "Adding LAN addresses is not supported in the web app version.": "LAN முகவரிகளைச் சேர்ப்பது வலை பயன்பாட்டில் ஆதரிக்கப்படவில்லை.", + "Invalid OPDS catalog. Please check the URL.": "தவறான OPDS பட்டியல். URL ஐச் சரிபார்க்கவும்.", + "Browse and download books from online catalogs": "ஆன்லைன் பட்டியல்களில் இருந்து புத்தகங்களை உலாவவும் மற்றும் பதிவிறக்கவும்", + "My Catalogs": "என் பட்டியல்கள்", + "Add Catalog": "பட்டியலைச் சேர்க்கவும்", + "No catalogs yet": "இன்னும் எந்த பட்டியலும் இல்லை", + "Add your first OPDS catalog to start browsing books": "புத்தகங்களை உலாவ தொடங்க உங்கள் முதல் OPDS பட்டியலைச் சேர்க்கவும்", + "Add Your First Catalog": "உங்கள் முதல் பட்டியலைச் சேர்க்கவும்", + "Browse": "உலாவவும்", + "Popular Catalogs": "பிரபல பட்டியல்கள்", + "Add": "சேர்க்கவும்", + "Add OPDS Catalog": "OPDS பட்டியலைச் சேர்க்கவும்", + "Catalog Name": "பட்டியல் பெயர்", + "My Calibre Library": "என் Calibre நூலகம்", + "OPDS URL": "OPDS URL", + "Username (optional)": "பயனர் பெயர் (விருப்பம்)", + "Password (optional)": "கடவுச்சொல் (விருப்பம்)", + "Description (optional)": "விவரிப்பு (விருப்பம்)", + "A brief description of this catalog": "இந்த பட்டியலின் சுருக்கமான விளக்கம்", + "Validating...": "சரிபார்க்கப்படுகிறது...", + "View All": "அனைத்தையும் பார்க்கவும்", + "Forward": "முன்னேற்று", + "OPDS Catalog": "OPDS பட்டியல்", + "Home": "முகப்பு", + "Library": "நூலகம்", + "{{count}} items_one": "{{count}} பொருள்", + "{{count}} items_other": "{{count}} பொருட்கள்", + "Download completed": "பதிவிறக்கம் முடிந்தது", + "Download failed": "பதிவிறக்கம் தோல்வியடைந்தது", + "Open Access": "திறந்த அணுகல்", + "Borrow": "கடன் எடுக்கவும்", + "Buy": "வாங்கவும்", + "Subscribe": "சந்தா செய்யவும்", + "Sample": "மாதிரிப் புத்தகம்", + "Download": "பதிவிறக்கம் செய்யவும்", + "Open & Read": "திறந்து வாசிக்கவும்", + "Tags": "குறிச்சொற்கள்", + "Tag": "குறிச்சொல்", + "First": "முதல்", + "Previous": "முந்தையது", + "Next": "அடுத்தது", + "Last": "இறுதி", + "Cannot Load Page": "பக்கம் ஏற்ற முடியவில்லை", + "An error occurred": "ஒரு பிழை ஏற்பட்டது" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index d5b222e0..ad5f2744 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -671,5 +671,49 @@ "Download from Cloud": "ดาวน์โหลดจากคลาวด์", "Upload to Cloud": "อัปโหลดไปยังคลาวด์", "Clear Custom Fonts": "ล้างฟอนต์กำหนดเอง", - "Columns": "คอลัมน์" + "Columns": "คอลัมน์", + "OPDS Catalogs": "แคตตาล็อก OPDS", + "Adding LAN addresses is not supported in the web app version.": "ไม่รองรับการเพิ่มที่อยู่ LAN ในเวอร์ชันเว็บแอป", + "Invalid OPDS catalog. Please check the URL.": "แคตตาล็อก OPDS ไม่ถูกต้อง กรุณาตรวจสอบ URL", + "Browse and download books from online catalogs": "เรียกดูและดาวน์โหลดหนังสือจากแคตตาล็อกออนไลน์", + "My Catalogs": "แคตตาล็อกของฉัน", + "Add Catalog": "เพิ่มแคตตาล็อก", + "No catalogs yet": "ยังไม่มีแคตตาล็อก", + "Add your first OPDS catalog to start browsing books": "เพิ่มแคตตาล็อก OPDS แรกของคุณเพื่อเริ่มเรียกดูหนังสือ", + "Add Your First Catalog": "เพิ่มแคตตาล็อกแรกของคุณ", + "Browse": "เรียกดู", + "Popular Catalogs": "แคตตาล็อกยอดนิยม", + "Add": "เพิ่ม", + "Add OPDS Catalog": "เพิ่มแคตตาล็อก OPDS", + "Catalog Name": "ชื่อแคตตาล็อก", + "My Calibre Library": "ห้องสมุด Calibre ของฉัน", + "OPDS URL": "URL OPDS", + "Username (optional)": "ชื่อผู้ใช้ (ไม่บังคับ)", + "Password (optional)": "รหัสผ่าน (ไม่บังคับ)", + "Description (optional)": "คำอธิบาย (ไม่บังคับ)", + "A brief description of this catalog": "คำอธิบายสั้น ๆ ของแคตตาล็อกนี้", + "Validating...": "กำลังตรวจสอบ...", + "View All": "ดูทั้งหมด", + "Forward": "ไปข้างหน้า", + "OPDS Catalog": "แคตตาล็อก OPDS", + "Home": "หน้าแรก", + "Library": "ห้องสมุด", + "{{count}} items_other": "{{count}} รายการ", + "Download completed": "ดาวน์โหลดเสร็จสิ้น", + "Download failed": "ดาวน์โหลดล้มเหลว", + "Open Access": "เข้าถึงได้ทันที", + "Borrow": "ยืม", + "Buy": "ซื้อ", + "Subscribe": "สมัครสมาชิก", + "Sample": "ตัวอย่าง", + "Download": "ดาวน์โหลด", + "Open & Read": "เปิดและอ่าน", + "Tags": "แท็ก", + "Tag": "แท็ก", + "First": "แรก", + "Previous": "ก่อนหน้า", + "Next": "ถัดไป", + "Last": "สุดท้าย", + "Cannot Load Page": "ไม่สามารถโหลดหน้าหน้าได้", + "An error occurred": "เกิดข้อผิดพลาด" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 2671a71b..87cd159b 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -675,5 +675,50 @@ "Download from Cloud": "Buluttan İndir", "Upload to Cloud": "Buluta Yükle", "Clear Custom Fonts": "Özel Yazı Tiplerini Temizle", - "Columns": "Sütunlar" + "Columns": "Sütunlar", + "OPDS Catalogs": "OPDS Katalogları", + "Adding LAN addresses is not supported in the web app version.": "Web uygulaması sürümünde LAN adresi ekleme desteklenmiyor.", + "Invalid OPDS catalog. Please check the URL.": "Geçersiz OPDS kataloğu. Lütfen URL'yi kontrol edin.", + "Browse and download books from online catalogs": "Çevrimiçi kataloglardan kitapları görüntüleyin ve indirin", + "My Catalogs": "Kataloglarım", + "Add Catalog": "Katalog Ekle", + "No catalogs yet": "Henüz katalog yok", + "Add your first OPDS catalog to start browsing books": "Kitapları görüntülemeye başlamak için ilk OPDS kataloğunuzu ekleyin", + "Add Your First Catalog": "İlk Kataloğunuzu Ekleyin", + "Browse": "Gözat", + "Popular Catalogs": "Popüler Kataloglar", + "Add": "Ekle", + "Add OPDS Catalog": "OPDS Kataloğu Ekle", + "Catalog Name": "Katalog Adı", + "My Calibre Library": "Calibre Kütüphanem", + "OPDS URL": "OPDS URL", + "Username (optional)": "Kullanıcı Adı (opsiyonel)", + "Password (optional)": "Şifre (opsiyonel)", + "Description (optional)": "Açıklama (opsiyonel)", + "A brief description of this catalog": "Bu katalog hakkında kısa açıklama", + "Validating...": "Doğrulanıyor...", + "View All": "Tümünü Görüntüle", + "Forward": "İleri", + "OPDS Catalog": "OPDS Kataloğu", + "Home": "Ana Sayfa", + "Library": "Kütüphane", + "{{count}} items_one": "{{count}} öğe", + "{{count}} items_other": "{{count}} öğe", + "Download completed": "İndirme tamamlandı", + "Download failed": "İndirme başarısız", + "Open Access": "Açık Erişim", + "Borrow": "Ödünç Al", + "Buy": "Satın Al", + "Subscribe": "Abone Ol", + "Sample": "Örnek", + "Download": "İndir", + "Open & Read": "Aç ve Oku", + "Tags": "Etiketler", + "Tag": "Etiket", + "First": "İlk", + "Previous": "Önceki", + "Next": "Sonraki", + "Last": "Son", + "Cannot Load Page": "Sayfa yüklenemiyor", + "An error occurred": "Bir hata oluştu" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 0ce00b4e..5a38803c 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -683,5 +683,52 @@ "Download from Cloud": "Завантажити з хмари", "Upload to Cloud": "Завантажити в хмару", "Clear Custom Fonts": "Очистити користувацькі шрифти", - "Columns": "Стовпці" + "Columns": "Стовпці", + "OPDS Catalogs": "Каталоги OPDS", + "Adding LAN addresses is not supported in the web app version.": "Додавання LAN-адрес не підтримується у веб-версії.", + "Invalid OPDS catalog. Please check the URL.": "Недійсний каталог OPDS. Будь ласка, перевірте URL.", + "Browse and download books from online catalogs": "Переглядайте та завантажуйте книги з онлайн-каталогів", + "My Catalogs": "Мої каталоги", + "Add Catalog": "Додати каталог", + "No catalogs yet": "Каталогів поки немає", + "Add your first OPDS catalog to start browsing books": "Додайте перший каталог OPDS, щоб почати перегляд книг", + "Add Your First Catalog": "Додайте свій перший каталог", + "Browse": "Переглянути", + "Popular Catalogs": "Популярні каталоги", + "Add": "Додати", + "Add OPDS Catalog": "Додати каталог OPDS", + "Catalog Name": "Назва каталогу", + "My Calibre Library": "Моя бібліотека Calibre", + "OPDS URL": "OPDS URL", + "Username (optional)": "Ім’я користувача (необов’язково)", + "Password (optional)": "Пароль (необов’язково)", + "Description (optional)": "Опис (необов’язково)", + "A brief description of this catalog": "Короткий опис цього каталогу", + "Validating...": "Перевірка...", + "View All": "Переглянути все", + "Forward": "Вперед", + "OPDS Catalog": "Каталог OPDS", + "Home": "Головна", + "Library": "Бібліотека", + "{{count}} items_one": "{{count}} елемент", + "{{count}} items_few": "{{count}} елементи", + "{{count}} items_many": "{{count}} елементів", + "{{count}} items_other": "{{count}} елементів", + "Download completed": "Завантаження завершено", + "Download failed": "Завантаження не вдалося", + "Open Access": "Вільний доступ", + "Borrow": "Позичити", + "Buy": "Купити", + "Subscribe": "Підписатися", + "Sample": "Зразок", + "Download": "Завантажити", + "Open & Read": "Відкрити та читати", + "Tags": "Теги", + "Tag": "Тег", + "First": "Перша", + "Previous": "Попередня", + "Next": "Наступна", + "Last": "Остання", + "Cannot Load Page": "Не вдалося завантажити сторінку", + "An error occurred": "Сталася помилка" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index c1123c48..7ecd44d7 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -671,5 +671,49 @@ "Download from Cloud": "Tải xuống từ đám mây", "Upload to Cloud": "Tải lên đám mây", "Clear Custom Fonts": "Xóa phông chữ tùy chỉnh", - "Columns": "Cột" + "Columns": "Cột", + "OPDS Catalogs": "Danh mục OPDS", + "Adding LAN addresses is not supported in the web app version.": "Việc thêm địa chỉ LAN không được hỗ trợ trong phiên bản web.", + "Invalid OPDS catalog. Please check the URL.": "Danh mục OPDS không hợp lệ. Vui lòng kiểm tra URL.", + "Browse and download books from online catalogs": "Duyệt và tải sách từ các danh mục trực tuyến", + "My Catalogs": "Danh mục của tôi", + "Add Catalog": "Thêm danh mục", + "No catalogs yet": "Chưa có danh mục nào", + "Add your first OPDS catalog to start browsing books": "Thêm danh mục OPDS đầu tiên để bắt đầu duyệt sách", + "Add Your First Catalog": "Thêm danh mục đầu tiên", + "Browse": "Duyệt", + "Popular Catalogs": "Danh mục phổ biến", + "Add": "Thêm", + "Add OPDS Catalog": "Thêm danh mục OPDS", + "Catalog Name": "Tên danh mục", + "My Calibre Library": "Thư viện Calibre của tôi", + "OPDS URL": "URL OPDS", + "Username (optional)": "Tên đăng nhập (tùy chọn)", + "Password (optional)": "Mật khẩu (tùy chọn)", + "Description (optional)": "Mô tả (tùy chọn)", + "A brief description of this catalog": "Mô tả ngắn gọn về danh mục này", + "Validating...": "Đang xác thực...", + "View All": "Xem tất cả", + "Forward": "Tiếp", + "OPDS Catalog": "Danh mục OPDS", + "Home": "Trang chủ", + "Library": "Thư viện", + "{{count}} items_other": "{{count}} mục", + "Download completed": "Tải xuống hoàn tất", + "Download failed": "Tải xuống thất bại", + "Open Access": "Truy cập mở", + "Borrow": "Mượn", + "Buy": "Mua", + "Subscribe": "Đăng ký", + "Sample": "Mẫu", + "Download": "Tải xuống", + "Open & Read": "Mở & Đọc", + "Tags": "Thẻ", + "Tag": "Thẻ", + "First": "Đầu tiên", + "Previous": "Trước", + "Next": "Tiếp theo", + "Last": "Cuối cùng", + "Cannot Load Page": "Không thể tải trang", + "An error occurred": "Đã xảy ra lỗi" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 67697129..5b95dcc0 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -671,5 +671,49 @@ "Download from Cloud": "从云端下载", "Upload to Cloud": "上传到云端", "Clear Custom Fonts": "清除自定义字体", - "Columns": "列数" + "Columns": "列数", + "OPDS Catalogs": "OPDS 目录", + "Adding LAN addresses is not supported in the web app version.": "Web 版不支持添加局域网地址。", + "Invalid OPDS catalog. Please check the URL.": "无效的 OPDS 目录。请检查 URL。", + "Browse and download books from online catalogs": "浏览并下载在线目录中的书籍", + "My Catalogs": "我的目录", + "Add Catalog": "添加目录", + "No catalogs yet": "尚无目录", + "Add your first OPDS catalog to start browsing books": "添加您的第一个 OPDS 目录以开始浏览书籍", + "Add Your First Catalog": "添加第一个目录", + "Browse": "浏览", + "Popular Catalogs": "热门目录", + "Add": "添加", + "Add OPDS Catalog": "添加 OPDS 目录", + "Catalog Name": "目录名称", + "My Calibre Library": "我的 Calibre 图书馆", + "OPDS URL": "OPDS URL", + "Username (optional)": "用户名(可选)", + "Password (optional)": "密码(可选)", + "Description (optional)": "描述(可选)", + "A brief description of this catalog": "该目录的简要描述", + "Validating...": "验证中...", + "View All": "查看全部", + "Forward": "前进", + "OPDS Catalog": "OPDS 目录", + "Home": "首页", + "Library": "图书馆", + "{{count}} items_other": "{{count}} 项", + "Download completed": "下载完成", + "Download failed": "下载失败", + "Open Access": "开放访问", + "Borrow": "借阅", + "Buy": "购买", + "Subscribe": "订阅", + "Sample": "样章", + "Download": "下载", + "Open & Read": "打开并阅读", + "Tags": "标签", + "Tag": "标签", + "First": "首页", + "Previous": "上一页", + "Next": "下一页", + "Last": "末页", + "Cannot Load Page": "无法加载页面", + "An error occurred": "发生错误" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 9286a862..307ac553 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -671,5 +671,49 @@ "Download from Cloud": "從雲端下載", "Upload to Cloud": "上傳到雲端", "Clear Custom Fonts": "清除自訂字型", - "Columns": "欄數" + "Columns": "欄數", + "OPDS Catalogs": "OPDS 目錄", + "Adding LAN addresses is not supported in the web app version.": "網頁版不支援新增區域網路位址。", + "Invalid OPDS catalog. Please check the URL.": "無效的 OPDS 目錄。請檢查 URL。", + "Browse and download books from online catalogs": "瀏覽並下載線上目錄的書籍", + "My Catalogs": "我的目錄", + "Add Catalog": "新增目錄", + "No catalogs yet": "尚無目錄", + "Add your first OPDS catalog to start browsing books": "新增第一個 OPDS 目錄以開始瀏覽書籍", + "Add Your First Catalog": "新增第一個目錄", + "Browse": "瀏覽", + "Popular Catalogs": "熱門目錄", + "Add": "新增", + "Add OPDS Catalog": "新增 OPDS 目錄", + "Catalog Name": "目錄名稱", + "My Calibre Library": "我的 Calibre 圖書館", + "OPDS URL": "OPDS URL", + "Username (optional)": "使用者名稱(可選)", + "Password (optional)": "密碼(可選)", + "Description (optional)": "描述(可選)", + "A brief description of this catalog": "此目錄的簡短描述", + "Validating...": "驗證中...", + "View All": "檢視全部", + "Forward": "前往", + "OPDS Catalog": "OPDS 目錄", + "Home": "首頁", + "Library": "圖書館", + "{{count}} items_other": "{{count}} 項", + "Download completed": "下載完成", + "Download failed": "下載失敗", + "Open Access": "開放存取", + "Borrow": "借閱", + "Buy": "購買", + "Subscribe": "訂閱", + "Sample": "試閱", + "Download": "下載", + "Open & Read": "開啟並閱讀", + "Tags": "標籤", + "Tag": "標籤", + "First": "第一頁", + "Previous": "上一頁", + "Next": "下一頁", + "Last": "最後一頁", + "Cannot Load Page": "無法載入頁面", + "An error occurred": "發生錯誤" } diff --git a/apps/readest-app/src-tauri/src/transfer_file.rs b/apps/readest-app/src-tauri/src/transfer_file.rs index aea84343..4ba7c5b6 100644 --- a/apps/readest-app/src-tauri/src/transfer_file.rs +++ b/apps/readest-app/src-tauri/src/transfer_file.rs @@ -107,6 +107,7 @@ pub async fn download_file( file_path: &str, headers: HashMap, body: Option, + single_threaded: Option, on_progress: Channel, ) -> Result<()> { use futures::stream::{self, StreamExt}; @@ -116,32 +117,23 @@ pub async fn download_file( const PART_SIZE: u64 = 1024 * 1024; let client = reqwest::Client::new(); + let force_single = single_threaded.unwrap_or(false); - // Check if server supports range requests - let range_resp = client.get(url).header("Range", "bytes=0-0").send().await?; - let accept_ranges = range_resp - .headers() - .get("accept-ranges") - .map(|v| v.to_str().unwrap_or("")) - .unwrap_or("") - .eq_ignore_ascii_case("bytes"); - let total = range_resp - .headers() - .get("content-range") - .and_then(|v| v.to_str().ok()) - .and_then(|s| s.split('/').nth(1)) - .and_then(|s| s.parse::().ok()) - .unwrap_or(0); - - if !accept_ranges || total == 0 { - // Fallback to original single-threaded logic + async fn single_threaded_download( + client: &reqwest::Client, + url: &str, + file_path: &str, + headers: &HashMap, + body: &Option, + on_progress: Channel, + ) -> Result<()> { let mut request = if let Some(body) = body { - client.post(url).body(body) + client.post(url).body(body.clone()) } else { client.get(url) }; - for (key, value) in headers.iter() { + for (key, value) in headers { request = request.header(key, value); } @@ -168,7 +160,38 @@ pub async fn download_file( }); } file.flush().await?; - return Ok(()); + + Ok(()) + } + + if force_single { + return single_threaded_download(&client, url, file_path, &headers, &body, on_progress) + .await; + } + + // Check if server supports range requests + let mut range_req = client.get(url).header("Range", "bytes=0-0"); + for (key, value) in headers.iter() { + range_req = range_req.header(key, value); + } + let range_resp = range_req.send().await?; + let accept_ranges = range_resp + .headers() + .get("accept-ranges") + .map(|v| v.to_str().unwrap_or("")) + .unwrap_or("") + .eq_ignore_ascii_case("bytes"); + let total = range_resp + .headers() + .get("content-range") + .and_then(|v| v.to_str().ok()) + .and_then(|s| s.split('/').nth(1)) + .and_then(|s| s.parse::().ok()) + .unwrap_or(0); + + if !accept_ranges || total == 0 { + return single_threaded_download(&client, url, file_path, &headers, &body, on_progress) + .await; } // Multi-part download with range access diff --git a/apps/readest-app/src/__tests__/helpers/supabase-mock.ts b/apps/readest-app/src/__tests__/helpers/supabase-mock.ts index 098b5243..487839ac 100644 --- a/apps/readest-app/src/__tests__/helpers/supabase-mock.ts +++ b/apps/readest-app/src/__tests__/helpers/supabase-mock.ts @@ -45,6 +45,7 @@ export const setupSupabaseMocks = async ( eq: vi.fn().mockResolvedValue(customResponses.update || { data: {}, error: null }), })), insert: vi.fn().mockResolvedValue(customResponses.insert || { data: {}, error: null }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); vi.mocked(createSupabaseAdminClient).mockReturnValue({ @@ -101,5 +102,6 @@ export const setupSupabaseMocks = async ( match: vi.fn().mockResolvedValue(customResponses.adminDelete || { data: {}, error: null }), })), })), + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); }; diff --git a/apps/readest-app/src/app/api/opds/proxy/route.ts b/apps/readest-app/src/app/api/opds/proxy/route.ts new file mode 100644 index 00000000..e557d01b --- /dev/null +++ b/apps/readest-app/src/app/api/opds/proxy/route.ts @@ -0,0 +1,167 @@ +import { NextRequest, NextResponse } from 'next/server'; + +async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') { + const url = request.nextUrl.searchParams.get('url'); + const auth = request.nextUrl.searchParams.get('auth'); + const stream = request.nextUrl.searchParams.get('stream'); + + if (!url) { + return NextResponse.json( + { error: 'Missing URL parameter. Usage: /api/opds/proxy?url=YOUR_OPDS_URL' }, + { status: 400 }, + ); + } + + try { + new URL(url); + } catch { + return NextResponse.json({ error: 'Invalid URL format' }, { status: 400 }); + } + + try { + console.log(`[OPDS Proxy] ${method}: ${url}`); + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 10000); + const headers: HeadersInit = { + 'User-Agent': 'Readest/1.0 (OPDS Browser)', + Accept: 'application/atom+xml, application/xml, text/xml, application/json, */*', + }; + + if (auth) { + headers['Authorization'] = auth; + } + + const response = await fetch(url, { + method, + headers, + signal: controller.signal, + }); + + clearTimeout(timeout); + + if (!response.ok) { + console.error(`[OPDS Proxy] HTTP ${response.status} for ${url}`); + if (method === 'HEAD') { + console.log(`[OPDS Proxy] Response headers:`, response.headers); + if (response.status === 401) { + return new NextResponse(null, { + status: 403, + 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', + }, + }); + } + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.text(); + if (response.status === 401) { + return new NextResponse(data, { + status: 403, + 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', + }, + }); + } + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const contentType = response.headers.get('Content-Type') || 'text/xml'; + const contentLength = response.headers.get('Content-Length'); + + if (method === 'HEAD') { + console.log(`[OPDS Proxy] HEAD Success: ${url}`); + return new NextResponse(null, { + status: 200, + headers: { + 'Content-Type': contentType, + 'Content-Length': contentLength || '', + 'Cache-Control': 'public, max-age=300', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + }, + }); + } + + if (stream === 'true' || (contentLength && parseInt(contentLength) > 1024 * 1024)) { + console.log(`[OPDS Proxy] Streaming: ${url}`); + + return new NextResponse(response.body, { + status: 200, + headers: { + 'Content-Type': contentType, + 'Content-Length': contentLength || '', + 'Cache-Control': 'public, max-age=300', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + }, + }); + } else { + const data = await response.text(); + console.log(`[OPDS Proxy] Success: ${url} (${data.length} bytes)`); + + return new NextResponse(data, { + status: 200, + headers: { + 'Content-Type': contentType, + 'Cache-Control': 'public, max-age=300', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + }, + }); + } + } catch (error) { + console.error('[OPDS Proxy] Error:', error); + + if (error instanceof Error) { + if (error.name === 'AbortError') { + return NextResponse.json( + { error: 'Request timeout - the OPDS server took too long to respond' }, + { status: 504 }, + ); + } + + return NextResponse.json( + { + error: error.message, + url: url, + hint: 'Check if the OPDS URL is accessible and returns valid OPDS/Atom/JSON content', + }, + { status: 500 }, + ); + } + + return NextResponse.json({ error: 'Failed to fetch OPDS feed', url: url }, { status: 500 }); + } +} + +export async function GET(request: NextRequest) { + return handleRequest(request, 'GET'); +} + +export async function HEAD(request: NextRequest) { + return handleRequest(request, 'HEAD'); +} + +export async function OPTIONS(_: NextRequest) { + return new NextResponse(null, { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + }, + }); +} diff --git a/apps/readest-app/src/app/auth/update/page.tsx b/apps/readest-app/src/app/auth/update/page.tsx index 3ecc4d38..a259a682 100644 --- a/apps/readest-app/src/app/auth/update/page.tsx +++ b/apps/readest-app/src/app/auth/update/page.tsx @@ -42,8 +42,8 @@ export default function UpdateEmailPage() { ), ); setEmail(''); - } catch (err: any) { - setError(err.message || _('Failed to update email')); + } catch (err) { + setError(err instanceof Error ? err.message : _('Failed to update email')); } finally { setLoading(false); } diff --git a/apps/readest-app/src/app/library/components/BookshelfItem.tsx b/apps/readest-app/src/app/library/components/BookshelfItem.tsx index 2a32610c..854e6f70 100644 --- a/apps/readest-app/src/app/library/components/BookshelfItem.tsx +++ b/apps/readest-app/src/app/library/components/BookshelfItem.tsx @@ -49,7 +49,7 @@ export const generateBookshelfItems = ( booksGroup.books.push(book); booksGroup.updatedAt = Math.max(booksGroup.updatedAt, book.updatedAt); } else { - let groupName = fullGroupName; + const groupName = fullGroupName; acc.push({ id: groupName === parentGroupName ? BOOK_UNGROUPED_ID : md5Fingerprint(groupName), name: groupName === parentGroupName ? BOOK_UNGROUPED_NAME : groupName, diff --git a/apps/readest-app/src/app/library/components/ImportMenu.tsx b/apps/readest-app/src/app/library/components/ImportMenu.tsx index aa057e81..636ebfcd 100644 --- a/apps/readest-app/src/app/library/components/ImportMenu.tsx +++ b/apps/readest-app/src/app/library/components/ImportMenu.tsx @@ -1,15 +1,23 @@ import clsx from 'clsx'; import { useEnv } from '@/context/EnvContext'; import { useTranslation } from '@/hooks/useTranslation'; +import { IoFileTray } from 'react-icons/io5'; +import { MdRssFeed } from 'react-icons/md'; + import MenuItem from '@/components/MenuItem'; import Menu from '@/components/Menu'; interface ImportMenuProps { setIsDropdownOpen?: (open: boolean) => void; onImportBooks: () => void; + onOpenCatalogManager: () => void; } -const ImportMenu: React.FC = ({ setIsDropdownOpen, onImportBooks }) => { +const ImportMenu: React.FC = ({ + setIsDropdownOpen, + onImportBooks, + onOpenCatalogManager, +}) => { const _ = useTranslation(); const { appService } = useEnv(); @@ -18,6 +26,11 @@ const ImportMenu: React.FC = ({ setIsDropdownOpen, onImportBook setIsDropdownOpen?.(false); }; + const handleOpenCatalogManager = () => { + onOpenCatalogManager(); + setIsDropdownOpen?.(false); + }; + return ( = ({ setIsDropdownOpen, onImportBook )} onCancel={() => setIsDropdownOpen?.(false)} > - + } + onClick={handleImportBooks} + /> + } + onClick={handleOpenCatalogManager} + /> ); }; diff --git a/apps/readest-app/src/app/library/components/LibraryHeader.tsx b/apps/readest-app/src/app/library/components/LibraryHeader.tsx index b9ff2845..05e331de 100644 --- a/apps/readest-app/src/app/library/components/LibraryHeader.tsx +++ b/apps/readest-app/src/app/library/components/LibraryHeader.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useRef, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { FaSearch } from 'react-icons/fa'; import { PiPlus } from 'react-icons/pi'; @@ -13,8 +13,8 @@ import { useThemeStore } from '@/store/themeStore'; import { useTranslation } from '@/hooks/useTranslation'; import { useLibraryStore } from '@/store/libraryStore'; import { useSettingsStore } from '@/store/settingsStore'; +import { useTrafficLight } from '@/hooks/useTrafficLight'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; -import { useTrafficLightStore } from '@/store/trafficLightStore'; import { debounce } from '@/utils/debounce'; import useShortcuts from '@/hooks/useShortcuts'; import WindowButtons from '@/components/WindowButtons'; @@ -27,6 +27,7 @@ interface LibraryHeaderProps { isSelectMode: boolean; isSelectAll: boolean; onImportBooks: () => void; + onOpenCatalogManager: () => void; onToggleSelectMode: () => void; onSelectAll: () => void; onDeselectAll: () => void; @@ -36,6 +37,7 @@ const LibraryHeader: React.FC = ({ isSelectMode, isSelectAll, onImportBooks, + onOpenCatalogManager, onToggleSelectMode, onSelectAll, onDeselectAll, @@ -47,13 +49,7 @@ const LibraryHeader: React.FC = ({ const { settings } = useSettingsStore(); const { systemUIVisible, statusBarHeight } = useThemeStore(); const { currentBookshelf } = useLibraryStore(); - const { - isTrafficLightVisible, - initializeTrafficLightStore, - initializeTrafficLightListeners, - setTrafficLightVisibility, - cleanupTrafficLightListeners, - } = useTrafficLightStore(); + const { isTrafficLightVisible } = useTrafficLight(); const [searchQuery, setSearchQuery] = useState(searchParams?.get('q') ?? ''); const viewSettings = settings.globalViewSettings; @@ -85,18 +81,6 @@ const LibraryHeader: React.FC = ({ debouncedUpdateQueryParam(newQuery); }; - useEffect(() => { - if (!appService?.hasTrafficLight) return; - - initializeTrafficLightStore(appService); - initializeTrafficLightListeners(); - setTrafficLightVisibility(true); - return () => { - cleanupTrafficLightListeners(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [appService?.hasTrafficLight]); - const windowButtonVisible = appService?.hasWindowBar && !isTrafficLightVisible; const currentBooksCount = currentBookshelf.reduce( (acc, item) => acc + ('books' in item ? item.books.length : 1), @@ -171,10 +155,13 @@ const LibraryHeader: React.FC = ({ 'exclude-title-bar-mousedown dropdown-bottom flex h-6 cursor-pointer justify-center', isMobile ? 'dropdown-end' : 'dropdown-center', )} - buttonClassName='p-0 h-6 min-h-6 w-6 flex items-center justify-center' + buttonClassName='p-0 h-6 min-h-6 w-6 flex items-center justify-center !bg-transparent' toggleButton={} > - + {isMobile ? null : ( + + + ); +} diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index 92f8efc8..f77952aa 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -49,6 +49,7 @@ import { BookMetadata } from '@/libs/document'; import { AboutWindow } from '@/components/AboutWindow'; import { BookDetailModal } from '@/components/metadata'; import { UpdaterWindow } from '@/components/UpdaterWindow'; +import { CatalogDialog } from './components/OPDSDialog'; import { MigrateDataWindow } from './components/MigrateDataWindow'; import { useDragDropImport } from './hooks/useDragDropImport'; import { Toast } from '@/components/Toast'; @@ -88,6 +89,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP const { safeAreaInsets: insets, isRoundedWindow } = useThemeStore(); const { settings, setSettings, saveSettings } = useSettingsStore(); const { isSettingsDialogOpen, setSettingsDialogOpen } = useSettingsStore(); + const [showCatalogManager, setShowCatalogManager] = useState(false); const [loading, setLoading] = useState(false); const [libraryLoaded, setLibraryLoaded] = useState(false); const [isSelectMode, setIsSelectMode] = useState(false); @@ -652,6 +654,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP isSelectMode={isSelectMode} isSelectAll={isSelectAll} onImportBooks={handleImportBooks} + onOpenCatalogManager={() => setShowCatalogManager(true)} onToggleSelectMode={() => handleSetSelectMode(!isSelectMode)} onSelectAll={handleSelectAll} onDeselectAll={handleDeselectAll} @@ -782,6 +785,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP {isSettingsDialogOpen && } + {showCatalogManager && setShowCatalogManager(false)} />} ); diff --git a/apps/readest-app/src/app/opds/CatelogManager.tsx b/apps/readest-app/src/app/opds/CatelogManager.tsx new file mode 100644 index 00000000..2e9016c1 --- /dev/null +++ b/apps/readest-app/src/app/opds/CatelogManager.tsx @@ -0,0 +1,398 @@ +'use client'; + +import { useState } from 'react'; +import { IoAdd, IoTrash, IoOpenOutline, IoBook, IoEyeOff, IoEye } from 'react-icons/io5'; +import { useRouter } from 'next/navigation'; +import { useEnv } from '@/context/EnvContext'; +import { useTranslation } from '@/hooks/useTranslation'; +import { useSettingsStore } from '@/store/settingsStore'; +import { isWebAppPlatform } from '@/services/environment'; +import { saveSysSettings } from '@/helpers/settings'; +import { OPDSCatalog } from '@/types/opds'; +import { isLanAddress } from '@/utils/network'; +import { validateOPDSURL } from './utils/opdsUtils'; +import ModalPortal from '@/components/ModalPortal'; + +const POPULAR_CATALOGS: OPDSCatalog[] = [ + { + id: 'gutenberg', + name: 'Project Gutenberg', + url: 'https://m.gutenberg.org/ebooks.opds/', + description: "World's largest collection of free ebooks", + icon: '🏛️', + }, + { + id: 'manybooks', + name: 'ManyBooks', + url: 'https://manybooks.net/opds/index.php', + description: 'Over 50,000 free ebooks', + icon: '📖', + }, +]; + +async function validateOPDSCatalog( + url: string, + username?: string, + password?: string, +): Promise<{ valid: boolean; error?: string }> { + const result = await validateOPDSURL(url, username, password, isWebAppPlatform()); + return { valid: result.isValid, error: result.error }; +} + +export function CatalogManager() { + const _ = useTranslation(); + const router = useRouter(); + const { envConfig } = useEnv(); + const { settings } = useSettingsStore(); + const [catalogs, setCatalogs] = useState(() => settings.opdsCatalogs || []); + const [showAddDialog, setShowAddDialog] = useState(false); + const [newCatalog, setNewCatalog] = useState({ + name: '', + url: '', + description: '', + username: '', + password: '', + }); + const [showPassword, setShowPassword] = useState(false); + const [urlError, setUrlError] = useState(''); + const [isValidating, setIsValidating] = useState(false); + + const saveCatalogs = (updatedCatalogs: OPDSCatalog[]) => { + setCatalogs(updatedCatalogs); + saveSysSettings(envConfig, 'opdsCatalogs', updatedCatalogs); + }; + + const handleAddCatalog = async () => { + if (!newCatalog.name || !newCatalog.url) return; + + if ( + process.env['NODE_ENV'] === 'production' && + isWebAppPlatform() && + isLanAddress(newCatalog.url) + ) { + setUrlError(_('Adding LAN addresses is not supported in the web app version.')); + return; + } + + setIsValidating(true); + setUrlError(''); + + const validation = await validateOPDSCatalog( + newCatalog.url, + newCatalog.username || undefined, + newCatalog.password || undefined, + ); + + if (!validation.valid) { + setUrlError(validation.error || _('Invalid OPDS catalog. Please check the URL.')); + setIsValidating(false); + return; + } + + const catalog: OPDSCatalog = { + id: Date.now().toString(), + name: newCatalog.name, + url: newCatalog.url, + description: newCatalog.description, + username: newCatalog.username || undefined, + password: newCatalog.password || undefined, + }; + + saveCatalogs([catalog, ...catalogs]); + setNewCatalog({ name: '', url: '', description: '', username: '', password: '' }); + setUrlError(''); + setIsValidating(false); + setShowAddDialog(false); + }; + + const handleAddPopularCatalog = (popularCatalog: OPDSCatalog) => { + if (catalogs.some((c) => c.url === popularCatalog.url)) { + return; + } + + saveCatalogs([...catalogs, { ...popularCatalog }]); + }; + + const handleRemoveCatalog = (id: string) => { + saveCatalogs(catalogs.filter((c) => c.id !== id)); + }; + + const handleOpenCatalog = (catalog: OPDSCatalog) => { + const params = new URLSearchParams({ url: catalog.url }); + if (catalog.username) params.set('username', catalog.username); + if (catalog.password) params.set('password', catalog.password); + router.push(`/opds?${params.toString()}`); + }; + + const handleCloseDialog = () => { + setShowAddDialog(false); + setNewCatalog({ name: '', url: '', description: '', username: '', password: '' }); + setUrlError(''); + setShowPassword(false); + }; + + return ( +
+
+

{_('OPDS Catalogs')}

+

+ {_('Browse and download books from online catalogs')} +

+
+ + {/* My Catalogs */} +
+
+

{_('My Catalogs')}

+ +
+ + {catalogs.length === 0 ? ( +
+ +

{_('No catalogs yet')}

+

+ {_('Add your first OPDS catalog to start browsing books')} +

+ +
+ ) : ( +
+ {catalogs.map((catalog) => ( +
+
+
+
+
+

+ {catalog.icon && {catalog.icon}} + {catalog.name} +

+ +
+ {catalog.description && ( +

+ {catalog.description} +

+ )} +

{catalog.url}

+ {catalog.username && ( +

+ {_('Username')}: {catalog.username} +

+ )} +
+
+
+ +
+
+
+ ))} +
+ )} +
+ + {/* Popular Catalogs */} +
+

{_('Popular Catalogs')}

+
+ {POPULAR_CATALOGS.map((catalog) => { + const isAdded = catalogs.some((c) => c.url === catalog.url); + return ( +
+
+

+ {catalog.icon && {catalog.icon}} + {catalog.name} +

+ {catalog.description && ( +

+ {catalog.description} +

+ )} +
+ {!isAdded && ( + + )} + +
+
+
+ ); + })} +
+
+ + {/* Add Catalog Dialog */} + {showAddDialog && ( + + +
+

{_('Add OPDS Catalog')}

+
{ + e.preventDefault(); + handleAddCatalog(); + }} + className='space-y-4' + > +
+
+ {_('Catalog Name')} * +
+ setNewCatalog({ ...newCatalog, name: e.target.value })} + placeholder={_('My Calibre Library')} + className='input input-bordered placeholder:text-sm' + disabled={isValidating} + required + /> +
+ +
+
+ {_('OPDS URL')} * +
+ setNewCatalog({ ...newCatalog, url: e.target.value })} + placeholder='https://example.com/opds' + className='input input-bordered placeholder:text-sm' + disabled={isValidating} + required + /> + {urlError && ( +
+ {urlError} +
+ )} +
+ +
+
+ {_('Username (optional)')} +
+ setNewCatalog({ ...newCatalog, username: e.target.value })} + placeholder={_('Username')} + className='input input-bordered placeholder:text-sm' + disabled={isValidating} + autoComplete='username' + /> +
+ +
+
+ {_('Password (optional)')} +
+
+ setNewCatalog({ ...newCatalog, password: e.target.value })} + placeholder={_('Password')} + className='input input-bordered w-full pr-10 placeholder:text-sm' + disabled={isValidating} + autoComplete='current-password' + /> + +
+
+ +
+
+ {_('Description (optional)')} +
+