feat: Readwise highlights sync (#3311)

This commit is contained in:
Matt Vogel
2026-02-23 11:08:59 -05:00
committed by GitHub
parent b99c1bc19a
commit ce53cd2b47
39 changed files with 998 additions and 48 deletions
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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}}"
}
@@ -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 },
},
});
@@ -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 }) => {
<AboutWindow />
<UpdaterWindow />
<KOSyncSettingsWindow />
<ReadwiseSettingsWindow />
<ProofreadRulesManager />
<Toast />
</Suspense>
@@ -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 (
<Dialog
id='readwise_settings_window'
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title={_('Readwise Settings')}
boxClassName='sm:!min-w-[520px] sm:h-auto'
>
{isOpen && (
<div className='mb-4 mt-0 flex flex-col gap-4 p-2 sm:p-4'>
{isConfigured ? (
<>
<div className='text-center'>
<p className='text-base-content/80 text-sm'>{_('Connected to Readwise')}</p>
<p className='text-base-content/60 mt-1 text-xs'>
{_('Last synced: {{time}}', { time: lastSyncedLabel })}
</p>
</div>
<div className='flex h-14 items-center justify-between'>
<span className='text-base-content/80'>{_('Sync Enabled')}</span>
<input
type='checkbox'
className='toggle'
checked={settings.readwise?.enabled ?? false}
onChange={handleToggleEnabled}
/>
</div>
<button className='btn btn-outline btn-sm mt-2' onClick={handleDisconnect}>
{_('Disconnect')}
</button>
</>
) : (
<>
<p className='text-base-content/70 text-center text-sm'>
{_('Connect your Readwise account to sync highlights.')}
</p>
<p className='text-base-content/60 text-center text-xs'>
{_('Get your access token at')}{' '}
<a
href='https://readwise.io/access_token'
target='_blank'
rel='noopener noreferrer'
className='link link-primary'
>
readwise.io/access_token
</a>
</p>
<div className='form-control w-full'>
<label className='label py-1'>
<span className='label-text font-medium'>{_('Access Token')}</span>
</label>
<input
type='password'
placeholder={_('Paste your Readwise access token')}
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
spellCheck='false'
value={accessToken}
onChange={(e) => setAccessToken(e.target.value)}
/>
</div>
<button
className='btn btn-primary mt-2 h-12 min-h-12 w-full'
onClick={handleConnect}
disabled={isConnecting || !accessToken}
>
{isConnecting ? <span className='loading loading-spinner'></span> : _('Connect')}
</button>
</>
)}
</div>
)}
</Dialog>
);
};
@@ -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)!;
@@ -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<BookMenuProps> = ({ 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<BookMenuProps> = ({ menuClassName, setIsDropdownOpen })
<MenuItem label={_('Enter Parallel Read')} onClick={handleSetParallel} />
))}
<hr aria-hidden='true' className='border-base-200 my-1' />
<MenuItem label={_('KOReader Sync')} onClick={showKoSyncSettingsWindow} />
{settings.kosync.enabled && (
<>
<MenuItem label={_('Push Progress')} onClick={handlePushKOSync} />
<MenuItem label={_('Pull Progress')} onClick={handlePullKOSync} />
</>
{settings.kosync.enabled ? (
<MenuItem label={_('KOReader Sync')} detailsOpen={false} buttonClass='py-2'>
<ul className='flex flex-col ps-1'>
<MenuItem label={_('Config')} noIcon onClick={showKoSyncSettingsWindow} />
<MenuItem label={_('Push Progress')} noIcon onClick={handlePushKOSync} />
<MenuItem label={_('Pull Progress')} noIcon onClick={handlePullKOSync} />
</ul>
</MenuItem>
) : (
<MenuItem label={_('KOReader Sync')} onClick={showKoSyncSettingsWindow} />
)}
{settings.readwise.enabled ? (
<MenuItem label={_('Readwise Sync')} detailsOpen={false} buttonClass='py-2'>
<ul className='flex flex-col ps-1'>
<MenuItem label={_('Config')} noIcon onClick={showReadwiseSettingsWindow} />
<MenuItem label={_('Push Highlights')} noIcon onClick={handlePushReadwise} />
</ul>
</MenuItem>
) : (
<MenuItem label={_('Readwise Sync')} onClick={showReadwiseSettingsWindow} />
)}
{appService?.isDesktopApp && (
<>
@@ -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 };
};
@@ -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<SystemSettings> = {
keepLogin: false,
autoUpload: true,
@@ -93,6 +102,7 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial<SystemSettings> = {
metadataDescriptionCollapsed: false,
kosync: DEFAULT_KOSYNC_SETTINGS,
readwise: DEFAULT_READWISE_SETTINGS,
aiSettings: DEFAULT_AI_SETTINGS,
lastSyncedAtBooks: 0,
@@ -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<HighlightColor, string> = {
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<Response> {
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 };
}
}
}
@@ -0,0 +1 @@
export { ReadwiseClient } from './ReadwiseClient';
+2 -1
View File
@@ -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;
+7
View File
@@ -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;