From cd614e3845b0e7cd269d1c194a6668a15aa076ad Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Sat, 22 Nov 2025 21:09:20 +0800 Subject: [PATCH] feat(cjk): add an option to replace quotation marks in vertical layout for CJK languages (#2511) --- .../public/locales/ar/translation.json | 5 +- .../public/locales/bn/translation.json | 5 +- .../public/locales/bo/translation.json | 5 +- .../public/locales/de/translation.json | 5 +- .../public/locales/el/translation.json | 5 +- .../public/locales/es/translation.json | 5 +- .../public/locales/fa/translation.json | 5 +- .../public/locales/fr/translation.json | 5 +- .../public/locales/hi/translation.json | 5 +- .../public/locales/id/translation.json | 5 +- .../public/locales/it/translation.json | 5 +- .../public/locales/ja/translation.json | 5 +- .../public/locales/ko/translation.json | 5 +- .../public/locales/nl/translation.json | 5 +- .../public/locales/pl/translation.json | 5 +- .../public/locales/pt/translation.json | 5 +- .../public/locales/ru/translation.json | 5 +- .../public/locales/si/translation.json | 5 +- .../public/locales/sv/translation.json | 5 +- .../public/locales/ta/translation.json | 5 +- .../public/locales/th/translation.json | 5 +- .../public/locales/tr/translation.json | 5 +- .../public/locales/uk/translation.json | 5 +- .../public/locales/vi/translation.json | 5 +- .../public/locales/zh-CN/translation.json | 5 +- .../public/locales/zh-TW/translation.json | 5 +- .../src/app/api/stripe/checkout/route.ts | 1 + .../app/reader/components/FoliateViewer.tsx | 4 +- .../src/app/reader/hooks/useTextSelector.ts | 6 +- .../app/user/components/PlansComparison.tsx | 2 +- .../src/components/settings/ControlPanel.tsx | 6 +- .../src/components/settings/LangPanel.tsx | 76 ++++++++++++++++--- .../src/components/settings/LayoutPanel.tsx | 27 +++---- apps/readest-app/src/helpers/settings.ts | 8 +- apps/readest-app/src/services/constants.ts | 3 +- .../src/services/transformers/punctuation.ts | 19 ++++- .../src/services/transformers/types.ts | 1 + apps/readest-app/src/types/book.ts | 1 + 38 files changed, 219 insertions(+), 65 deletions(-) diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 9b2fb5cd..108c32d0 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -672,5 +672,8 @@ "Current email": "البريد الإلكتروني الحالي", "Update Email": "تحديث البريد الإلكتروني", "All": "الكل", - "Unable to open book": "غير قادر على فتح الكتاب" + "Unable to open book": "غير قادر على فتح الكتاب", + "Punctuation": "علامات الترقيم", + "Replace Quotation Marks": "استبدال علامات الاقتباس", + "Enabled only in vertical layout.": "مفعل فقط في التخطيط الرأسي." } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index 098ac5ec..d1aced67 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -656,5 +656,8 @@ "Current email": "বর্তমান ইমেল", "Update Email": "ইমেল আপডেট করুন", "All": "সব", - "Unable to open book": "বই খুলতে অক্ষম" + "Unable to open book": "বই খুলতে অক্ষম", + "Punctuation": "বিরামচিহ্ন", + "Replace Quotation Marks": "উদ্ধৃতি চিহ্ন প্রতিস্থাপন করুন", + "Enabled only in vertical layout.": "শুধুমাত্র উল্লম্ব বিন্যাসে সক্রিয়।" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index c8074ee2..ef5245df 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -652,5 +652,8 @@ "Current email": "མིག་སྔའི་ཡིག་འཕྲིན།", "Update Email": "ཡིག་འཕྲིན་གསར་བསྒྱུར།", "All": "ཆེན་མོ།", - "Unable to open book": "དེབ་ཀྱི་སྤྱོད་བྱས་མ་ཐུབ།" + "Unable to open book": "དེབ་ཀྱི་སྤྱོད་བྱས་མ་ཐུབ།", + "Punctuation": "ཚིག་གྲུབ།", + "Replace Quotation Marks": "འདྲེན་འབྲེལ་མཐུད་ཐིག་བརྗེ་བ།", + "Enabled only in vertical layout.": "གཡས་སྒྲིག་ནང་མིང་བརྗེ་བ།" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 5d460e88..26cd803b 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -656,5 +656,8 @@ "Current email": "Aktuelle E-Mail", "Update Email": "E-Mail aktualisieren", "All": "Alle", - "Unable to open book": "Buch kann nicht geöffnet werden" + "Unable to open book": "Buch kann nicht geöffnet werden", + "Punctuation": "Interpunktion", + "Replace Quotation Marks": "Anführungszeichen ersetzen", + "Enabled only in vertical layout.": "Nur im vertikalen Layout aktiviert." } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index d95ba07f..2133c778 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -656,5 +656,8 @@ "Current email": "Τρέχον email", "Update Email": "Ενημέρωση email", "All": "Όλα", - "Unable to open book": "Αδύνατη η άνοιγμα του βιβλίου" + "Unable to open book": "Αδύνατη η άνοιγμα του βιβλίου", + "Punctuation": "Στίξη", + "Replace Quotation Marks": "Αντικατάσταση εισαγωγικών", + "Enabled only in vertical layout.": "Ενεργοποιημένο μόνο σε κάθετη διάταξη." } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index d2f0e46e..bdfb437b 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -660,5 +660,8 @@ "Current email": "Correo electrónico actual", "Update Email": "Actualizar correo electrónico", "All": "Todos", - "Unable to open book": "No se puede abrir el libro" + "Unable to open book": "No se puede abrir el libro", + "Punctuation": "Signos de puntuación", + "Replace Quotation Marks": "Reemplazar comillas", + "Enabled only in vertical layout.": "Habilitado solo en diseño vertical." } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 260ac652..ff32294b 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -656,5 +656,8 @@ "Current email": "ایمیل فعلی", "Update Email": "به‌روزرسانی ایمیل", "All": "همه", - "Unable to open book": "قادر به باز کردن کتاب نیست" + "Unable to open book": "قادر به باز کردن کتاب نیست", + "Punctuation": "علامت نگارشی", + "Replace Quotation Marks": "جایگزینی علامت نقل قول", + "Enabled only in vertical layout.": "فقط در طرح عمودی فعال است." } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 4a000055..61aaaf90 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -660,5 +660,8 @@ "Current email": "E-mail actuel", "Update Email": "Mettre à jour l’e-mail", "All": "Tous", - "Unable to open book": "Impossible d'ouvrir le livre" + "Unable to open book": "Impossible d'ouvrir le livre", + "Punctuation": "Ponctuation", + "Replace Quotation Marks": "Remplacer les guillemets", + "Enabled only in vertical layout.": "Activé uniquement en disposition verticale." } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 577ebed1..75340c83 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -656,5 +656,8 @@ "Current email": "वर्तमान ईमेल", "Update Email": "ईमेल अपडेट करें", "All": "सभी", - "Unable to open book": "पुस्तक खोलने में असमर्थ" + "Unable to open book": "पुस्तक खोलने में असमर्थ", + "Punctuation": "विराम चिह्न", + "Replace Quotation Marks": "उद्धरण चिह्न बदलें", + "Enabled only in vertical layout.": "केवल ऊर्ध्वाधर लेआउट में सक्षम।" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 95cf3592..4bfaf47f 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -652,5 +652,8 @@ "Current email": "Email saat ini", "Update Email": "Perbarui email", "All": "Semua", - "Unable to open book": "Tidak dapat membuka buku" + "Unable to open book": "Tidak dapat membuka buku", + "Punctuation": "Tanda Baca", + "Replace Quotation Marks": "Ganti Tanda Kutip", + "Enabled only in vertical layout.": "Hanya diaktifkan dalam tata letak vertikal." } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 18d24aa4..cba46c63 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -660,5 +660,8 @@ "Current email": "Email attuale", "Update Email": "Aggiorna email", "All": "Tutti", - "Unable to open book": "Impossibile aprire il libro" + "Unable to open book": "Impossibile aprire il libro", + "Punctuation": "Segni di punteggiatura", + "Replace Quotation Marks": "Sostituisci le virgolette", + "Enabled only in vertical layout.": "Abilitato solo in layout verticale." } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 307be0b5..9b4b7525 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -652,5 +652,8 @@ "Current email": "現在のメールアドレス", "Update Email": "メールを更新", "All": "すべて", - "Unable to open book": "本を開くことができません" + "Unable to open book": "本を開くことができません", + "Punctuation": "句読点", + "Replace Quotation Marks": "引用符を置き換える", + "Enabled only in vertical layout.": "縦書きレイアウトでのみ有効です。" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 4e1800b7..499025d3 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -652,5 +652,8 @@ "Current email": "현재 이메일", "Update Email": "이메일 업데이트", "All": "전체", - "Unable to open book": "책을 열 수 없습니다" + "Unable to open book": "책을 열 수 없습니다", + "Punctuation": "구두점", + "Replace Quotation Marks": "인용 부호 바꾸기", + "Enabled only in vertical layout.": "세로 레이아웃에서만 활성화됩니다." } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 9cf5fad7..8956730a 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -656,5 +656,8 @@ "Current email": "Huidig e-mailadres", "Update Email": "E-mail bijwerken", "All": "Alles", - "Unable to open book": "Kan boek niet openen" + "Unable to open book": "Kan boek niet openen", + "Punctuation": "Interpunctie", + "Replace Quotation Marks": "Aanhalingstekens vervangen", + "Enabled only in vertical layout.": "Alleen ingeschakeld in verticale lay-out." } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index e1d9d1b0..56a162b7 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -664,5 +664,8 @@ "Current email": "Obecny e-mail", "Update Email": "Zaktualizuj e-mail", "All": "Wszystkie", - "Unable to open book": "Nie można otworzyć książki" + "Unable to open book": "Nie można otworzyć książki", + "Punctuation": "Interpunkcja", + "Replace Quotation Marks": "Zamień cudzysłowy", + "Enabled only in vertical layout.": "Włączone tylko w układzie pionowym." } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 1dd8ad49..50823a5f 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -660,5 +660,8 @@ "Current email": "E-mail atual", "Update Email": "Atualizar e-mail", "All": "Todos", - "Unable to open book": "Não foi possível abrir o livro" + "Unable to open book": "Não foi possível abrir o livro", + "Punctuation": "Pontuação", + "Replace Quotation Marks": "Substituir Aspas", + "Enabled only in vertical layout.": "Habilitado apenas no layout vertical." } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 0f7bce44..13c97d40 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -664,5 +664,8 @@ "Current email": "Текущий адрес электронной почты", "Update Email": "Обновить электронную почту", "All": "Все", - "Unable to open book": "Не удалось открыть книгу" + "Unable to open book": "Не удалось открыть книгу", + "Punctuation": "Пунктуация", + "Replace Quotation Marks": "Заменить кавычки", + "Enabled only in vertical layout.": "Включено только в вертикальной раскладке." } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 98a4357d..3e6dbaa9 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -656,5 +656,8 @@ "Current email": "වත්මන් විද්‍යුත් තැපැල්", "Update Email": "විද්‍යුත් තැපැල් යාවත්කාලීන කරන්න", "All": "සියල්ල", - "Unable to open book": "පොත විවෘත කිරීමට නොහැක" + "Unable to open book": "පොත විවෘත කිරීමට නොහැක", + "Punctuation": "ලකුණු", + "Replace Quotation Marks": "උද්ධෘත ලකුණු ප්‍රතිස්ථාපනය කරන්න", + "Enabled only in vertical layout.": "සිරස් ආකෘතියේ පමණක් සක්‍රීයයි." } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index 6d705bdb..9102efa7 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -656,5 +656,8 @@ "Current email": "Nuvarande e-post", "Update Email": "Uppdatera e-post", "All": "Alla", - "Unable to open book": "Kunde inte öppna bok" + "Unable to open book": "Kunde inte öppna bok", + "Punctuation": "Interpunktion", + "Replace Quotation Marks": "Ersätt citattecken", + "Enabled only in vertical layout.": "Aktiverad endast i vertikal layout." } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index 11102189..94d6740e 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -656,5 +656,8 @@ "Current email": "தற்போதைய மின்னஞ்சல்", "Update Email": "மின்னஞ்சல் புதுப்பிக்கவும்", "All": "அனைத்து", - "Unable to open book": "புத்தகத்தைத் திறக்க முடியவில்லை" + "Unable to open book": "புத்தகத்தைத் திறக்க முடியவில்லை", + "Punctuation": "விருத்தி", + "Replace Quotation Marks": "உதயம்சுட்டிகளை மாற்றவும்", + "Enabled only in vertical layout.": "செங்குத்து அமைப்பில் மட்டுமே இயலுமைப்படுத்தப்பட்டது." } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index c4c5f8e4..d0163c7f 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -652,5 +652,8 @@ "Current email": "อีเมลปัจจุบัน", "Update Email": "อัปเดตอีเมล", "All": "ทั้งหมด", - "Unable to open book": "ไม่สามารถเปิดหนังสือได้" + "Unable to open book": "ไม่สามารถเปิดหนังสือได้", + "Punctuation": "เครื่องหมายวรรคตอน", + "Replace Quotation Marks": "แทนที่เครื่องหมายคำพูด", + "Enabled only in vertical layout.": "เปิดใช้งานเฉพาะในเค้าโครงแนวตั้งเท่านั้น" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 6daead42..8a4bd373 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -656,5 +656,8 @@ "Current email": "Mevcut e-posta", "Update Email": "E-postayı güncelle", "All": "Tümü", - "Unable to open book": "Kitap açılamıyor" + "Unable to open book": "Kitap açılamıyor", + "Punctuation": "Noktalama İşaretleri", + "Replace Quotation Marks": "Tırnak İşaretlerini Değiştir", + "Enabled only in vertical layout.": "Yalnızca dikey düzenle etkinleştirildi." } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 658fcbd4..7ecfb5ea 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -664,5 +664,8 @@ "Current email": "Поточна електронна пошта", "Update Email": "Оновити електронну пошту", "All": "Всі", - "Unable to open book": "Не вдалося відкрити книгу" + "Unable to open book": "Не вдалося відкрити книгу", + "Punctuation": "Пунктуація", + "Replace Quotation Marks": "Замінити лапки", + "Enabled only in vertical layout.": "Активовано лише у вертикальному макеті." } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 9871a7c7..92c787f0 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -652,5 +652,8 @@ "Current email": "Email hiện tại", "Update Email": "Cập nhật email", "All": "Tất cả", - "Unable to open book": "Không thể mở sách" + "Unable to open book": "Không thể mở sách", + "Punctuation": "Dấu câu", + "Replace Quotation Marks": "Thay thế dấu ngoặc kép", + "Enabled only in vertical layout.": "Chỉ bật trong bố cục dọc." } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index eb2599a9..9a711e48 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -652,5 +652,8 @@ "Current email": "当前邮箱", "Update Email": "更新邮箱", "All": "全部", - "Unable to open book": "无法打开书籍" + "Unable to open book": "无法打开书籍", + "Punctuation": "标点符号", + "Replace Quotation Marks": "替换引号", + "Enabled only in vertical layout.": "仅在竖排布局中启用" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index d088ccb3..30ac2673 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -652,5 +652,8 @@ "Current email": "當前電子郵件", "Update Email": "更新電子郵件", "All": "全部", - "Unable to open book": "無法打開書籍" + "Unable to open book": "無法打開書籍", + "Punctuation": "標點符號", + "Replace Quotation Marks": "替換引號", + "Enabled only in vertical layout.": "僅在直排佈局中啟用" } diff --git a/apps/readest-app/src/app/api/stripe/checkout/route.ts b/apps/readest-app/src/app/api/stripe/checkout/route.ts index c0473cba..9c4c273a 100644 --- a/apps/readest-app/src/app/api/stripe/checkout/route.ts +++ b/apps/readest-app/src/app/api/stripe/checkout/route.ts @@ -54,6 +54,7 @@ export async function POST(request: NextRequest) { ui_mode: embedded ? 'embedded' : 'hosted', customer: customerId, mode: planType === 'subscription' ? 'subscription' : 'payment', + allow_promotion_codes: true, line_items: [ { price: priceId, diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 91dbec8b..3e09f870 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -46,7 +46,6 @@ import { } from '../utils/iframeEventHandlers'; import { getMaxInlineSize } from '@/utils/config'; import { getDirFromUILanguage } from '@/utils/rtl'; -import { isCJKLang } from '@/utils/lang'; import { isTauriAppPlatform } from '@/services/environment'; import { TransformContext } from '@/services/transformers/types'; import { transformContent } from '@/services/transformService'; @@ -56,6 +55,8 @@ import { useBookCoverAutoSave } from '../hooks/useAutoSaveBookCover'; import { manageSyntaxHighlighting } from '@/utils/highlightjs'; import { getViewInsets } from '@/utils/insets'; import { removeTabIndex } from '@/utils/a11y'; +import { isCJKLang } from '@/utils/lang'; +import { getLocale } from '@/utils/misc'; import Spinner from '@/components/Spinner'; import KOSyncConflictResolver from './KOSyncResolver'; @@ -137,6 +138,7 @@ const FoliateViewer: React.FC<{ width, height, primaryLanguage: bookData.book?.primaryLanguage, + userLocale: getLocale(), content: data, transformers: [ 'style', diff --git a/apps/readest-app/src/app/reader/hooks/useTextSelector.ts b/apps/readest-app/src/app/reader/hooks/useTextSelector.ts index 15f0efd1..065a81f3 100644 --- a/apps/readest-app/src/app/reader/hooks/useTextSelector.ts +++ b/apps/readest-app/src/app/reader/hooks/useTextSelector.ts @@ -2,10 +2,11 @@ import { useEffect, useRef, useState } from 'react'; import { useEnv } from '@/context/EnvContext'; import { useReaderStore } from '@/store/readerStore'; import { useBookDataStore } from '@/store/bookDataStore'; -import { getOSPlatform } from '@/utils/misc'; +import { getLocale, getOSPlatform } from '@/utils/misc'; import { eventDispatcher } from '@/utils/event'; import { getTextFromRange, TextSelection } from '@/utils/sel'; import { transformContent } from '@/services/transformService'; +import { TransformContext } from '@/services/transformers/types'; export const useTextSelector = ( bookKey: string, @@ -31,9 +32,10 @@ export const useTextSelector = ( return sel && sel.toString().trim().length > 0 && sel.rangeCount > 0; }; - const transformCtx = { + const transformCtx: TransformContext = { bookKey, viewSettings: getViewSettings(bookKey)!, + userLocale: getLocale(), content: '', transformers: ['punctuation'], reversePunctuationTransform: true, diff --git a/apps/readest-app/src/app/user/components/PlansComparison.tsx b/apps/readest-app/src/app/user/components/PlansComparison.tsx index a4256bdd..caa7c179 100644 --- a/apps/readest-app/src/app/user/components/PlansComparison.tsx +++ b/apps/readest-app/src/app/user/components/PlansComparison.tsx @@ -155,7 +155,7 @@ const PlansComparison: React.FC = ({ plan={plan} comingSoon={false} isUserPlan={plan.plan === userPlan} - upgradable={index > userPlanIndex} + upgradable={index > 0 && (index > userPlanIndex || userPlan === 'purchase')} index={index} currentPlanIndex={currentPlanIndex} onSubscribe={onSubscribe} diff --git a/apps/readest-app/src/components/settings/ControlPanel.tsx b/apps/readest-app/src/components/settings/ControlPanel.tsx index 5ddc7789..257507e2 100644 --- a/apps/readest-app/src/components/settings/ControlPanel.tsx +++ b/apps/readest-app/src/components/settings/ControlPanel.tsx @@ -11,7 +11,6 @@ import { getStyles } from '@/utils/style'; import { getMaxInlineSize } from '@/utils/config'; import { saveSysSettings, saveViewSettings } from '@/helpers/settings'; import { SettingsPanelPanelProp } from './SettingsDialog'; -import { RELOAD_BEFORE_SAVED_TIMEOUT_MS } from '@/services/constants'; import NumberInput from './NumberInput'; const ControlPanel: React.FC = ({ bookKey, onRegisterReset }) => { @@ -143,10 +142,9 @@ const ControlPanel: React.FC = ({ bookKey, onRegisterRes useEffect(() => { if (viewSettings.allowScript === allowScript) return; - saveViewSettings(envConfig, bookKey, 'allowScript', allowScript, true, false); - setTimeout(() => { + saveViewSettings(envConfig, bookKey, 'allowScript', allowScript, true, false).then(() => { recreateViewer(envConfig, bookKey); - }, RELOAD_BEFORE_SAVED_TIMEOUT_MS); + }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [allowScript]); diff --git a/apps/readest-app/src/components/settings/LangPanel.tsx b/apps/readest-app/src/components/settings/LangPanel.tsx index 60f1300b..59a59f9c 100644 --- a/apps/readest-app/src/components/settings/LangPanel.tsx +++ b/apps/readest-app/src/components/settings/LangPanel.tsx @@ -8,12 +8,9 @@ import { useSettingsStore } from '@/store/settingsStore'; import { saveViewSettings } from '@/helpers/settings'; import { getTranslators } from '@/services/translators'; import { useResetViewSettings } from '@/hooks/useResetSettings'; -import { - RELOAD_BEFORE_SAVED_TIMEOUT_MS, - TRANSLATED_LANGS, - TRANSLATOR_LANGS, -} from '@/services/constants'; +import { TRANSLATED_LANGS, TRANSLATOR_LANGS } from '@/services/constants'; import { SettingsPanelPanelProp } from './SettingsDialog'; +import { isCJKEnv } from '@/utils/misc'; import Select from '@/components/Select'; const LangPanel: React.FC = ({ bookKey, onRegisterReset }) => { @@ -30,6 +27,9 @@ const LangPanel: React.FC = ({ bookKey, onRegisterReset const [translateTargetLang, setTranslateTargetLang] = useState(viewSettings.translateTargetLang); const [showTranslateSource, setShowTranslateSource] = useState(viewSettings.showTranslateSource); const [ttsReadAloudText, setTtsReadAloudText] = useState(viewSettings.ttsReadAloudText); + const [replaceQuotationMarks, setReplaceQuotationMarks] = useState( + viewSettings.replaceQuotationMarks, + ); const resetToDefaults = useResetViewSettings(); @@ -41,6 +41,7 @@ const LangPanel: React.FC = ({ bookKey, onRegisterReset translateTargetLang: setTranslateTargetLang, showTranslateSource: setShowTranslateSource, ttsReadAloudText: setTtsReadAloudText, + replaceQuotationMarks: setReplaceQuotationMarks, }); }; @@ -143,17 +144,33 @@ const LangPanel: React.FC = ({ bookKey, onRegisterReset useEffect(() => { if (translationEnabled === viewSettings.translationEnabled) return; - saveViewSettings(envConfig, bookKey, 'translationEnabled', translationEnabled, true, false); - if (!showTranslateSource && translationEnabled) { - setTimeout(() => recreateViewer(envConfig, bookKey), RELOAD_BEFORE_SAVED_TIMEOUT_MS); - } + saveViewSettings( + envConfig, + bookKey, + 'translationEnabled', + translationEnabled, + true, + false, + ).then(() => { + if (!showTranslateSource && translationEnabled) { + recreateViewer(envConfig, bookKey); + } + }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [translationEnabled]); useEffect(() => { if (showTranslateSource === viewSettings.showTranslateSource) return; - saveViewSettings(envConfig, bookKey, 'showTranslateSource', showTranslateSource, false, false); - setTimeout(() => recreateViewer(envConfig, bookKey), RELOAD_BEFORE_SAVED_TIMEOUT_MS); + saveViewSettings( + envConfig, + bookKey, + 'showTranslateSource', + showTranslateSource, + false, + false, + ).then(() => { + recreateViewer(envConfig, bookKey); + }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [showTranslateSource]); @@ -163,6 +180,21 @@ const LangPanel: React.FC = ({ bookKey, onRegisterReset // eslint-disable-next-line react-hooks/exhaustive-deps }, [ttsReadAloudText]); + useEffect(() => { + if (replaceQuotationMarks === viewSettings.replaceQuotationMarks) return; + saveViewSettings( + envConfig, + bookKey, + 'replaceQuotationMarks', + replaceQuotationMarks, + true, + false, + ).then(() => { + recreateViewer(envConfig, bookKey); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [replaceQuotationMarks]); + return (
@@ -234,6 +266,28 @@ const LangPanel: React.FC = ({ bookKey, onRegisterReset
+ + {isCJKEnv() && ( +
+

{_('Punctuation')}

+
+
+
+
+ {_('Replace Quotation Marks')} + {_('Enabled only in vertical layout.')} +
+ setReplaceQuotationMarks(!replaceQuotationMarks)} + /> +
+
+
+
+ )} ); }; diff --git a/apps/readest-app/src/components/settings/LayoutPanel.tsx b/apps/readest-app/src/components/settings/LayoutPanel.tsx index fccecf95..52bb2459 100644 --- a/apps/readest-app/src/components/settings/LayoutPanel.tsx +++ b/apps/readest-app/src/components/settings/LayoutPanel.tsx @@ -16,7 +16,7 @@ import { getMaxInlineSize } from '@/utils/config'; import { lockScreenOrientation } from '@/utils/bridge'; import { saveViewSettings } from '@/helpers/settings'; import { getBookDirFromWritingMode, getBookLangCode } from '@/utils/book'; -import { MIGHT_BE_RTL_LANGS, RELOAD_BEFORE_SAVED_TIMEOUT_MS } from '@/services/constants'; +import { MIGHT_BE_RTL_LANGS } from '@/services/constants'; import { SettingsPanelPanelProp } from './SettingsDialog'; import Select from '@/components/Select'; import NumberInput from './NumberInput'; @@ -273,18 +273,19 @@ const LayoutPanel: React.FC = ({ bookKey, onRegisterRese } else { viewSettings.vertical = false; } - saveViewSettings(envConfig, bookKey, 'writingMode', writingMode, true); - if (view) { - view.renderer.setStyles?.(getStyles(viewSettings)); - view.book.dir = getBookDirFromWritingMode(writingMode); - } - if ( - prevWritingMode !== writingMode && - (['horizontal-rl', 'vertical-rl'].includes(writingMode) || - ['horizontal-rl', 'vertical-rl'].includes(prevWritingMode)) - ) { - setTimeout(() => recreateViewer(envConfig, bookKey), RELOAD_BEFORE_SAVED_TIMEOUT_MS); - } + saveViewSettings(envConfig, bookKey, 'writingMode', writingMode, true).then(() => { + if (view) { + view.renderer.setStyles?.(getStyles(viewSettings)); + view.book.dir = getBookDirFromWritingMode(writingMode); + } + if ( + prevWritingMode !== writingMode && + (['horizontal-rl', 'vertical-rl'].includes(writingMode) || + ['horizontal-rl', 'vertical-rl'].includes(prevWritingMode)) + ) { + recreateViewer(envConfig, bookKey); + } + }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [writingMode]); diff --git a/apps/readest-app/src/helpers/settings.ts b/apps/readest-app/src/helpers/settings.ts index d5424079..e8adac36 100644 --- a/apps/readest-app/src/helpers/settings.ts +++ b/apps/readest-app/src/helpers/settings.ts @@ -18,7 +18,6 @@ export const saveViewSettings = async ( const { getView, getViewSettings, setViewSettings } = useReaderStore.getState(); const { getConfig, saveConfig } = useBookDataStore.getState(); const viewSettings = getViewSettings(bookKey); - const config = getConfig(bookKey); if (bookKey && viewSettings && viewSettings[key] !== value) { viewSettings[key] = value; if (applyStyles) { @@ -33,9 +32,12 @@ export const saveViewSettings = async ( await saveSettings(envConfig, settings); } - if (bookKey && config && viewSettings) { + if (bookKey && viewSettings) { setViewSettings(bookKey, viewSettings); - await saveConfig(envConfig, bookKey, config, settings); + const config = getConfig(bookKey); + if (config) { + await saveConfig(envConfig, bookKey, config, settings); + } } }; diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index b7b0efd2..adde40c2 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -148,6 +148,7 @@ export const DEFAULT_BOOK_LAYOUT: BookLayout = { writingMode: 'auto', vertical: false, rtl: false, + replaceQuotationMarks: true, scrollingOverlap: 0, allowScript: false, }; @@ -607,8 +608,6 @@ export const SYNC_NOTES_INTERVAL_SEC = 5; export const SYNC_BOOKS_INTERVAL_SEC = 5; export const CHECK_UPDATE_INTERVAL_SEC = 24 * 60 * 60; -export const RELOAD_BEFORE_SAVED_TIMEOUT_MS = 300; - export const MAX_ZOOM_LEVEL = 500; export const MIN_ZOOM_LEVEL = 50; export const ZOOM_STEP = 10; diff --git a/apps/readest-app/src/services/transformers/punctuation.ts b/apps/readest-app/src/services/transformers/punctuation.ts index 8e1c4846..b23cc8d4 100644 --- a/apps/readest-app/src/services/transformers/punctuation.ts +++ b/apps/readest-app/src/services/transformers/punctuation.ts @@ -1,20 +1,35 @@ import type { Transformer } from './types'; -const punctuationMap: Record = { +const punctuationMapHans: Record = { '“': '﹃', '”': '﹄', '‘': '﹁', '’': '﹂', }; +const punctuationMapHant: Record = { + '“': '﹁', + '”': '﹂', + '‘': '﹃', + '’': '﹄', +}; + export const punctuationTransformer: Transformer = { name: 'punctuation', transform: async (ctx) => { - const shouldTransform = ctx.viewSettings.vertical === true; + const shouldTransform = ctx.viewSettings.vertical && ctx.viewSettings.replaceQuotationMarks; if (!shouldTransform) return ctx.content; let result = ctx.content; + const traditionalChineseLocales = ['zh-Hant', 'zh-TW', 'zh_TW']; + let punctuationMap: Record = punctuationMapHans; + if ( + traditionalChineseLocales.includes(ctx.primaryLanguage || '') || + traditionalChineseLocales.includes(ctx.userLocale) + ) { + punctuationMap = punctuationMapHant; + } for (const [original, vertical] of Object.entries(punctuationMap)) { if (ctx.reversePunctuationTransform) { result = result.replace(new RegExp(vertical, 'g'), original); diff --git a/apps/readest-app/src/services/transformers/types.ts b/apps/readest-app/src/services/transformers/types.ts index 9d7bffd4..fe869f79 100644 --- a/apps/readest-app/src/services/transformers/types.ts +++ b/apps/readest-app/src/services/transformers/types.ts @@ -3,6 +3,7 @@ import { ViewSettings } from '@/types/book'; export type TransformContext = { bookKey: string; viewSettings: ViewSettings; + userLocale: string; primaryLanguage?: string; width?: number; height?: number; diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 2ad45fa7..213ae17e 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -112,6 +112,7 @@ export interface BookLayout { writingMode: WritingMode; vertical: boolean; rtl: boolean; + replaceQuotationMarks: boolean; scrollingOverlap: number; allowScript: boolean; }