diff --git a/apps/readest-app/next.config.mjs b/apps/readest-app/next.config.mjs index 0e0eb69d..0ca356d0 100644 --- a/apps/readest-app/next.config.mjs +++ b/apps/readest-app/next.config.mjs @@ -1,4 +1,7 @@ import withPWAInit from '@ducanh2912/next-pwa'; +import { initOpenNextCloudflareForDev } from '@opennextjs/cloudflare'; + +initOpenNextCloudflareForDev(); const isDev = process.env['NODE_ENV'] === 'development'; const appPlatform = process.env['NEXT_PUBLIC_APP_PLATFORM']; diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index ca6b878e..601d02d9 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "تسجيل الدخول عبر Apple", "Sign in with GitHub": "تسجيل الدخول عبر GitHub", "Account": "الحساب", - "Cloud Storage": "التخزين السحابي", "Failed to delete user. Please try again later.": "فشل حذف المستخدم. يرجى المحاولة مرة أخرى لاحقًا.", "Unlimited Offline/Online Reading": "قراءة غير محدودة بدون اتصال/متصلة بالإنترنت", "Unlimited Cloud Sync Devices": "أجهزة مزامنة سحابية غير محدودة", @@ -353,5 +352,9 @@ "Show Remaining Time": "إظهار الوقت المتبقي", "Show Page Number": "إظهار رقم الصفحة", "{{time}} min left in chapter": "{{time}} دقيقة متبقية في الفصل", - "Override Book Color": "تجاوز لون الكتاب" + "Override Book Color": "تجاوز لون الكتاب", + "Login Required": "يلزم الدخول", + "Quota Exceeded": "تجاوز السعة", + "{{percentage}}% of Daily Translation Characters Used.": "تم استخدام {{percentage}}% من أحرف الترجمة اليومية.", + "Translation Characters": "أحرف الترجمة" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 01271b40..a1a6322e 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Mit Apple anmelden", "Sign in with GitHub": "Mit GitHub anmelden", "Account": "Konto", - "Cloud Storage": "Cloud-Speicher", "Failed to delete user. Please try again later.": "Benutzer konnte nicht gelöscht werden. Bitte versuchen Sie es später erneut.", "Unlimited Offline/Online Reading": "Unbegrenztes Offline/Online Lesen", "Unlimited Cloud Sync Devices": "Unbegrenzte Cloud-Synchronisierungsgeräte", @@ -345,5 +344,9 @@ "Show Remaining Time": "Verbleibende Zeit anzeigen", "Show Page Number": "Seitenzahl anzeigen", "{{time}} min left in chapter": "{{time}} min verbleibend im Kapitel", - "Override Book Color": "Farbe des Buches überschreiben" + "Override Book Color": "Farbe des Buches überschreiben", + "Login Required": "Anmeldung nötig", + "Quota Exceeded": "Limit überschritten", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% der täglichen Übersetzungszeichen verwendet.", + "Translation Characters": "Übersetzungszeichen" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index f8db907b..b59f27ec 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Σύνδεση με Apple", "Sign in with GitHub": "Σύνδεση με GitHub", "Account": "Λογαριασμός", - "Cloud Storage": "Αποθηκευτικός χώρος Cloud", "Failed to delete user. Please try again later.": "Αποτυχία διαγραφής χρήστη. Παρακαλώ δοκιμάστε ξανά αργότερα.", "Unlimited Offline/Online Reading": "Απεριόριστη ανάγνωση εκτός/εντός σύνδεσης", "Unlimited Cloud Sync Devices": "Απεριόριστες συσκευές συγχρονισμού Cloud", @@ -345,5 +344,9 @@ "Show Remaining Time": "Εμφάνιση υπολειπόμενου χρόνου", "Show Page Number": "Εμφάνιση αριθμού σελίδας", "{{time}} min left in chapter": "{{time}} λεπτά απομένουν στο κεφάλαιο", - "Override Book Color": "Παράκαμψη χρώματος βιβλίου" + "Override Book Color": "Παράκαμψη χρώματος βιβλίου", + "Login Required": "Απαιτείται σύνδεση", + "Quota Exceeded": "Υπέρβαση ορίου", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% των ημερήσιων χαρακτήρων μετάφρασης χρησιμοποιήθηκαν.", + "Translation Characters": "Χαρακτήρες μετάφρασης" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 6547e934..7b2724c3 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Iniciar sesión con Apple", "Sign in with GitHub": "Iniciar sesión con GitHub", "Account": "Cuenta", - "Cloud Storage": "Almacenamiento en la nube", "Failed to delete user. Please try again later.": "Error al eliminar usuario. Por favor, inténtelo de nuevo más tarde.", "Unlimited Offline/Online Reading": "Lectura sin límites en línea/sin conexión", "Unlimited Cloud Sync Devices": "Sincronización ilimitada de dispositivos en la nube", @@ -347,5 +346,9 @@ "Show Remaining Time": "Mostrar tiempo restante", "Show Page Number": "Mostrar número de página", "{{time}} min left in chapter": "{{time}} min restantes en el capítulo", - "Override Book Color": "Sobre escribir color del libro" + "Override Book Color": "Sobre escribir color del libro", + "Login Required": "Iniciar sesión", + "Quota Exceeded": "Límite superado", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% de caracteres de traducción diarios utilizados.", + "Translation Characters": "Caracteres de traducción" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index e46df438..49686bb3 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Se connecter avec Apple", "Sign in with GitHub": "Se connecter avec GitHub", "Account": "Compte", - "Cloud Storage": "Stockage cloud", "Failed to delete user. Please try again later.": "Échec de la suppression de l'utilisateur. Veuillez réessayer plus tard.", "Unlimited Offline/Online Reading": "Lecture illimitée hors ligne/en ligne", "Unlimited Cloud Sync Devices": "Synchronisation cloud sur appareils illimitée", @@ -347,5 +346,9 @@ "Show Remaining Time": "Afficher le temps restant", "Show Page Number": "Afficher le numéro de page", "{{time}} min left in chapter": "{{time}} min restant dans le chapitre", - "Override Book Color": "Remplacer la couleur du livre" + "Override Book Color": "Remplacer la couleur du livre", + "Login Required": "Connexion requise", + "Quota Exceeded": "Quota dépassé", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% des caractères de traduction quotidiens utilisés.", + "Translation Characters": "Caractères de traduction" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index f58ba21b..1c5e09a9 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Apple के साथ साइन इन करें", "Sign in with GitHub": "GitHub के साथ साइन इन करें", "Account": "खाता", - "Cloud Storage": "क्लाउड स्टोरेज", "Failed to delete user. Please try again later.": "उपयोगकर्ता को हटाने में विफल। कृपया बाद में पुनः प्रयास करें।", "Unlimited Offline/Online Reading": "असीमित ऑफलाइन/ऑनलाइन पढ़ना", "Unlimited Cloud Sync Devices": "असीमित क्लाउड सिंक उपकरण", @@ -345,5 +344,9 @@ "Show Remaining Time": "शेष समय दिखाएं", "Show Page Number": "पृष्ठ संख्या दिखाएं", "{{time}} min left in chapter": "{{time}} मिनट शेष अध्याय में", - "Override Book Color": "पुस्तक रंग ओवरराइड करें" + "Override Book Color": "पुस्तक रंग ओवरराइड करें", + "Login Required": "लॉगिन ज़रूरी", + "Quota Exceeded": "सीमा पार", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% दैनिक अनुवाद वर्णों का उपयोग किया गया है।", + "Translation Characters": "अनुवाद वर्ण" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 071763c7..b05eb7b9 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Masuk dengan Apple", "Sign in with GitHub": "Masuk dengan GitHub", "Account": "Akun", - "Cloud Storage": "Penyimpanan Cloud", "Failed to delete user. Please try again later.": "Gagal menghapus pengguna. Silakan coba lagi nanti.", "Unlimited Offline/Online Reading": "Membaca Offline/Online Tanpa Batas", "Unlimited Cloud Sync Devices": "Perangkat Sinkronisasi Cloud Tanpa Batas", @@ -343,5 +342,9 @@ "Show Remaining Time": "Perlihatkan Waktu Tersisa", "Show Page Number": "Perlihatkan Nomor Halaman", "{{time}} min left in chapter": "{{time}} menit tersisa di bab", - "Override Book Color": "Ubah Warna Buku" + "Override Book Color": "Ubah Warna Buku", + "Login Required": "Perlu masuk", + "Quota Exceeded": "Kuota terlampaui", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% dari Karakter Terjemahan Harian Digunakan.", + "Translation Characters": "Karakter Terjemahan" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 397c11e2..d3b7669e 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Accedi con Apple", "Sign in with GitHub": "Accedi con GitHub", "Account": "Account", - "Cloud Storage": "Archiviazione Cloud", "Failed to delete user. Please try again later.": "Impossibile eliminare l'utente. Riprova più tardi.", "Unlimited Offline/Online Reading": "Lettura offline/online illimitata", "Unlimited Cloud Sync Devices": "Sincronizzazione cloud su dispositivi illimitati", @@ -347,5 +346,9 @@ "Show Remaining Time": "Mostra tempo rimanente", "Show Page Number": "Mostra numero pagina", "{{time}} min left in chapter": "{{time}} min rimasti nel capitolo", - "Override Book Color": "Override colore libro" + "Override Book Color": "Override colore libro", + "Login Required": "Accesso richiesto", + "Quota Exceeded": "Limite superato", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% dei caratteri di traduzione giornalieri utilizzati.", + "Translation Characters": "Caratteri di traduzione" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index d6e38520..c8757573 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Appleでサインイン", "Sign in with GitHub": "GitHubでサインイン", "Account": "アカウント", - "Cloud Storage": "クラウドストレージ", "Failed to delete user. Please try again later.": "ユーザーの削除に失敗しました。後ほど再試行してください。", "Unlimited Offline/Online Reading": "無制限のオフライン/オンライン読書", "Unlimited Cloud Sync Devices": "無制限のクラウド同期デバイス", @@ -343,5 +342,9 @@ "Show Remaining Time": "残り時間を表示", "Show Page Number": "ページ番号を表示", "{{time}} min left in chapter": "{{time}}分残り", - "Override Book Color": "書籍の色を上書き" + "Override Book Color": "書籍の色を上書き", + "Login Required": "ログイン必須", + "Quota Exceeded": "上限超過", + "{{percentage}}% of Daily Translation Characters Used.": "日次翻訳文字数の使用量は{{percentage}}%です。", + "Translation Characters": "翻訳文字数" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 3fc44a09..0ab2a538 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Apple로 로그인", "Sign in with GitHub": "GitHub로 로그인", "Account": "계정", - "Cloud Storage": "클라우드 스토리지", "Failed to delete user. Please try again later.": "사용자 삭제에 실패했습니다. 나중에 다시 시도해 주세요.", "Unlimited Offline/Online Reading": "무제한 오프라인/온라인 읽기", "Unlimited Cloud Sync Devices": "무제한 클라우드 동기화 기기", @@ -343,5 +342,9 @@ "Show Remaining Time": "남은 시간 표시", "Show Page Number": "페이지 번호 표시", "{{time}} min left in chapter": "{{time}}분 남음", - "Override Book Color": "책 색상 덮어쓰기" + "Override Book Color": "책 색상 덮어쓰기", + "Login Required": "로그인 필요", + "Quota Exceeded": "한도 초과", + "{{percentage}}% of Daily Translation Characters Used.": "일일 번역 문자 사용량 {{percentage}}%.", + "Translation Characters": "번역 문자" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 9ecd1585..d02c5c3e 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -228,7 +228,6 @@ "Slow": "Langzaam", "Fast": "Snel", "Reading Progress Synced": "Leesvoortgang gesynchroniseerd", - "Cloud Storage": "Cloudopslag", "Failed to delete user. Please try again later.": "Gebruiker verwijderen mislukt. Probeer het later opnieuw.", "Free Tier": "Gratis niveau", "Community Support": "Community-ondersteuning", @@ -345,5 +344,9 @@ "Show Remaining Time": "Toon resterende tijd", "Show Page Number": "Toon paginanummer", "{{time}} min left in chapter": "Nog {{time}} min in hoofdstuk", - "Override Book Color": "Boekkleur overschrijven" + "Override Book Color": "Boekkleur overschrijven", + "Login Required": "Inloggen vereist", + "Quota Exceeded": "Limiet overschreden", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% van de dagelijkse vertaaltekens gebruikt.", + "Translation Characters": "Vertaaltekens" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index cb9cbbfb..054d0c17 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Zaloguj się za pomocą Apple", "Sign in with GitHub": "Zaloguj się za pomocą GitHub", "Account": "Konto", - "Cloud Storage": "Przestrzeń w chmurze", "Failed to delete user. Please try again later.": "Nie udało się usunąć użytkownika. Spróbuj ponownie później.", "Unlimited Offline/Online Reading": "Nieograniczone czytanie offline/online", "Unlimited Cloud Sync Devices": "Nieograniczona liczba urządzeń synchronizowanych w chmurze", @@ -349,5 +348,9 @@ "Show Remaining Time": "Pokazuj pozostały czas", "Show Page Number": "Pokaż numer strony", "{{time}} min left in chapter": "{{time}} min pozostało do końca rozdziału", - "Override Book Color": "Nadpisz kolor książki" + "Override Book Color": "Nadpisz kolor książki", + "Login Required": "Wymagane logowanie", + "Quota Exceeded": "Limit przekroczony", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% użytych znaków tłumaczenia dziennego.", + "Translation Characters": "Znaki tłumaczenia" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index ad926ee6..bf117711 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Entrar com o Apple", "Sign in with GitHub": "Entrar com o GitHub", "Account": "Conta", - "Cloud Storage": "Armazenamento na nuvem", "Failed to delete user. Please try again later.": "Falha ao excluir usuário. Por favor, tente novamente mais tarde.", "Unlimited Offline/Online Reading": "Leitura offline/online ilimitada", "Unlimited Cloud Sync Devices": "Sincronização ilimitada de dispositivos na nuvem", @@ -347,5 +346,9 @@ "Show Remaining Time": "Mostrar Tempo Restante", "Show Page Number": "Mostrar Número da Página", "{{time}} min left in chapter": "{{time}} min restantes no capítulo", - "Override Book Color": "Substituir Cor do Livro" + "Override Book Color": "Substituir Cor do Livro", + "Login Required": "Login necessário", + "Quota Exceeded": "Limite excedido", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% dos caracteres de tradução diários usados.", + "Translation Characters": "Caracteres de Tradução" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 286251f3..82c2cb95 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Войти через Apple", "Sign in with GitHub": "Войти через GitHub", "Account": "Аккаунт", - "Cloud Storage": "Облачное хранилище", "Failed to delete user. Please try again later.": "Не удалось удалить пользователя. Пожалуйста, повторите попытку позже.", "Unlimited Offline/Online Reading": "Безлимитное чтение офлайн/онлайн", "Unlimited Cloud Sync Devices": "Неограниченное количество устройств для облачной синхронизации", @@ -349,5 +348,9 @@ "Show Remaining Time": "Показать оставшееся время", "Show Page Number": "Показать номер страницы", "{{time}} min left in chapter": "{{time}} мин осталось в главе", - "Override Book Color": "Переопределить цвет книги" + "Override Book Color": "Переопределить цвет книги", + "Login Required": "Требуется вход", + "Quota Exceeded": "Лимит превышен", + "{{percentage}}% of Daily Translation Characters Used.": "Использовано {{percentage}}% от суточного лимита символов перевода.", + "Translation Characters": "Символы перевода" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index ae1cb5eb..a53adf12 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Apple ile giriş yap", "Sign in with GitHub": "GitHub ile giriş yap", "Account": "Hesap", - "Cloud Storage": "Bulut Depolama", "Failed to delete user. Please try again later.": "Kullanıcı silinemedi. Lütfen daha sonra tekrar deneyin.", "Unlimited Offline/Online Reading": "Sınırsız Çevrimdışı/Çevrimiçi Okuma", "Unlimited Cloud Sync Devices": "Sınırsız Bulut Senkronizasyon Cihazları", @@ -345,5 +344,9 @@ "Show Remaining Time": "Kalan Süreyi Göster", "Show Page Number": "Sayfa Numarasını Göster", "{{time}} min left in chapter": "{{time}} dakika bölümde kaldı", - "Override Book Color": "Kitap Rengini Geçersiz Kıl" + "Override Book Color": "Kitap Rengini Geçersiz Kıl", + "Login Required": "Giriş gerekli", + "Quota Exceeded": "Kota aşıldı", + "{{percentage}}% of Daily Translation Characters Used.": "Çeviri için günlük karakterlerin %{{percentage}}'si kullanıldı.", + "Translation Characters": "Çeviri Karakterleri" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 982bb958..769d3cff 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Увійти через Apple", "Sign in with GitHub": "Увійти через GitHub", "Account": "Обліковий запис", - "Cloud Storage": "Хмарне сховище", "Failed to delete user. Please try again later.": "Не вдалося видалити користувача. Будь ласка, спробуйте пізніше.", "Unlimited Offline/Online Reading": "Необмежене читання офлайн/онлайн", "Unlimited Cloud Sync Devices": "Необмежена кількість пристроїв для хмарної синхронізації", @@ -349,5 +348,9 @@ "Show Remaining Time": "Показати залишок часу", "Show Page Number": "Показати номер сторінки", "{{time}} min left in chapter": "{{time}} хв до кінця розділу", - "Override Book Color": "Перевизначити колір книги" + "Override Book Color": "Перевизначити колір книги", + "Login Required": "Потрібен вхід", + "Quota Exceeded": "Перевищено ліміт", + "{{percentage}}% of Daily Translation Characters Used.": "Використано {{percentage}}% символів перекладу за день.", + "Translation Characters": "Символи перекладу" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index bec62326..ff6719bf 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "Đăng nhập với Apple", "Sign in with GitHub": "Đăng nhập với GitHub", "Account": "Tài khoản", - "Cloud Storage": "Lưu trữ đám mây", "Failed to delete user. Please try again later.": "Không thể xóa người dùng. Vui lòng thử lại sau.", "Unlimited Offline/Online Reading": "Đọc ngoại tuyến/trực tuyến không giới hạn", "Unlimited Cloud Sync Devices": "Đồng bộ hóa thiết bị đám mây không giới hạn", @@ -343,5 +342,9 @@ "Show Remaining Time": "Hiển thị thời gian còn lại", "Show Page Number": "Hiển thị số trang", "{{time}} min left in chapter": "{{time}} phút còn lại trong chương", - "Override Book Color": "Đổi màu sách" + "Override Book Color": "Đổi màu sách", + "Login Required": "Cần đăng nhập", + "Quota Exceeded": "Vượt hạn mức", + "{{percentage}}% of Daily Translation Characters Used.": "{{percentage}}% số ký tự dịch hàng ngày đã sử dụng.", + "Translation Characters": "Những ký tự dịch" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 2f6ea9c8..6d79bfc1 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "使用 Apple 登录", "Sign in with GitHub": "使用 GitHub 登录", "Account": "账户", - "Cloud Storage": "云存储", "Failed to delete user. Please try again later.": "删除用户失败。请稍后重试。", "Unlimited Offline/Online Reading": "无限离线和在线阅读", "Unlimited Cloud Sync Devices": "无限云同步设备个数", @@ -343,5 +342,9 @@ "Show Remaining Time": "显示剩余时间", "Show Page Number": "显示页码", "{{time}} min left in chapter": "本章剩余 {{time}} 分钟", - "Override Book Color": "覆盖书籍颜色" + "Override Book Color": "覆盖书籍颜色", + "Login Required": "需登录", + "Quota Exceeded": "额度不足", + "{{percentage}}% of Daily Translation Characters Used.": "已使用 {{percentage}}% 的每日翻译字符数", + "Translation Characters": "翻译字数" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 1094ff75..cb4e9e4a 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -186,7 +186,6 @@ "Sign in with Apple": "使用 Apple 登入", "Sign in with GitHub": "使用 GitHub 登入", "Account": "帳戶", - "Cloud Storage": "雲端儲存空間", "Failed to delete user. Please try again later.": "刪除使用者失敗。請稍後再試。", "Unlimited Offline/Online Reading": "無限離線/線上閱讀", "Unlimited Cloud Sync Devices": "無限雲端同步裝置", @@ -343,5 +342,9 @@ "Show Remaining Time": "顯示剩餘時間", "Show Page Number": "顯示頁碼", "{{time}} min left in chapter": "本章剩餘 {{time}} 分鐘", - "Override Book Color": "覆蓋書籍顏色" + "Override Book Color": "覆蓋書籍顏色", + "Login Required": "需登入", + "Quota Exceeded": "額度不足", + "{{percentage}}% of Daily Translation Characters Used.": "已使用 {{percentage}}% 的每日翻譯字數", + "Translation Characters": "翻譯字數" } diff --git a/apps/readest-app/src/app/library/components/SettingsMenu.tsx b/apps/readest-app/src/app/library/components/SettingsMenu.tsx index 6861fdd9..a546d6c0 100644 --- a/apps/readest-app/src/app/library/components/SettingsMenu.tsx +++ b/apps/readest-app/src/app/library/components/SettingsMenu.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { useRouter } from 'next/navigation'; import { PiUserCircle } from 'react-icons/pi'; import { PiUserCircleCheck } from 'react-icons/pi'; @@ -13,14 +13,13 @@ import { DOWNLOAD_READEST_URL } from '@/services/constants'; import { useAuth } from '@/context/AuthContext'; import { useEnv } from '@/context/EnvContext'; import { useThemeStore } from '@/store/themeStore'; +import { useQuotaStats } from '@/hooks/useQuotaStats'; import { useSettingsStore } from '@/store/settingsStore'; import { useTranslation } from '@/hooks/useTranslation'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; -import { getStoragePlanData } from '@/utils/access'; import { navigateToLogin, navigateToProfile } from '@/utils/nav'; import { tauriHandleSetAlwaysOnTop, tauriHandleToggleFullScreen } from '@/utils/window'; import { optInTelemetry, optOutTelemetry } from '@/utils/telemetry'; -import { QuotaType } from '@/types/user'; import UserAvatar from '@/components/UserAvatar'; import MenuItem from '@/components/MenuItem'; import Quota from '@/components/Quota'; @@ -33,10 +32,9 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { const _ = useTranslation(); const router = useRouter(); const { envConfig, appService } = useEnv(); - const { token, user } = useAuth(); + const { user } = useAuth(); const { themeMode, setThemeMode } = useThemeStore(); const { settings, setSettings, saveSettings } = useSettingsStore(); - const [quotas, setQuotas] = React.useState([]); const [isAutoUpload, setIsAutoUpload] = useState(settings.autoUpload); const [isAutoCheckUpdates, setIsAutoCheckUpdates] = useState(settings.autoCheckUpdates); const [isAlwaysOnTop, setIsAlwaysOnTop] = useState(settings.alwaysOnTop); @@ -48,6 +46,8 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { const [isTelemetryEnabled, setIsTelemetryEnabled] = useState(settings.telemetryEnabled); const iconSize = useResponsiveSize(16); + const { quotas } = useQuotaStats(); + const showAboutReadest = () => { setAboutDialogVisible(true); setIsDropdownOpen?.(false); @@ -143,22 +143,6 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { setIsTelemetryEnabled(settings.telemetryEnabled); }; - useEffect(() => { - if (!user || !token) return; - const storagPlan = getStoragePlanData(token); - const storageQuota: QuotaType = { - name: _('Storage'), - tooltip: _('{{percentage}}% of Cloud Storage Used.', { - percentage: Math.round((storagPlan.usage / storagPlan.quota) * 100), - }), - used: Math.round(storagPlan.usage / 1024 / 1024), - total: Math.round(storagPlan.quota / 1024 / 1024), - unit: 'MB', - }; - setQuotas([storageQuota]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [token]); - const avatarUrl = user?.user_metadata?.['picture'] || user?.user_metadata?.['avatar_url']; const userFullName = user?.user_metadata?.['full_name']; const userDisplayName = userFullName ? userFullName.split(' ')[0] : null; @@ -188,7 +172,7 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { } >
    - +
diff --git a/apps/readest-app/src/app/reader/components/annotator/TranslatorPopup.tsx b/apps/readest-app/src/app/reader/components/annotator/TranslatorPopup.tsx index 8069188e..1bb65929 100644 --- a/apps/readest-app/src/app/reader/components/annotator/TranslatorPopup.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/TranslatorPopup.tsx @@ -6,7 +6,7 @@ import { useSettingsStore } from '@/store/settingsStore'; import { useTranslation } from '@/hooks/useTranslation'; import { useTranslator } from '@/hooks/useTranslator'; import { TRANSLATED_LANGS } from '@/services/constants'; -import { UseTranslatorOptions } from '@/services/translators'; +import { UseTranslatorOptions, getTranslators } from '@/services/translators'; import { localeToLang } from '@/utils/lang'; import Select from '@/components/Select'; @@ -76,7 +76,12 @@ const TranslatorPopup: React.FC = ({ }; const handleProviderChange = (event: React.ChangeEvent) => { - const selectedTranslator = translators.find((t) => t.name === event.target.value); + const requestedProvider = event.target.value; + const availableTranslators = getTranslators().filter( + (t) => (t.authRequired ? !!token : true) && !t.quotaExceeded, + ); + const selectedTranslator = + availableTranslators.find((t) => t.name === requestedProvider) || availableTranslators[0]!; if (selectedTranslator) { settings.globalReadSettings.translationProvider = selectedTranslator.name; setSettings(settings); @@ -86,9 +91,16 @@ const TranslatorPopup: React.FC = ({ useEffect(() => { const availableProviders = translators.map((t) => { - return { name: t.name, label: t.label }; + let label = t.label; + if (t.authRequired && !token) { + label = `${label} (${_('Login Required')})`; + } else if (t.quotaExceeded) { + label = `${label} (${_('Quota Exceeded')})`; + } + return { name: t.name, label }; }); setProviders(availableProviders); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [translators]); useEffect(() => { diff --git a/apps/readest-app/src/app/reader/components/settings/LangPanel.tsx b/apps/readest-app/src/app/reader/components/settings/LangPanel.tsx index 8bef1bc0..8bd40aa2 100644 --- a/apps/readest-app/src/app/reader/components/settings/LangPanel.tsx +++ b/apps/readest-app/src/app/reader/components/settings/LangPanel.tsx @@ -2,6 +2,7 @@ import clsx from 'clsx'; import i18n from 'i18next'; import React, { useEffect, useState } from 'react'; import { useEnv } from '@/context/EnvContext'; +import { useAuth } from '@/context/AuthContext'; import { useReaderStore } from '@/store/readerStore'; import { useTranslation } from '@/hooks/useTranslation'; import { saveViewSettings } from '../../utils/viewSettingsHelper'; @@ -11,6 +12,7 @@ import Select from '@/components/Select'; const LangPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { const _ = useTranslation(); + const { token } = useAuth(); const { envConfig } = useEnv(); const { getViewSettings, setViewSettings } = useReaderStore(); const viewSettings = getViewSettings(bookKey)!; @@ -47,15 +49,27 @@ const LangPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { const getTranslationProviderOptions = () => { const translators = getTranslators(); const availableProviders = translators.map((t) => { - return { value: t.name, label: t.label }; + let label = t.label; + if (t.authRequired && !token) { + label = `${label} (${_('Login Required')})`; + } else if (t.quotaExceeded) { + label = `${label} (${_('Quota Exceeded')})`; + } + return { value: t.name, label }; }); return availableProviders; }; const getCurrentTranslationProviderOption = () => { const value = translationProvider; - const availableProviders = getTranslationProviderOptions(); - return availableProviders.find((p) => p.value === value) || availableProviders[0]!; + const allProviders = getTranslationProviderOptions(); + const availableTranslators = getTranslators().filter( + (t) => (t.authRequired ? !!token : true) && !t.quotaExceeded, + ); + const currentProvider = availableTranslators.find((t) => t.name === value) + ? value + : availableTranslators[0]?.name; + return allProviders.find((p) => p.value === currentProvider) || allProviders[0]!; }; const handleSelectTranslationProvider = (event: React.ChangeEvent) => { diff --git a/apps/readest-app/src/app/user/page.tsx b/apps/readest-app/src/app/user/page.tsx index 08a4ca9c..fad75eb1 100644 --- a/apps/readest-app/src/app/user/page.tsx +++ b/apps/readest-app/src/app/user/page.tsx @@ -1,18 +1,18 @@ 'use client'; import clsx from 'clsx'; -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useRef } from 'react'; import { useRouter } from 'next/navigation'; import { IoArrowBack } from 'react-icons/io5'; import { PiUserCircle } from 'react-icons/pi'; import { useEnv } from '@/context/EnvContext'; import { useAuth } from '@/context/AuthContext'; import { useTheme } from '@/hooks/useTheme'; +import { useQuotaStats } from '@/hooks/useQuotaStats'; import { useTranslation } from '@/hooks/useTranslation'; import { useSettingsStore } from '@/store/settingsStore'; import { useTrafficLightStore } from '@/store/trafficLightStore'; -import { QuotaType, UserPlan } from '@/types/user'; -import { getStoragePlanData, getUserPlan } from '@/utils/access'; +import { UserPlan } from '@/types/user'; import { navigateToLibrary } from '@/utils/nav'; import { deleteUser } from '@/libs/user'; import { eventDispatcher } from '@/utils/event'; @@ -28,36 +28,13 @@ const ProfilePage = () => { const { token, user, logout } = useAuth(); const { isTrafficLightVisible } = useTrafficLightStore(); const { settings, setSettings, saveSettings } = useSettingsStore(); - const [userPlan, setUserPlan] = useState('free'); - const [quotas, setQuotas] = React.useState([]); const [showConfirmDelete, setShowConfirmDelete] = useState(false); const headerRef = useRef(null); useTheme({ systemUIVisible: false }); - useEffect(() => { - if (!user || !token) return; - - try { - const userPlan = getUserPlan(token); - const storagePlan = getStoragePlanData(token); - const storageQuota = { - name: _('Cloud Storage'), - tooltip: _('{{percentage}}% of Cloud Storage Used.', { - percentage: Math.round((storagePlan.usage / storagePlan.quota) * 100), - }), - used: Math.round(storagePlan.usage / 1024 / 1024), - total: Math.round(storagePlan.quota / 1024 / 1024), - unit: 'MB', - }; - setUserPlan(userPlan); - setQuotas([storageQuota]); - } catch (error) { - console.error('Error loading user plan data:', error); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [token]); + const { quotas, userPlan } = useQuotaStats(); const handleGoBack = () => { navigateToLibrary(router); @@ -240,10 +217,15 @@ const ProfilePage = () => { -
+
{quotas && quotas.length > 0 ? ( - + ) : (
)} diff --git a/apps/readest-app/src/components/Quota.tsx b/apps/readest-app/src/components/Quota.tsx index f81936e1..c08f5984 100644 --- a/apps/readest-app/src/components/Quota.tsx +++ b/apps/readest-app/src/components/Quota.tsx @@ -10,12 +10,13 @@ type QuotaProps = { unit: string; }[]; className?: string; + labelClassName?: string; showProgress?: boolean; }; -const Quota: React.FC = ({ quotas, showProgress, className }) => { +const Quota: React.FC = ({ quotas, showProgress, className, labelClassName }) => { return ( -
+
{quotas.map((quota) => { const usagePercentage = (quota.used / quota.total) * 100; let bgColor = 'bg-green-500'; @@ -30,7 +31,7 @@ const Quota: React.FC = ({ quotas, showProgress, className }) => { key={quota.name} className={clsx( 'relative w-full overflow-hidden rounded-md', - showProgress && 'border-base-300 border', + showProgress && 'bg-base-300', )} > {showProgress && ( @@ -41,9 +42,12 @@ const Quota: React.FC = ({ quotas, showProgress, className }) => { )}
-
+
{quota.name}
diff --git a/apps/readest-app/src/hooks/useQuotaStats.ts b/apps/readest-app/src/hooks/useQuotaStats.ts new file mode 100644 index 00000000..4d291175 --- /dev/null +++ b/apps/readest-app/src/hooks/useQuotaStats.ts @@ -0,0 +1,46 @@ +import { useEffect, useState } from 'react'; +import { useAuth } from '@/context/AuthContext'; +import { QuotaType, UserPlan } from '@/types/user'; +import { getStoragePlanData, getTranslationPlanData, getUserPlan } from '@/utils/access'; +import { useTranslation } from './useTranslation'; + +export const useQuotaStats = () => { + const _ = useTranslation(); + const { token, user } = useAuth(); + const [quotas, setQuotas] = useState([]); + const [userPlan, setUserPlan] = useState('free'); + + useEffect(() => { + if (!user || !token) return; + + const userPlan = getUserPlan(token); + const storagPlan = getStoragePlanData(token); + const storageQuota: QuotaType = { + name: _('Storage'), + tooltip: _('{{percentage}}% of Cloud Storage Used.', { + percentage: Math.round((storagPlan.usage / storagPlan.quota) * 100), + }), + used: Math.round(storagPlan.usage / 1024 / 1024), + total: Math.round(storagPlan.quota / 1024 / 1024), + unit: 'MB', + }; + const translationPlan = getTranslationPlanData(token); + const translationQuota: QuotaType = { + name: _('Translation Characters'), + tooltip: _('{{percentage}}% of Daily Translation Characters Used.', { + percentage: Math.round((translationPlan.usage / translationPlan.quota) * 100), + }), + used: Math.round(translationPlan.usage / 1024), + total: Math.round(translationPlan.quota / 1024), + unit: 'K', + }; + setUserPlan(userPlan); + setQuotas([storageQuota, translationQuota]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token]); + + return { + quotas, + userPlan, + }; +}; diff --git a/apps/readest-app/src/hooks/useTranslator.ts b/apps/readest-app/src/hooks/useTranslator.ts index 926915af..9ee49538 100644 --- a/apps/readest-app/src/hooks/useTranslator.ts +++ b/apps/readest-app/src/hooks/useTranslator.ts @@ -1,6 +1,6 @@ import { useState, useCallback, useEffect } from 'react'; import { useAuth } from '@/context/AuthContext'; -import { getTranslator, getTranslators } from '@/services/translators'; +import { getTranslator, getTranslators, TranslatorName } from '@/services/translators'; import { getFromCache, storeInCache, polish, UseTranslatorOptions } from '@/services/translators'; export function useTranslator({ @@ -11,6 +11,7 @@ export function useTranslator({ }: UseTranslatorOptions = {}) { const { token } = useAuth(); const [loading, setLoading] = useState(false); + const [selectedProvider, setSelectedProvider] = useState(provider); const [translator, setTransltor] = useState(() => getTranslator(provider)); const [translators] = useState(() => getTranslators()); @@ -19,7 +20,15 @@ export function useTranslator({ }, [provider, sourceLang, targetLang]); useEffect(() => { - setTransltor(getTranslator(provider)); + const availableTranslators = getTranslators().filter( + (t) => (t.authRequired ? !!token : true) && !t.quotaExceeded, + ); + const selectedTranslator = + availableTranslators.find((t) => t.name === provider) || availableTranslators[0]!; + const selectedProviderName = selectedTranslator.name as TranslatorName; + setTransltor(getTranslator(selectedProviderName)); + setSelectedProvider(selectedProviderName); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [provider]); const translate = useCallback( @@ -47,7 +56,7 @@ export function useTranslator({ text, sourceLanguage, targetLanguage, - provider, + selectedProvider, ); if (cachedTranslation) return; @@ -59,7 +68,7 @@ export function useTranslator({ if (textsNeedingTranslation.length === 0) { const results = await Promise.all( textsToTranslate.map((text) => - getFromCache(text, sourceLanguage, targetLanguage, provider).then( + getFromCache(text, sourceLanguage, targetLanguage, selectedProvider).then( (cached) => cached || text, ), ), @@ -71,9 +80,9 @@ export function useTranslator({ setLoading(true); try { - const translator = translators.find((t) => t.name === provider); + const translator = translators.find((t) => t.name === selectedProvider); if (!translator) { - throw new Error(`No translator found for provider: ${provider}`); + throw new Error(`No translator found for provider: ${selectedProvider}`); } const translatedTexts = await translator.translate( textsNeedingTranslation, @@ -90,7 +99,7 @@ export function useTranslator({ translatedTexts[index] || '', sourceLanguage, targetLanguage, - provider, + selectedProvider, ); }), ); @@ -110,7 +119,7 @@ export function useTranslator({ originalText, sourceLanguage, targetLanguage, - provider, + selectedProvider, ); if (cachedTranslation) { @@ -129,7 +138,7 @@ export function useTranslator({ } }, // eslint-disable-next-line react-hooks/exhaustive-deps - [provider, sourceLang, targetLang, translator, token], + [selectedProvider, sourceLang, targetLang, translator, token], ); return { diff --git a/apps/readest-app/src/pages/api/deepl/translate.ts b/apps/readest-app/src/pages/api/deepl/translate.ts index dc71afea..47eb1498 100644 --- a/apps/readest-app/src/pages/api/deepl/translate.ts +++ b/apps/readest-app/src/pages/api/deepl/translate.ts @@ -4,17 +4,11 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { corsAllMethods, runMiddleware } from '@/utils/cors'; import { getCloudflareContext } from '@opennextjs/cloudflare'; import { getDailyTranslationPlanData, getUserPlan } from '@/utils/access'; +import { ErrorCodes } from '@/services/translators'; const DEFAULT_DEEPL_FREE_API = 'https://api-free.deepl.com/v2/translate'; const DEFAULT_DEEPL_PRO_API = 'https://api.deepl.com/v2/translate'; -const ErrorCodes = { - UNAUTHORIZED: 'Unauthorized', - DEEPL_API_ERROR: 'DeepL API Error', - DAILY_QUOTA_EXCEEDED: 'Daily Quota Exceeded', - INTERNAL_SERVER_ERROR: 'Internal Server Error', -}; - interface KVNamespace { get(key: string): Promise; put(key: string, value: string, options?: { expirationTtl?: number }): Promise; @@ -109,7 +103,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { } } - // if (!user || !token) return res.status(401).json({ error: ErrorCodes.UNAUTHORIZED }); + if (!user || !token) return res.status(401).json({ error: ErrorCodes.UNAUTHORIZED }); return await callDeepLAPI( user?.id, diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 75f3097f..c7cf7866 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -536,9 +536,9 @@ export const DEFAULT_STORAGE_QUOTA: UserStorageQuota = { }; export const DEFAULT_DAILY_TRANSLATION_QUOTA: UserDailyTranslationQuota = { - free: 100 * 1024, - plus: 1 * 1024 * 1024, - pro: 10 * 1024 * 1024, + free: 50 * 1024, + plus: 500 * 1024, + pro: 1024 * 1024, }; export const DOUBLE_CLICK_INTERVAL_THRESHOLD_MS = 250; diff --git a/apps/readest-app/src/services/translators/providers/deepl.ts b/apps/readest-app/src/services/translators/providers/deepl.ts index c44cd0a5..ae7b9a71 100644 --- a/apps/readest-app/src/services/translators/providers/deepl.ts +++ b/apps/readest-app/src/services/translators/providers/deepl.ts @@ -1,12 +1,18 @@ import { getAPIBaseUrl } from '@/services/environment'; import { stubTranslation as _ } from '@/utils/misc'; -import { TranslationProvider } from '../types'; +import { ErrorCodes, TranslationProvider } from '../types'; +import { UserPlan } from '@/types/user'; +import { getUserPlan } from '@/utils/access'; +import { DEFAULT_DAILY_TRANSLATION_QUOTA } from '@/services/constants'; +import { saveDailyUsage } from '../utils'; const DEEPL_API_ENDPOINT = getAPIBaseUrl() + '/deepl/translate'; export const deeplProvider: TranslationProvider = { name: 'deepl', label: _('DeepL'), + authRequired: true, + quotaExceeded: false, translate: async ( text: string[], sourceLang: string, @@ -14,14 +20,22 @@ export const deeplProvider: TranslationProvider = { token?: string | null, useCache: boolean = false, ): Promise => { + const authRequired = deeplProvider.authRequired; + const headers: Record = { 'Content-Type': 'application/json', }; + let userPlan: UserPlan = 'free'; if (token) { + userPlan = getUserPlan(token); headers['Authorization'] = `Bearer ${token}`; } + if (authRequired && !token) { + throw new Error('Authentication token is required for DeepL translation'); + } + const body = JSON.stringify({ text: text, source_lang: sourceLang.toUpperCase(), @@ -29,22 +43,36 @@ export const deeplProvider: TranslationProvider = { use_cache: useCache, }); - const response = await fetch(DEEPL_API_ENDPOINT, { method: 'POST', headers, body }); + const quota = DEFAULT_DAILY_TRANSLATION_QUOTA[userPlan]; + try { + const response = await fetch(DEEPL_API_ENDPOINT, { method: 'POST', headers, body }); - if (!response.ok) { - throw new Error(`Translation failed with status ${response.status}`); - } - - const data = await response.json(); - if (!data || !data.translations) { - throw new Error('Invalid response from translation service'); - } - - return text.map((line, i) => { - if (!line?.trim().length) { - return line; + if (!response.ok) { + throw new Error(`Translation failed with status ${response.status}`); } - return data.translations?.[i]?.text || line; - }); + + const data = await response.json(); + if (!data || !data.translations) { + throw new Error('Invalid response from translation service'); + } + + return text.map((line, i) => { + if (!line?.trim().length) { + return line; + } + const translation = data.translations?.[i]; + if (translation?.daily_usage) { + saveDailyUsage(translation.daily_usage); + deeplProvider.quotaExceeded = data.daily_usage >= quota; + } + return translation?.text || line; + }); + } catch (error) { + if (error instanceof Error && error.message.includes(ErrorCodes.DAILY_QUOTA_EXCEEDED)) { + saveDailyUsage(quota); + deeplProvider.quotaExceeded = true; + } + throw error; + } }, }; diff --git a/apps/readest-app/src/services/translators/types.ts b/apps/readest-app/src/services/translators/types.ts index 7933c9a7..513e1b25 100644 --- a/apps/readest-app/src/services/translators/types.ts +++ b/apps/readest-app/src/services/translators/types.ts @@ -3,6 +3,8 @@ import { TranslatorName } from './providers'; export interface TranslationProvider { name: string; label: string; + authRequired?: boolean; + quotaExceeded?: boolean; translate: ( texts: string[], sourceLang: string, @@ -22,3 +24,10 @@ export interface UseTranslatorOptions { targetLang?: string; enablePolishing?: boolean; } + +export const ErrorCodes = { + UNAUTHORIZED: 'Unauthorized', + DEEPL_API_ERROR: 'DeepL API Error', + DAILY_QUOTA_EXCEEDED: 'Daily Quota Exceeded', + INTERNAL_SERVER_ERROR: 'Internal Server Error', +}; diff --git a/apps/readest-app/src/services/translators/utils.ts b/apps/readest-app/src/services/translators/utils.ts new file mode 100644 index 00000000..83b8ab8f --- /dev/null +++ b/apps/readest-app/src/services/translators/utils.ts @@ -0,0 +1,23 @@ +const DAILY_USAGE_KEY = 'translationDailyUsage'; + +export const saveDailyUsage = (usage: number, date?: string) => { + if (typeof window !== 'undefined') { + const isoDate = date || new Date().toISOString().split('T')[0]!; + const dailyUsage = { [isoDate]: usage }; + localStorage.setItem(DAILY_USAGE_KEY, JSON.stringify(dailyUsage)); + } +}; + +export const getDailyUsage = (date?: string): number | null => { + if (typeof window !== 'undefined') { + const isoDate = date || new Date().toISOString().split('T')[0]!; + const usage = localStorage.getItem(DAILY_USAGE_KEY); + if (usage) { + const dailyUsage = JSON.parse(usage); + if (dailyUsage[isoDate]) { + return dailyUsage[isoDate]; + } + } + } + return null; +}; diff --git a/apps/readest-app/src/utils/access.ts b/apps/readest-app/src/utils/access.ts index f6df285d..fdea5c7a 100644 --- a/apps/readest-app/src/utils/access.ts +++ b/apps/readest-app/src/utils/access.ts @@ -1,8 +1,9 @@ import { jwtDecode } from 'jwt-decode'; +import { supabase } from '@/utils/supabase'; import { UserPlan } from '@/types/user'; import { DEFAULT_DAILY_TRANSLATION_QUOTA, DEFAULT_STORAGE_QUOTA } from '@/services/constants'; import { isWebAppPlatform } from '@/services/environment'; -import { supabase } from '@/utils/supabase'; +import { getDailyUsage } from '@/services/translators/utils'; interface Token { plan: UserPlan; @@ -29,6 +30,19 @@ export const getStoragePlanData = (token: string) => { }; }; +export const getTranslationPlanData = (token: string) => { + const data = jwtDecode(token) || {}; + const plan: UserPlan = data['plan'] || 'free'; + const usage = getDailyUsage() || 0; + const quota = DEFAULT_DAILY_TRANSLATION_QUOTA[plan]; + + return { + plan, + usage, + quota, + }; +}; + export const getDailyTranslationPlanData = (token: string) => { const data = jwtDecode(token) || {}; const plan = data['plan'] || 'free';