diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index fa16379d..62cf0c72 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -1006,5 +1006,19 @@ "Finished": "منتهى", "Unread": "غير مقروء", "Clear Status": "مسح الحالة", - "Status": "الحالة" + "Status": "الحالة", + "Set status for {{count}} book(s)_zero": "تعيين الحالة للكتب", + "Set status for {{count}} book(s)_two": "تعيين الحالة للكتابين", + "Set status for {{count}} book(s)_few": "تعيين الحالة لـ {{count}} كتب", + "Set status for {{count}} book(s)_many": "تعيين الحالة لـ {{count}} كتاباً", + "Loading": "جارٍ التحميل...", + "Exit Paragraph Mode": "الخروج من وضع الفقرة", + "Paragraph Mode": "وضع الفقرة", + "Embedding Model": "نموذج التضمين", + "{{count}} book(s) synced_zero": "لم تتم مزامنة أي كتب", + "{{count}} book(s) synced_one": "تمت مزامنة كتاب واحد", + "{{count}} book(s) synced_two": "تمت مزامنة كتابين", + "{{count}} book(s) synced_few": "تمت مزامنة {{count}} كتب", + "{{count}} book(s) synced_many": "تمت مزامنة {{count}} كتاباً", + "{{count}} book(s) synced_other": "تمت مزامنة {{count}} كتاب" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index 58acf000..32fab338 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -966,5 +966,11 @@ "Finished": "শেষ হয়েছে", "Unread": "অপঠিত", "Clear Status": "স্থিতি মুছুন", - "Status": "অবস্থা" + "Status": "অবস্থা", + "Loading": "লোড হচ্ছে...", + "Exit Paragraph Mode": "অনুচ্ছেদ মোড থেকে প্রস্থান করুন", + "Paragraph Mode": "অনুচ্ছেদ মোড", + "Embedding Model": "এম্বেডিং মডেল", + "{{count}} book(s) synced_one": "{{count}}টি বই সিঙ্ক করা হয়েছে", + "{{count}} book(s) synced_other": "{{count}}টি বই সিঙ্ক করা হয়েছে" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index f8f9ebad..d319a2f5 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -949,12 +949,16 @@ "AI Gateway Model": "AI Gateway དཔེ་གཞི།", "Actions": "བྱ་འགུལ།", "Navigation": "འགྲུལ་བཞུད།", - "Set status for {{count}} book(s)_one": "དེབ་ {{count}} གི་གནས་སྟངས་གཏན་འཁེལ་བྱེད།", "Set status for {{count}} book(s)_other": "དེབ་ {{count}} གི་གནས་སྟངས་གཏན་འཁེལ་བྱེད།", "Mark as Unread": "མ་ཀློག་པར་རྟགས་རྒྱག", "Mark as Finished": "ཚར་བར་རྟགས་རྒྱག", "Finished": "ཚར་སོང་།", "Unread": "མ་ཀློག་པ།", "Clear Status": "གནས་སྟངས་གཙང་མ།", - "Status": "གནས་སྟངས།" + "Status": "གནས་སྟངས།", + "Loading": "ལེན་བཞིན་པ།...", + "Exit Paragraph Mode": "དུམ་མཚམས་རྣམ་པ་ནས་ཕྱིར་ཐོན།", + "Paragraph Mode": "དུམ་མཚམས་རྣམ་པ།", + "Embedding Model": "གནས་སྒྲིག་དཔེ་གཞི།", + "{{count}} book(s) synced_other": "དེབ་ {{count}} མཉམ་འགྲིག་བྱས་ཟིན།" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index df4c12e9..30dc4812 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -966,5 +966,11 @@ "Finished": "Beendet", "Unread": "Ungelesen", "Clear Status": "Status löschen", - "Status": "Status" + "Status": "Status", + "Loading": "Laden...", + "Exit Paragraph Mode": "Absatzmodus verlassen", + "Paragraph Mode": "Absatzmodus", + "Embedding Model": "Einbettungsmodell", + "{{count}} book(s) synced_one": "{{count}} Buch synchronisiert", + "{{count}} book(s) synced_other": "{{count}} Bücher synchronisiert" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 3062ebe9..e4576273 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -966,5 +966,11 @@ "Finished": "Ολοκληρώθηκε", "Unread": "Μη αναγνωσμένο", "Clear Status": "Εκκαθάριση κατάστασης", - "Status": "Κατάσταση" + "Status": "Κατάσταση", + "Loading": "Φόρτωση...", + "Exit Paragraph Mode": "Έξοδος από τη λειτουργία παραγράφου", + "Paragraph Mode": "Λειτουργία παραγράφου", + "Embedding Model": "Μοντέλο ενσωμάτωσης", + "{{count}} book(s) synced_one": "{{count}} βιβλίο συγχρονίστηκε", + "{{count}} book(s) synced_other": "{{count}} βιβλία συγχρονίστηκαν" } diff --git a/apps/readest-app/public/locales/en/translation.json b/apps/readest-app/public/locales/en/translation.json index d9327915..8e510ec2 100644 --- a/apps/readest-app/public/locales/en/translation.json +++ b/apps/readest-app/public/locales/en/translation.json @@ -22,10 +22,6 @@ "Successfully imported {{count}} book(s)_other": "Successfully imported {{count}} books", "Set status for {{count}} book(s)_one": "Set status for {{count}} book", "Set status for {{count}} book(s)_other": "Set status for {{count}} books", - "Mark as Finished": "Mark as Finished", - "Mark as Unread": "Mark as Unread", - "Finished": "Finished", - "Unread": "Unread", - "Clear Status": "Clear Status", - "Status": "Status" + "{{count}} book(s) synced_one": "{{count}} book synced", + "{{count}} book(s) synced_other": "{{count}} books synced" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index ddb4179b..61748b64 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -976,5 +976,13 @@ "Finished": "Terminado", "Unread": "Sin leer", "Clear Status": "Borrar estado", - "Status": "Estado" + "Status": "Estado", + "Set status for {{count}} book(s)_many": "Establecer estado para {{count}} libros", + "Loading": "Cargando...", + "Exit Paragraph Mode": "Salir del modo de párrafo", + "Paragraph Mode": "Modo de párrafo", + "Embedding Model": "Modelo de incrustación", + "{{count}} book(s) synced_one": "{{count}} libro sincronizado", + "{{count}} book(s) synced_many": "{{count}} libros sincronizados", + "{{count}} book(s) synced_other": "{{count}} libros sincronizados" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 4c67bfd4..53a9b8b1 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -966,5 +966,11 @@ "Finished": "تمام‌شده", "Unread": "خوانده‌نشده", "Clear Status": "پاک کردن وضعیت", - "Status": "وضعیت" + "Status": "وضعیت", + "Loading": "در حال بارگذاری...", + "Exit Paragraph Mode": "خروج از حالت بند", + "Paragraph Mode": "حالت بند", + "Embedding Model": "مدل جاسازی", + "{{count}} book(s) synced_one": "{{count}} کتاب همگام شد", + "{{count}} book(s) synced_other": "{{count}} کتاب همگام شدند" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 729908ce..64c8bffa 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -976,5 +976,13 @@ "Finished": "Terminé", "Unread": "Non lu", "Clear Status": "Effacer le statut", - "Status": "Statut" + "Status": "Statut", + "Set status for {{count}} book(s)_many": "Définir le statut pour {{count}} livres", + "Loading": "Chargement...", + "Exit Paragraph Mode": "Quitter le mode paragraphe", + "Paragraph Mode": "Mode paragraphe", + "Embedding Model": "Modèle d'incorporation", + "{{count}} book(s) synced_one": "{{count}} livre synchronisé", + "{{count}} book(s) synced_many": "{{count}} livres synchronisés", + "{{count}} book(s) synced_other": "{{count}} livres synchronisés" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 1e74604a..43aab8e8 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -966,5 +966,11 @@ "Finished": "समाप्त", "Unread": "अपठित", "Clear Status": "स्थिति साफ़ करें", - "Status": "स्थिति" + "Status": "स्थिति", + "Loading": "लोड हो रहा है...", + "Exit Paragraph Mode": "पैराग्राफ मोड से बाहर निकलें", + "Paragraph Mode": "पैराग्राफ मोड", + "Embedding Model": "एंबेडिंग मॉडल", + "{{count}} book(s) synced_one": "{{count}} पुस्तक सिंक की गई", + "{{count}} book(s) synced_other": "{{count}} पुस्तकें सिंक की गईं" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 52b8253a..9942291d 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -949,12 +949,16 @@ "AI Gateway Model": "Model AI Gateway", "Actions": "Tindakan", "Navigation": "Navigasi", - "Set status for {{count}} book(s)_one": "Atur status untuk {{count}} buku", "Set status for {{count}} book(s)_other": "Atur status untuk {{count}} buku", "Mark as Unread": "Tandai sebagai Belum Dibaca", "Mark as Finished": "Tandai sebagai Selesai", "Finished": "Selesai", "Unread": "Belum Dibaca", "Clear Status": "Hapus Status", - "Status": "Status" + "Status": "Status", + "Loading": "Memuat...", + "Exit Paragraph Mode": "Keluar dari Mode Paragraf", + "Paragraph Mode": "Mode Paragraf", + "Embedding Model": "Model Penyematan", + "{{count}} book(s) synced_other": "{{count}} buku telah disinkronkan" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 2e1c00b3..b2386f79 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -976,5 +976,13 @@ "Finished": "Finito", "Unread": "Non letto", "Clear Status": "Cancella stato", - "Status": "Stato" + "Status": "Stato", + "Set status for {{count}} book(s)_many": "Imposta stato per {{count}} libri", + "Loading": "Caricamento...", + "Exit Paragraph Mode": "Esci dalla modalità paragrafo", + "Paragraph Mode": "Modalità paragrafo", + "Embedding Model": "Modello di incorporazione", + "{{count}} book(s) synced_one": "{{count}} libro sincronizzato", + "{{count}} book(s) synced_many": "{{count}} libri sincronizzati", + "{{count}} book(s) synced_other": "{{count}} libri sincronizzati" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index e09d5080..1f555768 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -949,12 +949,16 @@ "AI Gateway Model": "AI Gatewayモデル", "Actions": "アクション", "Navigation": "ナビゲーション", - "Set status for {{count}} book(s)_one": "{{count}} 冊の本のステータスを設定", "Set status for {{count}} book(s)_other": "{{count}} 冊の本のステータスを設定", "Mark as Unread": "未読にする", "Mark as Finished": "読了にする", "Finished": "読了", "Unread": "未読", "Clear Status": "ステータスをクリア", - "Status": "ステータス" + "Status": "ステータス", + "Loading": "読み込み中", + "Exit Paragraph Mode": "段落モードを終了", + "Paragraph Mode": "段落モード", + "Embedding Model": "埋め込みモデル", + "{{count}} book(s) synced_other": "{{count}} 冊の本が同期されました" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 10264e76..53ed6def 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -949,12 +949,16 @@ "AI Gateway Model": "AI 게이트웨이 모델", "Actions": "작업", "Navigation": "탐색", - "Set status for {{count}} book(s)_one": "책 {{count}}권 상태 설정", "Set status for {{count}} book(s)_other": "책 {{count}}권 상태 설정", "Mark as Unread": "읽지 않음으로 표시", "Mark as Finished": "완료로 표시", "Finished": "완료", "Unread": "읽지 않음", "Clear Status": "상태 지우기", - "Status": "상태" + "Status": "상태", + "Loading": "로드 중...", + "Exit Paragraph Mode": "단락 모드 종료", + "Paragraph Mode": "단락 모드", + "Embedding Model": "임베딩 모델", + "{{count}} book(s) synced_other": "{{count}}권의 책이 동기화되었습니다" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index f3a31be5..bcc9544e 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -949,12 +949,16 @@ "AI Gateway Model": "Model AI Gateway", "Actions": "Tindakan", "Navigation": "Navigasi", - "Set status for {{count}} book(s)_one": "Tetapkan status untuk {{count}} buku", "Set status for {{count}} book(s)_other": "Tetapkan status untuk {{count}} buku", "Mark as Unread": "Tandakan sebagai Belum Dibaca", "Mark as Finished": "Tandakan sebagai Selesai", "Finished": "Selesai", "Unread": "Belum Dibaca", "Clear Status": "Kosongkan Status", - "Status": "Status" + "Status": "Status", + "Loading": "Memuatkan...", + "Exit Paragraph Mode": "Keluar dari Mod Perenggan", + "Paragraph Mode": "Mod Perenggan", + "Embedding Model": "Model Benaman", + "{{count}} book(s) synced_other": "{{count}} buku telah disegerakkan" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index b3a36c61..697d2e6e 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -966,5 +966,11 @@ "Finished": "Voltooid", "Unread": "Ongelezen", "Clear Status": "Status wissen", - "Status": "Status" + "Status": "Status", + "Loading": "Laden...", + "Exit Paragraph Mode": "Alineamodus verlaten", + "Paragraph Mode": "Alineamodus", + "Embedding Model": "Insluitmodel", + "{{count}} book(s) synced_one": "{{count}} boek gesynchroniseerd", + "{{count}} book(s) synced_other": "{{count}} boeken gesynchroniseerd" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index ab55c326..0df0bb8a 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -986,5 +986,15 @@ "Finished": "Ukończone", "Unread": "Nieprzeczytane", "Clear Status": "Wyczyść status", - "Status": "Status" + "Status": "Status", + "Set status for {{count}} book(s)_few": "Ustaw status dla {{count}} książek", + "Set status for {{count}} book(s)_many": "Ustaw status dla {{count}} książek", + "Loading": "Ładowanie...", + "Exit Paragraph Mode": "Wyjdź z trybu akapitu", + "Paragraph Mode": "Tryb akapitu", + "Embedding Model": "Model osadzania", + "{{count}} book(s) synced_one": "{{count}} książka zsynchronizowana", + "{{count}} book(s) synced_few": "{{count}} książki zsynchronizowane", + "{{count}} book(s) synced_many": "{{count}} książek zsynchronizowanych", + "{{count}} book(s) synced_other": "{{count}} książek zsynchronizowanych" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index a58eeeb7..0c9979b2 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -976,5 +976,13 @@ "Finished": "Concluído", "Unread": "Não lido", "Clear Status": "Limpar estado", - "Status": "Estado" + "Status": "Estado", + "Set status for {{count}} book(s)_many": "Definir status para {{count}} livros", + "Loading": "Carregando...", + "Exit Paragraph Mode": "Sair do modo de parágrafo", + "Paragraph Mode": "Modo de parágrafo", + "Embedding Model": "Modelo de incorporação", + "{{count}} book(s) synced_one": "{{count}} livro sincronizado", + "{{count}} book(s) synced_many": "{{count}} livros sincronizados", + "{{count}} book(s) synced_other": "{{count}} livros sincronizados" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 60630e42..5ad5e9cb 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -986,5 +986,15 @@ "Finished": "Прочитано", "Unread": "Не прочитано", "Clear Status": "Очистить статус", - "Status": "Статус" + "Status": "Статус", + "Set status for {{count}} book(s)_few": "Установить статус для {{count}} книг", + "Set status for {{count}} book(s)_many": "Установить статус для {{count}} книг", + "Loading": "Загрузка...", + "Exit Paragraph Mode": "Выйти из режима абзаца", + "Paragraph Mode": "Режим абзаца", + "Embedding Model": "Модель встраивания", + "{{count}} book(s) synced_one": "{{count}} книга синхронизирована", + "{{count}} book(s) synced_few": "{{count}} книги синхронизированы", + "{{count}} book(s) synced_many": "{{count}} книг синхронизировано", + "{{count}} book(s) synced_other": "{{count}} книг синхронизировано" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 66b85368..6131857c 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -966,5 +966,11 @@ "Finished": "අවසන්", "Unread": "නොකියවූ", "Clear Status": "තත්ත්වය හිස් කරන්න", - "Status": "තත්ත්වය" + "Status": "තත්ත්වය", + "Loading": "පූරණය වෙමින් පවතී...", + "Exit Paragraph Mode": "ඡේද ප්‍රකාරයෙන් ඉවත් වන්න", + "Paragraph Mode": "ඡේද ප්‍රකාරය", + "Embedding Model": "එබ්බවීමේ ආකෘතිය", + "{{count}} book(s) synced_one": "{{count}} පොතක් සමමුහුර්ත කරන ලදී", + "{{count}} book(s) synced_other": "{{count}} පොත් සමමුහුර්ත කරන ලදී" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index 695e85ac..d9933d80 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -966,5 +966,11 @@ "Finished": "Läst", "Unread": "Oläst", "Clear Status": "Rensa status", - "Status": "Status" + "Status": "Status", + "Loading": "Laddar...", + "Exit Paragraph Mode": "Avsluta styckeläge", + "Paragraph Mode": "Styckeläge", + "Embedding Model": "Inbäddningsmodell", + "{{count}} book(s) synced_one": "{{count}} bok synkroniserad", + "{{count}} book(s) synced_other": "{{count}} böcker synkroniserade" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index c1ff048a..6a48246e 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -966,5 +966,11 @@ "Finished": "முடிந்தது", "Unread": "படிக்கவில்லை", "Clear Status": "நிலையை அழி", - "Status": "நிலை" + "Status": "நிலை", + "Loading": "ஏற்றுகிறது...", + "Exit Paragraph Mode": "பத்தி பயன்முறையிலிருந்து வெளியேறு", + "Paragraph Mode": "பத்தி பயன்முறை", + "Embedding Model": "உட்பொதி மாதிரி", + "{{count}} book(s) synced_one": "{{count}} புத்தகம் ஒத்திசைக்கப்பட்டது", + "{{count}} book(s) synced_other": "{{count}} புத்தகங்கள் ஒத்திசைக்கப்பட்டன" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 2ced883f..fb9312a3 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -949,12 +949,16 @@ "AI Gateway Model": "โมเดล AI Gateway", "Actions": "การกระทำ", "Navigation": "การนำทาง", - "Set status for {{count}} book(s)_one": "ตั้งสถานะสำหรับหนังสือ {{count}} เล่ม", "Set status for {{count}} book(s)_other": "ตั้งสถานะสำหรับหนังสือ {{count}} เล่ม", "Mark as Unread": "ทำเครื่องหมายว่ายังไม่อ่าน", "Mark as Finished": "ทำเครื่องหมายว่าอ่านจบแล้ว", "Finished": "อ่านจบแล้ว", "Unread": "ยังไม่ได้อ่าน", "Clear Status": "ล้างสถานะ", - "Status": "สถานะ" + "Status": "สถานะ", + "Loading": "กำลังโหลด...", + "Exit Paragraph Mode": "ออกจากโหมดพารากราฟ", + "Paragraph Mode": "โหมดพารากราฟ", + "Embedding Model": "โมเดลการฝังตัว", + "{{count}} book(s) synced_other": "ซิงค์หนังสือ {{count}} เล่มแล้ว" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 30f5f182..b722a36e 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -966,5 +966,11 @@ "Finished": "Bitirildi", "Unread": "Okunmadı", "Clear Status": "Durumu Temizle", - "Status": "Durum" + "Status": "Durum", + "Loading": "Yükleniyor...", + "Exit Paragraph Mode": "Paragraf modundan çık", + "Paragraph Mode": "Paragraf modu", + "Embedding Model": "Gömme Modeli", + "{{count}} book(s) synced_one": "{{count}} kitap senkronize edildi", + "{{count}} book(s) synced_other": "{{count}} kitap senkronize edildi" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index d3dfca7e..57b5e07d 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -986,5 +986,15 @@ "Finished": "Завершено", "Unread": "Непрочитане", "Clear Status": "Очистити статус", - "Status": "Статус" + "Status": "Статус", + "Set status for {{count}} book(s)_few": "Встановити статус для {{count}} книг", + "Set status for {{count}} book(s)_many": "Встановити статус для {{count}} книг", + "Loading": "Завантаження...", + "Exit Paragraph Mode": "Вийти з режиму абзацу", + "Paragraph Mode": "Режим абзацу", + "Embedding Model": "Модель вбудовування", + "{{count}} book(s) synced_one": "{{count}} книга синхронізована", + "{{count}} book(s) synced_few": "{{count}} книги синхронізовані", + "{{count}} book(s) synced_many": "{{count}} книг синхронізовано", + "{{count}} book(s) synced_other": "{{count}} книг синхронізовано" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 497b4b65..0b22420d 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -949,12 +949,16 @@ "AI Gateway Model": "Mô hình AI Gateway", "Actions": "Hành động", "Navigation": "Điều hướng", - "Set status for {{count}} book(s)_one": "Đặt trạng thái cho {{count}} cuốn sách", "Set status for {{count}} book(s)_other": "Đặt trạng thái cho {{count}} cuốn sách", "Mark as Unread": "Đánh dấu là chưa đọc", "Mark as Finished": "Đánh dấu là đã hoàn thành", "Finished": "Đã hoàn thành", "Unread": "Chưa đọc", "Clear Status": "Xóa trạng thái", - "Status": "Trạng thái" + "Status": "Trạng thái", + "Loading": "Đang tải...", + "Exit Paragraph Mode": "Thoát chế độ đoạn văn", + "Paragraph Mode": "Chế độ đoạn văn", + "Embedding Model": "Mô hình nhúng", + "{{count}} book(s) synced_other": "Đã đồng bộ {{count}} cuốn sách" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index c7923eef..a3545f69 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -949,12 +949,16 @@ "AI Gateway Model": "AI Gateway 模型", "Actions": "操作", "Navigation": "导航", - "Set status for {{count}} book(s)_one": "设置 {{count}} 本书的状态", "Set status for {{count}} book(s)_other": "设置 {{count}} 本书的状态", "Mark as Unread": "标记为未读", "Mark as Finished": "标记为已读", "Finished": "已读", "Unread": "未读", "Clear Status": "清除状态", - "Status": "状态" + "Status": "状态", + "Loading": "正在加载", + "Exit Paragraph Mode": "退出段落模式", + "Paragraph Mode": "段落模式", + "Embedding Model": "嵌入模型", + "{{count}} book(s) synced_other": "{{count}} 本书已同步" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 85886bd0..390e5e3d 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -949,12 +949,16 @@ "AI Gateway Model": "AI Gateway 模型", "Actions": "操作", "Navigation": "導覽", - "Set status for {{count}} book(s)_one": "設定 {{count}} 本書的狀態", "Set status for {{count}} book(s)_other": "設定 {{count}} 本書的狀態", "Mark as Unread": "標示為未讀", "Mark as Finished": "標示為已讀", "Finished": "已讀", "Unread": "未讀", "Clear Status": "清除狀態", - "Status": "狀態" + "Status": "狀態", + "Loading": "正在載入", + "Exit Paragraph Mode": "退出段落模式", + "Paragraph Mode": "段落模式", + "Embedding Model": "嵌入模型", + "{{count}} book(s) synced_other": "{{count}} 本書已同步" } diff --git a/apps/readest-app/src/app/library/hooks/useBooksSync.ts b/apps/readest-app/src/app/library/hooks/useBooksSync.ts index 62b4daeb..02855888 100644 --- a/apps/readest-app/src/app/library/hooks/useBooksSync.ts +++ b/apps/readest-app/src/app/library/hooks/useBooksSync.ts @@ -4,11 +4,14 @@ import { useSync } from '@/hooks/useSync'; import { useEnv } from '@/context/EnvContext'; import { useAuth } from '@/context/AuthContext'; import { useLibraryStore } from '@/store/libraryStore'; +import { useTranslation } from '@/hooks/useTranslation'; import { SYNC_BOOKS_INTERVAL_SEC } from '@/services/constants'; import { throttle } from '@/utils/throttle'; import { debounce } from '@/utils/debounce'; +import { eventDispatcher } from '@/utils/event'; export const useBooksSync = () => { + const _ = useTranslation(); const { user } = useAuth(); const { appService } = useEnv(); const { library, isSyncing, libraryLoaded } = useLibraryStore(); @@ -31,20 +34,33 @@ export const useBooksSync = () => { }; }, [user, lastSyncedAtBooks]); - const pullLibrary = useCallback(async () => { - if (!user) return; - if (isPullingRef.current) { - console.log('Pull already in progress, skipping...'); - return; - } - try { - isPullingRef.current = true; - const library = useLibraryStore.getState().library; - await syncBooks([], 'pull', libraryLoaded && library.length === 0 ? 0 : undefined); - } finally { - isPullingRef.current = false; - } - }, [user, libraryLoaded, syncBooks]); + const pullLibrary = useCallback( + async (fullRefresh = false, verbose = false) => { + if (!user) return; + if (isPullingRef.current) { + console.log('Pull already in progress, skipping...'); + return; + } + try { + isPullingRef.current = true; + const library = useLibraryStore.getState().library; + const syncedBooksCount = await syncBooks( + [], + 'pull', + (libraryLoaded && library.length === 0) || fullRefresh ? 0 : undefined, + ); + if (verbose) { + eventDispatcher.dispatch('toast', { + type: 'info', + message: _('{{count}} book(s) synced', { count: syncedBooksCount }), + }); + } + } finally { + isPullingRef.current = false; + } + }, + [_, user, libraryLoaded, syncBooks], + ); // eslint-disable-next-line react-hooks/exhaustive-deps const handleAutoSync = useCallback( diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index 5f7a9cb8..6b5686af 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -132,7 +132,11 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP const { pullLibrary, pushLibrary } = useBooksSync(); const { isDragging } = useDragDropImport(); - usePullToRefresh(containerRef, pullLibrary); + usePullToRefresh( + containerRef, + pullLibrary.bind(null, false, true), + pullLibrary.bind(null, true, true), + ); useScreenWakeLock(settings.screenWakeLock); useShortcuts({ diff --git a/apps/readest-app/src/hooks/usePullToRefresh.ts b/apps/readest-app/src/hooks/usePullToRefresh.ts index 606e319a..fa1895e6 100644 --- a/apps/readest-app/src/hooks/usePullToRefresh.ts +++ b/apps/readest-app/src/hooks/usePullToRefresh.ts @@ -1,22 +1,42 @@ import { useEffect } from 'react'; +import { getOSPlatform } from '@/utils/misc'; -const TRIGGER_THRESHOLD = 120; -const SHOW_INDICATOR_THRESHOLD = 60; +const TRIGGER_THRESHOLD_STAGE1 = 120; +const TRIGGER_THRESHOLD_STAGE2 = 400; +const MAX_LOADING_POSITION = 80; +const PARALLAX_EFFECT = 0.3; -const MAX = 128; -const k = 0.4; -function appr(x: number) { - return MAX * (1 - Math.exp((-k * x) / MAX)); +// Platform-specific damping parameters for pull resistance +const getPlatformDamping = () => { + const platform = getOSPlatform(); + + if (platform === 'ios') { + // iOS - tighter resistance (lower k = more damping) + return { MAX: 120, k: 0.35 }; + } else if (platform === 'android') { + // Android - looser resistance (higher k = less damping) + return { MAX: 140, k: 0.5 }; + } + return { MAX: 128, k: 0.4 }; +}; + +function createApprFunction(MAX: number, k: number) { + return (x: number) => MAX * (1 - Math.exp((-k * x) / MAX)); } export const usePullToRefresh = ( ref: React.RefObject, - onTrigger: () => void, + onTriggerStage1: () => Promise | void, + onTriggerStage2?: () => Promise | void, ) => { useEffect(() => { const el = ref.current; if (!el) return; + const damping = getPlatformDamping(); + const appr = createApprFunction(damping.MAX, damping.k); + let isLoading = false; + el.addEventListener('touchstart', handleTouchStart, { passive: true }); function handleTouchStart(startEvent: TouchEvent) { @@ -34,6 +54,7 @@ export const usePullToRefresh = ( function handleTouchMove(moveEvent: TouchEvent) { const el = ref.current; if (!el) return; + if (isLoading) return; const currentX = moveEvent.touches[0]!.clientX; const currentY = moveEvent.touches[0]!.clientY; @@ -41,79 +62,174 @@ export const usePullToRefresh = ( const dy = currentY - initialY; if (dy < 0 || Math.abs(dx) * 2 > Math.abs(dy)) return; + const transformValue = appr(dy); + const parentEl = el.parentNode as HTMLDivElement; - if (dy > TRIGGER_THRESHOLD) { - flipArrow(parentEl); - } else if (dy > SHOW_INDICATOR_THRESHOLD) { - addPullIndicator(parentEl); + if (dy > 10) { + const opacity = Math.min(1, 0.3 + (dy / TRIGGER_THRESHOLD_STAGE2) * 0.7); + showLoadingSpinner(parentEl, transformValue, opacity); } else { - removePullIndicator(parentEl); + hideLoadingSpinner(parentEl); } + // Update loading spinner position and opacity with parallax if it exists + updateLoadingSpinnerPosition(parentEl, transformValue, dy); + const wrapper = el.querySelector('.transform-wrapper') as HTMLElement; if (wrapper) { - wrapper.style.transform = `translate3d(0, ${appr(dy)}px, 0)`; + wrapper.style.transform = `translate3d(0, ${transformValue}px, 0)`; } } - function addPullIndicator(el: HTMLDivElement) { - const indicator = el.querySelector('.pull-indicator'); - if (indicator) { - if (indicator.classList.contains('flip')) { - indicator.classList.remove('flip'); - } - return; - } + function showLoadingSpinner(el: HTMLDivElement, transform: number, opacity: number = 1) { + const existing = el.querySelector('.pull-refresh-loading'); + if (existing) return; const headerbar = document.querySelector('.titlebar'); - const pullIndicator = document.createElement('div'); + const loadingSpinner = document.createElement('div'); const headerBottom = headerbar?.getBoundingClientRect().bottom || 0; - pullIndicator.style.top = `${headerBottom + 30}px`; - pullIndicator.className = 'pull-indicator text-gray-500'; - pullIndicator.innerHTML = ` - - - - `; - el.appendChild(pullIndicator); + const parallaxOffset = transform * PARALLAX_EFFECT; + loadingSpinner.style.top = `${headerBottom + parallaxOffset}px`; + loadingSpinner.style.transition = 'opacity 0.15s ease-out'; + loadingSpinner.style.opacity = opacity.toString(); + loadingSpinner.className = 'pull-refresh-loading'; + loadingSpinner.innerHTML = ``; + el.appendChild(loadingSpinner); } - function removePullIndicator(el: HTMLDivElement) { - const pullIndicator = el.querySelector('.pull-indicator'); - if (pullIndicator) { - pullIndicator.remove(); + function updateLoadingSpinnerPosition(el: HTMLDivElement, transform: number, dy: number) { + const loadingSpinner = el.querySelector('.pull-refresh-loading') as HTMLElement; + if (!loadingSpinner) return; + + const headerbar = document.querySelector('.titlebar'); + const headerBottom = headerbar?.getBoundingClientRect().bottom || 0; + const parallaxOffset = transform * PARALLAX_EFFECT; + const opacity = Math.min(1, 0.2 + (dy / TRIGGER_THRESHOLD_STAGE2) * 0.8); + loadingSpinner.style.top = `${headerBottom + parallaxOffset}px`; + loadingSpinner.style.opacity = opacity.toString(); + } + + function hideLoadingSpinner(el: HTMLDivElement) { + const loadingSpinner = el.querySelector('.pull-refresh-loading'); + if (loadingSpinner) { + loadingSpinner.remove(); } } - function flipArrow(el: HTMLDivElement) { - const pullIndicator = el.querySelector('.pull-indicator'); - if (pullIndicator && !pullIndicator.classList.contains('flip')) { - pullIndicator.classList.add('flip'); - } - } - - function handleTouchEnd(endEvent: TouchEvent) { + async function handleTouchEnd(endEvent: TouchEvent) { const el = ref.current; if (!el) return; const wrapper = el.querySelector('.transform-wrapper') as HTMLElement; - if (wrapper) { - wrapper.style.transform = 'translateY(0)'; - } - removePullIndicator(el.parentNode as HTMLDivElement); - - el.style.transition = 'transform 0.2s'; + const parentEl = el.parentNode as HTMLDivElement; const y = endEvent.changedTouches[0]!.clientY; const dy = y - initialY; - if (dy > TRIGGER_THRESHOLD) { - onTrigger(); - } - - el.addEventListener('transitionend', onTransitionEnd); el.removeEventListener('touchmove', handleTouchMove); el.removeEventListener('touchend', handleTouchEnd); + + const isStage2 = onTriggerStage2 && dy > TRIGGER_THRESHOLD_STAGE2; + const isStage1 = dy > TRIGGER_THRESHOLD_STAGE1; + + if (isStage2 || isStage1) { + isLoading = true; + + // Calculate current transform value with damping + const transformValue = appr(dy); + const targetPosition = Math.min(transformValue, MAX_LOADING_POSITION); + + if (wrapper) { + wrapper.style.transition = 'transform 0.2s ease-out'; + wrapper.style.transform = `translateY(${targetPosition}px)`; + } + + const loadingSpinner = parentEl.querySelector('.pull-refresh-loading') as HTMLElement; + if (loadingSpinner) { + const headerbar = document.querySelector('.titlebar'); + const headerBottom = headerbar?.getBoundingClientRect().bottom || 0; + const parallaxOffset = targetPosition * PARALLAX_EFFECT; + loadingSpinner.style.transition = 'top 0.2s ease-out'; + loadingSpinner.style.top = `${headerBottom + parallaxOffset}px`; + loadingSpinner.style.opacity = '1'; + + // Remove transition after snap animation completes for smooth touch tracking + setTimeout(() => { + if (loadingSpinner) { + loadingSpinner.style.transition = 'opacity 0.15s ease-out'; + } + }, 200); + } + + // Add touch listeners during loading to detect pull up and update parallax + let loadingTouchStartY = 0; + const handleLoadingTouchStart = (e: TouchEvent) => { + loadingTouchStartY = e.touches[0]!.clientY; + }; + const handleLoadingTouchMove = (e: TouchEvent) => { + const currentY = e.touches[0]!.clientY; + const pullDelta = currentY - loadingTouchStartY; + + // Update both wrapper and spinner position to maintain parallax consistency + const newTransform = targetPosition + pullDelta; + if (newTransform > 0) { + if (wrapper) { + wrapper.style.transform = `translateY(${newTransform}px)`; + } + + // Update spinner position with same parallax calculation + const loadingSpinner = parentEl.querySelector('.pull-refresh-loading') as HTMLElement; + if (loadingSpinner) { + const headerbar = document.querySelector('.titlebar'); + const headerBottom = headerbar?.getBoundingClientRect().bottom || 0; + const parallaxOffset = newTransform * PARALLAX_EFFECT; + loadingSpinner.style.top = `${headerBottom + parallaxOffset}px`; + loadingSpinner.style.opacity = '1'; + } + } + + // User pulled up significantly, reset + if (pullDelta < -30) { + if (wrapper) { + wrapper.style.transition = 'transform 0.3s ease-out'; + wrapper.style.transform = 'translateY(0)'; + } + hideLoadingSpinner(parentEl); + el.removeEventListener('touchstart', handleLoadingTouchStart); + el.removeEventListener('touchmove', handleLoadingTouchMove); + } + }; + + el.addEventListener('touchstart', handleLoadingTouchStart, { passive: true }); + el.addEventListener('touchmove', handleLoadingTouchMove, { passive: true }); + + try { + const triggerFn = isStage2 ? onTriggerStage2 : onTriggerStage1; + await Promise.resolve(triggerFn()); + } catch (error) { + console.error('Pull to refresh error:', error); + } finally { + isLoading = false; + hideLoadingSpinner(parentEl); + if (wrapper) { + wrapper.style.transition = 'transform 0.3s ease-out'; + wrapper.style.transform = 'translateY(0)'; + setTimeout(() => { + if (wrapper) wrapper.style.transition = ''; + }, 300); + } + el.removeEventListener('touchstart', handleLoadingTouchStart); + el.removeEventListener('touchmove', handleLoadingTouchMove); + } + } else { + hideLoadingSpinner(parentEl); + if (wrapper) { + wrapper.style.transition = 'transform 0.2s'; + wrapper.style.transform = 'translateY(0)'; + } + + el.addEventListener('transitionend', onTransitionEnd); + } } function onTransitionEnd() { diff --git a/apps/readest-app/src/hooks/useSync.ts b/apps/readest-app/src/hooks/useSync.ts index a7eae8e3..6ebb8671 100644 --- a/apps/readest-app/src/hooks/useSync.ts +++ b/apps/readest-app/src/hooks/useSync.ts @@ -110,7 +110,7 @@ export function useSync(bookKey?: string) { const result = await syncClient.pullChanges(since, type, bookId, metaHash); setSyncResult({ ...syncResult, [type]: result[type] }); const records = result[type]; - if (since > 1000 && !records?.length) return; + if (since > 1000 && !records?.length) return 0; // For since <= 1000, we set lastSyncedAt to now if no records returned const maxTime = records?.length ? computeMaxTimestamp(records) : Date.now(); setLastSyncedAt(maxTime); @@ -140,6 +140,7 @@ export function useSync(bookKey?: string) { } break; } + return records?.length || 0; } catch (err: unknown) { console.error(err); if (err instanceof Error) { @@ -152,6 +153,7 @@ export function useSync(bookKey?: string) { } else { setSyncError(`Error pulling ${type}`); } + return 0; } finally { setSyncing(false); saveSettings(envConfig, settings); @@ -184,13 +186,14 @@ export function useSync(bookKey?: string) { await pushChanges({ books }); } if (op === 'pull' || op === 'both') { - await pullChanges( + return await pullChanges( 'books', since ?? lastSyncedAtBooks + 1, setLastSyncedAtBooks, setSyncingBooks, ); } + return; }, // eslint-disable-next-line react-hooks/exhaustive-deps [lastSyncedAtInited, lastSyncedAtBooks], diff --git a/apps/readest-app/src/styles/globals.css b/apps/readest-app/src/styles/globals.css index 66e2f46f..3cd28e03 100644 --- a/apps/readest-app/src/styles/globals.css +++ b/apps/readest-app/src/styles/globals.css @@ -296,7 +296,7 @@ foliate-fxl { visibility: visible; } -.pull-indicator { +.pull-refresh-loading { position: fixed; width: 100%; background-color: transparent; @@ -304,18 +304,9 @@ foliate-fxl { align-items: center; justify-content: center; color: var(--color-text-lighter); - transition: transform 0.2s ease-in-out; z-index: 10; } -.pull-indicator svg { - transition: transform 0.2s ease-in-out; -} - -.pull-indicator.flip svg { - transform: rotate(180deg); -} - .content { font-size: 16px; }