feat(reader): custom hardware-button page turning (#4177)

* feat(reader): add custom hardware-button page turning (#4139)

Lets users bind hardware remote keys (media keys, D-pad/arrow keys) to
previous/next page via a learn-mode capture UI in reader settings — an
accessibility feature for page-turner remotes.

- New global hardwarePageTurner system setting (enabled + key bindings).
- hardwareKeys.ts: key normalization, matching, and page-turn resolution.
- deviceStore: reference-counted media-key interception + learn mode.
- usePagination: flips pages from bound media keys (native bridge) and
  D-pad/keyboard keys (DOM keydown), scoped to the active book and
  suppressed while the toolbar is visible.
- Page Turner settings section on all platforms; web/desktop bind keys
  via DOM keydown only, native media-key interception stays mobile-only.
- Android: intercept media + learn-mode keys in dispatchKeyEvent.
- iOS: forward media keys via MPRemoteCommandCenter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(i18n): add and translate hardware page turner strings (#4139)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(reader): refine hardware page turner (#4139)

- Handle book-iframe key events (iframe-keydown messages) so custom
  bindings work as soon as a book is open, not only after the settings
  panel has been shown.
- Add Previous/Next Section bindings alongside the page bindings.
- Rename the hardwareKeys util to keybinding.
- Wire the Page Turner section into the settings Reset action.
- Drop the focus ring on the capture buttons; BoxedList gains an
  optional description.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(i18n): translate page turner section and key strings (#4139)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-05-16 01:57:33 +08:00
committed by GitHub
parent f5e729a174
commit 787bbf2103
48 changed files with 1569 additions and 154 deletions
@@ -227,7 +227,6 @@
"CJK Font": "خط CJK",
"Clear Search": "مسح البحث",
"Header & Footer": "الترويسة والتذييل",
"Apply also in Scrolled Mode": "تطبيق أيضًا في وضع التمرير",
"A new version of Readest is available!": "يتوفر إصدار جديد من Readest!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "إصدار {{newVersion}} من ريديست متاح للتنزيل (الإصدار المثبت حاليًا {{currentVersion}}).",
"Download and install now?": "هل ترغب في تنزيله وتثبيته الآن؟",
@@ -251,7 +250,6 @@
"Grid": "شبكة",
"(from 'As You Like It', Act II)": "(من 'كما تحب'، الفصل الثاني)",
"Link Color": "لون الرابط",
"Volume Keys for Page Flip": "مفاتيح الصوت لتقليب الصفحات",
"Screen": "الشاشة",
"Orientation": "الاتجاه",
"Portrait": "عمودي",
@@ -1512,5 +1510,27 @@
"Start your library": "ابدأ مكتبتك",
"Pick a book from your device to add it to your library.": "اختر كتابًا من جهازك لإضافته إلى مكتبتك.",
"Drop a book anywhere on this window, or pick one from your computer.": "أفلِت كتابًا في أي مكان على هذه النافذة، أو اختر واحدًا من حاسوبك.",
"Sign in to sync your library": "سجّل الدخول لمزامنة مكتبتك"
"Sign in to sync your library": "سجّل الدخول لمزامنة مكتبتك",
"Clear": "مسح",
"Listening…": "يستمع…",
"Set key": "تعيين مفتاح",
"Page Turner": "قالب الصفحات",
"Media Next": "وسائط التالي",
"Media Previous": "وسائط السابق",
"Media Play/Pause": "وسائط تشغيل/إيقاف مؤقت",
"Media Fast Forward": "وسائط تقديم سريع",
"Media Rewind": "وسائط إرجاع",
"Volume Up": "رفع الصوت",
"Volume Down": "خفض الصوت",
"Arrow Left": "سهم يسار",
"Arrow Right": "سهم يمين",
"Arrow Up": "سهم أعلى",
"Arrow Down": "سهم أسفل",
"Page Up": "صفحة للأعلى",
"Page Down": "صفحة للأسفل",
"Space": "مسافة",
"Enter": "إدخال",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "اضغط على زر في جهاز التحكم عن بُعد أو لوحة المفاتيح بعد النقر على \"تعيين مفتاح\".",
"Use Volume Keys": "استخدام مفاتيح الصوت",
"Custom Page Turner": "قلّاب الصفحات المخصص"
}
@@ -195,7 +195,6 @@
"Scrolled Mode": "স্ক্রল মোড",
"Overlap Pixels": "ওভারল্যাপ পিক্সেল",
"Disable Double Click": "ডাবল ক্লিক নিষ্ক্রিয়",
"Volume Keys for Page Flip": "পৃষ্ঠা উল্টানোর জন্য ভলিউম কী",
"Animation": "অ্যানিমেশন",
"Paging Animation": "পৃষ্ঠা অ্যানিমেশন",
"Security": "নিরাপত্তা",
@@ -269,7 +268,6 @@
"Reading Progress Style": "পড়ার অগ্রগতির স্টাইল",
"Page Number": "পৃষ্ঠা নম্বর",
"Percentage": "শতাংশ",
"Apply also in Scrolled Mode": "স্ক্রল মোডেও প্রয়োগ করুন",
"Screen": "স্ক্রিন",
"Orientation": "দিকবিন্যাস",
"Portrait": "পোর্ট্রেট",
@@ -1412,5 +1410,27 @@
"Start your library": "আপনার লাইব্রেরি শুরু করুন",
"Pick a book from your device to add it to your library.": "আপনার লাইব্রেরিতে যোগ করতে ডিভাইস থেকে একটি বই বেছে নিন।",
"Drop a book anywhere on this window, or pick one from your computer.": "এই উইন্ডোর যেকোনো জায়গায় একটি বই ছাড়ুন, অথবা আপনার কম্পিউটার থেকে একটি বেছে নিন।",
"Sign in to sync your library": "আপনার লাইব্রেরি সিঙ্ক করতে সাইন ইন করুন"
"Sign in to sync your library": "আপনার লাইব্রেরি সিঙ্ক করতে সাইন ইন করুন",
"Clear": "মুছুন",
"Listening…": "শুনছে…",
"Set key": "কী সেট করুন",
"Page Turner": "পেজ টার্নার",
"Media Next": "মিডিয়া পরবর্তী",
"Media Previous": "মিডিয়া পূর্ববর্তী",
"Media Play/Pause": "মিডিয়া প্লে/পজ",
"Media Fast Forward": "মিডিয়া ফাস্ট ফরওয়ার্ড",
"Media Rewind": "মিডিয়া রিওয়াইন্ড",
"Volume Up": "ভলিউম বাড়ান",
"Volume Down": "ভলিউম কমান",
"Arrow Left": "বাম তীর",
"Arrow Right": "ডান তীর",
"Arrow Up": "উপরের তীর",
"Arrow Down": "নিচের তীর",
"Page Up": "পেজ আপ",
"Page Down": "পেজ ডাউন",
"Space": "স্পেস",
"Enter": "এন্টার",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "\"কী সেট করুন\" ট্যাপ করার পর আপনার রিমোট কন্ট্রোলার বা কীবোর্ডের একটি বোতাম চাপুন।",
"Use Volume Keys": "ভলিউম কী ব্যবহার করুন",
"Custom Page Turner": "কাস্টম পেজ টার্নার"
}
@@ -227,7 +227,6 @@
"CJK Font": "རྒྱ་ཡིག་གི་ཡིག་གཟུགས།",
"Clear Search": "འཚོལ་བཤེར་བསལ་བ།",
"Header & Footer": "ཤོག་ངོས་ཀྱི་མགོ་དང་མཇུག",
"Apply also in Scrolled Mode": "འཁོར་རྒྱུག་རྣམ་པར་སྤྱོད་པ།",
"A new version of Readest is available!": "Readest ཐོན་རིམ་གསར་པ་བསྐྱར་བརྗེ་བྱ་རྒྱུ་ཡོད།",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} བསྐྱར་བརྗེ་བྱ་རྒྱུ་ཡོད།(བསྒྲིགས་ཟིན་པའི་ཐོན་རིམ {{currentVersion}}",
"Download and install now?": "ད་ལྟ་ཕབ་ལེན་དང་བསྒྲིགས་དགོས་སམ།",
@@ -251,7 +250,6 @@
"Grid": "དྲྭ་རྡལ།",
"(from 'As You Like It', Act II)": "《ཐམས་ཅད་དགའ་བ》ནས་དྲངས་པ། ལེའུ་གཉིས་པ།",
"Link Color": "སྦྲེལ་ཐག་གི་ཚོན་མདོག",
"Volume Keys for Page Flip": "སྒྲ་ཚད་ལྡེ་མིག་གིས་ཤོག་ལྷེ་བསྒྱུར་བ།",
"Screen": "བརྡ་ཁྱབ་ངོས།",
"Orientation": "བརྡ་ཁྱབ་ངོས་ཀྱི་ཁ་ཕྱོགས།",
"Portrait": "ཀྲིང་ངོས།",
@@ -1387,5 +1385,27 @@
"Start your library": "ཁྱེད་ཀྱི་དཔེ་མཛོད་འགོ་འཛུགས།",
"Pick a book from your device to add it to your library.": "ཁྱེད་ཀྱི་སྒྲིག་ཆས་ནས་དཔེ་དེབ་ཅིག་འདེམས་ནས་ཁྱེད་ཀྱི་དཔེ་མཛོད་ལ་ཁ་སྣོན་གྱིས།",
"Drop a book anywhere on this window, or pick one from your computer.": "སྒེའུ་ཁུང་འདིའི་གང་སར་དཔེ་དེབ་ཅིག་འཇོག་པའམ་ཡང་ན་ཁྱེད་ཀྱི་གློག་ཀླད་ནས་འདེམས་རོགས།",
"Sign in to sync your library": "ཁྱེད་ཀྱི་དཔེ་མཛོད་མཉམ་སྦྱོར་གྱི་ཆེད་ནང་བསྐྱོད་གནང་རོགས།"
"Sign in to sync your library": "ཁྱེད་ཀྱི་དཔེ་མཛོད་མཉམ་སྦྱོར་གྱི་ཆེད་ནང་བསྐྱོད་གནང་རོགས།",
"Clear": "སུབ་པ།",
"Listening…": "ཉན་གྱི་ཡོད…",
"Set key": "མཐེབ་གཞི་གཏན་འཁེལ།",
"Page Turner": "ཤོག་གྲུ་འཕར་མཁན།",
"Media Next": "མི་ཡིའི་རྗེས་མ།",
"Media Previous": "མི་ཡིའི་སྔོན་མ།",
"Media Play/Pause": "མི་ཡི་བཏང་/བཀག",
"Media Fast Forward": "མི་ཡི་མྱུར་མགྱོགས་སྔར་བསྐྱོད།",
"Media Rewind": "མི་ཡི་ཕྱིར་བསྐྱོད།",
"Volume Up": "སྒྲ་སྐད་མཐོར་གཏོང་།",
"Volume Down": "སྒྲ་སྐད་དམའར་གཏོང་།",
"Arrow Left": "གཡོན་མདའ།",
"Arrow Right": "གཡས་མདའ།",
"Arrow Up": "ཡར་མདའ།",
"Arrow Down": "མར་མདའ།",
"Page Up": "ཤོག་ལྷེ་ཡར།",
"Page Down": "ཤོག་ལྷེ་མར།",
"Space": "བར་ཐག",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "\"མཐེབ་གཞི་གཏན་འཁེལ།\" ལ་རེག་རྗེས། ཁྱེད་ཀྱི་རྒྱང་འཐེན་སྒྲིག་ཆས་སམ་མཐེབ་གཞི་གང་རུང་གི་ཐོག་མཐེབ་གཅིག་གནོན་རོགས།",
"Use Volume Keys": "སྒྲ་སྐད་མཐེབ་གཞི་སྤྱོད་པ།",
"Custom Page Turner": "རང་སྒྲུབ་ཤོག་ལྷེ་སྤོ་བའི་ལམ་ལུགས།"
}
@@ -227,7 +227,6 @@
"CJK Font": "CJK-Schriftart",
"Clear Search": "Suche löschen",
"Header & Footer": "Kopf- & Fußzeile",
"Apply also in Scrolled Mode": "Auch im Scroll-Modus anwenden",
"A new version of Readest is available!": "Eine neue Version von Readest ist verfügbar!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} ist verfügbar (installierte Version {{currentVersion}}).",
"Download and install now?": "Jetzt herunterladen und installieren?",
@@ -251,7 +250,6 @@
"Grid": "Raster",
"(from 'As You Like It', Act II)": "(aus 'Wie es euch gefällt', Akt II)",
"Link Color": "Linkfarbe",
"Volume Keys for Page Flip": "Volumen-Tasten für Seitenwechsel",
"Screen": "Bildschirm",
"Orientation": "Orientierung",
"Portrait": "Hochformat",
@@ -1412,5 +1410,27 @@
"Start your library": "Starten Sie Ihre Bibliothek",
"Pick a book from your device to add it to your library.": "Wählen Sie ein Buch von Ihrem Gerät, um es Ihrer Bibliothek hinzuzufügen.",
"Drop a book anywhere on this window, or pick one from your computer.": "Ziehen Sie ein Buch an eine beliebige Stelle dieses Fensters oder wählen Sie eines von Ihrem Computer aus.",
"Sign in to sync your library": "Melden Sie sich an, um Ihre Bibliothek zu synchronisieren"
"Sign in to sync your library": "Melden Sie sich an, um Ihre Bibliothek zu synchronisieren",
"Clear": "Löschen",
"Listening…": "Warte auf Eingabe…",
"Set key": "Taste festlegen",
"Page Turner": "Seitenblätterer",
"Media Next": "Medien: Weiter",
"Media Previous": "Medien: Zurück",
"Media Play/Pause": "Medien: Wiedergabe/Pause",
"Media Fast Forward": "Medien: Schnellvorlauf",
"Media Rewind": "Medien: Rücklauf",
"Volume Up": "Lautstärke erhöhen",
"Volume Down": "Lautstärke verringern",
"Arrow Left": "Pfeil links",
"Arrow Right": "Pfeil rechts",
"Arrow Up": "Pfeil hoch",
"Arrow Down": "Pfeil runter",
"Page Up": "Bild auf",
"Page Down": "Bild ab",
"Space": "Leertaste",
"Enter": "Eingabe",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Drücken Sie eine Taste auf Ihrer Fernbedienung oder Tastatur, nachdem Sie auf \"Taste festlegen\" getippt haben.",
"Use Volume Keys": "Lautstärketasten verwenden",
"Custom Page Turner": "Benutzerdefinierter Seitenumblätterer"
}
@@ -228,7 +228,6 @@
"CJK Font": "Γραμματοσειρά CJK",
"Clear Search": "Καθαρισμός αναζήτησης",
"Header & Footer": "Κεφαλίδα & Υποσέλιδο",
"Apply also in Scrolled Mode": "Εφαρμογή και σε λειτουργία κύλισης",
"A new version of Readest is available!": "Μια νέα έκδοση του Readest είναι διαθέσιμη!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Η έκδοση Readest {{newVersion}} είναι διαθέσιμη (έκδοση που είναι εγκατεστημένη {{currentVersion}}).",
"Download and install now?": "Θέλετε να την κατεβάσετε και να την εγκαταστήσετε τώρα;",
@@ -252,7 +251,6 @@
"Grid": "Πλέγμα",
"(from 'As You Like It', Act II)": "('Όπως σας αρέσει', Πράξη II)",
"Link Color": "Χρώμα συνδέσμου",
"Volume Keys for Page Flip": "Ανατροπή με πλήκτρα έντασης",
"Screen": "Οθόνη",
"Orientation": "Προσανατολισμός",
"Portrait": "Πορτραίτο",
@@ -1412,5 +1410,27 @@
"Start your library": "Ξεκινήστε τη βιβλιοθήκη σας",
"Pick a book from your device to add it to your library.": "Επιλέξτε ένα βιβλίο από τη συσκευή σας για να το προσθέσετε στη βιβλιοθήκη σας.",
"Drop a book anywhere on this window, or pick one from your computer.": "Αφήστε ένα βιβλίο οπουδήποτε σε αυτό το παράθυρο ή επιλέξτε ένα από τον υπολογιστή σας.",
"Sign in to sync your library": "Συνδεθείτε για να συγχρονίσετε τη βιβλιοθήκη σας"
"Sign in to sync your library": "Συνδεθείτε για να συγχρονίσετε τη βιβλιοθήκη σας",
"Clear": "Εκκαθάριση",
"Listening…": "Αναμονή…",
"Set key": "Ορισμός πλήκτρου",
"Page Turner": "Στροφέας σελίδων",
"Media Next": "Πολυμέσα: Επόμενο",
"Media Previous": "Πολυμέσα: Προηγούμενο",
"Media Play/Pause": "Πολυμέσα: Αναπαραγωγή/Παύση",
"Media Fast Forward": "Πολυμέσα: Γρήγορη προώθηση",
"Media Rewind": "Πολυμέσα: Επαναφορά",
"Volume Up": "Αύξηση έντασης",
"Volume Down": "Μείωση έντασης",
"Arrow Left": "Βέλος αριστερά",
"Arrow Right": "Βέλος δεξιά",
"Arrow Up": "Βέλος πάνω",
"Arrow Down": "Βέλος κάτω",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Κενό",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Πατήστε ένα κουμπί στο τηλεχειριστήριο ή το πληκτρολόγιό σας αφού πατήσετε «Ορισμός πλήκτρου».",
"Use Volume Keys": "Χρήση πλήκτρων έντασης",
"Custom Page Turner": "Προσαρμοσμένος αναλτήρας σελίδων"
}
@@ -250,7 +250,6 @@
"CJK Font": "Fuente CJK",
"Clear Search": "Limpiar búsqueda",
"Header & Footer": "Encabezado y pie de página",
"Apply also in Scrolled Mode": "Aplicar también en modo desplazamiento",
"A new version of Readest is available!": "¡Una nueva versión de Readest está disponible!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} está disponible (versión instalada {{currentVersion}}).",
"Download and install now?": "Descargar e instalar ahora?",
@@ -274,7 +273,6 @@
"Grid": "Cuadrícula",
"(from 'As You Like It', Act II)": "(de 'Como gustéis', Acto II)",
"Link Color": "Color del enlace",
"Volume Keys for Page Flip": "Botones de volumen para pasar página",
"Screen": "Pantalla",
"Orientation": "Orientación",
"Portrait": "Vertical",
@@ -1437,5 +1435,27 @@
"Start your library": "Empieza tu biblioteca",
"Pick a book from your device to add it to your library.": "Elige un libro de tu dispositivo para añadirlo a tu biblioteca.",
"Drop a book anywhere on this window, or pick one from your computer.": "Suelta un libro en cualquier parte de esta ventana o elige uno desde tu computadora.",
"Sign in to sync your library": "Inicia sesión para sincronizar tu biblioteca"
"Sign in to sync your library": "Inicia sesión para sincronizar tu biblioteca",
"Clear": "Borrar",
"Listening…": "Escuchando…",
"Set key": "Asignar tecla",
"Page Turner": "Pasador de páginas",
"Media Next": "Multimedia: Siguiente",
"Media Previous": "Multimedia: Anterior",
"Media Play/Pause": "Multimedia: Reproducir/Pausa",
"Media Fast Forward": "Multimedia: Avance rápido",
"Media Rewind": "Multimedia: Retroceso",
"Volume Up": "Subir volumen",
"Volume Down": "Bajar volumen",
"Arrow Left": "Flecha izquierda",
"Arrow Right": "Flecha derecha",
"Arrow Up": "Flecha arriba",
"Arrow Down": "Flecha abajo",
"Page Up": "Re Pág",
"Page Down": "Av Pág",
"Space": "Espacio",
"Enter": "Intro",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Pulse un botón en su mando a distancia o teclado después de tocar \"Asignar tecla\".",
"Use Volume Keys": "Usar teclas de volumen",
"Custom Page Turner": "Pasador de páginas personalizado"
}
@@ -227,7 +227,6 @@
"CJK Font": "فونت CJK",
"Clear Search": "پاک کردن جستجو",
"Header & Footer": "سربرگ و پاورقی",
"Apply also in Scrolled Mode": "همچنین در حالت پیمایشی اِعمال شود",
"A new version of Readest is available!": "نسخه‌ی جدیدی از Readest در دسترس است!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "نسخه‌ی {{newVersion}} Readest در دسترس است (نسخه‌ی نصب‌شده: {{currentVersion}}).",
"Download and install now?": "الان دانلود و نصب شود؟",
@@ -251,7 +250,6 @@
"Grid": "جدول",
"(from 'As You Like It', Act II)": "(از «همان‌طور که دوست دارید»، پرده‌ی دوم)",
"Link Color": "رنگ لینک",
"Volume Keys for Page Flip": "استفاده از کلیدهای صدا برای ورق زدن صفحه",
"Screen": "صفحه",
"Orientation": "جهت",
"Portrait": "عمودی",
@@ -1412,5 +1410,27 @@
"Start your library": "کتابخانه‌ی خود را آغاز کنید",
"Pick a book from your device to add it to your library.": "از دستگاه خود کتابی انتخاب کنید تا به کتابخانه‌تان افزوده شود.",
"Drop a book anywhere on this window, or pick one from your computer.": "کتابی را در هر جای این پنجره رها کنید، یا یکی را از رایانه‌ی خود انتخاب کنید.",
"Sign in to sync your library": "برای همگام‌سازی کتابخانه‌ی خود وارد شوید"
"Sign in to sync your library": "برای همگام‌سازی کتابخانه‌ی خود وارد شوید",
"Clear": "پاک کردن",
"Listening…": "در حال گوش دادن…",
"Set key": "تنظیم کلید",
"Page Turner": "ورق‌زن صفحه",
"Media Next": "رسانه: بعدی",
"Media Previous": "رسانه: قبلی",
"Media Play/Pause": "رسانه: پخش/مکث",
"Media Fast Forward": "رسانه: جلو بردن سریع",
"Media Rewind": "رسانه: برگشت سریع",
"Volume Up": "افزایش صدا",
"Volume Down": "کاهش صدا",
"Arrow Left": "فلش چپ",
"Arrow Right": "فلش راست",
"Arrow Up": "فلش بالا",
"Arrow Down": "فلش پایین",
"Page Up": "صفحه بالا",
"Page Down": "صفحه پایین",
"Space": "فاصله",
"Enter": "اینتر",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "پس از ضربه زدن به «تنظیم کلید»، دکمه‌ای روی کنترلر یا صفحه‌کلید خود فشار دهید.",
"Use Volume Keys": "استفاده از کلیدهای صدا",
"Custom Page Turner": "صفحه‌گردان سفارشی"
}
@@ -227,7 +227,6 @@
"CJK Font": "Police CJK",
"Clear Search": "Effacer la recherche",
"Header & Footer": "En-tête et pied de page",
"Apply also in Scrolled Mode": "Appliquer également en mode défilement",
"A new version of Readest is available!": "Avis de mise à jour",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} est disponible (version installée {{currentVersion}}).",
"Download and install now?": "Télécharger et installer maintenant ?",
@@ -251,7 +250,6 @@
"Grid": "Grille",
"(from 'As You Like It', Act II)": "(de 'Comme il vous plaira', Acte II)",
"Link Color": "Couleur du lien",
"Volume Keys for Page Flip": "Boutons volume pour tourner la page",
"Screen": "Écran",
"Orientation": "Orientation",
"Portrait": "Portrait",
@@ -1437,5 +1435,27 @@
"Start your library": "Commencez votre bibliothèque",
"Pick a book from your device to add it to your library.": "Choisissez un livre depuis votre appareil à ajouter à votre bibliothèque.",
"Drop a book anywhere on this window, or pick one from your computer.": "Déposez un livre n'importe où sur cette fenêtre, ou choisissez-en un depuis votre ordinateur.",
"Sign in to sync your library": "Connectez-vous pour synchroniser votre bibliothèque"
"Sign in to sync your library": "Connectez-vous pour synchroniser votre bibliothèque",
"Clear": "Effacer",
"Listening…": "En attente…",
"Set key": "Définir la touche",
"Page Turner": "Tourne-page",
"Media Next": "Média : Suivant",
"Media Previous": "Média : Précédent",
"Media Play/Pause": "Média : Lecture/Pause",
"Media Fast Forward": "Média : Avance rapide",
"Media Rewind": "Média : Retour arrière",
"Volume Up": "Volume +",
"Volume Down": "Volume -",
"Arrow Left": "Flèche gauche",
"Arrow Right": "Flèche droite",
"Arrow Up": "Flèche haut",
"Arrow Down": "Flèche bas",
"Page Up": "Page préc.",
"Page Down": "Page suiv.",
"Space": "Espace",
"Enter": "Entrée",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Appuyez sur un bouton de votre télécommande ou de votre clavier après avoir appuyé sur « Définir la touche ».",
"Use Volume Keys": "Utiliser les touches de volume",
"Custom Page Turner": "Tourne-page personnalisé"
}
@@ -850,7 +850,6 @@
"Swap Click Sides": "החלף צדדי לחיצה",
"Disable Double Tap": "השבת הקשה כפולה",
"Disable Double Click": "השבת לחיצה כפולה",
"Volume Keys for Page Flip": "מקשי עוצמת קול לדפדוף",
"Show Page Navigation Buttons": "הצג כפתורי ניווט בעמוד",
"Annotation Tools": "כלי הערות",
"Enable Quick Actions": "הפעל פעולות מהירות",
@@ -938,7 +937,6 @@
"Maximum Number of Columns": "מספר עמודות מקסימלי",
"Maximum Column Height": "גובה עמודה מקסימלי",
"Maximum Column Width": "רוחב עמודה מקסימלי",
"Apply also in Scrolled Mode": "החל גם במצב גלילה",
"Header & Footer": "כותרת עליונה ותחתונה",
"Show Header": "הצג כותרת עליונה",
"Show Footer": "הצג כותרת תחתונה",
@@ -1437,5 +1435,27 @@
"Start your library": "התחילו את הספרייה שלכם",
"Pick a book from your device to add it to your library.": "בחרו ספר מהמכשיר שלכם כדי להוסיף אותו לספרייה.",
"Drop a book anywhere on this window, or pick one from your computer.": "שחררו ספר בכל מקום בחלון הזה, או בחרו אחד מהמחשב שלכם.",
"Sign in to sync your library": "התחברו לסנכרון הספרייה שלכם"
"Sign in to sync your library": "התחברו לסנכרון הספרייה שלכם",
"Clear": "נקה",
"Listening…": "מאזין…",
"Set key": "הגדר מקש",
"Page Turner": "מהפך עמודים",
"Media Next": "מדיה הבא",
"Media Previous": "מדיה הקודם",
"Media Play/Pause": "מדיה ניגון/השהייה",
"Media Fast Forward": "מדיה הרצה מהירה",
"Media Rewind": "מדיה הרצה לאחור",
"Volume Up": "הגבר עוצמה",
"Volume Down": "הנמך עוצמה",
"Arrow Left": "חץ שמאלה",
"Arrow Right": "חץ ימינה",
"Arrow Up": "חץ למעלה",
"Arrow Down": "חץ למטה",
"Page Up": "עמוד קודם",
"Page Down": "עמוד הבא",
"Space": "רווח",
"Enter": "אנטר",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "לחץ על כפתור בשלט הרחוק או במקלדת לאחר הקשה על \"הגדר מקש\".",
"Use Volume Keys": "השתמש במקשי עוצמת קול",
"Custom Page Turner": "מהפך עמודים מותאם אישית"
}
@@ -228,7 +228,6 @@
"CJK Font": "CJK फ़ॉन्ट",
"Clear Search": "खोज साफ करें",
"Header & Footer": "हेडर और फ़ुटर",
"Apply also in Scrolled Mode": "स्क्रॉल मोड में भी लागू करें",
"A new version of Readest is available!": "Readest का एक नया संस्करण उपलब्ध है!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} उपलब्ध है (स्थापित संस्करण {{currentVersion}})।",
"Download and install now?": "अब डाउनलोड और इंस्टॉल करें?",
@@ -252,7 +251,6 @@
"Grid": "ग्रिड",
"(from 'As You Like It', Act II)": "('जैसा आप इसे पसंद करते हैं', अधिनियम II)",
"Link Color": "लिंक रंग",
"Volume Keys for Page Flip": "पृष्ठ फ़्लिप के लिए वॉल्यूम कुंजियाँ",
"Screen": "स्क्रीन",
"Orientation": "अवयव",
"Portrait": "पोर्ट्रेट",
@@ -1412,5 +1410,27 @@
"Start your library": "अपनी लाइब्रेरी शुरू करें",
"Pick a book from your device to add it to your library.": "अपनी लाइब्रेरी में जोड़ने के लिए अपने डिवाइस से एक किताब चुनें।",
"Drop a book anywhere on this window, or pick one from your computer.": "इस विंडो में कहीं भी एक किताब छोड़ें, या अपने कंप्यूटर से कोई एक चुनें।",
"Sign in to sync your library": "अपनी लाइब्रेरी सिंक करने के लिए साइन इन करें"
"Sign in to sync your library": "अपनी लाइब्रेरी सिंक करने के लिए साइन इन करें",
"Clear": "साफ़ करें",
"Listening…": "सुन रहे हैं…",
"Set key": "कुंजी सेट करें",
"Page Turner": "पेज टर्नर",
"Media Next": "मीडिया अगला",
"Media Previous": "मीडिया पिछला",
"Media Play/Pause": "मीडिया प्ले/पॉज़",
"Media Fast Forward": "मीडिया फ़ास्ट फ़ॉरवर्ड",
"Media Rewind": "मीडिया रिवाइंड",
"Volume Up": "वॉल्यूम बढ़ाएं",
"Volume Down": "वॉल्यूम घटाएं",
"Arrow Left": "बाएं तीर",
"Arrow Right": "दाएं तीर",
"Arrow Up": "ऊपर तीर",
"Arrow Down": "नीचे तीर",
"Page Up": "पेज ऊपर",
"Page Down": "पेज नीचे",
"Space": "स्पेस",
"Enter": "एंटर",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "\"कुंजी सेट करें\" दबाने के बाद अपने रिमोट कंट्रोलर या कीबोर्ड पर एक बटन दबाएं।",
"Use Volume Keys": "वॉल्यूम कुंजियों का उपयोग करें",
"Custom Page Turner": "कस्टम पेज टर्नर"
}
@@ -930,7 +930,6 @@
"Swap Click Sides": "Kattintási oldalak cseréje",
"Disable Double Tap": "Dupla koppintás letiltása",
"Disable Double Click": "Dupla kattintás letiltása",
"Volume Keys for Page Flip": "Hangerőgombok a lapozáshoz",
"Show Page Navigation Buttons": "Oldalnavigációs gombok megjelenítése",
"Annotation Tools": "Jegyzetelési eszközök",
"Enable Quick Actions": "Gyorsműveletek engedélyezése",
@@ -1018,7 +1017,6 @@
"Maximum Number of Columns": "Oszlopok maximális száma",
"Maximum Column Height": "Maximális oszlopmagasság",
"Maximum Column Width": "Maximális oszlopszélesség",
"Apply also in Scrolled Mode": "Alkalmazás görgetett módban is",
"Header & Footer": "Fejléc és lábléc",
"Show Header": "Fejléc megjelenítése",
"Show Footer": "Lábléc megjelenítése",
@@ -1412,5 +1410,27 @@
"Start your library": "Indítsa el a könyvtárát",
"Pick a book from your device to add it to your library.": "Válasszon egy könyvet az eszközéről, hogy hozzáadja a könyvtárához.",
"Drop a book anywhere on this window, or pick one from your computer.": "Húzzon egy könyvet bárhová ezen az ablakon, vagy válasszon egyet a számítógépéről.",
"Sign in to sync your library": "Jelentkezzen be a könyvtár szinkronizálásához"
"Sign in to sync your library": "Jelentkezzen be a könyvtár szinkronizálásához",
"Clear": "Törlés",
"Listening…": "Figyelés…",
"Set key": "Billentyű beállítása",
"Page Turner": "Lapozó eszköz",
"Media Next": "Média következő",
"Media Previous": "Média előző",
"Media Play/Pause": "Média lejátszás/szünet",
"Media Fast Forward": "Média gyors előre",
"Media Rewind": "Média visszatekerés",
"Volume Up": "Hangerő növelése",
"Volume Down": "Hangerő csökkentése",
"Arrow Left": "Bal nyíl",
"Arrow Right": "Jobb nyíl",
"Arrow Up": "Fel nyíl",
"Arrow Down": "Le nyíl",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Szóköz",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Nyomjon meg egy gombot a távirányítón vagy a billentyűzeten a „Billentyű beállítása” érintése után.",
"Use Volume Keys": "Hangerőgombok használata",
"Custom Page Turner": "Egyéni lapozó eszköz"
}
@@ -228,7 +228,6 @@
"CJK Font": "Font CJK",
"Clear Search": "Hapus Pencarian",
"Header & Footer": "Header & Footer",
"Apply also in Scrolled Mode": "Terapkan juga di Mode Gulir",
"A new version of Readest is available!": "Versi baru Readest Tersedia!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} tersedia (versi terinstal {{currentVersion}}).",
"Download and install now?": "Unduh dan instal sekarang?",
@@ -252,7 +251,6 @@
"Grid": "Jala",
"(from 'As You Like It', Act II)": "(dari 'Seperti yang Anda Inginkan', Bab II)",
"Link Color": "Warna Tautan",
"Volume Keys for Page Flip": "Volume Keys untuk Pembalikan Halaman",
"Screen": "Screen",
"Orientation": "Orientasi",
"Portrait": "Potret",
@@ -1387,5 +1385,27 @@
"Start your library": "Mulai perpustakaan Anda",
"Pick a book from your device to add it to your library.": "Pilih buku dari perangkat Anda untuk ditambahkan ke perpustakaan Anda.",
"Drop a book anywhere on this window, or pick one from your computer.": "Jatuhkan buku di mana saja pada jendela ini, atau pilih satu dari komputer Anda.",
"Sign in to sync your library": "Masuk untuk menyinkronkan perpustakaan Anda"
"Sign in to sync your library": "Masuk untuk menyinkronkan perpustakaan Anda",
"Clear": "Hapus",
"Listening…": "Mendengarkan…",
"Set key": "Atur tombol",
"Page Turner": "Pembalik Halaman",
"Media Next": "Media Berikutnya",
"Media Previous": "Media Sebelumnya",
"Media Play/Pause": "Media Putar/Jeda",
"Media Fast Forward": "Media Maju Cepat",
"Media Rewind": "Media Mundur",
"Volume Up": "Volume Naik",
"Volume Down": "Volume Turun",
"Arrow Left": "Panah Kiri",
"Arrow Right": "Panah Kanan",
"Arrow Up": "Panah Atas",
"Arrow Down": "Panah Bawah",
"Page Up": "Halaman Atas",
"Page Down": "Halaman Bawah",
"Space": "Spasi",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Tekan tombol pada remote controller atau keyboard Anda setelah mengetuk \"Atur tombol\".",
"Use Volume Keys": "Gunakan Tombol Volume",
"Custom Page Turner": "Pembalik Halaman Kustom"
}
@@ -228,7 +228,6 @@
"CJK Font": "Font CJK",
"Clear Search": "Cancella ricerca",
"Header & Footer": "Intestazione e piè di pagina",
"Apply also in Scrolled Mode": "Applica anche in modalità scorrimento",
"A new version of Readest is available!": "È disponibile una nuova versione di Readest!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} è disponibile (versione installata {{currentVersion}}).",
"Download and install now?": "Scarica e installa ora?",
@@ -252,7 +251,6 @@
"Grid": "Griglia",
"(from 'As You Like It', Act II)": "(da 'Come vi piace', Atto II)",
"Link Color": "Colore link",
"Volume Keys for Page Flip": "Tasti volume per girare pagina",
"Screen": "Schermo",
"Orientation": "Orientamento",
"Portrait": "Ritratto",
@@ -1437,5 +1435,27 @@
"Start your library": "Inizia la tua biblioteca",
"Pick a book from your device to add it to your library.": "Scegli un libro dal tuo dispositivo per aggiungerlo alla tua biblioteca.",
"Drop a book anywhere on this window, or pick one from your computer.": "Trascina un libro in qualsiasi punto di questa finestra, o sceglilo dal tuo computer.",
"Sign in to sync your library": "Accedi per sincronizzare la tua biblioteca"
"Sign in to sync your library": "Accedi per sincronizzare la tua biblioteca",
"Clear": "Cancella",
"Listening…": "In ascolto…",
"Set key": "Imposta tasto",
"Page Turner": "Giratore di pagine",
"Media Next": "Multimediale: Successivo",
"Media Previous": "Multimediale: Precedente",
"Media Play/Pause": "Multimediale: Riproduci/Pausa",
"Media Fast Forward": "Multimediale: Avanzamento rapido",
"Media Rewind": "Multimediale: Riavvolgimento",
"Volume Up": "Volume su",
"Volume Down": "Volume giù",
"Arrow Left": "Freccia sinistra",
"Arrow Right": "Freccia destra",
"Arrow Up": "Freccia su",
"Arrow Down": "Freccia giù",
"Page Up": "Pag su",
"Page Down": "Pag giù",
"Space": "Spazio",
"Enter": "Invio",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Premi un pulsante sul telecomando o sulla tastiera dopo aver toccato \"Imposta tasto\".",
"Use Volume Keys": "Usa i tasti del volume",
"Custom Page Turner": "Giratore di pagine personalizzato"
}
@@ -228,7 +228,6 @@
"CJK Font": "CJKフォント",
"Clear Search": "検索をクリア",
"Header & Footer": "ヘッダーとフッター",
"Apply also in Scrolled Mode": "スクロールモードでも適用",
"A new version of Readest is available!": "新しいバージョンのReadestが利用可能です!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}}が利用可能です(インストールされたバージョン{{currentVersion}})。",
"Download and install now?": "今すぐダウンロードしてインストールしますか?",
@@ -252,7 +251,6 @@
"Grid": "グリッド",
"(from 'As You Like It', Act II)": "(「お気に召すまま」第2幕)",
"Link Color": "リンクカラー",
"Volume Keys for Page Flip": "音量キーでページめくり",
"Screen": "画面",
"Orientation": "向き",
"Portrait": "縦",
@@ -1387,5 +1385,27 @@
"Start your library": "ライブラリーを作成しましょう",
"Pick a book from your device to add it to your library.": "デバイスから本を選んでライブラリーに追加しましょう。",
"Drop a book anywhere on this window, or pick one from your computer.": "このウィンドウの好きな場所に本をドロップするか、コンピューターから選んでください。",
"Sign in to sync your library": "サインインしてライブラリーを同期"
"Sign in to sync your library": "サインインしてライブラリーを同期",
"Clear": "クリア",
"Listening…": "待機中…",
"Set key": "キーを設定",
"Page Turner": "ページターナー",
"Media Next": "メディア: 次へ",
"Media Previous": "メディア: 前へ",
"Media Play/Pause": "メディア: 再生/一時停止",
"Media Fast Forward": "メディア: 早送り",
"Media Rewind": "メディア: 巻き戻し",
"Volume Up": "音量を上げる",
"Volume Down": "音量を下げる",
"Arrow Left": "左矢印",
"Arrow Right": "右矢印",
"Arrow Up": "上矢印",
"Arrow Down": "下矢印",
"Page Up": "ページアップ",
"Page Down": "ページダウン",
"Space": "スペース",
"Enter": "エンター",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "「キーを設定」をタップした後、リモコンまたはキーボードのボタンを押してください。",
"Use Volume Keys": "音量キーを使用",
"Custom Page Turner": "カスタムページターナー"
}
@@ -228,7 +228,6 @@
"CJK Font": "CJK 글꼴",
"Clear Search": "검색 지우기",
"Header & Footer": "헤더 및 푸터",
"Apply also in Scrolled Mode": "스크롤 모드에서도 적용",
"A new version of Readest is available!": "Readest의 새 버전이 있습니다!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}}가 사용 가능 (설치된 버전 {{currentVersion}}).",
"Download and install now?": "지금 다운로드하고 설치하시겠습니까?",
@@ -252,7 +251,6 @@
"Grid": "격자",
"(from 'As You Like It', Act II)": "(당신이 좋아하는 대로, 2막)",
"Link Color": "링크 색상",
"Volume Keys for Page Flip": "페이지 넘김을 위한 볼륨 키",
"Screen": "화면",
"Orientation": "방향",
"Portrait": "세로",
@@ -1387,5 +1385,27 @@
"Start your library": "서재 만들기",
"Pick a book from your device to add it to your library.": "기기에서 책을 골라 서재에 추가하세요.",
"Drop a book anywhere on this window, or pick one from your computer.": "이 창의 아무 곳에나 책을 놓거나, 컴퓨터에서 골라 보세요.",
"Sign in to sync your library": "로그인하여 서재 동기화"
"Sign in to sync your library": "로그인하여 서재 동기화",
"Clear": "지우기",
"Listening…": "대기 중…",
"Set key": "키 설정",
"Page Turner": "페이지 터너",
"Media Next": "미디어: 다음",
"Media Previous": "미디어: 이전",
"Media Play/Pause": "미디어: 재생/일시정지",
"Media Fast Forward": "미디어: 빨리 감기",
"Media Rewind": "미디어: 되감기",
"Volume Up": "볼륨 높이기",
"Volume Down": "볼륨 낮추기",
"Arrow Left": "왼쪽 화살표",
"Arrow Right": "오른쪽 화살표",
"Arrow Up": "위쪽 화살표",
"Arrow Down": "아래쪽 화살표",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "스페이스",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "\"키 설정\"을 탭한 후 리모컨 또는 키보드의 버튼을 누르세요.",
"Use Volume Keys": "볼륨 키 사용",
"Custom Page Turner": "사용자 지정 페이지 넘기기"
}
@@ -502,7 +502,6 @@
"Swap Click Sides": "Tukar Sisi Klik",
"Disable Double Tap": "Lumpuhkan Ketuk Dua Kali",
"Disable Double Click": "Lumpuhkan Klik Dua Kali",
"Volume Keys for Page Flip": "Kekunci Volum untuk Balik Halaman",
"Animation": "Animasi",
"Paging Animation": "Animasi Halaman",
"Device": "Peranti",
@@ -584,7 +583,6 @@
"Maximum Number of Columns": "Bilangan Maksimum Lajur",
"Maximum Column Height": "Tinggi Maksimum Lajur",
"Maximum Column Width": "Lebar Maksimum Lajur",
"Apply also in Scrolled Mode": "Gunakan juga dalam Mod Tatal",
"Header & Footer": "Pengepala & Pengaki",
"Show Header": "Tunjuk Pengepala",
"Show Footer": "Tunjuk Pengaki",
@@ -1387,5 +1385,27 @@
"Start your library": "Mulakan perpustakaan anda",
"Pick a book from your device to add it to your library.": "Pilih buku dari peranti anda untuk ditambahkan ke perpustakaan anda.",
"Drop a book anywhere on this window, or pick one from your computer.": "Lepaskan buku di mana-mana pada tetingkap ini, atau pilih satu dari komputer anda.",
"Sign in to sync your library": "Daftar masuk untuk menyegerakkan perpustakaan anda"
"Sign in to sync your library": "Daftar masuk untuk menyegerakkan perpustakaan anda",
"Clear": "Kosongkan",
"Listening…": "Mendengar…",
"Set key": "Tetapkan kekunci",
"Page Turner": "Pembalik Halaman",
"Media Next": "Media Seterusnya",
"Media Previous": "Media Sebelumnya",
"Media Play/Pause": "Media Main/Jeda",
"Media Fast Forward": "Media Maju Laju",
"Media Rewind": "Media Undur",
"Volume Up": "Naikkan Kelantangan",
"Volume Down": "Turunkan Kelantangan",
"Arrow Left": "Anak Panah Kiri",
"Arrow Right": "Anak Panah Kanan",
"Arrow Up": "Anak Panah Atas",
"Arrow Down": "Anak Panah Bawah",
"Page Up": "Halaman Atas",
"Page Down": "Halaman Bawah",
"Space": "Ruang",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Tekan butang pada pengawal jauh atau papan kekunci anda selepas mengetuk \"Tetapkan kekunci\".",
"Use Volume Keys": "Guna Kekunci Kelantangan",
"Custom Page Turner": "Pembalik Halaman Tersuai"
}
@@ -168,7 +168,6 @@
"Header & Footer": "Koptekst & voettekst",
"Show Header": "Koptekst weergeven",
"Show Footer": "Voettekst weergeven",
"Apply also in Scrolled Mode": "Ook toepassen in scroll-modus",
"Auto": "Automatisch",
"Language": "Taal",
"Animation": "Animatie",
@@ -178,7 +177,6 @@
"Portrait": "Portret",
"Landscape": "Landschap",
"Behavior": "Gedrag",
"Volume Keys for Page Flip": "Volumetoetsen voor pagina omslaan",
"Apply": "Toepassen",
"Font": "Lettertype",
"Layout": "Opmaak",
@@ -1412,5 +1410,27 @@
"Start your library": "Begin je bibliotheek",
"Pick a book from your device to add it to your library.": "Kies een boek van je apparaat om aan je bibliotheek toe te voegen.",
"Drop a book anywhere on this window, or pick one from your computer.": "Sleep een boek ergens in dit venster, of kies er een van je computer.",
"Sign in to sync your library": "Meld je aan om je bibliotheek te synchroniseren"
"Sign in to sync your library": "Meld je aan om je bibliotheek te synchroniseren",
"Clear": "Wissen",
"Listening…": "Luisteren…",
"Set key": "Toets instellen",
"Page Turner": "Pagina-omslaan",
"Media Next": "Media: Volgende",
"Media Previous": "Media: Vorige",
"Media Play/Pause": "Media: Afspelen/Pauzeren",
"Media Fast Forward": "Media: Snel vooruitspoelen",
"Media Rewind": "Media: Terugspoelen",
"Volume Up": "Volume omhoog",
"Volume Down": "Volume omlaag",
"Arrow Left": "Pijl links",
"Arrow Right": "Pijl rechts",
"Arrow Up": "Pijl omhoog",
"Arrow Down": "Pijl omlaag",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Spatie",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Druk op een knop op uw afstandsbediening of toetsenbord nadat u op \"Toets instellen\" hebt getikt.",
"Use Volume Keys": "Volumetoetsen gebruiken",
"Custom Page Turner": "Aangepaste paginaknop"
}
@@ -228,7 +228,6 @@
"CJK Font": "Czcionka CJK",
"Clear Search": "Wyczyść wyszukiwanie",
"Header & Footer": "Nagłówek i stopka",
"Apply also in Scrolled Mode": "Zastosuj również w trybie przewijania",
"A new version of Readest is available!": "Nowa wersja Readest jest dostępna!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} jest dostępny (zainstalowana wersja {{currentVersion}}).",
"Download and install now?": "Pobierz i zainstaluj teraz?",
@@ -252,7 +251,6 @@
"Grid": "Siatka",
"(from 'As You Like It', Act II)": "(z „Jak wam się podoba”, Akt II)",
"Link Color": "Kolor linku",
"Volume Keys for Page Flip": "Klucze głośności do przewracania stron",
"Screen": "Ekran",
"Orientation": "Orientacja",
"Portrait": "Portret",
@@ -1462,5 +1460,27 @@
"Start your library": "Rozpocznij swoją bibliotekę",
"Pick a book from your device to add it to your library.": "Wybierz książkę z urządzenia, aby dodać ją do swojej biblioteki.",
"Drop a book anywhere on this window, or pick one from your computer.": "Upuść książkę w dowolnym miejscu tego okna lub wybierz ją z komputera.",
"Sign in to sync your library": "Zaloguj się, aby zsynchronizować bibliotekę"
"Sign in to sync your library": "Zaloguj się, aby zsynchronizować bibliotekę",
"Clear": "Wyczyść",
"Listening…": "Nasłuchiwanie…",
"Set key": "Ustaw klawisz",
"Page Turner": "Przewijacz stron",
"Media Next": "Media: Następny",
"Media Previous": "Media: Poprzedni",
"Media Play/Pause": "Media: Odtwórz/Pauza",
"Media Fast Forward": "Media: Przewiń do przodu",
"Media Rewind": "Media: Przewiń do tyłu",
"Volume Up": "Głośność+",
"Volume Down": "Głośność−",
"Arrow Left": "Strzałka w lewo",
"Arrow Right": "Strzałka w prawo",
"Arrow Up": "Strzałka w górę",
"Arrow Down": "Strzałka w dół",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Spacja",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Naciśnij przycisk na pilocie lub klawiaturze po dotknięciu \"Ustaw klawisz\".",
"Use Volume Keys": "Używaj klawiszy głośności",
"Custom Page Turner": "Własne przełączanie stron"
}
@@ -1046,7 +1046,6 @@
"Swap Click Sides": "Trocar lados do clique",
"Disable Double Tap": "Desativar toque duplo",
"Disable Double Click": "Desativar clique duplo",
"Volume Keys for Page Flip": "Teclas de volume para virar página",
"Show Page Navigation Buttons": "Botões de navegação",
"Annotation Tools": "Ferramentas de anotação",
"Enable Quick Actions": "Ativar ações rápidas",
@@ -1167,7 +1166,6 @@
"Maximum Number of Columns": "Número máximo de colunas",
"Maximum Column Height": "Altura máxima da coluna",
"Maximum Column Width": "Largura máxima da coluna",
"Apply also in Scrolled Mode": "Aplicar também no modo de rolagem",
"Header & Footer": "Cabeçalho e rodapé",
"Show Header": "Mostrar cabeçalho",
"Show Footer": "Mostrar rodapé",
@@ -1437,5 +1435,27 @@
"Start your library": "Comece sua biblioteca",
"Pick a book from your device to add it to your library.": "Escolha um livro do seu dispositivo para adicionar à sua biblioteca.",
"Drop a book anywhere on this window, or pick one from your computer.": "Solte um livro em qualquer parte desta janela, ou escolha um do seu computador.",
"Sign in to sync your library": "Entre para sincronizar sua biblioteca"
"Sign in to sync your library": "Entre para sincronizar sua biblioteca",
"Clear": "Limpar",
"Listening…": "Aguardando…",
"Set key": "Definir tecla",
"Page Turner": "Virador de páginas",
"Media Next": "Mídia: Próxima",
"Media Previous": "Mídia: Anterior",
"Media Play/Pause": "Mídia: Reproduzir/Pausar",
"Media Fast Forward": "Mídia: Avançar",
"Media Rewind": "Mídia: Retroceder",
"Volume Up": "Volume para cima",
"Volume Down": "Volume para baixo",
"Arrow Left": "Seta para esquerda",
"Arrow Right": "Seta para direita",
"Arrow Up": "Seta para cima",
"Arrow Down": "Seta para baixo",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Espaço",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Pressione um botão no controle remoto ou teclado após tocar em \"Definir tecla\".",
"Use Volume Keys": "Usar teclas de volume",
"Custom Page Turner": "Virador de páginas personalizado"
}
@@ -228,7 +228,6 @@
"CJK Font": "Fonte CJK",
"Clear Search": "Limpar Pesquisa",
"Header & Footer": "Cabeçalho e Rodapé",
"Apply also in Scrolled Mode": "Aplicar também no Modo Rolado",
"A new version of Readest is available!": "Uma nova versão do Readest está disponível!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} está disponível (versão instalada {{currentVersion}}).",
"Download and install now?": "Baixar e instalar agora?",
@@ -252,7 +251,6 @@
"Grid": "Grade",
"(from 'As You Like It', Act II)": "(de 'Como Gostais', Ato II)",
"Link Color": "Cor do Link",
"Volume Keys for Page Flip": "Teclas de Volume para Virar Página",
"Screen": "Tela",
"Orientation": "Orientação",
"Portrait": "Retrato",
@@ -1437,5 +1435,27 @@
"Start your library": "Comece a sua biblioteca",
"Pick a book from your device to add it to your library.": "Escolha um livro do seu dispositivo para adicionar à sua biblioteca.",
"Drop a book anywhere on this window, or pick one from your computer.": "Largue um livro em qualquer parte desta janela, ou escolha um do seu computador.",
"Sign in to sync your library": "Inicie sessão para sincronizar a sua biblioteca"
"Sign in to sync your library": "Inicie sessão para sincronizar a sua biblioteca",
"Clear": "Limpar",
"Listening…": "A aguardar…",
"Set key": "Definir tecla",
"Page Turner": "Virador de páginas",
"Media Next": "Multimédia: Seguinte",
"Media Previous": "Multimédia: Anterior",
"Media Play/Pause": "Multimédia: Reproduzir/Pausar",
"Media Fast Forward": "Multimédia: Avançar rapidamente",
"Media Rewind": "Multimédia: Retroceder",
"Volume Up": "Volume+",
"Volume Down": "Volume",
"Arrow Left": "Seta esquerda",
"Arrow Right": "Seta direita",
"Arrow Up": "Seta cima",
"Arrow Down": "Seta baixo",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Espaço",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Pressione um botão no seu controle remoto ou teclado após tocar em \"Definir tecla\".",
"Use Volume Keys": "Usar teclas de volume",
"Custom Page Turner": "Virador de página personalizado"
}
@@ -918,7 +918,6 @@
"Swap Click Sides": "Schimbă părțile de clic",
"Disable Double Tap": "Dezactivează dubla apăsare",
"Disable Double Click": "Dezactivează dublu clic",
"Volume Keys for Page Flip": "Taste de volum pentru schimbarea paginii",
"Show Page Navigation Buttons": "Afișează butoanele de navigare pe pagină",
"Annotation Tools": "Instrumente de adnotare",
"Enable Quick Actions": "Activează acțiunile rapide",
@@ -1006,7 +1005,6 @@
"Maximum Number of Columns": "Număr maxim de coloane",
"Maximum Column Height": "Înălțimea maximă a coloanei",
"Maximum Column Width": "Lățimea maximă a coloanei",
"Apply also in Scrolled Mode": "Aplică și în modul Derulare",
"Header & Footer": "Antet și Subsol",
"Show Header": "Afișează antetul",
"Show Footer": "Afișează subsolul",
@@ -1437,5 +1435,27 @@
"Start your library": "Începe-ți biblioteca",
"Pick a book from your device to add it to your library.": "Alege o carte de pe dispozitiv pentru a o adăuga în biblioteca ta.",
"Drop a book anywhere on this window, or pick one from your computer.": "Lasă o carte oriunde în această fereastră sau alege una de pe computer.",
"Sign in to sync your library": "Conectează-te pentru a-ți sincroniza biblioteca"
"Sign in to sync your library": "Conectează-te pentru a-ți sincroniza biblioteca",
"Clear": "Șterge",
"Listening…": "Ascultă…",
"Set key": "Setează tasta",
"Page Turner": "Dispozitiv de întoarcere pagini",
"Media Next": "Media Următor",
"Media Previous": "Media Anterior",
"Media Play/Pause": "Media Redare/Pauză",
"Media Fast Forward": "Media Derulare înainte",
"Media Rewind": "Media Derulare înapoi",
"Volume Up": "Volum mai mare",
"Volume Down": "Volum mai mic",
"Arrow Left": "Săgeată stânga",
"Arrow Right": "Săgeată dreapta",
"Arrow Up": "Săgeată sus",
"Arrow Down": "Săgeată jos",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Spațiu",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Apăsați un buton pe telecomandă sau tastatură după ce atingeți \"Setează tasta\".",
"Use Volume Keys": "Folosiți tastele de volum",
"Custom Page Turner": "Dispozitiv personalizat de întoarcere pagini"
}
@@ -228,7 +228,6 @@
"CJK Font": "CJK Шрифт",
"Clear Search": "Очистить поиск",
"Header & Footer": "Заголовок и нижний колонтитул",
"Apply also in Scrolled Mode": "Применить также в прокручиваемом режиме",
"A new version of Readest is available!": "Доступна новая версия Readest!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} доступен (установленная версия {{currentVersion}}).",
"Download and install now?": "Скачать и установить сейчас?",
@@ -252,7 +251,6 @@
"Grid": "Сетка",
"(from 'As You Like It', Act II)": "(из 'Как вам это понравится', Акт II)",
"Link Color": "Цвет ссылки",
"Volume Keys for Page Flip": "Клавиши громкости для перелистывания страниц",
"Screen": "Экран",
"Orientation": "Ориентация",
"Portrait": "Портрет",
@@ -1462,5 +1460,27 @@
"Start your library": "Создайте свою библиотеку",
"Pick a book from your device to add it to your library.": "Выберите книгу с устройства, чтобы добавить её в библиотеку.",
"Drop a book anywhere on this window, or pick one from your computer.": "Перетащите книгу в любое место этого окна или выберите её на компьютере.",
"Sign in to sync your library": "Войдите, чтобы синхронизировать библиотеку"
"Sign in to sync your library": "Войдите, чтобы синхронизировать библиотеку",
"Clear": "Очистить",
"Listening…": "Ожидание…",
"Set key": "Назначить клавишу",
"Page Turner": "Листалка страниц",
"Media Next": "Медиа: Следующий",
"Media Previous": "Медиа: Предыдущий",
"Media Play/Pause": "Медиа: Воспроизведение/Пауза",
"Media Fast Forward": "Медиа: Перемотка вперёд",
"Media Rewind": "Медиа: Перемотка назад",
"Volume Up": "Громкость+",
"Volume Down": "Громкость−",
"Arrow Left": "Стрелка влево",
"Arrow Right": "Стрелка вправо",
"Arrow Up": "Стрелка вверх",
"Arrow Down": "Стрелка вниз",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Пробел",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Нажмите кнопку на пульте дистанционного управления или клавиатуре после нажатия \"Назначить клавишу\".",
"Use Volume Keys": "Использовать клавиши громкости",
"Custom Page Turner": "Пользовательское перелистывание страниц"
}
@@ -195,7 +195,6 @@
"Scrolled Mode": "අනුචලන ආකාරය",
"Overlap Pixels": "අතිච්ඡාදන පික්සල්",
"Disable Double Click": "ද්විත්ව ක්ලික් අබල කරන්න",
"Volume Keys for Page Flip": "පිටු පෙරැළීම සඳහා ශබ්ද යතුර",
"Animation": "සජීවිකරණය",
"Paging Animation": "පිටු සජීවිකරණය",
"Security": "ආරක්ෂාව",
@@ -269,7 +268,6 @@
"Reading Progress Style": "කියවීමේ ප්‍රගති ශෛලිය",
"Page Number": "පිටු අංකය",
"Percentage": "ප්‍රතිශතය",
"Apply also in Scrolled Mode": "අනුචලන ආකාරයේදීද අනුගත කරන්න",
"Screen": "තිරය",
"Orientation": "දිශානතිය",
"Portrait": "සිරස්",
@@ -1412,5 +1410,27 @@
"Start your library": "ඔබේ පුස්තකාලය ආරම්භ කරන්න",
"Pick a book from your device to add it to your library.": "ඔබේ පුස්තකාලයට එක් කිරීමට ඔබේ උපාංගයෙන් පොතක් තෝරන්න.",
"Drop a book anywhere on this window, or pick one from your computer.": "මෙම කවුළුවේ ඕනෑම තැනක පොතක් දමන්න, නැතහොත් ඔබේ පරිගණකයෙන් එකක් තෝරන්න.",
"Sign in to sync your library": "ඔබේ පුස්තකාලය සමමුහුර්ත කිරීමට පුරනය වන්න"
"Sign in to sync your library": "ඔබේ පුස්තකාලය සමමුහුර්ත කිරීමට පුරනය වන්න",
"Clear": "හිස් කරන්න",
"Listening…": "සවන් දෙමින්…",
"Set key": "යතුර සකසන්න",
"Page Turner": "පිටු හරවනය",
"Media Next": "මාධ්‍ය ඊළඟ",
"Media Previous": "මාධ්‍ය පෙර",
"Media Play/Pause": "මාධ්‍ය ධාවනය/විරාමය",
"Media Fast Forward": "මාධ්‍ය වේගයෙන් ඉදිරියට",
"Media Rewind": "මාධ්‍ය ආපසු",
"Volume Up": "ශබ්දය වැඩි කරන්න",
"Volume Down": "ශබ්දය අඩු කරන්න",
"Arrow Left": "වම් ඊතලය",
"Arrow Right": "දකුණු ඊතලය",
"Arrow Up": "ඉහළ ඊතලය",
"Arrow Down": "පහළ ඊතලය",
"Page Up": "පිටුව ඉහළට",
"Page Down": "පිටුව පහළට",
"Space": "අවකාශය",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "\"යතුර සකසන්න\" තට්ටු කිරීමෙන් පසු ඔබේ දුරස්ථ පාලකයේ හෝ යතුරු පුවරුවේ බොත්තමක් එබෙන්න.",
"Use Volume Keys": "ශබ්ද කතා යතුරු භාවිතා කරන්න",
"Custom Page Turner": "අභිරුචි පිටු හරවනය"
}
@@ -889,7 +889,6 @@
"Swap Click Sides": "Zamenjaj strani za klike",
"Disable Double Tap": "Onemogoči dvojni tap",
"Disable Double Click": "Onemogoči dvojni klik",
"Volume Keys for Page Flip": "Tipke za glasnost za obračanje strani",
"Show Page Navigation Buttons": "Prikaži gumbe za navigacijo po straneh",
"Annotation Tools": "Orodja za opombe",
"Enable Quick Actions": "Omogoči hitra dejanja",
@@ -978,7 +977,6 @@
"Maximum Number of Columns": "Največje število stolpcev",
"Maximum Column Height": "Največja višina stolpca",
"Maximum Column Width": "Največja širina stolpca",
"Apply also in Scrolled Mode": "Uporabi tudi v načinu pomikanja",
"Header & Footer": "Glava in noga",
"Show Header": "Prikaži glavo",
"Show Footer": "Prikaži nogo",
@@ -1462,5 +1460,27 @@
"Start your library": "Začnite svojo knjižnico",
"Pick a book from your device to add it to your library.": "Izberite knjigo iz svoje naprave, da jo dodate v svojo knjižnico.",
"Drop a book anywhere on this window, or pick one from your computer.": "Spustite knjigo kjer koli v tem oknu ali jo izberite iz svojega računalnika.",
"Sign in to sync your library": "Prijavite se za sinhronizacijo svoje knjižnice"
"Sign in to sync your library": "Prijavite se za sinhronizacijo svoje knjižnice",
"Clear": "Počisti",
"Listening…": "Poslušanje…",
"Set key": "Nastavi tipko",
"Page Turner": "Obračalnik strani",
"Media Next": "Mediji: Naslednje",
"Media Previous": "Mediji: Prejšnje",
"Media Play/Pause": "Mediji: Predvajaj/Premor",
"Media Fast Forward": "Mediji: Hitro naprej",
"Media Rewind": "Mediji: Previj nazaj",
"Volume Up": "Glasnost+",
"Volume Down": "Glasnost",
"Arrow Left": "Puščica levo",
"Arrow Right": "Puščica desno",
"Arrow Up": "Puščica gor",
"Arrow Down": "Puščica dol",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Preslednica",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Pritisnite gumb na daljinskem upravljalniku ali tipkovnici po tapkanju na \"Nastavi tipko\".",
"Use Volume Keys": "Uporabi tipki za glasnost",
"Custom Page Turner": "Tipka za obračanje strani po meri"
}
@@ -441,7 +441,6 @@
"Scroll": "Rullning",
"Overlap Pixels": "Överlappande pixlar",
"Disable Double Click": "Inaktivera dubbelklick",
"Volume Keys for Page Flip": "Volymknappar för sidbyte",
"Animation": "Animation",
"Paging Animation": "Sidanimation",
"Security": "Säkerhet",
@@ -511,7 +510,6 @@
"Reading Progress Style": "Läsframstegsstil",
"Page Number": "Sidnummer",
"Percentage": "Procent",
"Apply also in Scrolled Mode": "Tillämpa även i rullningsläge",
"Screen": "Skärm",
"Orientation": "Orientering",
"Portrait": "Porträtt",
@@ -1412,5 +1410,27 @@
"Start your library": "Starta ditt bibliotek",
"Pick a book from your device to add it to your library.": "Välj en bok från din enhet att lägga till i ditt bibliotek.",
"Drop a book anywhere on this window, or pick one from your computer.": "Släpp en bok var som helst på det här fönstret, eller välj en från din dator.",
"Sign in to sync your library": "Logga in för att synkronisera ditt bibliotek"
"Sign in to sync your library": "Logga in för att synkronisera ditt bibliotek",
"Clear": "Rensa",
"Listening…": "Lyssnar…",
"Set key": "Ange tangent",
"Page Turner": "Sidvändare",
"Media Next": "Media: Nästa",
"Media Previous": "Media: Föregående",
"Media Play/Pause": "Media: Spela/Pausa",
"Media Fast Forward": "Media: Snabbspolning framåt",
"Media Rewind": "Media: Snabbspolning bakåt",
"Volume Up": "Volym upp",
"Volume Down": "Volym ned",
"Arrow Left": "Pil vänster",
"Arrow Right": "Pil höger",
"Arrow Up": "Pil upp",
"Arrow Down": "Pil ned",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Mellanslag",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Tryck på en knapp på din fjärrkontroll eller tangentbord efter att ha tryckt på \"Ange tangent\".",
"Use Volume Keys": "Använd volymknappar",
"Custom Page Turner": "Anpassad sidvändare"
}
@@ -195,7 +195,6 @@
"Scrolled Mode": "உருட்டல் பயன்முறை",
"Overlap Pixels": "ஒன்றுடன் ஒன்று பிக்சல்கள்",
"Disable Double Click": "இரட்டை கிளிக்கை முடக்கவும்",
"Volume Keys for Page Flip": "பக்கம் புரட்டுவதற்கு ஒலி விசைகள்",
"Animation": "அனிமேஷன்",
"Paging Animation": "பக்க அனிமேஷன்",
"Security": "பாதுகாப்பு",
@@ -269,7 +268,6 @@
"Reading Progress Style": "வாசிப்பு முன்னேற்ற பாணி",
"Page Number": "பக்க எண்",
"Percentage": "சதவீதம்",
"Apply also in Scrolled Mode": "உருட்டல் பயன்முறையிலும் பொருந்தும்",
"Screen": "திரை",
"Orientation": "திசைநிலை",
"Portrait": "நிலைமான்",
@@ -1412,5 +1410,27 @@
"Start your library": "உங்கள் நூலகத்தைத் தொடங்குங்கள்",
"Pick a book from your device to add it to your library.": "உங்கள் நூலகத்தில் சேர்க்க உங்கள் சாதனத்திலிருந்து ஒரு புத்தகத்தைத் தேர்ந்தெடுக்கவும்.",
"Drop a book anywhere on this window, or pick one from your computer.": "இந்த சாளரத்தில் எங்கேனும் ஒரு புத்தகத்தை விடவும், அல்லது உங்கள் கணினியிலிருந்து ஒன்றைத் தேர்ந்தெடுக்கவும்.",
"Sign in to sync your library": "உங்கள் நூலகத்தை ஒத்திசைக்க உள்நுழையவும்"
"Sign in to sync your library": "உங்கள் நூலகத்தை ஒத்திசைக்க உள்நுழையவும்",
"Clear": "அழி",
"Listening…": "கேட்கிறது…",
"Set key": "விசை அமை",
"Page Turner": "பக்க திருப்பி",
"Media Next": "மீடியா அடுத்தது",
"Media Previous": "மீடியா முந்தையது",
"Media Play/Pause": "மீடியா இயக்கு/இடைநிறுத்து",
"Media Fast Forward": "மீடியா வேகமாக முன்னேறு",
"Media Rewind": "மீடியா பின்னோக்கி",
"Volume Up": "ஒலி அளவு அதிகரி",
"Volume Down": "ஒலி அளவு குறை",
"Arrow Left": "இடது அம்பு",
"Arrow Right": "வலது அம்பு",
"Arrow Up": "மேல் அம்பு",
"Arrow Down": "கீழ் அம்பு",
"Page Up": "பக்கம் மேலே",
"Page Down": "பக்கம் கீழே",
"Space": "இடைவெளி",
"Enter": "என்டர்",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "\"விசை அமை\" தட்டிய பிறகு உங்கள் ரிமோட் கன்ட்ரோலர் அல்லது கீபோர்டில் ஒரு பட்டனை அழுத்தவும்.",
"Use Volume Keys": "ஒலி அளவு விசைகளைப் பயன்படுத்து",
"Custom Page Turner": "தனிப்பயன் பக்க திருப்பி"
}
@@ -164,7 +164,6 @@
"Scroll": "เลื่อน",
"Scrolled Mode": "โหมดเลื่อน",
"Overlap Pixels": "พิกเซลซ้อน",
"Volume Keys for Page Flip": "ปุ่มเสียงเพื่อพลิกหน้า",
"Animation": "ภาพเคลื่อนไหว",
"Paging Animation": "ภาพเคลื่อนไหวพลิกหน้า",
"Security": "ความปลอดภัย",
@@ -227,7 +226,6 @@
"Show Footer": "แสดงท้ายกระดาษ",
"Show Remaining Time": "แสดงเวลาที่เหลือ",
"Show Remaining Pages": "แสดงหน้าที่เหลือ",
"Apply also in Scrolled Mode": "ใช้ในโหมดเลื่อนด้วย",
"Screen": "หน้าจอ",
"Orientation": "การวางแนว",
"Portrait": "แนวตั้ง",
@@ -1387,5 +1385,27 @@
"Start your library": "เริ่มต้นห้องสมุดของคุณ",
"Pick a book from your device to add it to your library.": "เลือกหนังสือจากอุปกรณ์ของคุณเพื่อเพิ่มลงในห้องสมุดของคุณ",
"Drop a book anywhere on this window, or pick one from your computer.": "วางหนังสือที่ใดก็ได้บนหน้าต่างนี้ หรือเลือกจากคอมพิวเตอร์ของคุณ",
"Sign in to sync your library": "ลงชื่อเข้าใช้เพื่อซิงค์ห้องสมุดของคุณ"
"Sign in to sync your library": "ลงชื่อเข้าใช้เพื่อซิงค์ห้องสมุดของคุณ",
"Clear": "ล้าง",
"Listening…": "กำลังฟัง…",
"Set key": "ตั้งปุ่ม",
"Page Turner": "ตัวพลิกหน้า",
"Media Next": "มีเดียถัดไป",
"Media Previous": "มีเดียก่อนหน้า",
"Media Play/Pause": "มีเดียเล่น/หยุดชั่วคราว",
"Media Fast Forward": "มีเดียเดินหน้าเร็ว",
"Media Rewind": "มีเดียย้อนกลับ",
"Volume Up": "เพิ่มระดับเสียง",
"Volume Down": "ลดระดับเสียง",
"Arrow Left": "ลูกศรซ้าย",
"Arrow Right": "ลูกศรขวา",
"Arrow Up": "ลูกศรขึ้น",
"Arrow Down": "ลูกศรลง",
"Page Up": "หน้าขึ้น",
"Page Down": "หน้าลง",
"Space": "สเปซบาร์",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "กดปุ่มบนรีโมทคอนโทรลหรือคีย์บอร์ดหลังจากแตะ \"ตั้งปุ่ม\"",
"Use Volume Keys": "ใช้ปุ่มระดับเสียง",
"Custom Page Turner": "ปุ่มพลิกหน้าแบบกำหนดเอง"
}
@@ -228,7 +228,6 @@
"CJK Font": "CJK Yazı Tipi",
"Clear Search": "Aramayı Temizle",
"Header & Footer": "Başlık ve Altbilgi",
"Apply also in Scrolled Mode": "Kaydırılmış Modda da Uygula",
"A new version of Readest is available!": "Yeni bir Readest sürümü mevcut!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} mevcut (yüklü sürüm {{currentVersion}}).",
"Download and install now?": "Şimdi indirip yükleyelim mi?",
@@ -252,7 +251,6 @@
"Grid": "Izgara",
"(from 'As You Like It', Act II)": "(As You Like It, 2. Perde)",
"Link Color": "Bağlantı Rengi",
"Volume Keys for Page Flip": "Sayfa Çevirme için Ses Seviyesi Tuşları",
"Screen": "Ekran",
"Orientation": "Yönlendirme",
"Portrait": "Portre",
@@ -1412,5 +1410,27 @@
"Start your library": "Kütüphanenizi başlatın",
"Pick a book from your device to add it to your library.": "Kütüphanenize eklemek için cihazınızdan bir kitap seçin.",
"Drop a book anywhere on this window, or pick one from your computer.": "Bu pencerede herhangi bir yere bir kitap bırakın veya bilgisayarınızdan birini seçin.",
"Sign in to sync your library": "Kütüphanenizi senkronize etmek için oturum açın"
"Sign in to sync your library": "Kütüphanenizi senkronize etmek için oturum açın",
"Clear": "Temizle",
"Listening…": "Dinleniyor…",
"Set key": "Tuş ayarla",
"Page Turner": "Sayfa Çevirici",
"Media Next": "Medya: Sonraki",
"Media Previous": "Medya: Önceki",
"Media Play/Pause": "Medya: Oynat/Duraklat",
"Media Fast Forward": "Medya: Hızlı İleri",
"Media Rewind": "Medya: Geri Sar",
"Volume Up": "Ses Yükselt",
"Volume Down": "Ses Kıs",
"Arrow Left": "Sol Ok",
"Arrow Right": "Sağ Ok",
"Arrow Up": "Yukarı Ok",
"Arrow Down": "Aşağı Ok",
"Page Up": "Page Up",
"Page Down": "Page Down",
"Space": "Boşluk",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "\"Tuş ayarla\" düğmesine dokunduktan sonra uzaktan kumandanızdaki veya klavyenizdeki bir düğmeye basın.",
"Use Volume Keys": "Ses Tuşlarını Kullan",
"Custom Page Turner": "Özel Sayfa Çevirici"
}
@@ -228,7 +228,6 @@
"CJK Font": "CJK шрифт",
"Clear Search": "Очистити пошук",
"Header & Footer": "Заголовок і нижній колонтитул",
"Apply also in Scrolled Mode": "Застосувати також у прокрученому режимі",
"A new version of Readest is available!": "Доступна нова версія Readest!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Нова версія Readest {{newVersion}} доступна. (Поточна {{currentVersion}}).",
"Download and install now?": "Завантажити та встановити зараз?",
@@ -252,7 +251,6 @@
"Grid": "Сітка",
"(from 'As You Like It', Act II)": "(із 'Як вам це подобається', Дія II)",
"Link Color": "Колір посилання",
"Volume Keys for Page Flip": "Перегортати сторінки клавішами гучності",
"Screen": "Екран",
"Orientation": "Орієнтація",
"Portrait": "Портрет",
@@ -1462,5 +1460,27 @@
"Start your library": "Створіть свою бібліотеку",
"Pick a book from your device to add it to your library.": "Виберіть книжку з вашого пристрою, щоб додати її до бібліотеки.",
"Drop a book anywhere on this window, or pick one from your computer.": "Перетягніть книжку будь-куди у це вікно або виберіть її з комп’ютера.",
"Sign in to sync your library": "Увійдіть, щоб синхронізувати бібліотеку"
"Sign in to sync your library": "Увійдіть, щоб синхронізувати бібліотеку",
"Clear": "Очистити",
"Listening…": "Очікування…",
"Set key": "Призначити клавішу",
"Page Turner": "Перегортач сторінок",
"Media Next": "Медіа: Наступний",
"Media Previous": "Медіа: Попередній",
"Media Play/Pause": "Медіа: Відтворення/Пауза",
"Media Fast Forward": "Медіа: Перемотування вперед",
"Media Rewind": "Медіа: Перемотування назад",
"Volume Up": "Гучність+",
"Volume Down": "Гучність−",
"Arrow Left": "Стрілка ліворуч",
"Arrow Right": "Стрілка праворуч",
"Arrow Up": "Стрілка вгору",
"Arrow Down": "Стрілка вниз",
"Page Up": "Сторінка вгору",
"Page Down": "Сторінка вниз",
"Space": "Пробіл",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Натисніть кнопку на пульті або клавіатурі після натискання \"Призначити клавішу\".",
"Use Volume Keys": "Використовувати клавіші гучності",
"Custom Page Turner": "Власне перегортання сторінок"
}
@@ -1026,7 +1026,6 @@
"Swap Click Sides": "Bosish tomonlarini almashtirish",
"Disable Double Tap": "Ikki marta bosishni oʻchirish",
"Disable Double Click": "Ikki marta bosishni oʻchirish",
"Volume Keys for Page Flip": "Sahifani aylantirish uchun ovoz tugmalari",
"Show Page Navigation Buttons": "Sahifalararo navigatsiya tugmalarini koʻrsatish",
"Annotation Tools": "Izoh vositalari",
"Enable Quick Actions": "Tezkor amallarni yoqish",
@@ -1146,7 +1145,6 @@
"Maximum Number of Columns": "Maksimal ustunlar soni",
"Maximum Column Height": "Maksimal ustun balandligi",
"Maximum Column Width": "Maksimal ustun kengligi",
"Apply also in Scrolled Mode": "Aylantirish rejimida ham qoʻllash",
"Header & Footer": "Yuqori va pastki kolontitul",
"Show Header": "Yuqori kolontitulni koʻrsatish",
"Show Footer": "Pastki kolontitulni koʻrsatish",
@@ -1412,5 +1410,27 @@
"Start your library": "Kutubxonangizni boshlang",
"Pick a book from your device to add it to your library.": "Kutubxonangizga qoshish uchun qurilmangizdan kitob tanlang.",
"Drop a book anywhere on this window, or pick one from your computer.": "Ushbu oynaning istalgan joyiga kitob tashlang yoki kompyuteringizdan birini tanlang.",
"Sign in to sync your library": "Kutubxonangizni sinxronlash uchun tizimga kiring"
"Sign in to sync your library": "Kutubxonangizni sinxronlash uchun tizimga kiring",
"Clear": "Tozalash",
"Listening…": "Tinglanmoqda…",
"Set key": "Tugmani o'rnatish",
"Page Turner": "Sahifa buruvchi",
"Media Next": "Media keyingi",
"Media Previous": "Media oldingi",
"Media Play/Pause": "Media ijro/to'xtatish",
"Media Fast Forward": "Media tez oldinga",
"Media Rewind": "Media orqaga",
"Volume Up": "Ovoz balandligi oshirish",
"Volume Down": "Ovoz balandligi kamaytirish",
"Arrow Left": "Chap o'q",
"Arrow Right": "O'ng o'q",
"Arrow Up": "Yuqori o'q",
"Arrow Down": "Pastki o'q",
"Page Up": "Sahifa yuqori",
"Page Down": "Sahifa pastga",
"Space": "Bo'sh joy",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "\"Tugmani o'rnatish\" tugmasini bosgandan so'ng masofadan boshqarish pultingiz yoki klaviaturangizdagi tugmani bosing.",
"Use Volume Keys": "Ovoz balandligi tugmalarini ishlatish",
"Custom Page Turner": "Maxsus sahifa buruvchi"
}
@@ -228,7 +228,6 @@
"CJK Font": "Phông chữ CJK",
"Clear Search": "Xóa tìm kiếm",
"Header & Footer": "Đầu trang & Chân trang",
"Apply also in Scrolled Mode": "Áp dụng cũng trong chế độ cuộn",
"A new version of Readest is available!": "Đã có phiên bản mới của Readest!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} đã có sẵn (phiên bản đã cài đặt {{currentVersion}}).",
"Download and install now?": "Tải xuống và cài đặt ngay bây giờ?",
@@ -252,7 +251,6 @@
"Grid": "Lưới",
"(from 'As You Like It', Act II)": "(từ 'Như bạn thích', Hành động II)",
"Link Color": "Màu liên kết",
"Volume Keys for Page Flip": "Phím âm lượng để lật trang",
"Screen": "Màn hình",
"Orientation": "Định hướng",
"Portrait": "Chân dung",
@@ -1387,5 +1385,27 @@
"Start your library": "Bắt đầu thư viện của bạn",
"Pick a book from your device to add it to your library.": "Chọn một cuốn sách từ thiết bị để thêm vào thư viện của bạn.",
"Drop a book anywhere on this window, or pick one from your computer.": "Thả một cuốn sách vào bất kỳ đâu trên cửa sổ này, hoặc chọn một cuốn từ máy tính của bạn.",
"Sign in to sync your library": "Đăng nhập để đồng bộ thư viện của bạn"
"Sign in to sync your library": "Đăng nhập để đồng bộ thư viện của bạn",
"Clear": "Xóa",
"Listening…": "Đang lắng nghe…",
"Set key": "Đặt phím",
"Page Turner": "Bộ Lật Trang",
"Media Next": "Media Tiếp Theo",
"Media Previous": "Media Trước",
"Media Play/Pause": "Media Phát/Tạm Dừng",
"Media Fast Forward": "Media Tua Nhanh",
"Media Rewind": "Media Tua Lại",
"Volume Up": "Tăng Âm Lượng",
"Volume Down": "Giảm Âm Lượng",
"Arrow Left": "Mũi Tên Trái",
"Arrow Right": "Mũi Tên Phải",
"Arrow Up": "Mũi Tên Lên",
"Arrow Down": "Mũi Tên Xuống",
"Page Up": "Trang Trên",
"Page Down": "Trang Dưới",
"Space": "Dấu Cách",
"Enter": "Enter",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "Nhấn một nút trên bộ điều khiển từ xa hoặc bàn phím sau khi chạm \"Đặt phím\".",
"Use Volume Keys": "Dùng Phím Âm Lượng",
"Custom Page Turner": "Nút Lật Trang Tùy Chỉnh"
}
@@ -229,7 +229,6 @@
"CJK Font": "中文字体",
"Clear Search": "清除搜索",
"Header & Footer": "页眉页脚",
"Apply also in Scrolled Mode": "应用到滚动模式",
"A new version of Readest is available!": "有新版本的 Readest 可更新!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} 可更新(已安装版本 {{currentVersion}})。",
"Download and install now?": "立即下载并安装?",
@@ -253,7 +252,6 @@
"Grid": "网格",
"(from 'As You Like It', Act II)": "出自《皆大欢喜》,第二幕",
"Link Color": "链接颜色",
"Volume Keys for Page Flip": "音量键翻页",
"Screen": "屏幕",
"Orientation": "屏幕方向",
"Portrait": "竖屏",
@@ -1387,5 +1385,27 @@
"Start your library": "开启你的书库",
"Pick a book from your device to add it to your library.": "从设备中挑一本书加入书库吧。",
"Drop a book anywhere on this window, or pick one from your computer.": "把书拖到窗口任意位置,或从电脑中挑选一本。",
"Sign in to sync your library": "登录以同步书库"
"Sign in to sync your library": "登录以同步书库",
"Clear": "清除",
"Listening…": "监听中…",
"Set key": "设置按键",
"Page Turner": "翻页器",
"Media Next": "媒体下一首",
"Media Previous": "媒体上一首",
"Media Play/Pause": "媒体播放/暂停",
"Media Fast Forward": "媒体快进",
"Media Rewind": "媒体快退",
"Volume Up": "音量增大",
"Volume Down": "音量减小",
"Arrow Left": "左箭头",
"Arrow Right": "右箭头",
"Arrow Up": "上箭头",
"Arrow Down": "下箭头",
"Page Up": "向上翻页",
"Page Down": "向下翻页",
"Space": "空格键",
"Enter": "回车键",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "点击「设置按键」后,按下遥控器或键盘上的按钮。",
"Use Volume Keys": "使用音量键",
"Custom Page Turner": "自定义翻页器"
}
@@ -228,7 +228,6 @@
"CJK Font": "中文字體",
"Clear Search": "清除搜索",
"Header & Footer": "頁眉頁腳",
"Apply also in Scrolled Mode": "滾動模式同樣適用",
"A new version of Readest is available!": "有新版本的 Readest 可更新!",
"Readest {{newVersion}} is available (installed version {{currentVersion}}).": "Readest {{newVersion}} 可更新(已安裝版本 {{currentVersion}})。",
"Download and install now?": "立即下載並安裝?",
@@ -252,7 +251,6 @@
"Grid": "網格",
"(from 'As You Like It', Act II)": "(出自《皆大歡喜》,第二幕)",
"Link Color": "連結顏色",
"Volume Keys for Page Flip": "音量鍵翻頁",
"Screen": "螢幕",
"Orientation": "螢幕方向",
"Portrait": "豎屏",
@@ -1387,5 +1385,27 @@
"Start your library": "開啟你的書庫",
"Pick a book from your device to add it to your library.": "從裝置中挑一本書加入書庫吧。",
"Drop a book anywhere on this window, or pick one from your computer.": "把書拖到視窗任一處,或從電腦中挑選一本。",
"Sign in to sync your library": "登入以同步書庫"
"Sign in to sync your library": "登入以同步書庫",
"Clear": "清除",
"Listening…": "監聽中…",
"Set key": "設定按鍵",
"Page Turner": "翻頁器",
"Media Next": "媒體下一首",
"Media Previous": "媒體上一首",
"Media Play/Pause": "媒體播放/暫停",
"Media Fast Forward": "媒體快進",
"Media Rewind": "媒體快退",
"Volume Up": "音量增大",
"Volume Down": "音量減小",
"Arrow Left": "左箭頭",
"Arrow Right": "右箭頭",
"Arrow Up": "上箭頭",
"Arrow Down": "下箭頭",
"Page Up": "向上翻頁",
"Page Down": "向下翻頁",
"Space": "空白鍵",
"Enter": "Enter 鍵",
"Press a button on your remote controller or keyboard after tapping \"Set key\".": "點擊「設定按鍵」後,按下遙控器或鍵盤上的按鈕。",
"Use Volume Keys": "使用音量鍵",
"Custom Page Turner": "自訂翻頁器"
}
@@ -27,6 +27,8 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
private var wv: WebView? = null
private var interceptVolumeKeysEnabled = false
private var interceptBackKeyEnabled = false
private var interceptPageTurnerKeysEnabled = false
private var keyLearnModeEnabled = false
override fun onWebViewCreate(webView: WebView) {
wv = webView
@@ -38,6 +40,24 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
KeyEvent.KEYCODE_VOLUME_UP to "VolumeUp"
)
private val mediaKeyMap = mapOf(
KeyEvent.KEYCODE_MEDIA_NEXT to "MediaNext",
KeyEvent.KEYCODE_MEDIA_PREVIOUS to "MediaPrevious",
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE to "MediaPlayPause",
KeyEvent.KEYCODE_MEDIA_FAST_FORWARD to "MediaFastForward",
KeyEvent.KEYCODE_MEDIA_REWIND to "MediaRewind"
)
private fun keyNameFor(keyCode: Int): String =
keyEventMap[keyCode] ?: mediaKeyMap[keyCode] ?: "Keycode$keyCode"
private fun forwardKeyToWebView(keyName: String, keyCode: Int) {
wv?.evaluateJavascript(
"""try { window.onNativeKeyDown("$keyName", $keyCode); } catch (_) {}""",
null
)
}
override fun interceptVolumeKeys(enabled: Boolean) {
Log.d("MainActivity", "Intercept volume keys: $enabled")
interceptVolumeKeysEnabled = enabled
@@ -48,6 +68,16 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
interceptBackKeyEnabled = enabled
}
override fun interceptPageTurnerKeys(enabled: Boolean) {
Log.d("MainActivity", "Intercept page turner keys: $enabled")
interceptPageTurnerKeysEnabled = enabled
}
override fun setKeyLearnMode(enabled: Boolean) {
Log.d("MainActivity", "Key learn mode: $enabled")
keyLearnModeEnabled = enabled
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
val action = when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> "touchstart"
@@ -93,22 +123,31 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_DOWN) {
val keyCode = event.keyCode
val keyName = keyEventMap[keyCode]
// Learn mode: forward and consume every key so the settings UI
// can capture whatever the remote sends.
if (keyLearnModeEnabled && keyCode != KeyEvent.KEYCODE_BACK) {
forwardKeyToWebView(keyNameFor(keyCode), keyCode)
return true
}
// Hardware page turner: intercept media keys when enabled.
if (interceptPageTurnerKeysEnabled && mediaKeyMap.containsKey(keyCode)) {
forwardKeyToWebView(mediaKeyMap[keyCode]!!, keyCode)
return true
}
val keyName = keyEventMap[keyCode]
if (keyName != null) {
val shouldIntercept = when (keyCode) {
KeyEvent.KEYCODE_BACK -> interceptBackKeyEnabled
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> interceptVolumeKeysEnabled
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN ->
interceptVolumeKeysEnabled
else -> false
}
if (shouldIntercept) {
wv?.evaluateJavascript(
"""
try { window.onNativeKeyDown("$keyName", $keyCode); } catch (_) {}
""".trimIndent(),
null
)
forwardKeyToWebView(keyName, keyCode)
return true
}
}
@@ -66,6 +66,8 @@ class SetSystemUIVisibilityRequestArgs {
class InterceptKeysRequestArgs {
var volumeKeys: Boolean? = null
var backKey: Boolean? = null
var pageTurnerKeys: Boolean? = null
var learnMode: Boolean? = null
}
@InvokeArg
@@ -115,6 +117,8 @@ data class PurchaseData(
interface KeyDownInterceptor {
fun interceptVolumeKeys(enabled: Boolean)
fun interceptBackKey(enabled: Boolean)
fun interceptPageTurnerKeys(enabled: Boolean)
fun setKeyLearnMode(enabled: Boolean)
}
@TauriPlugin(
@@ -401,16 +405,11 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
fun intercept_keys(invoke: Invoke) {
val args = invoke.parseArgs(InterceptKeysRequestArgs::class.java)
if (activity is KeyDownInterceptor) {
when (args.backKey) {
true -> (activity as KeyDownInterceptor).interceptBackKey(true)
false -> (activity as KeyDownInterceptor).interceptBackKey(false)
else -> {}
}
when (args.volumeKeys) {
true -> (activity as KeyDownInterceptor).interceptVolumeKeys(true)
false -> (activity as KeyDownInterceptor).interceptVolumeKeys(false)
else -> {}
}
val interceptor = activity as KeyDownInterceptor
args.backKey?.let { interceptor.interceptBackKey(it) }
args.volumeKeys?.let { interceptor.interceptVolumeKeys(it) }
args.pageTurnerKeys?.let { interceptor.interceptPageTurnerKeys(it) }
args.learnMode?.let { interceptor.setKeyLearnMode(it) }
} else {
Log.e("NativeBridgePlugin", "Activity does not implement KeyDownInterceptor")
}
@@ -43,6 +43,8 @@ class SetSystemUIVisibilityRequestArgs: Decodable {
class InterceptKeysRequestArgs: Decodable {
let backKey: Bool?
let volumeKeys: Bool?
let pageTurnerKeys: Bool?
let learnMode: Bool?
}
class LockScreenOrientationRequestArgs: Decodable {
@@ -192,6 +194,44 @@ class VolumeKeyHandler: NSObject {
}
}
class MediaKeyHandler {
private weak var webView: WKWebView?
private var registered = false
private let commandCenter = MPRemoteCommandCenter.shared()
func start(webView: WKWebView) {
self.webView = webView
if registered { return }
registered = true
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.previousTrackCommand.isEnabled = true
commandCenter.nextTrackCommand.addTarget { [weak self] _ in
self?.forward("MediaNext")
return .success
}
commandCenter.previousTrackCommand.addTarget { [weak self] _ in
self?.forward("MediaPrevious")
return .success
}
logger.log("MediaKeyHandler: started")
}
func stop() {
if !registered { return }
registered = false
commandCenter.nextTrackCommand.removeTarget(nil)
commandCenter.previousTrackCommand.removeTarget(nil)
logger.log("MediaKeyHandler: stopped")
}
private func forward(_ name: String) {
DispatchQueue.main.async { [weak self] in
self?.webView?.evaluateJavaScript(
"try { window.onNativeKeyDown('\(name)', 0); } catch (_) {}", completionHandler: nil)
}
}
}
class WebViewLifecycleManager: NSObject {
private weak var webView: WKWebView?
private var originalNavigationDelegate: WKNavigationDelegate?
@@ -503,6 +543,8 @@ class NativeBridgePlugin: Plugin {
private struct AssociatedKeys {
static var volumeKeyHandler = "volumeKeyHandler"
static var interceptingVolumeKeys = "interceptingVolumeKeys"
static var mediaKeyHandler = "mediaKeyHandler"
static var mediaKeyState = "mediaKeyState"
}
private var volumeKeyHandler: VolumeKeyHandler? {
@@ -526,6 +568,44 @@ class NativeBridgePlugin: Plugin {
}
}
private var mediaKeyHandler: MediaKeyHandler? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.mediaKeyHandler) as? MediaKeyHandler
}
set {
objc_setAssociatedObject(
self, &AssociatedKeys.mediaKeyHandler, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
// Bit 0 = pageTurnerKeys interception, bit 1 = learn mode.
private var mediaKeyState: Int {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.mediaKeyState) as? Int ?? 0
}
set {
objc_setAssociatedObject(
self, &AssociatedKeys.mediaKeyState, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
private func updateMediaKeyHandler() {
let shouldRun = mediaKeyState != 0
if shouldRun {
if mediaKeyHandler == nil {
mediaKeyHandler = MediaKeyHandler()
}
if let webView = self.webView {
mediaKeyHandler?.start(webView: webView)
} else {
logger.warning("Cannot start media key handler: webView is nil")
}
} else {
mediaKeyHandler?.stop()
mediaKeyHandler = nil
}
}
@objc public func use_background_audio(_ invoke: Invoke) {
do {
let args = try invoke.parseArgs(UseBackgroundAudioRequestArgs.self)
@@ -633,6 +713,19 @@ class NativeBridgePlugin: Plugin {
}
}
}
if let pageTurnerKeys = args.pageTurnerKeys {
mediaKeyState = pageTurnerKeys ? (mediaKeyState | 1) : (mediaKeyState & ~1)
}
if let learnMode = args.learnMode {
mediaKeyState = learnMode ? (mediaKeyState | 2) : (mediaKeyState & ~2)
}
if args.pageTurnerKeys != nil || args.learnMode != nil {
DispatchQueue.main.async { [weak self] in
self?.updateMediaKeyHandler()
}
}
invoke.resolve()
} catch {
invoke.reject(error.localizedDescription)
@@ -293,6 +293,16 @@ describe('services/constants', () => {
expect(Array.isArray(DEFAULT_SYSTEM_SETTINGS.lastOpenBooks)).toBe(true);
expect(DEFAULT_SYSTEM_SETTINGS.lastOpenBooks!.length).toBe(0);
});
it('has a disabled hardwarePageTurner with empty bindings', () => {
const hw = DEFAULT_SYSTEM_SETTINGS.hardwarePageTurner!;
expect(hw).toBeDefined();
expect(hw.enabled).toBe(false);
expect(hw.bindings.pagePrev).toBeNull();
expect(hw.bindings.pageNext).toBeNull();
expect(hw.bindings.sectionPrev).toBeNull();
expect(hw.bindings.sectionNext).toBeNull();
});
});
describe('DEFAULT_MOBILE_SYSTEM_SETTINGS', () => {
@@ -22,6 +22,8 @@ beforeEach(() => {
backKeyIntercepted: false,
volumeKeysInterceptionCount: 0,
backKeyInterceptionCount: 0,
pageTurnerKeysIntercepted: false,
pageTurnerKeysInterceptionCount: 0,
});
vi.clearAllMocks();
// Clean up window handlers
@@ -224,4 +226,71 @@ describe('deviceStore', () => {
expect(state.backKeyInterceptionCount).toBe(0);
});
});
// ── Page turner key interception ───────────────────────────────
describe('page turner key interception', () => {
test('acquire sets state and intercepts page turner keys', async () => {
const { interceptKeys } = await import('@/utils/bridge');
useDeviceControlStore.getState().acquirePageTurnerKeyInterception();
expect(useDeviceControlStore.getState().pageTurnerKeysIntercepted).toBe(true);
expect(useDeviceControlStore.getState().pageTurnerKeysInterceptionCount).toBe(1);
expect(interceptKeys).toHaveBeenCalledWith({ pageTurnerKeys: true });
});
test('release after single acquire stops interception', async () => {
const { interceptKeys } = await import('@/utils/bridge');
useDeviceControlStore.getState().acquirePageTurnerKeyInterception();
useDeviceControlStore.getState().releasePageTurnerKeyInterception();
expect(useDeviceControlStore.getState().pageTurnerKeysIntercepted).toBe(false);
expect(useDeviceControlStore.getState().pageTurnerKeysInterceptionCount).toBe(0);
expect(interceptKeys).toHaveBeenCalledWith({ pageTurnerKeys: false });
});
test('second acquire increments count without re-intercepting', async () => {
const { interceptKeys } = await import('@/utils/bridge');
useDeviceControlStore.getState().acquirePageTurnerKeyInterception();
useDeviceControlStore.getState().acquirePageTurnerKeyInterception();
expect(useDeviceControlStore.getState().pageTurnerKeysInterceptionCount).toBe(2);
expect(interceptKeys).toHaveBeenCalledTimes(1);
});
test('setKeyLearnMode toggles learn mode through the bridge', async () => {
const { interceptKeys } = await import('@/utils/bridge');
useDeviceControlStore.getState().setKeyLearnMode(true);
expect(interceptKeys).toHaveBeenCalledWith({ learnMode: true });
useDeviceControlStore.getState().setKeyLearnMode(false);
expect(interceptKeys).toHaveBeenCalledWith({ learnMode: false });
});
});
// ── Native key forwarding ──────────────────────────────────────
describe('handleNativeKeyDown forwarding', () => {
test('forwards a media key through the native-key-down event', async () => {
const { eventDispatcher } = await import('@/utils/event');
useDeviceControlStore.getState().acquirePageTurnerKeyInterception();
window.onNativeKeyDown?.('MediaNext', 87);
expect(eventDispatcher.dispatch).toHaveBeenCalledWith('native-key-down', {
keyName: 'MediaNext',
keyCode: 87,
});
});
test('forwards the Back key synchronously', async () => {
const { eventDispatcher } = await import('@/utils/event');
useDeviceControlStore.getState().acquirePageTurnerKeyInterception();
window.onNativeKeyDown?.('Back', 4);
expect(eventDispatcher.dispatchSync).toHaveBeenCalledWith('native-key-down', {
keyName: 'Back',
keyCode: 4,
});
});
});
});
@@ -0,0 +1,103 @@
import { describe, test, expect } from 'vitest';
import {
normalizeNativeKey,
normalizeDomKeyEvent,
matchesBinding,
resolvePageTurn,
} from '@/utils/keybinding';
import { HardwarePageTurnerSettings } from '@/types/settings';
describe('normalizeNativeKey', () => {
test('maps a known native key to a friendly label', () => {
expect(normalizeNativeKey('MediaNext')).toEqual({
source: 'native',
id: 'MediaNext',
label: 'Media Next',
});
});
test('falls back to the raw id for an unknown native key', () => {
expect(normalizeNativeKey('Keycode99')).toEqual({
source: 'native',
id: 'Keycode99',
label: 'Keycode99',
});
});
});
describe('normalizeDomKeyEvent', () => {
test('uses event.code and a friendly label for a known key', () => {
const event = { code: 'ArrowLeft', key: 'ArrowLeft' } as KeyboardEvent;
expect(normalizeDomKeyEvent(event)).toEqual({
source: 'dom',
id: 'ArrowLeft',
label: 'Arrow Left',
});
});
test('falls back to event.key when event.code is empty', () => {
const event = { code: '', key: 'MediaTrackNext' } as KeyboardEvent;
expect(normalizeDomKeyEvent(event)).toEqual({
source: 'dom',
id: 'MediaTrackNext',
label: 'Media Next',
});
});
});
describe('matchesBinding', () => {
const binding = { source: 'native' as const, id: 'MediaNext', label: 'Media Next' };
test('matches same source and id', () => {
expect(matchesBinding(binding, { source: 'native', id: 'MediaNext' })).toBe(true);
});
test('rejects different id', () => {
expect(matchesBinding(binding, { source: 'native', id: 'MediaPrevious' })).toBe(false);
});
test('rejects different source', () => {
expect(matchesBinding(binding, { source: 'dom', id: 'MediaNext' })).toBe(false);
});
test('rejects a null binding', () => {
expect(matchesBinding(null, { source: 'native', id: 'MediaNext' })).toBe(false);
});
});
describe('resolvePageTurn', () => {
const settings: HardwarePageTurnerSettings = {
enabled: true,
bindings: {
pagePrev: { source: 'native', id: 'MediaPrevious', label: 'Media Previous' },
pageNext: { source: 'native', id: 'MediaNext', label: 'Media Next' },
sectionPrev: { source: 'dom', id: 'PageUp', label: 'Page Up' },
sectionNext: { source: 'dom', id: 'PageDown', label: 'Page Down' },
},
};
test('returns "pagePrev" for the pagePrev binding', () => {
expect(resolvePageTurn(settings, { source: 'native', id: 'MediaPrevious' })).toBe('pagePrev');
});
test('returns "pageNext" for the pageNext binding', () => {
expect(resolvePageTurn(settings, { source: 'native', id: 'MediaNext' })).toBe('pageNext');
});
test('returns "sectionPrev" for the sectionPrev binding', () => {
expect(resolvePageTurn(settings, { source: 'dom', id: 'PageUp' })).toBe('sectionPrev');
});
test('returns "sectionNext" for the sectionNext binding', () => {
expect(resolvePageTurn(settings, { source: 'dom', id: 'PageDown' })).toBe('sectionNext');
});
test('returns null for an unbound key', () => {
expect(resolvePageTurn(settings, { source: 'native', id: 'MediaPlayPause' })).toBeNull();
});
test('returns null when the feature is disabled', () => {
const disabled = { ...settings, enabled: false };
expect(resolvePageTurn(disabled, { source: 'native', id: 'MediaNext' })).toBeNull();
});
});
@@ -5,7 +5,10 @@ import { ViewSettings } from '@/types/book';
import { useReaderStore } from '@/store/readerStore';
import { useBookDataStore } from '@/store/bookDataStore';
import { useDeviceControlStore } from '@/store/deviceStore';
import { useSettingsStore } from '@/store/settingsStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { eventDispatcher } from '@/utils/event';
import { resolvePageTurn, normalizeDomKeyEvent, KeyCandidate } from '@/utils/keybinding';
import { isTauriAppPlatform } from '@/services/environment';
import { tauriGetWindowLogicalPosition } from '@/utils/window';
import { getReadingRulerMoveDirection } from '../utils/readingRuler';
@@ -109,7 +112,15 @@ export const usePagination = (
const { getBookData } = useBookDataStore();
const { getViewSettings, getViewState } = useReaderStore();
const { hoveredBookKey, setHoveredBookKey } = useReaderStore();
const { acquireVolumeKeyInterception, releaseVolumeKeyInterception } = useDeviceControlStore();
const {
acquireVolumeKeyInterception,
releaseVolumeKeyInterception,
acquirePageTurnerKeyInterception,
releasePageTurnerKeyInterception,
} = useDeviceControlStore();
// Reactive subscription: drives the effect dependency array below. The
// handlers themselves re-read via getState() to avoid stale closures.
const hardwarePageTurner = useSettingsStore((s) => s.settings.hardwarePageTurner);
const handlePageFlip = async (
msg: MessageEvent | CustomEvent | React.MouseEvent<HTMLDivElement, MouseEvent>,
@@ -247,6 +258,76 @@ export const usePagination = (
}
};
// Hardware page turner: media keys arrive via the `native-key-down`
// event; D-pad / keyboard keys arrive either as a top-window `keydown`
// or — when focus is inside a book iframe — as an `iframe-keydown`
// postMessage (mirroring useShortcuts' unified window + iframe handling).
// All resolve through the shared binding registry. Suppressed while the
// toolbar is visible so D-pad keys keep driving toolbar spatial navigation.
const handleHardwarePageTurn = (candidate: KeyCandidate): boolean => {
const settings = useSettingsStore.getState().settings.hardwarePageTurner;
if (!settings?.enabled) return false;
if (useReaderStore.getState().hoveredBookKey) return false;
// Only the active book (the one driving the sidebar) responds, so a
// single key press doesn't flip every book open in a parallel view.
if (useSidebarStore.getState().sideBarBookKey !== bookKey) return false;
const viewState = getViewState(bookKey);
if (!viewState?.inited) return false;
const action = resolvePageTurn(settings, candidate);
if (!action) return false;
const viewSettings = getViewSettings(bookKey);
const side = action === 'pagePrev' || action === 'sectionPrev' ? 'up' : 'down';
const mode = action === 'sectionPrev' || action === 'sectionNext' ? 'section' : 'page';
setHoveredBookKey('');
if (
mode === 'page' &&
viewSettings?.readingRulerEnabled &&
eventDispatcher.dispatchSync('reading-ruler-move', {
bookKey,
direction: getReadingRulerMoveDirection(side, viewRef.current?.book.dir),
})
) {
return true;
}
viewPagination(viewRef.current, viewSettings, side, mode);
return true;
};
const handleHardwareNativeKey = (msg: CustomEvent) => {
const keyName = msg.detail?.keyName;
if (typeof keyName !== 'string') return;
handleHardwarePageTurn({ source: 'native', id: keyName });
};
const handleHardwareDomKey = (event: KeyboardEvent | MessageEvent) => {
let candidate: KeyCandidate;
if (event instanceof KeyboardEvent) {
if (event.repeat) return;
const target = event.target as HTMLElement | null;
if (target?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target?.tagName ?? '')) {
return;
}
candidate = normalizeDomKeyEvent(event);
} else if (event.data?.type === 'iframe-keydown' && event.data.bookKey === bookKey) {
const id = event.data.code || event.data.key;
if (typeof id !== 'string' || !id) return;
candidate = { source: 'dom', id };
} else {
return;
}
if (handleHardwarePageTurn(candidate)) {
// Stop `useShortcuts` from also paging on this key — capture-phase
// for the window keydown, registration order for the iframe message.
if (event instanceof KeyboardEvent) event.preventDefault();
event.stopImmediatePropagation();
}
};
useEffect(() => {
if (!appService?.isMobileApp) return;
@@ -265,6 +346,45 @@ export const usePagination = (
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Hardware page turner: native-key + DOM-key listeners and native
// media-key interception, re-evaluated whenever the setting changes.
useEffect(() => {
const hasNativeBinding =
hardwarePageTurner?.bindings.pagePrev?.source === 'native' ||
hardwarePageTurner?.bindings.pageNext?.source === 'native' ||
hardwarePageTurner?.bindings.sectionPrev?.source === 'native' ||
hardwarePageTurner?.bindings.sectionNext?.source === 'native';
const needsNativeInterception =
!!appService?.isMobileApp && !!hardwarePageTurner?.enabled && hasNativeBinding;
if (needsNativeInterception) {
acquirePageTurnerKeyInterception();
}
if (hasNativeBinding) {
eventDispatcher.on('native-key-down', handleHardwareNativeKey);
}
window.addEventListener('keydown', handleHardwareDomKey, true);
window.addEventListener('message', handleHardwareDomKey);
return () => {
if (needsNativeInterception) {
releasePageTurnerKeyInterception();
}
if (hasNativeBinding) {
eventDispatcher.off('native-key-down', handleHardwareNativeKey);
}
window.removeEventListener('keydown', handleHardwareDomKey, true);
window.removeEventListener('message', handleHardwareDomKey);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
hardwarePageTurner?.enabled,
hardwarePageTurner?.bindings.pagePrev?.source,
hardwarePageTurner?.bindings.pageNext?.source,
hardwarePageTurner?.bindings.sectionPrev?.source,
hardwarePageTurner?.bindings.sectionNext?.source,
]);
// Touch swipe page flip for fixed-layout books — registered as a touch interceptor
// so it participates in the priority-based consumption chain.
useTouchInterceptor(
@@ -1,7 +1,6 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useEnv } from '@/context/EnvContext';
import { useReaderStore } from '@/store/readerStore';
import { useDeviceControlStore } from '@/store/deviceStore';
import { useTranslation } from '@/hooks/useTranslation';
import { useBookDataStore } from '@/store/bookDataStore';
import { useSettingsStore } from '@/store/settingsStore';
@@ -14,6 +13,7 @@ import { SettingsPanelPanelProp } from './SettingsDialog';
import { annotationToolQuickActions } from '@/app/reader/components/annotator/AnnotationTools';
import { BoxedList, SettingsRow, SettingsSelect, SettingsSwitchRow } from './primitives';
import NumberInput from './NumberInput';
import PageTurnerSettings from './PageTurnerSettings';
const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
const _ = useTranslation();
@@ -22,7 +22,6 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
const { getBookData } = useBookDataStore();
const { settings } = useSettingsStore();
const { applyEinkMode } = useEinkMode();
const { acquireVolumeKeyInterception, releaseVolumeKeyInterception } = useDeviceControlStore();
const bookData = getBookData(bookKey);
const viewSettings = getViewSettings(bookKey) || settings.globalViewSettings;
@@ -30,7 +29,6 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
const [noContinuousScroll, setNoContinuousScroll] = useState(viewSettings.noContinuousScroll);
const [scrollingOverlap, setScrollingOverlap] = useState(viewSettings.scrollingOverlap);
const [hideScrollbar, setHideScrollbar] = useState(viewSettings.hideScrollbar || false);
const [volumeKeysToFlip, setVolumeKeysToFlip] = useState(viewSettings.volumeKeysToFlip);
const [showPaginationButtons, setShowPaginationButtons] = useState(
viewSettings.showPaginationButtons,
);
@@ -53,6 +51,7 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
const [allowScript, setAllowScript] = useState(viewSettings.allowScript);
const resetToDefaults = useResetViewSettings();
const pageTurnerResetRef = useRef<() => void>(() => {});
const handleReset = () => {
resetToDefaults({
@@ -60,7 +59,6 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
noContinuousScroll: setNoContinuousScroll,
scrollingOverlap: setScrollingOverlap,
hideScrollbar: setHideScrollbar,
volumeKeysToFlip: setVolumeKeysToFlip,
showPaginationButtons: setShowPaginationButtons,
disableClick: setIsDisableClick,
swapClickArea: setSwapClickArea,
@@ -72,6 +70,7 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
enableAnnotationQuickActions: setEnableAnnotationQuickActions,
copyToNotebook: setCopyToNotebook,
});
pageTurnerResetRef.current();
};
useEffect(() => {
@@ -113,18 +112,6 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [scrollingOverlap]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'volumeKeysToFlip', volumeKeysToFlip, false, false);
if (appService?.isMobileApp) {
if (volumeKeysToFlip) {
acquireVolumeKeyInterception();
} else {
releaseVolumeKeyInterception();
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [volumeKeysToFlip]);
useEffect(() => {
saveViewSettings(
envConfig,
@@ -300,13 +287,6 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
onChange={() => setIsDisableDoubleClick(!isDisableDoubleClick)}
data-setting-id='settings.control.disableDoubleClick'
/>
{appService?.isMobileApp && (
<SettingsSwitchRow
label={_('Volume Keys for Page Flip')}
checked={volumeKeysToFlip}
onChange={() => setVolumeKeysToFlip(!volumeKeysToFlip)}
/>
)}
<SettingsSwitchRow
label={_('Show Page Navigation Buttons')}
checked={showPaginationButtons}
@@ -315,6 +295,13 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
/>
</BoxedList>
<PageTurnerSettings
bookKey={bookKey}
onRegisterReset={(fn) => {
pageTurnerResetRef.current = fn;
}}
/>
<BoxedList
title={_('Annotation Tools')}
data-setting-id='settings.control.enableQuickActions'
@@ -0,0 +1,201 @@
import React, { useEffect, useRef, useState } from 'react';
import { useEnv } from '@/context/EnvContext';
import { useTranslation } from '@/hooks/useTranslation';
import { useSettingsStore } from '@/store/settingsStore';
import { useDeviceControlStore } from '@/store/deviceStore';
import { saveSysSettings, saveViewSettings } from '@/helpers/settings';
import { useResetViewSettings } from '@/hooks/useResetSettings';
import { eventDispatcher } from '@/utils/event';
import {
normalizeNativeKey,
normalizeDomKeyEvent,
PAGE_TURN_ACTIONS,
PageTurnAction,
} from '@/utils/keybinding';
import { HardwarePageTurnerSettings, KeyBinding } from '@/types/settings';
import { BoxedList, SettingsRow, SettingsSwitchRow } from './primitives';
import { useReaderStore } from '@/store/readerStore';
type Slot = PageTurnAction;
const LEARN_TIMEOUT_MS = 8000;
interface PageTurnerSettingsProps {
bookKey: string;
onRegisterReset?: (resetFn: () => void) => void;
}
const PageTurnerSettings: React.FC<PageTurnerSettingsProps> = ({ bookKey, onRegisterReset }) => {
const _ = useTranslation();
const { envConfig, appService } = useEnv();
const { getViewSettings } = useReaderStore();
const { setKeyLearnMode, acquireVolumeKeyInterception, releaseVolumeKeyInterception } =
useDeviceControlStore();
const { settings } = useSettingsStore();
const viewSettings = getViewSettings(bookKey) || settings.globalViewSettings;
const resetToDefaults = useResetViewSettings();
const [volumeKeysToFlip, setVolumeKeysToFlip] = useState(viewSettings.volumeKeysToFlip);
const [config, setConfig] = useState<HardwarePageTurnerSettings>(settings.hardwarePageTurner);
const configRef = useRef(config);
configRef.current = config;
const [listening, setListening] = useState<Slot | null>(null);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const persist = (next: HardwarePageTurnerSettings) => {
setConfig(next);
saveSysSettings(envConfig, 'hardwarePageTurner', next);
};
// Native key interception exists only on mobile; on web and desktop
// learn mode relies on standard DOM keydown events alone.
const setNativeLearnMode = (enabled: boolean) => {
if (appService?.isMobileApp) setKeyLearnMode(enabled);
};
const stopListening = () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
timeoutRef.current = null;
setNativeLearnMode(false);
setListening(null);
};
const captureBinding = (slot: Slot, binding: KeyBinding) => {
const current = configRef.current;
const bindings = { ...current.bindings, [slot]: binding };
// A key can drive only one action — clear it from every other slot.
for (const other of PAGE_TURN_ACTIONS) {
if (
other !== slot &&
bindings[other]?.source === binding.source &&
bindings[other]?.id === binding.id
) {
bindings[other] = null;
}
}
persist({ ...current, bindings });
stopListening();
};
useEffect(() => {
if (!listening) return;
const onNativeKey = (msg: CustomEvent) => {
const keyName = msg.detail?.keyName;
if (typeof keyName !== 'string') return;
// Back, and volume keys (which have their own dedicated page-flip
// toggle), are not bindable here — binding them would be inert or
// would double-fire alongside the volume-keys handler.
if (keyName === 'Back' || keyName === 'VolumeUp' || keyName === 'VolumeDown') return;
captureBinding(listening, normalizeNativeKey(keyName));
};
const onDomKey = (event: KeyboardEvent) => {
if (event.repeat) return;
event.preventDefault();
event.stopImmediatePropagation();
captureBinding(listening, normalizeDomKeyEvent(event));
};
setNativeLearnMode(true);
eventDispatcher.on('native-key-down', onNativeKey);
window.addEventListener('keydown', onDomKey, true);
timeoutRef.current = setTimeout(stopListening, LEARN_TIMEOUT_MS);
return () => {
eventDispatcher.off('native-key-down', onNativeKey);
window.removeEventListener('keydown', onDomKey, true);
if (timeoutRef.current) clearTimeout(timeoutRef.current);
timeoutRef.current = null;
setNativeLearnMode(false);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listening]);
useEffect(() => {
saveViewSettings(envConfig, bookKey, 'volumeKeysToFlip', volumeKeysToFlip, false, false);
if (appService?.isMobileApp) {
if (volumeKeysToFlip) {
acquireVolumeKeyInterception();
} else {
releaseVolumeKeyInterception();
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [volumeKeysToFlip]);
useEffect(() => {
onRegisterReset?.(() => resetToDefaults({ volumeKeysToFlip: setVolumeKeysToFlip }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (!config.enabled && listening) stopListening();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [config.enabled]);
const renderSlot = (slot: Slot, label: string) => {
const binding = config.bindings[slot];
const isListening = listening === slot;
return (
<SettingsRow
label={label}
disabled={!config.enabled}
data-setting-id={`settings.control.pageTurner.${slot}`}
>
<div className='flex items-center gap-5'>
{binding && !isListening && (
<button
type='button'
className='text-base-content/70 hover:text-base-content text-end text-[0.85em] focus:outline-none'
disabled={!config.enabled}
aria-label={`${_('Clear')}: ${label}`}
onClick={() => persist({ ...config, bindings: { ...config.bindings, [slot]: null } })}
>
{_('Clear')}
</button>
)}
<button
type='button'
className='text-base-content/70 hover:text-base-content py-1 text-end text-[0.85em] focus:outline-none'
disabled={!config.enabled}
aria-pressed={isListening}
aria-label={`${label}: ${isListening ? _('Listening…') : _('Set key')}`}
onClick={() => (isListening ? stopListening() : setListening(slot))}
>
{isListening ? _('Listening…') : binding ? _(binding.label) : _('Set key')}
</button>
</div>
</SettingsRow>
);
};
return (
<div className='space-y-2'>
<BoxedList
title={_('Page Turner')}
data-setting-id='settings.control.pageTurner'
description={_(
'Press a button on your remote controller or keyboard after tapping "Set key".',
)}
>
{appService?.isMobileApp && (
<SettingsSwitchRow
label={_('Use Volume Keys')}
checked={volumeKeysToFlip}
onChange={() => setVolumeKeysToFlip(!volumeKeysToFlip)}
/>
)}
<SettingsSwitchRow
label={_('Custom Page Turner')}
checked={config.enabled}
onChange={() => persist({ ...config, enabled: !config.enabled })}
/>
{renderSlot('pagePrev', _('Previous Page'))}
{renderSlot('pageNext', _('Next Page'))}
{renderSlot('sectionPrev', _('Previous Section'))}
{renderSlot('sectionNext', _('Next Section'))}
</BoxedList>
</div>
);
};
export default PageTurnerSettings;
@@ -42,14 +42,14 @@ const BoxedList: React.FC<BoxedListProps> = ({
return (
<div className={clsx('w-full', className)} data-setting-id={dataSettingId}>
{title && <SectionTitle className='mb-2'>{title}</SectionTitle>}
{description && (
<p className='text-base-content/65 -mt-1 mb-2 text-[0.7em] leading-relaxed'>
{description}
</p>
)}
<div className={clsx('card eink-bordered border-base-200 bg-base-100 border', cardClassName)}>
<div className={clsx('divide-base-200 divide-y ps-4', innerClassName)}>{children}</div>
</div>
{description && (
<p className='text-base-content/65 mb-2 mt-1 ps-4 text-[0.8em] leading-relaxed'>
{description}
</p>
)}
</div>
);
};
@@ -94,6 +94,10 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial<SystemSettings> = {
screenWakeLock: false,
screenBrightness: -1, // -1~100, -1 for system default
autoScreenBrightness: true,
hardwarePageTurner: {
enabled: false,
bindings: { pagePrev: null, pageNext: null, sectionPrev: null, sectionNext: null },
},
openLastBooks: false,
lastOpenBooks: [],
autoImportBooksOnOpen: false,
+41 -7
View File
@@ -5,19 +5,19 @@ import { NativeTouchEventType } from '@/types/system';
declare global {
interface Window {
onNativeKeyDown?: (keyName: string) => void;
onNativeKeyDown?: (keyName: string, keyCode?: number) => void;
onNativeTouch?: (event: NativeTouchEventType) => void;
}
}
const handleNativeKeyDown = (keyName: string) => {
if (keyName === 'VolumeUp' || keyName === 'VolumeDown') {
return eventDispatcher.dispatch('native-key-down', { keyName });
}
const handleNativeKeyDown = (keyName: string, keyCode?: number) => {
// Back is handled synchronously so dialogs can consume it before it
// bubbles; every other key (volume, media, learn-mode captures) is
// dispatched asynchronously through the same channel.
if (keyName === 'Back') {
return eventDispatcher.dispatchSync('native-key-down', { keyName });
return eventDispatcher.dispatchSync('native-key-down', { keyName, keyCode });
}
return false;
return eventDispatcher.dispatch('native-key-down', { keyName, keyCode });
};
type DeviceControlState = {
@@ -31,6 +31,11 @@ type DeviceControlState = {
releaseVolumeKeyInterception: () => void;
acquireBackKeyInterception: () => void;
releaseBackKeyInterception: () => void;
pageTurnerKeysIntercepted: boolean;
pageTurnerKeysInterceptionCount: number;
acquirePageTurnerKeyInterception: () => void;
releasePageTurnerKeyInterception: () => void;
setKeyLearnMode: (enabled: boolean) => void;
listenToNativeTouchEvents: () => void;
};
@@ -39,6 +44,8 @@ export const useDeviceControlStore = create<DeviceControlState>((set, get) => ({
backKeyIntercepted: false,
volumeKeysInterceptionCount: 0,
backKeyInterceptionCount: 0,
pageTurnerKeysIntercepted: false,
pageTurnerKeysInterceptionCount: 0,
acquireVolumeKeyInterception: () => {
const { volumeKeysInterceptionCount } = get();
@@ -80,6 +87,33 @@ export const useDeviceControlStore = create<DeviceControlState>((set, get) => ({
}
},
acquirePageTurnerKeyInterception: () => {
const { pageTurnerKeysInterceptionCount } = get();
if (pageTurnerKeysInterceptionCount == 0) {
window.onNativeKeyDown = handleNativeKeyDown;
interceptKeys({ pageTurnerKeys: true });
set({ pageTurnerKeysIntercepted: true });
}
set({ pageTurnerKeysInterceptionCount: pageTurnerKeysInterceptionCount + 1 });
},
releasePageTurnerKeyInterception: () => {
const { pageTurnerKeysInterceptionCount } = get();
if (pageTurnerKeysInterceptionCount <= 1) {
interceptKeys({ pageTurnerKeys: false });
set({ pageTurnerKeysIntercepted: false, pageTurnerKeysInterceptionCount: 0 });
} else {
set({ pageTurnerKeysInterceptionCount: pageTurnerKeysInterceptionCount - 1 });
}
},
// Learn mode is a stateless UI toggle (used while capturing a binding),
// not reference-counted like the acquire/release interception actions.
setKeyLearnMode: (enabled: boolean) => {
window.onNativeKeyDown = handleNativeKeyDown;
interceptKeys({ learnMode: enabled });
},
listenToNativeTouchEvents: () => {
window.onNativeTouch = (event: NativeTouchEventType) => {
return eventDispatcher.dispatch('native-touch', event);
+20
View File
@@ -111,6 +111,25 @@ export const SYNC_CATEGORIES: readonly SyncCategory[] = [
'credentials',
] as const;
export interface KeyBinding {
/** `native` = media keys forwarded by the OS bridge; `dom` = keyboard/D-pad keys. */
source: 'native' | 'dom';
/** Native key name (e.g. `MediaNext`) or DOM `event.code` (e.g. `ArrowLeft`). */
id: string;
/** Human-readable label shown in settings. */
label: string;
}
export interface HardwarePageTurnerSettings {
enabled: boolean;
bindings: {
pagePrev: KeyBinding | null;
pageNext: KeyBinding | null;
sectionPrev: KeyBinding | null;
sectionNext: KeyBinding | null;
};
}
export interface SystemSettings {
version: number;
migrationVersion: number;
@@ -125,6 +144,7 @@ export interface SystemSettings {
screenWakeLock: boolean;
screenBrightness: number;
autoScreenBrightness: boolean;
hardwarePageTurner: HardwarePageTurnerSettings;
alwaysShowStatusBar: boolean;
alwaysInForeground: boolean;
openLastBooks: boolean;
+4
View File
@@ -46,6 +46,10 @@ export interface GetSystemFontsListResponse {
export interface InterceptKeysRequest {
volumeKeys?: boolean;
backKey?: boolean;
/** Intercept media keys (next/previous/play-pause) for the hardware page turner. */
pageTurnerKeys?: boolean;
/** Forward every key press to JS so the settings UI can capture a binding. */
learnMode?: boolean;
}
export interface LockScreenRequest {
+72
View File
@@ -0,0 +1,72 @@
import { HardwarePageTurnerSettings, KeyBinding } from '@/types/settings';
import { stubTranslation as _ } from '@/utils/misc';
export type KeyCandidate = { source: 'native' | 'dom'; id: string };
export type PageTurnAction = 'pagePrev' | 'pageNext' | 'sectionPrev' | 'sectionNext';
export const PAGE_TURN_ACTIONS: PageTurnAction[] = [
'pagePrev',
'pageNext',
'sectionPrev',
'sectionNext',
];
const NATIVE_KEY_LABELS: Record<string, string> = {
MediaNext: _('Media Next'),
MediaPrevious: _('Media Previous'),
MediaPlayPause: _('Media Play/Pause'),
MediaFastForward: _('Media Fast Forward'),
MediaRewind: _('Media Rewind'),
VolumeUp: _('Volume Up'),
VolumeDown: _('Volume Down'),
};
const DOM_KEY_LABELS: Record<string, string> = {
ArrowLeft: _('Arrow Left'),
ArrowRight: _('Arrow Right'),
ArrowUp: _('Arrow Up'),
ArrowDown: _('Arrow Down'),
PageUp: _('Page Up'),
PageDown: _('Page Down'),
Space: _('Space'),
Enter: _('Enter'),
MediaTrackNext: _('Media Next'),
MediaTrackPrevious: _('Media Previous'),
MediaPlayPause: _('Media Play/Pause'),
};
/** Normalize a native key name (from the OS bridge) into a `KeyBinding`. */
export const normalizeNativeKey = (name: string): KeyBinding => ({
source: 'native',
id: name,
label: NATIVE_KEY_LABELS[name] ?? name,
});
/** Normalize a DOM `KeyboardEvent` into a `KeyBinding`. */
export const normalizeDomKeyEvent = (event: KeyboardEvent): KeyBinding => {
const id = event.code || event.key;
return {
source: 'dom',
id,
label: DOM_KEY_LABELS[id] ?? id,
};
};
/** True when `candidate` is the key described by `binding`. */
export const matchesBinding = (binding: KeyBinding | null, candidate: KeyCandidate): boolean =>
!!binding && binding.source === candidate.source && binding.id === candidate.id;
/**
* Decide which page-turn action an incoming key triggers. Returns the
* action, or `null` when the feature is disabled or the key is unbound.
*/
export const resolvePageTurn = (
settings: HardwarePageTurnerSettings,
candidate: KeyCandidate,
): PageTurnAction | null => {
if (!settings.enabled) return null;
for (const action of PAGE_TURN_ACTIONS) {
if (matchesBinding(settings.bindings[action], candidate)) return action;
}
return null;
};