From ce53cd2b4792d45e3d60d940bf39c90a4d9cb0dd Mon Sep 17 00:00:00 2001 From: Matt Vogel Date: Mon, 23 Feb 2026 11:08:59 -0500 Subject: [PATCH] feat: Readwise highlights sync (#3311) --- .../public/locales/ar/translation.json | 21 ++- .../public/locales/bn/translation.json | 21 ++- .../public/locales/bo/translation.json | 21 ++- .../public/locales/de/translation.json | 21 ++- .../public/locales/el/translation.json | 21 ++- .../public/locales/es/translation.json | 21 ++- .../public/locales/fa/translation.json | 21 ++- .../public/locales/fr/translation.json | 21 ++- .../public/locales/he/translation.json | 21 ++- .../public/locales/hi/translation.json | 21 ++- .../public/locales/id/translation.json | 21 ++- .../public/locales/it/translation.json | 21 ++- .../public/locales/ja/translation.json | 21 ++- .../public/locales/ko/translation.json | 21 ++- .../public/locales/ms/translation.json | 21 ++- .../public/locales/nl/translation.json | 21 ++- .../public/locales/pl/translation.json | 21 ++- .../public/locales/pt/translation.json | 21 ++- .../public/locales/ru/translation.json | 21 ++- .../public/locales/si/translation.json | 21 ++- .../public/locales/sv/translation.json | 21 ++- .../public/locales/ta/translation.json | 21 ++- .../public/locales/th/translation.json | 21 ++- .../public/locales/tr/translation.json | 21 ++- .../public/locales/uk/translation.json | 21 ++- .../public/locales/vi/translation.json | 21 ++- .../public/locales/zh-CN/translation.json | 21 ++- .../public/locales/zh-TW/translation.json | 21 ++- .../components/ProofreadRules.test.tsx | 22 +-- .../src/app/reader/components/Reader.tsx | 2 + .../reader/components/ReadwiseSettings.tsx | 172 ++++++++++++++++++ .../reader/components/annotator/Annotator.tsx | 2 + .../reader/components/sidebar/BookMenu.tsx | 35 +++- .../src/app/reader/hooks/useReadwiseSync.ts | 111 +++++++++++ apps/readest-app/src/services/constants.ts | 10 + .../src/services/readwise/ReadwiseClient.ts | 93 ++++++++++ .../src/services/readwise/index.ts | 1 + apps/readest-app/src/styles/globals.css | 3 +- apps/readest-app/src/types/settings.ts | 7 + 39 files changed, 998 insertions(+), 48 deletions(-) create mode 100644 apps/readest-app/src/app/reader/components/ReadwiseSettings.tsx create mode 100644 apps/readest-app/src/app/reader/hooks/useReadwiseSync.ts create mode 100644 apps/readest-app/src/services/readwise/ReadwiseClient.ts create mode 100644 apps/readest-app/src/services/readwise/index.ts diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 2d85f23b..58a4bebb 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -1085,5 +1085,24 @@ "Next Image": "الصورة التالية", "Zoomed": "مكبّر", "Zoom level": "مستوى التكبير", - "Table viewer": "عارض الجداول" + "Table viewer": "عارض الجداول", + "Unable to connect to Readwise. Please check your network connection.": "تعذر الاتصال بـ Readwise. يرجى التحقق من اتصال الشبكة لديك.", + "Invalid Readwise access token": "رمز وصول Readwise غير صالح", + "Disconnected from Readwise": "تم قطع الاتصال بـ Readwise", + "Never": "أبداً", + "Readwise Settings": "إعدادات Readwise", + "Connected to Readwise": "متصل بـ Readwise", + "Last synced: {{time}}": "آخر مزامنة: {{time}}", + "Sync Enabled": "تم تمكين المزامنة", + "Disconnect": "قطع الاتصال", + "Connect your Readwise account to sync highlights.": "قم بتوصيل حساب Readwise الخاص بك لمزامنة التمييزات.", + "Get your access token at": "احصل على رمز الوصول الخاص بك من", + "Access Token": "رمز الوصول", + "Paste your Readwise access token": "الصق رمز وصول Readwise الخاص بك", + "Config": "تكوين", + "Readwise Sync": "مزامنة Readwise", + "Push Highlights": "إرسال التمييزات", + "Highlights synced to Readwise": "تمت مزامنة التمييزات مع Readwise", + "Readwise sync failed: no internet connection": "فشلت مزامنة Readwise: لا يوجد اتصال بالإنترنت", + "Readwise sync failed: {{error}}": "فشلت مزامنة Readwise: {{error}}" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index 2ef6145d..ea12f8b4 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -1037,5 +1037,24 @@ "Next Image": "পরবর্তী চিত্র", "Zoomed": "জুম করা হয়েছে", "Zoom level": "জুম স্তর", - "Table viewer": "টেবিল প্রদর্শক" + "Table viewer": "টেবিল প্রদর্শক", + "Unable to connect to Readwise. Please check your network connection.": "Readwise এর সাথে সংযোগ করতে অক্ষম। আপনার নেটওয়ার্ক সংযোগটি পরীক্ষা করুন।", + "Invalid Readwise access token": "অকার্যকর Readwise অ্যাক্সেস টোকেন", + "Disconnected from Readwise": "Readwise থেকে সংযোগ বিচ্ছিন্ন হয়েছে", + "Never": "কখনো না", + "Readwise Settings": "Readwise সেটিংস", + "Connected to Readwise": "Readwise এর সাথে সংযুক্ত", + "Last synced: {{time}}": "শেষ সিঙ্ক করা হয়েছে: {{time}}", + "Sync Enabled": "সিঙ্কিং সক্ষম", + "Disconnect": "সংযোগ বিচ্ছিন্ন করুন", + "Connect your Readwise account to sync highlights.": "হাইলাইটগুলি সিঙ্ক করার জন্য আপনার Readwise অ্যাকাউন্টটি সংযোগ করুন।", + "Get your access token at": "আপনার অ্যাক্সেস টোকেনটি এখানে পাবেন", + "Access Token": "অ্যাক্সেস টোকেন", + "Paste your Readwise access token": "আপনার Readwise অ্যাক্সেস টোকেনটি পেস্ট করুন", + "Config": "কনফিগ", + "Readwise Sync": "Readwise সিঙ্ক", + "Push Highlights": "হাইলাইট পাঠান", + "Highlights synced to Readwise": "হাইলাইটগুলি Readwise এ সিঙ্ক করা হয়েছে", + "Readwise sync failed: no internet connection": "Readwise সিঙ্ক ব্যর্থ হয়েছে: ইন্টারনেট সংযোগ নেই", + "Readwise sync failed: {{error}}": "Readwise সিঙ্ক ব্যর্থ হয়েছে: {{error}}" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index d32ee4dc..ba41c333 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -1025,5 +1025,24 @@ "Next Image": "རྗེས་མའི་པར་རིས།", "Zoomed": "ཆེར་བསྐྱེད་ཟིན།", "Zoom level": "ཆེར་བསྐྱེད་རིམ་པ།", - "Table viewer": "རེའུ་མིག་ལྟ་བྱེད།" + "Table viewer": "རེའུ་མིག་ལྟ་བྱེད།", + "Unable to connect to Readwise. Please check your network connection.": "Readwise ལ་མཐུད་ཐུབ་མ་སོང་། ཁྱེད་ཀྱི་དྲ་རྒྱའི་མཐུད་ལམ་ལ་བརྟག་དཔྱད་གནང་རོགས།", + "Invalid Readwise access token": "Readwise འཛུལ་སྤྱོད་ལག་ཁྱེར་ནོར་འདུག", + "Disconnected from Readwise": "Readwise ནས་མཐུད་ལམ་བཅད་ཟིན།", + "Never": "ནམ་ཡང་མིན།", + "Readwise Settings": "Readwise སྒྲིག་བཀོད།", + "Connected to Readwise": "Readwise ལ་མཐུད་ཟིན།", + "Last synced: {{time}}": "མཐའ་མའི་མཉམ་བྱུང་དུས་ཚོད། {{time}}", + "Sync Enabled": "མཉམ་བྱུང་ནུས་པ་སྤར་ཟིན།", + "Disconnect": "མཐུད་ལམ་གཅོད་པ།", + "Connect your Readwise account to sync highlights.": "ཁྱེད་ཀྱི་ Readwise རྩིས་ཐོ་མཐུད་ནས་བཀོད་མཆན་མཉམ་བྱུང་གནང་རོགས།", + "Get your access token at": "འཛུལ་སྤྱོད་ལག་ཁྱེར་འདི་ནས་ལེན་རོགས།", + "Access Token": "འཛུལ་སྤྱོད་ལག་ཁྱེེར།", + "Paste your Readwise access token": "Readwise འཛུལ་སྤྱོད་ལག་ཁྱེར་འདིར་སྦྱོར་རོགས།", + "Config": "སྒྲིག་བཀོད།", + "Readwise Sync": "Readwise མཉམ་བྱུང་།", + "Push Highlights": "བཀོད་མཆན་སྐྱེལ་བ།", + "Highlights synced to Readwise": "བཀོད་མཆན་ Readwise ལ་མཉམ་བྱུང་བྱས་ཟིན།", + "Readwise sync failed: no internet connection": "Readwise མཉམ་བྱུང་མ་ཐུབ། དྲ་རྒྱའི་མཐུད་ལམ་མི་འདུག", + "Readwise sync failed: {{error}}": "Readwise མཉམ་བྱུང་མ་ཐུབ། {{error}}" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 3ddf458b..286efe70 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -1037,5 +1037,24 @@ "Next Image": "Nächstes Bild", "Zoomed": "Gezoomt", "Zoom level": "Zoomstufe", - "Table viewer": "Tabellenbetrachter" + "Table viewer": "Tabellenbetrachter", + "Unable to connect to Readwise. Please check your network connection.": "Verbindung zu Readwise nicht möglich. Bitte überprüfen Sie Ihre Netzwerkverbindung.", + "Invalid Readwise access token": "Ungültiges Readwise-Zugriffstoken", + "Disconnected from Readwise": "Von Readwise getrennt", + "Never": "Nie", + "Readwise Settings": "Readwise-Einstellungen", + "Connected to Readwise": "Mit Readwise verbunden", + "Last synced: {{time}}": "Zuletzt synchronisiert: {{time}}", + "Sync Enabled": "Synchronisierung aktiviert", + "Disconnect": "Trennen", + "Connect your Readwise account to sync highlights.": "Verbinden Sie Ihr Readwise-Konto, um Highlights zu synchronisieren.", + "Get your access token at": "Holen Sie sich Ihr Zugriffstoken unter", + "Access Token": "Zugriffstoken", + "Paste your Readwise access token": "Fügen Sie Ihr Readwise-Zugriffstoken ein", + "Config": "Konfiguration", + "Readwise Sync": "Readwise-Synchronisierung", + "Push Highlights": "Highlights übertragen", + "Highlights synced to Readwise": "Highlights mit Readwise synchronisiert", + "Readwise sync failed: no internet connection": "Readwise-Synchronisierung fehlgeschlagen: Keine Internetverbindung", + "Readwise sync failed: {{error}}": "Readwise-Synchronisierung fehlgeschlagen: {{error}}" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 71845e10..37feddb7 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -1037,5 +1037,24 @@ "Next Image": "Επόμενη εικόνα", "Zoomed": "Ζουμαρισμένο", "Zoom level": "Επίπεδο ζουμ", - "Table viewer": "Πρόγραμμα προβολής πινάκων" + "Table viewer": "Πρόγραμμα προβολής πινάκων", + "Unable to connect to Readwise. Please check your network connection.": "Αδυναμία σύνδεσης στο Readwise. Ελέγξτε τη σύνδεση του δικτύου σας.", + "Invalid Readwise access token": "Μη έγκυρο διακριτικό πρόσβασης Readwise", + "Disconnected from Readwise": "Αποσυνδέθηκε από το Readwise", + "Never": "Ποτέ", + "Readwise Settings": "Ρυθμίσεις Readwise", + "Connected to Readwise": "Συνδέθηκε στο Readwise", + "Last synced: {{time}}": "Τελευταίος συγχρονισμός: {{time}}", + "Sync Enabled": "Ο συγχρονισμός ενεργοποιήθηκε", + "Disconnect": "Αποσύνδεση", + "Connect your Readwise account to sync highlights.": "Συνδέστε τον λογαριασμό σας Readwise για να συγχρονίσετε τις επισημάνσεις.", + "Get your access token at": "Αποκτήστε το διακριτικό πρόσβασής σας στο", + "Access Token": "Διακριτικό πρόσβασης", + "Paste your Readwise access token": "Επικολλήστε το διακριτικό πρόσβασης Readwise", + "Config": "Ρύθμιση παραμέτρων", + "Readwise Sync": "Συγχρονισμός Readwise", + "Push Highlights": "Προώθηση επισημάνσεων", + "Highlights synced to Readwise": "Οι επισημάνσεις συγχρονίστηκαν στο Readwise", + "Readwise sync failed: no internet connection": "Ο συγχρονισμός Readwise απέτυχε: δεν υπάρχει σύνδεση στο διαδίκτυο", + "Readwise sync failed: {{error}}": "Ο συγχρονισμός Readwise απέτυχε: {{error}}" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index d80864b9..8d481a0b 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -1049,5 +1049,24 @@ "Next Image": "Imagen siguiente", "Zoomed": "Con zoom", "Zoom level": "Nivel de zoom", - "Table viewer": "Visor de tablas" + "Table viewer": "Visor de tablas", + "Unable to connect to Readwise. Please check your network connection.": "No se pudo conectar a Readwise. Por favor, comprueba tu conexión a la red.", + "Invalid Readwise access token": "Token de acceso a Readwise no válido", + "Disconnected from Readwise": "Desconectado de Readwise", + "Never": "Nunca", + "Readwise Settings": "Ajustes de Readwise", + "Connected to Readwise": "Conectado a Readwise", + "Last synced: {{time}}": "Última sincronización: {{time}}", + "Sync Enabled": "Sincronización habilitada", + "Disconnect": "Desconectar", + "Connect your Readwise account to sync highlights.": "Conecta tu cuenta de Readwise para sincronizar los resaltados.", + "Get your access token at": "Obtén tu token de acceso en", + "Access Token": "Token de acceso", + "Paste your Readwise access token": "Pega tu token de acceso de Readwise", + "Config": "Configuración", + "Readwise Sync": "Sincronización con Readwise", + "Push Highlights": "Enviar resaltados", + "Highlights synced to Readwise": "Resaltados sincronizados con Readwise", + "Readwise sync failed: no internet connection": "Error en la sincronización con Readwise: sin conexión a internet", + "Readwise sync failed: {{error}}": "Error en la sincronización con Readwise: {{error}}" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 6f865096..18905058 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -1037,5 +1037,24 @@ "Next Image": "تصویر بعدی", "Zoomed": "بزرگنمایی شده", "Zoom level": "سطح بزرگنمایی", - "Table viewer": "نمایشگر جدول" + "Table viewer": "نمایشگر جدول", + "Unable to connect to Readwise. Please check your network connection.": "اتصال به Readwise امکان‌پذیر نیست. لطفاً اتصال شبکه خود را بررسی کنید.", + "Invalid Readwise access token": "توکن دسترسی Readwise نامعتبر است", + "Disconnected from Readwise": "اتصال از Readwise قطع شد", + "Never": "هرگز", + "Readwise Settings": "تنظیمات Readwise", + "Connected to Readwise": "به Readwise متصل شد", + "Last synced: {{time}}": "آخرین همگام‌سازی: {{time}}", + "Sync Enabled": "همگام‌سازی فعال شد", + "Disconnect": "قطع اتصال", + "Connect your Readwise account to sync highlights.": "برای همگام‌سازی هایلایت‌ها، حساب Readwise خود را متصل کنید.", + "Get your access token at": "توکن دسترسی خود را از اینجا دریافت کنید:", + "Access Token": "توکن دسترسی", + "Paste your Readwise access token": "توکن دسترسی Readwise خود را جای‌گذاری کنید", + "Config": "پیکربندی", + "Readwise Sync": "همگام‌سازی Readwise", + "Push Highlights": "ارسال هایلایت‌ها", + "Highlights synced to Readwise": "هایلایت‌ها با Readwise همگام‌سازی شدند", + "Readwise sync failed: no internet connection": "همگام‌سازی Readwise ناموفق بود: اتصال اینترنت برقرار نیست", + "Readwise sync failed: {{error}}": "همگام‌سازی Readwise ناموفق بود: {{error}}" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 4ec7ce30..0163e921 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -1049,5 +1049,24 @@ "Next Image": "Image suivante", "Zoomed": "Zoomé", "Zoom level": "Niveau de zoom", - "Table viewer": "Visionneuse de tableaux" + "Table viewer": "Visionneuse de tableaux", + "Unable to connect to Readwise. Please check your network connection.": "Impossible de se connecter à Readwise. Veuillez vérifier votre connexion réseau.", + "Invalid Readwise access token": "Jeton d'accès Readwise invalide", + "Disconnected from Readwise": "Déconnecté de Readwise", + "Never": "Jamais", + "Readwise Settings": "Paramètres Readwise", + "Connected to Readwise": "Connecté à Readwise", + "Last synced: {{time}}": "Dernière synchronisation : {{time}}", + "Sync Enabled": "Synchronisation activée", + "Disconnect": "Déconnecter", + "Connect your Readwise account to sync highlights.": "Connectez votre compte Readwise pour synchroniser les surlignages.", + "Get your access token at": "Obtenez votre jeton d'accès sur", + "Access Token": "Jeton d'accès", + "Paste your Readwise access token": "Collez votre jeton d'accès Readwise", + "Config": "Configuration", + "Readwise Sync": "Synchronisation Readwise", + "Push Highlights": "Pousser les surlignages", + "Highlights synced to Readwise": "Surlignages synchronisés avec Readwise", + "Readwise sync failed: no internet connection": "Échec de la synchronisation Readwise : pas de connexion internet", + "Readwise sync failed: {{error}}": "Échec de la synchronisation Readwise : {{error}}" } diff --git a/apps/readest-app/public/locales/he/translation.json b/apps/readest-app/public/locales/he/translation.json index 0337a001..2f6e202a 100644 --- a/apps/readest-app/public/locales/he/translation.json +++ b/apps/readest-app/public/locales/he/translation.json @@ -1049,5 +1049,24 @@ "Next Image": "תמונה הבאה", "Zoomed": "בזום", "Zoom level": "רמת זום", - "Table viewer": "מציג טבלאות" + "Table viewer": "מציג טבלאות", + "Unable to connect to Readwise. Please check your network connection.": "לא ניתן להתחבר ל-Readwise. אנא בדוק את חיבור הרשת שלך.", + "Invalid Readwise access token": "אסימون גישה של Readwise לא חוקי", + "Disconnected from Readwise": "נותק מ-Readwise", + "Never": "לעולם לא", + "Readwise Settings": "הגדרות Readwise", + "Connected to Readwise": "מחובר ל-Readwise", + "Last synced: {{time}}": "סונכרן לאחרונה: {{time}}", + "Sync Enabled": "סנכרון מופעל", + "Disconnect": "נתק", + "Connect your Readwise account to sync highlights.": "חבר את חשבון Readwise שלך כדי לסנכרן את ההדגשות.", + "Get your access token at": "קבל את אסימון הגישה שלך ב-", + "Access Token": "אסימון גישה", + "Paste your Readwise access token": "הדבק את אסימון הגישה של Readwise שלך", + "Config": "תצורה", + "Readwise Sync": "סנכרון Readwise", + "Push Highlights": "שלח הדגשות", + "Highlights synced to Readwise": "ההדגשות סונכרנו ל-Readwise", + "Readwise sync failed: no internet connection": "סנכרון Readwise נכשל: אין חיבור לאינטרנט", + "Readwise sync failed: {{error}}": "סנכרון Readwise נכשל: {{error}}" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index c2acad14..01795854 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -1037,5 +1037,24 @@ "Next Image": "अगली छवि", "Zoomed": "ज़ूम किया गया", "Zoom level": "ज़ूम स्तर", - "Table viewer": "तालिका दर्शक" + "Table viewer": "तालिका दर्शक", + "Unable to connect to Readwise. Please check your network connection.": "Readwise से कनेक्ट करने में असमर्थ। कृपया अपना नेटवर्क कनेक्शन जांचें।", + "Invalid Readwise access token": "अमान्य Readwise एक्सेस टोकन", + "Disconnected from Readwise": "Readwise से डिस्कनेक्ट हो गया", + "Never": "कभी नहीं", + "Readwise Settings": "Readwise सेटिंग्स", + "Connected to Readwise": "Readwise से जुड़ा हुआ", + "Last synced: {{time}}": "पिछला सिंक: {{time}}", + "Sync Enabled": "सिंक सक्षम", + "Disconnect": "डिस्कनेक्ट करें", + "Connect your Readwise account to sync highlights.": "हाइलाइट्स सिंक करने के लिए अपना Readwise खाता कनेक्ट करें।", + "Get your access token at": "अपना एक्सेस टोकन यहाँ प्राप्त करें", + "Access Token": "एक्सेस टोकन", + "Paste your Readwise access token": "अपना Readwise एक्सेस टोकन पेस्ट करें", + "Config": "कॉन्फिग", + "Readwise Sync": "Readwise सिंक", + "Push Highlights": "हाइलाइट्स भेजें", + "Highlights synced to Readwise": "हाइलाइट्स Readwise में सिंक हो गए", + "Readwise sync failed: no internet connection": "Readwise सिंक विफल रहा: कोई इंटरनेट कनेक्शन नहीं", + "Readwise sync failed: {{error}}": "Readwise सिंक विफल रहा: {{error}}" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index f4522bb8..2f1d997b 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -1025,5 +1025,24 @@ "Next Image": "Gambar Berikutnya", "Zoomed": "Diperbesar", "Zoom level": "Tingkat zoom", - "Table viewer": "Penampil tabel" + "Table viewer": "Penampil tabel", + "Unable to connect to Readwise. Please check your network connection.": "Tidak dapat terhubung ke Readwise. Silakan periksa koneksi jaringan Anda.", + "Invalid Readwise access token": "Token akses Readwise tidak valid", + "Disconnected from Readwise": "Terputus dari Readwise", + "Never": "Tidak pernah", + "Readwise Settings": "Pengaturan Readwise", + "Connected to Readwise": "Terhubung ke Readwise", + "Last synced: {{time}}": "Terakhir disinkronkan: {{time}}", + "Sync Enabled": "Sinkronisasi Diaktifkan", + "Disconnect": "Putuskan sambungan", + "Connect your Readwise account to sync highlights.": "Hubungkan akun Readwise Anda untuk menyinkronkan sorotan.", + "Get your access token at": "Dapatkan token akses Anda di", + "Access Token": "Token Akses", + "Paste your Readwise access token": "Tempelkan token akses Readwise Anda", + "Config": "Konfigurasi", + "Readwise Sync": "Sinkronisasi Readwise", + "Push Highlights": "Kirim Sorotan", + "Highlights synced to Readwise": "Sorotan disinkronkan ke Readwise", + "Readwise sync failed: no internet connection": "Sinkronisasi Readwise gagal: tidak ada koneksi internet", + "Readwise sync failed: {{error}}": "Sinkronisasi Readwise gagal: {{error}}" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 8d609cc6..468b3752 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -1049,5 +1049,24 @@ "Next Image": "Immagine successiva", "Zoomed": "Zoomato", "Zoom level": "Livello di zoom", - "Table viewer": "Visualizzatore tabelle" + "Table viewer": "Visualizzatore tabelle", + "Unable to connect to Readwise. Please check your network connection.": "Impossibile connettersi a Readwise. Controlla la tua connessione di rete.", + "Invalid Readwise access token": "Token di accesso Readwise non valido", + "Disconnected from Readwise": "Disconnesso da Readwise", + "Never": "Mai", + "Readwise Settings": "Impostazioni Readwise", + "Connected to Readwise": "Connesso a Readwise", + "Last synced: {{time}}": "Ultima sincronizzazione: {{time}}", + "Sync Enabled": "Sincronizzazione abilitata", + "Disconnect": "Disconnetti", + "Connect your Readwise account to sync highlights.": "Connetti il tuo account Readwise per sincronizzare le evidenziazioni.", + "Get your access token at": "Ottieni il tuo token di accesso su", + "Access Token": "Token di accesso", + "Paste your Readwise access token": "Incolla il tuo token di accesso Readwise", + "Config": "Configurazione", + "Readwise Sync": "Sincronizzazione Readwise", + "Push Highlights": "Invia evidenziazioni", + "Highlights synced to Readwise": "Evidenziazioni sincronizzate su Readwise", + "Readwise sync failed: no internet connection": "Sincronizzazione Readwise fallita: nessuna connessione internet", + "Readwise sync failed: {{error}}": "Sincronizzazione Readwise fallita: {{error}}" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 01abffbe..7033897f 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -1025,5 +1025,24 @@ "Next Image": "次の画像", "Zoomed": "ズーム済み", "Zoom level": "ズームレベル", - "Table viewer": "テーブルビューア" + "Table viewer": "テーブルビューア", + "Unable to connect to Readwise. Please check your network connection.": "Readwise に接続できません。ネットワーク接続を確認してください。", + "Invalid Readwise access token": "無効な Readwise アクセストークン", + "Disconnected from Readwise": "Readwise から切断されました", + "Never": "一度もなし", + "Readwise Settings": "Readwise 設定", + "Connected to Readwise": "Readwise に接続済み", + "Last synced: {{time}}": "最終同期:{{time}}", + "Sync Enabled": "同期有効", + "Disconnect": "切断", + "Connect your Readwise account to sync highlights.": "ハイライトを同期するには Readwise アカウントを接続してください。", + "Get your access token at": "アクセストークンの取得先:", + "Access Token": "アクセストークン", + "Paste your Readwise access token": "Readwise アクセストークンを貼り付けてください", + "Config": "設定", + "Readwise Sync": "Readwise 同期", + "Push Highlights": "ハイライトをプッシュ", + "Highlights synced to Readwise": "ハイライトが Readwise に同期されました", + "Readwise sync failed: no internet connection": "Readwise 同期失敗:インターネット接続がありません", + "Readwise sync failed: {{error}}": "Readwise 同期失敗:{{error}}" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 6f8af098..41a916fb 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -1025,5 +1025,24 @@ "Next Image": "다음 이미지", "Zoomed": "확대됨", "Zoom level": "확대 수준", - "Table viewer": "테이블 뷰어" + "Table viewer": "테이블 뷰어", + "Unable to connect to Readwise. Please check your network connection.": "Readwise에 연결할 수 없습니다. 네트워크 연결을 확인해 주세요.", + "Invalid Readwise access token": "유효하지 않은 Readwise 액세스 토큰입니다", + "Disconnected from Readwise": "Readwise와 연결이 끊어졌습니다", + "Never": "안 함", + "Readwise Settings": "Readwise 설정", + "Connected to Readwise": "Readwise에 연결됨", + "Last synced: {{time}}": "마지막 동기화: {{time}}", + "Sync Enabled": "동기화 활성화됨", + "Disconnect": "연결 해제", + "Connect your Readwise account to sync highlights.": "하이라이트를 동기화하려면 Readwise 계정렬 연결하세요.", + "Get your access token at": "액세스 토큰 받기:", + "Access Token": "액세스 토큰", + "Paste your Readwise access token": "Readwise 액세스 토큰을 붙여넣으세요", + "Config": "구성", + "Readwise Sync": "Readwise 동기화", + "Push Highlights": "하이라이트 푸시", + "Highlights synced to Readwise": "하이라이트가 Readwise에 동기화되었습니다", + "Readwise sync failed: no internet connection": "Readwise 동기화 실패: 인터넷 연결 없음", + "Readwise sync failed: {{error}}": "Readwise 동기화 실패: {{error}}" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index e6e4f437..7178c0c9 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -1025,5 +1025,24 @@ "Next Image": "Imej Seterusnya", "Zoomed": "Dizum", "Zoom level": "Tahap zum", - "Table viewer": "Penyorot jadual" + "Table viewer": "Penyorot jadual", + "Unable to connect to Readwise. Please check your network connection.": "Tidak dapat menyambung ke Readwise. Sila periksa sambungan rangkaian anda.", + "Invalid Readwise access token": "Token akses Readwise tidak sah", + "Disconnected from Readwise": "Terputus sambungan daripada Readwise", + "Never": "Tidak pernah", + "Readwise Settings": "Tetapan Readwise", + "Connected to Readwise": "Disambungkan ke Readwise", + "Last synced: {{time}}": "Kali terakhir disinkronkan: {{time}}", + "Sync Enabled": "Penyinkronan Didayakan", + "Disconnect": "Putuskan sambungan", + "Connect your Readwise account to sync highlights.": "Sambungkan akaun Readwise anda untuk menyinkronkan sorotan.", + "Get your access token at": "Dapatkan token akses anda di", + "Access Token": "Token Akses", + "Paste your Readwise access token": "Tampal token akses Readwise anda", + "Config": "Konfigurasi", + "Readwise Sync": "Penyinkronan Readwise", + "Push Highlights": "Hantar Sorotan", + "Highlights synced to Readwise": "Sorotan disinkronkan ke Readwise", + "Readwise sync failed: no internet connection": "Penyinkronan Readwise gagal: tiada sambungan internet", + "Readwise sync failed: {{error}}": "Penyinkronan Readwise gagal: {{error}}" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 510e3e9e..34af0bd2 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -1037,5 +1037,24 @@ "Next Image": "Volgende afbeelding", "Zoomed": "Gezoomd", "Zoom level": "Zoomniveau", - "Table viewer": "Tabelviewer" + "Table viewer": "Tabelviewer", + "Unable to connect to Readwise. Please check your network connection.": "Kan geen verbinding maken met Readwise. Controleer uw netwerkverbinding.", + "Invalid Readwise access token": "Ongeldige Readwise-toegangstoken", + "Disconnected from Readwise": "Verbinding met Readwise verbroken", + "Never": "Nooit", + "Readwise Settings": "Readwise-instellingen", + "Connected to Readwise": "Verbonden met Readwise", + "Last synced: {{time}}": "Laatst gesynchroniseerd: {{time}}", + "Sync Enabled": "Synchronisatie ingeschakeld", + "Disconnect": "Verbinding verbreken", + "Connect your Readwise account to sync highlights.": "Verbind uw Readwise-account om markeringen te synchroniseren.", + "Get your access token at": "Verkrijg uw toegangstoken op", + "Access Token": "Toegangstoken", + "Paste your Readwise access token": "Plak uw Readwise-toegangstoken", + "Config": "Configuratie", + "Readwise Sync": "Readwise-synchronisatie", + "Push Highlights": "Markeringen verzenden", + "Highlights synced to Readwise": "Markeringen gesynchroniseerd met Readwise", + "Readwise sync failed: no internet connection": "Readwise-synchronisatie mislukt: geen internetverbinding", + "Readwise sync failed: {{error}}": "Readwise-synchronisatie mislukt: {{error}}" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 534281ef..038dce30 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -1061,5 +1061,24 @@ "Next Image": "Następny obraz", "Zoomed": "Powiększone", "Zoom level": "Poziom powiększenia", - "Table viewer": "Przeglądarka tabel" + "Table viewer": "Przeglądarka tabel", + "Unable to connect to Readwise. Please check your network connection.": "Nie można połączyć się z Readwise. Sprawdź połączenie sieciowe.", + "Invalid Readwise access token": "Nieprawidłowy token dostępu Readwise", + "Disconnected from Readwise": "Rozłączono z Readwise", + "Never": "Nigdy", + "Readwise Settings": "Ustawienia Readwise", + "Connected to Readwise": "Połączono z Readwise", + "Last synced: {{time}}": "Ostatnia synchronizacja: {{time}}", + "Sync Enabled": "Synchronizacja włączona", + "Disconnect": "Rozłącz", + "Connect your Readwise account to sync highlights.": "Połącz swoje konto Readwise, aby synchronizować wyróżnienia.", + "Get your access token at": "Pobierz token dostępu pod adresem", + "Access Token": "Token dostępu", + "Paste your Readwise access token": "Wklej swój token dostępu Readwise", + "Config": "Konfiguracja", + "Readwise Sync": "Synchronizacja Readwise", + "Push Highlights": "Wyślij wyróżnienia", + "Highlights synced to Readwise": "Wyróżnienia zsynchronizowane z Readwise", + "Readwise sync failed: no internet connection": "Synchronizacja Readwise nie powiodła się: brak połączenia z Internetem", + "Readwise sync failed: {{error}}": "Synchronizacja Readwise nie powiodła się: {{error}}" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 94bded06..d92af615 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -1049,5 +1049,24 @@ "Next Image": "Próxima imagem", "Zoomed": "Com zoom", "Zoom level": "Nível de zoom", - "Table viewer": "Visualizador de tabelas" + "Table viewer": "Visualizador de tabelas", + "Unable to connect to Readwise. Please check your network connection.": "Não foi possível conectar ao Readwise. Por favor, verifique sua conexão de rede.", + "Invalid Readwise access token": "Token de acesso do Readwise inválido", + "Disconnected from Readwise": "Desconectado do Readwise", + "Never": "Nunca", + "Readwise Settings": "Configurações do Readwise", + "Connected to Readwise": "Conectado ao Readwise", + "Last synced: {{time}}": "Última sincronização: {{time}}", + "Sync Enabled": "Sincronização ativada", + "Disconnect": "Desconectar", + "Connect your Readwise account to sync highlights.": "Conecte sua conta do Readwise para sincronizar os destaques.", + "Get your access token at": "Obtenha seu token de acesso em", + "Access Token": "Token de acesso", + "Paste your Readwise access token": "Cole seu token de acesso do Readwise", + "Config": "Configuração", + "Readwise Sync": "Sincronização com o Readwise", + "Push Highlights": "Enviar destaques", + "Highlights synced to Readwise": "Destaques sincronizados com o Readwise", + "Readwise sync failed: no internet connection": "Sincronização com o Readwise falhou: sem conexão com a internet", + "Readwise sync failed: {{error}}": "Sincronização com o Readwise falhou: {{error}}" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index ec36dd1a..e34a774b 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -1061,5 +1061,24 @@ "Next Image": "Следующее изображение", "Zoomed": "Увеличено", "Zoom level": "Уровень масштаба", - "Table viewer": "Просмотрщик таблиц" + "Table viewer": "Просмотрщик таблиц", + "Unable to connect to Readwise. Please check your network connection.": "Не удалось подключиться к Readwise. Пожалуйста, проверьте ваше сетевое соединение.", + "Invalid Readwise access token": "Недействительный токен доступа Readwise", + "Disconnected from Readwise": "Отключено от Readwise", + "Never": "Никогда", + "Readwise Settings": "Настройки Readwise", + "Connected to Readwise": "Подключено к Readwise", + "Last synced: {{time}}": "Последняя синхронизация: {{time}}", + "Sync Enabled": "Синхронизация включена", + "Disconnect": "Отключить", + "Connect your Readwise account to sync highlights.": "Подключите ваш аккаунт Readwise для синхронизации выделений.", + "Get your access token at": "Получите ваш токен доступа на", + "Access Token": "Токен доступа", + "Paste your Readwise access token": "Вставьте ваш токен доступа Readwise", + "Config": "Конфигурация", + "Readwise Sync": "Синхронизация Readwise", + "Push Highlights": "Отправить выделения", + "Highlights synced to Readwise": "Выделения синхронизированы с Readwise", + "Readwise sync failed: no internet connection": "Синхронизация Readwise не удалась: нет интернет-соединения", + "Readwise sync failed: {{error}}": "Синхронизация Readwise не удалась: {{error}}" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 6f409992..ed989443 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -1037,5 +1037,24 @@ "Next Image": "මීළඟ රූපය", "Zoomed": "විශාලනය කරන ලදී", "Zoom level": "විශාලන මට්ටම", - "Table viewer": "වගු නරඹන්නා" + "Table viewer": "වගු නරඹන්නා", + "Unable to connect to Readwise. Please check your network connection.": "Readwise වෙත සම්බන්ධ වීමට නොහැක. කරුණාකර ඔබගේ ජාල සම්බන්ධතාවය පරීක්ෂා කරන්න.", + "Invalid Readwise access token": "අවලංගු Readwise ප්‍රවේශ ටෝකනය", + "Disconnected from Readwise": "Readwise වෙතින් විසන්ධි විය", + "Never": "කවදාවත් නැහැ", + "Readwise Settings": "Readwise සැකසුම්", + "Connected to Readwise": "Readwise වෙත සම්බන්ධ විය", + "Last synced: {{time}}": "අවසානයට සමමුහුර්ත කළේ: {{time}}", + "Sync Enabled": "සමමුහුර්ත කිරීම සබල කර ඇත", + "Disconnect": "විසන්ධි කරන්න", + "Connect your Readwise account to sync highlights.": "විශේෂ අවස්ථා සමමුහුර්ත කිරීමට ඔබගේ Readwise ගිණුම සම්බන්ධ කරන්න.", + "Get your access token at": "ඔබගේ ප්‍රවේශ ටෝකනය මෙතැනින් ලබා ගන්න", + "Access Token": "ප්‍රවේශ ටෝකනය", + "Paste your Readwise access token": "ඔබගේ Readwise ප්‍රවේශ ටෝකනය මෙහි අලවන්න", + "Config": "වින්‍යාසය", + "Readwise Sync": "Readwise සමමුහුර්තකරණය", + "Push Highlights": "විශේෂ අවස්ථා යොමු කරන්න", + "Highlights synced to Readwise": "විශේෂ අවස්ථා Readwise සමඟ සමමුහුර්ත විය", + "Readwise sync failed: no internet connection": "Readwise සමමුහුර්තකරණය අසාර්ථක විය: අන්තර්ජාල සම්බන්ධතාවයක් නොමැත", + "Readwise sync failed: {{error}}": "Readwise සමමුහුර්තකරණය අසාර්ථක විය: {{error}}" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index 57523d84..64606d8b 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -1037,5 +1037,24 @@ "Next Image": "Nästa bild", "Zoomed": "Zoomad", "Zoom level": "Zoomnivå", - "Table viewer": "Tabellvisare" + "Table viewer": "Tabellvisare", + "Unable to connect to Readwise. Please check your network connection.": "Kunde inte ansluta till Readwise. Kontrollera din nätverksanslutning.", + "Invalid Readwise access token": "Ogiltig Readwise-åtkomsttoken", + "Disconnected from Readwise": "Frånkopplad från Readwise", + "Never": "Aldrig", + "Readwise Settings": "Readwise-inställningar", + "Connected to Readwise": "Ansluten till Readwise", + "Last synced: {{time}}": "Senast synkroniserad: {{time}}", + "Sync Enabled": "Synkronisering aktiverad", + "Disconnect": "Koppla från", + "Connect your Readwise account to sync highlights.": "Anslut ditt Readwise-konto för att synkronisera markeringar.", + "Get your access token at": "Hämta din åtkomsttoken på", + "Access Token": "Åtkomsttoken", + "Paste your Readwise access token": "Klistra in din Readwise-åtkomsttoken", + "Config": "Konfiguration", + "Readwise Sync": "Readwise-synkronisering", + "Push Highlights": "Skicka markeringar", + "Highlights synced to Readwise": "Markeringar synkroniserade till Readwise", + "Readwise sync failed: no internet connection": "Readwise-synkronisering misslyckades: ingen internetanslutning", + "Readwise sync failed: {{error}}": "Readwise-synkronisering misslyckades: {{error}}" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index f43e0c8c..94f5d4f9 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -1037,5 +1037,24 @@ "Next Image": "அடுத்த படம்", "Zoomed": "பெரிதாக்கப்பட்டது", "Zoom level": "பெரிதாக்கும் நிலை", - "Table viewer": "அட்டவணை காட்சிப்பவர்" + "Table viewer": "அட்டவணை காட்சிப்பவர்", + "Unable to connect to Readwise. Please check your network connection.": "Readwise உடன் இணைக்க முடியவில்லை. உங்கள் நெட்வொர்க் இணைப்பைச் சரிபார்க்கவும்.", + "Invalid Readwise access token": "தவறான Readwise அணுகல் டோக்கன்", + "Disconnected from Readwise": "Readwise இலிருந்து துண்டிக்கப்பட்டது", + "Never": "ஒருபோதும் இல்லை", + "Readwise Settings": "Readwise அமைப்புகள்", + "Connected to Readwise": "Readwise உடன் இணைக்கப்பட்டது", + "Last synced: {{time}}": "கடைசியாக ஒத்திசைக்கப்பட்டது: {{time}}", + "Sync Enabled": "ஒத்திசைவு இயக்கப்பட்டது", + "Disconnect": "துண்டி", + "Connect your Readwise account to sync highlights.": "சிறப்பம்சங்களை ஒத்திசைக்க உங்கள் Readwise கணக்கை இணைக்கவும்.", + "Get your access token at": "உங்கள் அணுகல் டோக்கனை இங்கே பெறவும்", + "Access Token": "அணுகல் டோக்கன்", + "Paste your Readwise access token": "உங்கள் Readwise அணுகல் டோக்கனை ஒட்டவும்", + "Config": "கட்டமைப்பு", + "Readwise Sync": "Readwise ஒத்திசைவு", + "Push Highlights": "சிறப்பம்சங்களை அனுப்பு", + "Highlights synced to Readwise": "சிறப்பம்சங்கள் Readwise உடன் ஒத்திசைக்கப்பட்டன", + "Readwise sync failed: no internet connection": "Readwise ஒத்திசைவு தோல்வியடைந்தது: இணைய இணைப்பு இல்லை", + "Readwise sync failed: {{error}}": "Readwise ஒத்திசைவு தோல்வியடைந்தது: {{error}}" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index d6c61208..25bb56fe 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -1025,5 +1025,24 @@ "Next Image": "รูปภาพถัดไป", "Zoomed": "ซูมแล้ว", "Zoom level": "ระดับการซูม", - "Table viewer": "ตัวดูตาราง" + "Table viewer": "ตัวดูตาราง", + "Unable to connect to Readwise. Please check your network connection.": "ไม่สามารถเชื่อมต่อกับ Readwise ได้ โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ", + "Invalid Readwise access token": "โทเคนการเข้าถึง Readwise ไม่ถูกต้อง", + "Disconnected from Readwise": "ยกเลิกการเชื่อมต่อกับ Readwise แล้ว", + "Never": "ไม่เคย", + "Readwise Settings": "การตั้งค่า Readwise", + "Connected to Readwise": "เชื่อมต่อกับ Readwise แล้ว", + "Last synced: {{time}}": "ซิงค์ล่าสุดเมื่อ: {{time}}", + "Sync Enabled": "เปิดใช้งานการซิงค์", + "Disconnect": "ยกเลิกการเชื่อมต่อ", + "Connect your Readwise account to sync highlights.": "เชื่อมต่อบัญชี Readwise ของคุณเพื่อซิงค์ไฮไลต์", + "Get your access token at": "รับโทเคนการเข้าถึงของคุณได้ที่", + "Access Token": "โทเคนการเข้าถึง", + "Paste your Readwise access token": "วางโทเคนการเข้าถึง Readwise ของคุณ", + "Config": "การกำหนดค่า", + "Readwise Sync": "การซิงค์ Readwise", + "Push Highlights": "พุชไฮไลต์", + "Highlights synced to Readwise": "ซิงค์ไฮไลต์ไปยัง Readwise แล้ว", + "Readwise sync failed: no internet connection": "การซิงค์ Readwise ล้มเหลว: ไม่มีการเชื่อมต่ออินเทอร์เน็ต", + "Readwise sync failed: {{error}}": "การซิงค์ Readwise ล้มเหลว: {{error}}" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 5784eb5a..0b1e8ebc 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -1037,5 +1037,24 @@ "Next Image": "Sonraki Resim", "Zoomed": "Yakınlaştırıldı", "Zoom level": "Yakınlaştırma düzeyi", - "Table viewer": "Tablo görüntüleyici" + "Table viewer": "Tablo görüntüleyici", + "Unable to connect to Readwise. Please check your network connection.": "Readwise'a bağlanılamadı. Lütfen ağ bağlantınızı kontrol edin.", + "Invalid Readwise access token": "Geçersiz Readwise erişim kodu", + "Disconnected from Readwise": "Readwise bağlantısı kesildi", + "Never": "Asla", + "Readwise Settings": "Readwise Ayarları", + "Connected to Readwise": "Readwise'a Bağlandı", + "Last synced: {{time}}": "Son senkronizasyon: {{time}}", + "Sync Enabled": "Senkronizasyon Etkin", + "Disconnect": "Bağlantıyı Kes", + "Connect your Readwise account to sync highlights.": "Vurguları senkronize etmek için Readwise hesabınızı bağlayın.", + "Get your access token at": "Erişim kodunuzu şuradan alın:", + "Access Token": "Erişim Kodu", + "Paste your Readwise access token": "Readwise erişim kodunuzu yapıştırın", + "Config": "Yapılandırma", + "Readwise Sync": "Readwise Senkronizasyonu", + "Push Highlights": "Vurguları Gönder", + "Highlights synced to Readwise": "Vurgular Readwise ile senkronize edildi", + "Readwise sync failed: no internet connection": "Readwise senkronizasyonu başarısız: internet bağlantısı yok", + "Readwise sync failed: {{error}}": "Readwise senkronizasyonu başarısız: {{error}}" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index eafb9200..90d3f094 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -1061,5 +1061,24 @@ "Next Image": "Наступне зображення", "Zoomed": "Збільшено", "Zoom level": "Рівень масштабу", - "Table viewer": "Переглядач таблиць" + "Table viewer": "Переглядач таблиць", + "Unable to connect to Readwise. Please check your network connection.": "Не вдалося підключитися до Readwise. Будь ласка, перевірте мережеве з'єднання.", + "Invalid Readwise access token": "Недійсний токен доступу Readwise", + "Disconnected from Readwise": "Відключено від Readwise", + "Never": "Ніколи", + "Readwise Settings": "Налаштування Readwise", + "Connected to Readwise": "Підключено до Readwise", + "Last synced: {{time}}": "Остання синхронізація: {{time}}", + "Sync Enabled": "Синхронізацію увімкнено", + "Disconnect": "Відключити", + "Connect your Readwise account to sync highlights.": "Підключіть свій обліковий запис Readwise, щоб синхронізувати виділення.", + "Get your access token at": "Отримайте токен доступу за адресою", + "Access Token": "Токен доступу", + "Paste your Readwise access token": "Вставте ваш токен доступу Readwise", + "Config": "Конфігурація", + "Readwise Sync": "Синхронізація Readwise", + "Push Highlights": "Надіслати виділення", + "Highlights synced to Readwise": "Виділення синхронізовано з Readwise", + "Readwise sync failed: no internet connection": "Синхронізація Readwise не вдалася: немає інтернет-з'єднання", + "Readwise sync failed: {{error}}": "Синхронізація Readwise не вдалася: {{error}}" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 824f9d1e..000546ba 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -1025,5 +1025,24 @@ "Next Image": "Ảnh sau", "Zoomed": "Đã thu phóng", "Zoom level": "Mức thu phóng", - "Table viewer": "Trình xem bảng" + "Table viewer": "Trình xem bảng", + "Unable to connect to Readwise. Please check your network connection.": "Không thể kết nối với Readwise. Vui lòng kiểm tra kết nối mạng của bạn.", + "Invalid Readwise access token": "Mã xác thực Readwise không hợp lệ", + "Disconnected from Readwise": "Đã ngắt kết nối với Readwise", + "Never": "Không bao giờ", + "Readwise Settings": "Cài đặt Readwise", + "Connected to Readwise": "Đã kết nối với Readwise", + "Last synced: {{time}}": "Lần đồng bộ cuối: {{time}}", + "Sync Enabled": "Đã bật đồng bộ", + "Disconnect": "Ngắt kết nối", + "Connect your Readwise account to sync highlights.": "Kết nối tài khoản Readwise của bạn để đồng bộ các phần đánh dấu.", + "Get your access token at": "Lấy mã xác thực tại", + "Access Token": "Mã xác thực", + "Paste your Readwise access token": "Dán mã xác thực Readwise của bạn vào đây", + "Config": "Cấu hình", + "Readwise Sync": "Đồng bộ Readwise", + "Push Highlights": "Đẩy các phần đánh dấu", + "Highlights synced to Readwise": "Các phần đánh dấu đã được đồng bộ với Readwise", + "Readwise sync failed: no internet connection": "Đồng bộ Readwise thất bại: không có kết nối internet", + "Readwise sync failed: {{error}}": "Đồng bộ Readwise thất bại: {{error}}" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 6d2e92bd..25f764ae 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -1025,5 +1025,24 @@ "Next Image": "下一张图片", "Zoomed": "已缩放", "Zoom level": "缩放级别", - "Table viewer": "表格查看器" + "Table viewer": "表格查看器", + "Unable to connect to Readwise. Please check your network connection.": "无法连接到 Readwise。请检查您的网络连接。", + "Invalid Readwise access token": "无效的 Readwise 访问令牌", + "Disconnected from Readwise": "已断开与 Readwise 的连接", + "Never": "从不", + "Readwise Settings": "Readwise 设置", + "Connected to Readwise": "已连接到 Readwise", + "Last synced: {{time}}": "上次同步时间:{{time}}", + "Sync Enabled": "同步已启用", + "Disconnect": "断开连接", + "Connect your Readwise account to sync highlights.": "连接您的 Readwise 账户以同步高亮内容。", + "Get your access token at": "获取您的访问令牌:", + "Access Token": "访问令牌", + "Paste your Readwise access token": "粘贴您的 Readwise 访问令牌", + "Config": "配置", + "Readwise Sync": "Readwise 同步", + "Push Highlights": "推送高亮内容", + "Highlights synced to Readwise": "高亮内容已同步到 Readwise", + "Readwise sync failed: no internet connection": "Readwise 同步失败:无网络连接", + "Readwise sync failed: {{error}}": "Readwise 同步失败:{{error}}" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 45b5f23c..dc82c44c 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -1025,5 +1025,24 @@ "Next Image": "下一張圖片", "Zoomed": "已縮放", "Zoom level": "縮放級別", - "Table viewer": "表格檢視器" + "Table viewer": "表格檢視器", + "Unable to connect to Readwise. Please check your network connection.": "無法連接到 Readwise。請檢查您的網路連線。", + "Invalid Readwise access token": "無效的 Readwise 存取權杖", + "Disconnected from Readwise": "已斷開與 Readwise 的連線", + "Never": "從不", + "Readwise Settings": "Readwise 設定", + "Connected to Readwise": "已連接到 Readwise", + "Last synced: {{time}}": "上次同步時間:{{time}}", + "Sync Enabled": "同步已啟用", + "Disconnect": "斷開連線", + "Connect your Readwise account to sync highlights.": "連接您的 Readwise 帳戶以同步高亮內容。", + "Get your access token at": "獲取您的存取權杖:", + "Access Token": "存取權杖", + "Paste your Readwise access token": "貼上您的 Readwise 存取權杖", + "Config": "配置", + "Readwise Sync": "Readwise 同步", + "Push Highlights": "推送高亮內容", + "Highlights synced to Readwise": "高亮內容已同步到 Readwise", + "Readwise sync failed: no internet connection": "Readwise 同步失敗:無網路連線", + "Readwise sync failed: {{error}}": "Readwise 同步失敗:{{error}}" } diff --git a/apps/readest-app/src/__tests__/components/ProofreadRules.test.tsx b/apps/readest-app/src/__tests__/components/ProofreadRules.test.tsx index 53c52a5e..f195ee6f 100644 --- a/apps/readest-app/src/__tests__/components/ProofreadRules.test.tsx +++ b/apps/readest-app/src/__tests__/components/ProofreadRules.test.tsx @@ -68,6 +68,7 @@ vi.mock('@/services/environment', async (importOriginal) => { import { EnvProvider } from '@/context/EnvContext'; import { AuthProvider } from '@/context/AuthContext'; +import { DEFAULT_SYSTEM_SETTINGS } from '@/services/constants'; function renderWithProviders(ui: React.ReactNode) { return render( @@ -81,12 +82,7 @@ describe('ProofreadRulesManager', () => { beforeEach(() => { // Reset stores (useSettingsStore.setState as unknown as (state: unknown) => void)({ - settings: { - globalViewSettings: { proofreadRules: [] }, - kosync: { - enabled: false, - }, - }, + settings: DEFAULT_SYSTEM_SETTINGS, }); (useReaderStore.setState as unknown as (state: unknown) => void)({ viewStates: {} }); useSidebarStore.setState({ sideBarBookKey: null }); @@ -101,6 +97,7 @@ describe('ProofreadRulesManager', () => { // Arrange: populate stores (useSettingsStore.setState as unknown as (state: unknown) => void)({ settings: { + ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [ { @@ -127,7 +124,6 @@ describe('ProofreadRulesManager', () => { }, ], }, - kosync: { enabled: false }, }, }); @@ -164,8 +160,8 @@ describe('ProofreadRulesManager', () => { // Arrange: populate stores with a selection rule persisted in book config (useSettingsStore.setState as unknown as (state: unknown) => void)({ settings: { + ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [] }, - kosync: { enabled: false }, }, }); @@ -284,10 +280,10 @@ describe('ProofreadRulesManager', () => { (useSettingsStore.setState as unknown as (state: unknown) => void)({ settings: { + ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [libraryRule], }, - kosync: { enabled: false }, }, }); @@ -364,8 +360,8 @@ describe('ProofreadRulesManager', () => { (useSettingsStore.setState as unknown as (state: unknown) => void)({ settings: { + ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [] }, - kosync: { enabled: false }, }, }); @@ -418,8 +414,8 @@ describe('ProofreadRulesManager', () => { // Arrange stores (useSettingsStore.setState as unknown as (state: unknown) => void)({ settings: { + ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [] }, - kosync: { enabled: false }, }, }); (useReaderStore.setState as unknown as (state: unknown) => void)({ @@ -453,8 +449,8 @@ describe('ProofreadRulesManager', () => { it('shows empty state messages when no rules exist', async () => { (useSettingsStore.setState as unknown as (state: unknown) => void)({ settings: { + ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [] }, - kosync: { enabled: false }, }, }); @@ -509,10 +505,10 @@ describe('ProofreadRulesManager', () => { (useSettingsStore.setState as unknown as (state: unknown) => void)({ settings: { + ...DEFAULT_SYSTEM_SETTINGS, globalViewSettings: { proofreadRules: [libraryRule], }, - kosync: { enabled: false }, }, }); diff --git a/apps/readest-app/src/app/reader/components/Reader.tsx b/apps/readest-app/src/app/reader/components/Reader.tsx index 7e7ef34f..c6ba2fad 100644 --- a/apps/readest-app/src/app/reader/components/Reader.tsx +++ b/apps/readest-app/src/app/reader/components/Reader.tsx @@ -24,6 +24,7 @@ import { getSysFontsList, setSystemUIVisibility } from '@/utils/bridge'; import { AboutWindow } from '@/components/AboutWindow'; import { UpdaterWindow } from '@/components/UpdaterWindow'; import { KOSyncSettingsWindow } from './KOSyncSettings'; +import { ReadwiseSettingsWindow } from './ReadwiseSettings'; import { ProofreadRulesManager } from './ProofreadRules'; import { Toast } from '@/components/Toast'; import { getLocale } from '@/utils/misc'; @@ -169,6 +170,7 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => { + diff --git a/apps/readest-app/src/app/reader/components/ReadwiseSettings.tsx b/apps/readest-app/src/app/reader/components/ReadwiseSettings.tsx new file mode 100644 index 00000000..9066f44e --- /dev/null +++ b/apps/readest-app/src/app/reader/components/ReadwiseSettings.tsx @@ -0,0 +1,172 @@ +import React, { useState, useEffect } from 'react'; +import { useEnv } from '@/context/EnvContext'; +import { useTranslation } from '@/hooks/useTranslation'; +import { useSettingsStore } from '@/store/settingsStore'; +import { eventDispatcher } from '@/utils/event'; +import { ReadwiseClient } from '@/services/readwise'; +import Dialog from '@/components/Dialog'; + +export const setReadwiseSettingsWindowVisible = (visible: boolean) => { + const dialog = document.getElementById('readwise_settings_window'); + if (dialog) { + const event = new CustomEvent('setReadwiseSettingsVisibility', { + detail: { visible }, + }); + dialog.dispatchEvent(event); + } +}; + +export const ReadwiseSettingsWindow: React.FC = () => { + const _ = useTranslation(); + const { envConfig } = useEnv(); + const { settings, setSettings, saveSettings } = useSettingsStore(); + + const [isOpen, setIsOpen] = useState(false); + const [accessToken, setAccessToken] = useState(''); + const [isConnecting, setIsConnecting] = useState(false); + + const isConfigured = !!settings.readwise?.accessToken; + + useEffect(() => { + const handleCustomEvent = (event: CustomEvent) => { + setIsOpen(event.detail.visible); + if (event.detail.visible) { + setAccessToken(''); + } + }; + const el = document.getElementById('readwise_settings_window'); + el?.addEventListener('setReadwiseSettingsVisibility', handleCustomEvent as EventListener); + return () => { + el?.removeEventListener('setReadwiseSettingsVisibility', handleCustomEvent as EventListener); + }; + }, []); + + const handleConnect = async () => { + setIsConnecting(true); + try { + const client = new ReadwiseClient({ enabled: true, accessToken, lastSyncedAt: 0 }); + const { valid, isNetworkError } = await client.validateToken(); + if (valid) { + const newSettings = { + ...settings, + readwise: { + enabled: true, + accessToken, + lastSyncedAt: settings.readwise?.lastSyncedAt ?? 0, + }, + }; + setSettings(newSettings); + await saveSettings(envConfig, newSettings); + } else if (isNetworkError) { + eventDispatcher.dispatch('toast', { + message: _('Unable to connect to Readwise. Please check your network connection.'), + type: 'error', + }); + } else { + eventDispatcher.dispatch('toast', { + message: _('Invalid Readwise access token'), + type: 'error', + }); + } + } finally { + setIsConnecting(false); + setAccessToken(''); + } + }; + + const handleDisconnect = async () => { + const newSettings = { + ...settings, + readwise: { enabled: false, accessToken: '', lastSyncedAt: 0 }, + }; + setSettings(newSettings); + await saveSettings(envConfig, newSettings); + eventDispatcher.dispatch('toast', { message: _('Disconnected from Readwise'), type: 'info' }); + }; + + const handleToggleEnabled = async () => { + const newSettings = { + ...settings, + readwise: { ...settings.readwise, enabled: !settings.readwise?.enabled }, + }; + setSettings(newSettings); + await saveSettings(envConfig, newSettings); + }; + + const lastSyncedAt = settings.readwise?.lastSyncedAt ?? 0; + const lastSyncedLabel = lastSyncedAt ? new Date(lastSyncedAt).toLocaleString() : _('Never'); + + return ( + setIsOpen(false)} + title={_('Readwise Settings')} + boxClassName='sm:!min-w-[520px] sm:h-auto' + > + {isOpen && ( +
+ {isConfigured ? ( + <> +
+

{_('Connected to Readwise')}

+

+ {_('Last synced: {{time}}', { time: lastSyncedLabel })} +

+
+
+ {_('Sync Enabled')} + +
+ + + ) : ( + <> +

+ {_('Connect your Readwise account to sync highlights.')} +

+

+ {_('Get your access token at')}{' '} + + readwise.io/access_token + +

+
+ + setAccessToken(e.target.value)} + /> +
+ + + )} +
+ )} +
+ ); +}; diff --git a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx index 29e36d03..e7af1f96 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -18,6 +18,7 @@ import { useResponsiveSize } from '@/hooks/useResponsiveSize'; import { useDeviceControlStore } from '@/store/deviceStore'; import { useFoliateEvents } from '../../hooks/useFoliateEvents'; import { useNotesSync } from '../../hooks/useNotesSync'; +import { useReadwiseSync } from '../../hooks/useReadwiseSync'; import { useTextSelector } from '../../hooks/useTextSelector'; import { Position, TextSelection } from '@/utils/sel'; import { getPopupPosition, getPosition, getTextFromRange } from '@/utils/sel'; @@ -51,6 +52,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { const { listenToNativeTouchEvents } = useDeviceControlStore(); useNotesSync(bookKey); + useReadwiseSync(bookKey); const osPlatform = getOSPlatform(); const config = getConfig(bookKey)!; diff --git a/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx b/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx index f1ad2e04..ad731678 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx @@ -19,6 +19,7 @@ import { DOWNLOAD_READEST_URL } from '@/services/constants'; import { navigateToLogin } from '@/utils/nav'; import { saveSysSettings } from '@/helpers/settings'; import { setKOSyncSettingsWindowVisible } from '@/app/reader/components/KOSyncSettings'; +import { setReadwiseSettingsWindowVisible } from '@/app/reader/components/ReadwiseSettings'; import { setProofreadRulesVisibility } from '@/app/reader/components/ProofreadRules'; import { setAboutDialogVisible } from '@/components/AboutWindow'; import useBooksManager from '../../hooks/useBooksManager'; @@ -99,6 +100,14 @@ const BookMenu: React.FC = ({ menuClassName, setIsDropdownOpen }) eventDispatcher.dispatch('push-kosync', { bookKey: sideBarBookKey }); setIsDropdownOpen?.(false); }; + const showReadwiseSettingsWindow = () => { + setReadwiseSettingsWindowVisible(true); + setIsDropdownOpen?.(false); + }; + const handlePushReadwise = () => { + eventDispatcher.dispatch('readwise-push-all', { bookKey: sideBarBookKey }); + setIsDropdownOpen?.(false); + }; const toggleDiscordPresence = () => { const discordRichPresenceEnabled = !settings.discordRichPresenceEnabled; saveSysSettings(envConfig, 'discordRichPresenceEnabled', discordRichPresenceEnabled); @@ -160,12 +169,26 @@ const BookMenu: React.FC = ({ menuClassName, setIsDropdownOpen }) ))} - - {settings.kosync.enabled && ( - <> - - - + {settings.kosync.enabled ? ( + +
    + + + +
+
+ ) : ( + + )} + {settings.readwise.enabled ? ( + +
    + + +
+
+ ) : ( + )} {appService?.isDesktopApp && ( <> diff --git a/apps/readest-app/src/app/reader/hooks/useReadwiseSync.ts b/apps/readest-app/src/app/reader/hooks/useReadwiseSync.ts new file mode 100644 index 00000000..3956f01a --- /dev/null +++ b/apps/readest-app/src/app/reader/hooks/useReadwiseSync.ts @@ -0,0 +1,111 @@ +import { useCallback, useEffect, useMemo } from 'react'; +import { useSettingsStore } from '@/store/settingsStore'; +import { useBookDataStore } from '@/store/bookDataStore'; +import { useEnv } from '@/context/EnvContext'; +import { useTranslation } from '@/hooks/useTranslation'; +import { eventDispatcher } from '@/utils/event'; +import { debounce } from '@/utils/debounce'; +import { ReadwiseClient } from '@/services/readwise'; + +const READWISE_SYNC_DEBOUNCE_MS = 5000; + +export const useReadwiseSync = (bookKey: string) => { + const _ = useTranslation(); + const { envConfig } = useEnv(); + const { getConfig, getBookData } = useBookDataStore(); + + // Read settings from store at call time to avoid stale closures + const updateLastSyncedAt = useCallback( + async (timestamp: number) => { + const { settings, setSettings, saveSettings } = useSettingsStore.getState(); + const newSettings = { + ...settings, + readwise: { ...settings.readwise, lastSyncedAt: timestamp }, + }; + setSettings(newSettings); + await saveSettings(envConfig, newSettings); + }, + [envConfig], + ); + + // useMemo (not useCallback) so the debounce timer isn't reset on every render + // eslint-disable-next-line react-hooks/exhaustive-deps + const debouncedPush = useMemo( + () => + debounce(async () => { + const { settings } = useSettingsStore.getState(); + if (!settings.readwise?.enabled || !settings.readwise?.accessToken) return; + const client = new ReadwiseClient(settings.readwise); + const book = getBookData(bookKey)?.book; + const config = getConfig(bookKey); + if (!book || !config?.booknotes) return; + + const lastSyncedAt = settings.readwise.lastSyncedAt ?? 0; + const newNotes = config.booknotes.filter( + (n) => n.updatedAt > lastSyncedAt || (n.deletedAt ?? 0) > lastSyncedAt, + ); + if (newNotes.length === 0) return; + + const result = await client.pushHighlights(newNotes, book); + if (result.success) { + await updateLastSyncedAt(Date.now()); + } else if (!result.isNetworkError) { + console.error('Readwise sync failed:', result.message); + } + }, READWISE_SYNC_DEBOUNCE_MS), + [bookKey, getBookData, getConfig, updateLastSyncedAt], + ); + + // Manual "Push All": sends every annotation/excerpt regardless of sync timestamp + const pushAllHighlights = useCallback(async () => { + const { settings } = useSettingsStore.getState(); + if (!settings.readwise?.enabled || !settings.readwise?.accessToken) return; + const client = new ReadwiseClient(settings.readwise); + const book = getBookData(bookKey)?.book; + const config = getConfig(bookKey); + if (!book || !config?.booknotes) return; + + const result = await client.pushHighlights(config.booknotes, book); + if (result.success) { + await updateLastSyncedAt(Date.now()); + eventDispatcher.dispatch('toast', { + message: _('Highlights synced to Readwise'), + type: 'success', + }); + } else { + const message = result.isNetworkError + ? _('Readwise sync failed: no internet connection') + : _('Readwise sync failed: {{error}}', { error: result.message }); + eventDispatcher.dispatch('toast', { message, type: 'error' }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bookKey, getBookData, getConfig, updateLastSyncedAt]); + + // Cancel any pending debounced sync on unmount to avoid background network requests + useEffect(() => { + return () => { + debouncedPush.cancel(); + }; + }, [debouncedPush]); + + // Listen for manual push-all events dispatched from BookMenu / BooknoteView + useEffect(() => { + const handlePushAll = async (e: CustomEvent) => { + if (e.detail.bookKey !== bookKey) return; + await pushAllHighlights(); + }; + eventDispatcher.on('readwise-push-all', handlePushAll); + return () => { + eventDispatcher.off('readwise-push-all', handlePushAll); + }; + }, [bookKey, pushAllHighlights]); + + // Auto-sync whenever booknotes change; debouncedPush reads enabled state internally + const config = getConfig(bookKey); + useEffect(() => { + debouncedPush(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [config?.booknotes]); + + return { pushAllHighlights }; +}; diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index f5a4b162..46399941 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -20,6 +20,7 @@ import { LibraryGroupByType, LibrarySortByType, ReadSettings, + ReadwiseSettings, SystemSettings, } from '@/types/settings'; import { UserStorageQuota, UserDailyTranslationQuota } from '@/types/quota'; @@ -64,6 +65,14 @@ export const DEFAULT_KOSYNC_SETTINGS = { enabled: false, } as KOSyncSettings; +export const READWISE_API_BASE_URL = 'https://readwise.io/api/v2'; + +export const DEFAULT_READWISE_SETTINGS = { + enabled: false, + accessToken: '', + lastSyncedAt: 0, +} as ReadwiseSettings; + export const DEFAULT_SYSTEM_SETTINGS: Partial = { keepLogin: false, autoUpload: true, @@ -93,6 +102,7 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial = { metadataDescriptionCollapsed: false, kosync: DEFAULT_KOSYNC_SETTINGS, + readwise: DEFAULT_READWISE_SETTINGS, aiSettings: DEFAULT_AI_SETTINGS, lastSyncedAtBooks: 0, diff --git a/apps/readest-app/src/services/readwise/ReadwiseClient.ts b/apps/readest-app/src/services/readwise/ReadwiseClient.ts new file mode 100644 index 00000000..745d37be --- /dev/null +++ b/apps/readest-app/src/services/readwise/ReadwiseClient.ts @@ -0,0 +1,93 @@ +import { Book, BookNote, HighlightColor } from '@/types/book'; +import { ReadwiseSettings } from '@/types/settings'; +import { READWISE_API_BASE_URL } from '@/services/constants'; + +const READEST_TO_READWISE_COLOR: Record = { + red: 'pink', + yellow: 'yellow', + green: 'green', + blue: 'blue', + violet: 'purple', +}; + +export class ReadwiseClient { + private config: ReadwiseSettings; + + constructor(config: ReadwiseSettings) { + this.config = config; + } + + private async request( + endpoint: string, + options: { method?: 'GET' | 'POST'; body?: string } = {}, + ): Promise { + const { method = 'GET', body } = options; + return fetch(`${READWISE_API_BASE_URL}${endpoint}`, { + method, + headers: { + Authorization: `Token ${this.config.accessToken}`, + ...(body ? { 'Content-Type': 'application/json' } : {}), + }, + body, + }); + } + + async validateToken(): Promise<{ valid: boolean; isNetworkError?: boolean }> { + try { + const res = await this.request('/auth/'); + return { valid: res.status === 204 }; + } catch { + return { valid: false, isNetworkError: true }; + } + } + + async pushHighlights( + notes: BookNote[], + book: Book, + ): Promise<{ success: boolean; message?: string; isNetworkError?: boolean }> { + const syncable = notes.filter( + (n) => (n.type === 'annotation' || n.type === 'excerpt') && !n.deletedAt && n.text, + ); + if (syncable.length === 0) return { success: true }; + + const isPublicUrl = (url?: string | null) => + !!url && /^https?:\/\/(?!localhost|127\.|asset\.localhost)/.test(url); + + const highlights = syncable.map((note) => ({ + text: note.text!, + title: book.title, + author: book.author, + ...(isPublicUrl(book.coverImageUrl) ? { image_url: book.coverImageUrl } : {}), + source_type: 'readest', + category: 'books', + note: note.note || undefined, + location: 0, + location_type: 'none', + highlighted_at: new Date(note.createdAt).toISOString(), + highlight_url: `readest://annotation/${book.hash}/${note.id}`, + color: note.color ? (READEST_TO_READWISE_COLOR[note.color] ?? 'yellow') : 'yellow', + })); + + try { + const res = await this.request('/highlights/', { + method: 'POST', + body: JSON.stringify({ highlights }), + }); + if (!res.ok) { + const errText = await res.text().catch(() => ''); + console.error('Readwise API error:', res.status, errText); + let message = `HTTP ${res.status}`; + try { + const err = JSON.parse(errText); + message = err.detail || err.message || JSON.stringify(err) || message; + } catch { + if (errText) message = errText; + } + return { success: false, message }; + } + return { success: true }; + } catch (e) { + return { success: false, message: (e as Error).message, isNetworkError: true }; + } + } +} diff --git a/apps/readest-app/src/services/readwise/index.ts b/apps/readest-app/src/services/readwise/index.ts new file mode 100644 index 00000000..4905c33d --- /dev/null +++ b/apps/readest-app/src/services/readwise/index.ts @@ -0,0 +1 @@ +export { ReadwiseClient } from './ReadwiseClient'; diff --git a/apps/readest-app/src/styles/globals.css b/apps/readest-app/src/styles/globals.css index 5ca8898d..0db70268 100644 --- a/apps/readest-app/src/styles/globals.css +++ b/apps/readest-app/src/styles/globals.css @@ -547,7 +547,8 @@ foliate-fxl { color: theme('colors.base-content') !important; } -[data-eink='true'] .btn-primary { +[data-eink='true'] .btn-primary, +[data-eink='true'] .btn-outline { background-color: theme('colors.base-content') !important; border: 1px solid theme('colors.base-content') !important; color: theme('colors.base-100') !important; diff --git a/apps/readest-app/src/types/settings.ts b/apps/readest-app/src/types/settings.ts index 448e4204..07623345 100644 --- a/apps/readest-app/src/types/settings.ts +++ b/apps/readest-app/src/types/settings.ts @@ -64,6 +64,12 @@ export interface KOSyncSettings { strategy: KOSyncStrategy; } +export interface ReadwiseSettings { + enabled: boolean; + accessToken: string; + lastSyncedAt: number; +} + export interface SystemSettings { version: number; localBooksDir: string; @@ -101,6 +107,7 @@ export interface SystemSettings { metadataDescriptionCollapsed: boolean; kosync: KOSyncSettings; + readwise: ReadwiseSettings; lastSyncedAtBooks: number; lastSyncedAtConfigs: number;