feat(telemetry): opt-out by default for new users; consent prompt for 10% (#4340)
Fixes #4339. PostHog telemetry was previously enabled by default for every install, which surprised privacy-conscious users on self-hosted setups. Behavior for new users only (existing users are migrated to a decision that preserves their current `telemetryEnabled` setting): - 90% are silently opted out on first launch. - 10% see a one-time consent prompt; accepting opts in, declining opts out. Decision is persisted via a new `readest-telemetry-decision` localStorage key so subsequent boots don't re-roll. PostHog now inits with `opt_out_capturing_by_default` so brand-new users never ping before the decision is finalized. New `TelemetryConsentDialog` uses the project's `btn-contrast` (theme-neutral) CTA and `eink-bordered` chassis so it renders correctly under `[data-eink]` without color-mode-only assumptions. Strings translated across all 33 locales. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1771,5 +1771,12 @@
|
||||
"No new dictionaries were imported": "لم يتم استيراد أي قواميس جديدة",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "اقرأ الكتب من مجلداتها الأصلية بدلاً من نسخها إلى المكتبة. يوفر مساحة على القرص؛ ولا يزال الرفع التلقائي إلى السحابة يعمل إذا كان مفعلاً.",
|
||||
"This folder is an external library. Books here are always read in place.": "هذا المجلد هو مكتبة خارجية. تُقرأ الكتب هنا دائمًا من مكانها.",
|
||||
"Swipe to Paginate": "اسحب لتصفح الصفحات"
|
||||
"Swipe to Paginate": "اسحب لتصفح الصفحات",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "شارك بيانات الاستخدام المجهولة لنفهم كيف يُستخدم Readest ونحسّنه.",
|
||||
"Anonymous, aggregated feature usage": "استخدام مجهول ومجمَّع للميزات",
|
||||
"No personal information": "بدون معلومات شخصية",
|
||||
"No book content or reading data": "بدون محتوى كتب أو بيانات قراءة",
|
||||
"Share anonymous data": "مشاركة بيانات مجهولة",
|
||||
"Not now": "ليس الآن",
|
||||
"You can change this anytime in Settings.": "يمكنك تغيير هذا في أي وقت من الإعدادات."
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "কোনো নতুন অভিধান আমদানি করা হয়নি",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "বইগুলিকে লাইব্রেরিতে অনুলিপি করার পরিবর্তে তাদের মূল ফোল্ডার থেকে পড়ুন। ডিস্ক স্পেস সঞ্চয় করে; সক্ষম থাকলে ক্লাউড স্বয়ংক্রিয়-আপলোড এখনও কাজ করে।",
|
||||
"This folder is an external library. Books here are always read in place.": "এই ফোল্ডারটি একটি বহিরাগত লাইব্রেরি। এখানে বইগুলি সর্বদা মূল স্থানে পড়া হয়।",
|
||||
"Swipe to Paginate": "পৃষ্ঠা পরিবর্তনে সোয়াইপ করুন"
|
||||
"Swipe to Paginate": "পৃষ্ঠা পরিবর্তনে সোয়াইপ করুন",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "বেনামি ব্যবহারের ডেটা শেয়ার করুন যাতে আমরা বুঝতে পারি Readest কীভাবে ব্যবহৃত হয় এবং এটি উন্নত করতে পারি।",
|
||||
"Anonymous, aggregated feature usage": "বেনামি, সমষ্টিগত ফিচার ব্যবহার",
|
||||
"No personal information": "কোনো ব্যক্তিগত তথ্য নেই",
|
||||
"No book content or reading data": "কোনো বইয়ের বিষয়বস্তু বা পড়ার ডেটা নেই",
|
||||
"Share anonymous data": "বেনামি ডেটা শেয়ার করুন",
|
||||
"Not now": "এখন নয়",
|
||||
"You can change this anytime in Settings.": "আপনি যেকোনো সময় সেটিংসে এটি পরিবর্তন করতে পারেন।"
|
||||
}
|
||||
|
||||
@@ -1616,5 +1616,12 @@
|
||||
"No new dictionaries were imported": "ཚིག་མཛོད་གསར་པ་ནང་འདྲེན་མ་བྱུང་།",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "དཔེ་ཆ་དཔེ་མཛོད་དུ་ཕབ་ལེན་མི་བྱེད་པར་ཁོ་ཚོའི་ཐོག་མའི་ཡིག་སྣོད་ནས་ཀློག་པ། སྒྲིག་ཆས་ས་ཆ་ཉར་ཚགས་བྱེད་པ། སྔོན་ཆད་སྤྲིན་གྱི་རང་འགུལ་སྦྱར་འཇུག་གནས་སྟངས་ཡོད་ན་འདས་ལྡན་པ་ཡིན།",
|
||||
"This folder is an external library. Books here are always read in place.": "ཡིག་སྣོད་འདི་ནི་ཕྱི་ཕྱོགས་ཀྱི་དཔེ་མཛོད་ཡིན། འདིར་ཡོད་པའི་དཔེ་ཆ་རྣམས་རྟག་ཏུ་རང་གི་ས་གནས་སུ་ཀློག་པ་ཡིན།",
|
||||
"Swipe to Paginate": "མཆོངས་གཤིབ་ཀྱིས་ཤོག་ངོས་སྒྱུར་བ།"
|
||||
"Swipe to Paginate": "མཆོངས་གཤིབ་ཀྱིས་ཤོག་ངོས་སྒྱུར་བ།",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Readest བཀོལ་སྤྱོད་ཇི་ལྟར་བྱས་པ་རྟོགས་ཤིང་ལེགས་སྒྱུར་གྱི་ཆེད་དུ་མིང་མེད་སྤྱོད་སྦྱོར་གྱི་གནས་ཚུལ་མཉམ་སྤྱོད་གནང་རོགས།",
|
||||
"Anonymous, aggregated feature usage": "མིང་མེད་པའི་སྤྱི་སྡོམ་ནུས་པའི་སྤྱོད་སྦྱོར།",
|
||||
"No personal information": "མི་སྒེར་གྱི་ཆ་འཕྲིན་མེད།",
|
||||
"No book content or reading data": "དཔེ་ཆའི་ནང་དོན་ནམ་ཀློག་པའི་གནས་ཚུལ་མེད།",
|
||||
"Share anonymous data": "མིང་མེད་གནས་ཚུལ་མཉམ་སྤྱོད།",
|
||||
"Not now": "ད་ལྟ་མིན།",
|
||||
"You can change this anytime in Settings.": "ཁྱེད་ཀྱིས་འདི་སྒྲིག་འགོད་ནང་དུས་ནམ་ཡང་བཟོ་བཅོས་གནང་ཆོག།"
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "Keine neuen Wörterbücher importiert",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Liest Bücher aus ihren ursprünglichen Ordnern, anstatt sie in die Bibliothek zu kopieren. Spart Speicherplatz; Cloud-Auto-Upload funktioniert weiterhin, falls aktiviert.",
|
||||
"This folder is an external library. Books here are always read in place.": "Dieser Ordner ist eine externe Bibliothek. Bücher hier werden immer am Originalort gelesen.",
|
||||
"Swipe to Paginate": "Wischen zum Blättern"
|
||||
"Swipe to Paginate": "Wischen zum Blättern",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Teile anonyme Nutzungsdaten, damit wir verstehen können, wie Readest verwendet wird, und es verbessern können.",
|
||||
"Anonymous, aggregated feature usage": "Anonyme, aggregierte Funktionsnutzung",
|
||||
"No personal information": "Keine persönlichen Daten",
|
||||
"No book content or reading data": "Keine Buchinhalte oder Lesedaten",
|
||||
"Share anonymous data": "Anonyme Daten teilen",
|
||||
"Not now": "Nicht jetzt",
|
||||
"You can change this anytime in Settings.": "Du kannst dies jederzeit in den Einstellungen ändern."
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "Δεν εισήχθησαν νέα λεξικά",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Διαβάστε βιβλία από τους αρχικούς τους φακέλους αντί να τα αντιγράφετε στη βιβλιοθήκη. Εξοικονομεί χώρο στο δίσκο· η αυτόματη μεταφόρτωση στο cloud εξακολουθεί να λειτουργεί εάν είναι ενεργοποιημένη.",
|
||||
"This folder is an external library. Books here are always read in place.": "Αυτός ο φάκελος είναι εξωτερική βιβλιοθήκη. Τα βιβλία εδώ διαβάζονται πάντα στη θέση τους.",
|
||||
"Swipe to Paginate": "Σύρετε για Σελιδοποίηση"
|
||||
"Swipe to Paginate": "Σύρετε για Σελιδοποίηση",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Μοιραστείτε ανώνυμα δεδομένα χρήσης για να καταλάβουμε πώς χρησιμοποιείται το Readest και να το βελτιώσουμε.",
|
||||
"Anonymous, aggregated feature usage": "Ανώνυμη, συγκεντρωτική χρήση λειτουργιών",
|
||||
"No personal information": "Καμία προσωπική πληροφορία",
|
||||
"No book content or reading data": "Κανένα περιεχόμενο βιβλίου ή δεδομένα ανάγνωσης",
|
||||
"Share anonymous data": "Κοινοποίηση ανώνυμων δεδομένων",
|
||||
"Not now": "Όχι τώρα",
|
||||
"You can change this anytime in Settings.": "Μπορείτε να το αλλάξετε ανά πάσα στιγμή από τις Ρυθμίσεις."
|
||||
}
|
||||
|
||||
@@ -1678,5 +1678,12 @@
|
||||
"No new dictionaries were imported": "No se importaron diccionarios nuevos",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Lee los libros desde sus carpetas originales en lugar de copiarlos a la biblioteca. Ahorra espacio en disco; la carga automática en la nube sigue funcionando si está habilitada.",
|
||||
"This folder is an external library. Books here are always read in place.": "Esta carpeta es una biblioteca externa. Los libros aquí siempre se leen desde su ubicación original.",
|
||||
"Swipe to Paginate": "Deslizar para Pasar Página"
|
||||
"Swipe to Paginate": "Deslizar para Pasar Página",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Comparte datos de uso anónimos para que podamos entender cómo se usa Readest y mejorarlo.",
|
||||
"Anonymous, aggregated feature usage": "Uso de funciones anónimo y agregado",
|
||||
"No personal information": "Sin información personal",
|
||||
"No book content or reading data": "Sin contenido de libros ni datos de lectura",
|
||||
"Share anonymous data": "Compartir datos anónimos",
|
||||
"Not now": "Ahora no",
|
||||
"You can change this anytime in Settings.": "Puedes cambiar esto en cualquier momento en Ajustes."
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "هیچ فرهنگ لغت جدیدی وارد نشد",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "کتابها را به جای کپیکردن در کتابخانه، از پوشههای اصلیشان بخوانید. در فضای دیسک صرفهجویی میکند؛ بارگذاری خودکار ابری در صورت فعال بودن همچنان کار میکند.",
|
||||
"This folder is an external library. Books here are always read in place.": "این پوشه یک کتابخانهی بیرونی است. کتابهای این پوشه همیشه از همان مکان خوانده میشوند.",
|
||||
"Swipe to Paginate": "کشیدن برای ورق زدن"
|
||||
"Swipe to Paginate": "کشیدن برای ورق زدن",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "دادههای استفاده ناشناس را به اشتراک بگذارید تا بفهمیم Readest چگونه استفاده میشود و آن را بهبود دهیم.",
|
||||
"Anonymous, aggregated feature usage": "استفاده ناشناس و کلی از ویژگیها",
|
||||
"No personal information": "بدون اطلاعات شخصی",
|
||||
"No book content or reading data": "بدون محتوای کتاب یا دادههای مطالعه",
|
||||
"Share anonymous data": "اشتراک داده ناشناس",
|
||||
"Not now": "اکنون نه",
|
||||
"You can change this anytime in Settings.": "میتوانید این تنظیم را هر زمان از تنظیمات تغییر دهید."
|
||||
}
|
||||
|
||||
@@ -1678,5 +1678,12 @@
|
||||
"No new dictionaries were imported": "Aucun nouveau dictionnaire n’a été importé",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Lit les livres depuis leur dossier d’origine au lieu de les copier dans la bibliothèque. Économise de l’espace disque ; l’envoi automatique vers le cloud continue de fonctionner s’il est activé.",
|
||||
"This folder is an external library. Books here are always read in place.": "Ce dossier est une bibliothèque externe. Les livres y sont toujours lus depuis leur emplacement d’origine.",
|
||||
"Swipe to Paginate": "Glisser pour Tourner la Page"
|
||||
"Swipe to Paginate": "Glisser pour Tourner la Page",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Partagez des données d'utilisation anonymes pour nous aider à comprendre comment Readest est utilisé et à l'améliorer.",
|
||||
"Anonymous, aggregated feature usage": "Utilisation des fonctionnalités anonyme et agrégée",
|
||||
"No personal information": "Aucune information personnelle",
|
||||
"No book content or reading data": "Aucun contenu de livre ni donnée de lecture",
|
||||
"Share anonymous data": "Partager les données anonymes",
|
||||
"Not now": "Pas maintenant",
|
||||
"You can change this anytime in Settings.": "Vous pouvez modifier ce choix à tout moment dans les Paramètres."
|
||||
}
|
||||
|
||||
@@ -1678,5 +1678,12 @@
|
||||
"No new dictionaries were imported": "לא יובאו מילונים חדשים",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "קריאת ספרים מתיקיותיהם המקוריות במקום העתקתם לספרייה. חוסך מקום בדיסק; העלאה אוטומטית לענן עדיין פועלת אם היא מופעלת.",
|
||||
"This folder is an external library. Books here are always read in place.": "תיקייה זו היא ספרייה חיצונית. הספרים בה נקראים תמיד ממיקומם המקורי.",
|
||||
"Swipe to Paginate": "החלקה להחלפת עמוד"
|
||||
"Swipe to Paginate": "החלקה להחלפת עמוד",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "שתפו נתוני שימוש אנונימיים כדי שנוכל להבין כיצד נעשה שימוש ב‑Readest ולשפר אותו.",
|
||||
"Anonymous, aggregated feature usage": "שימוש בתכונות באופן אנונימי ומצרפי",
|
||||
"No personal information": "ללא מידע אישי",
|
||||
"No book content or reading data": "ללא תוכן ספרים או נתוני קריאה",
|
||||
"Share anonymous data": "שתף נתונים אנונימיים",
|
||||
"Not now": "לא עכשיו",
|
||||
"You can change this anytime in Settings.": "אפשר לשנות זאת בכל עת בהגדרות."
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "कोई नया शब्दकोश आयात नहीं किया गया",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "पुस्तकों को लाइब्रेरी में कॉपी करने के बजाय उनके मूल फ़ोल्डर से पढ़ें। डिस्क स्थान बचाता है; क्लाउड ऑटो-अपलोड सक्षम होने पर भी काम करता है।",
|
||||
"This folder is an external library. Books here are always read in place.": "यह फ़ोल्डर एक बाहरी लाइब्रेरी है। यहाँ की पुस्तकें हमेशा अपने मूल स्थान पर ही पढ़ी जाती हैं।",
|
||||
"Swipe to Paginate": "पन्ना बदलने के लिए स्वाइप करें"
|
||||
"Swipe to Paginate": "पन्ना बदलने के लिए स्वाइप करें",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "गुमनाम उपयोग डेटा साझा करें ताकि हम समझ सकें कि Readest कैसे उपयोग किया जाता है और इसे बेहतर बना सकें।",
|
||||
"Anonymous, aggregated feature usage": "गुमनाम, समग्र सुविधा उपयोग",
|
||||
"No personal information": "कोई व्यक्तिगत जानकारी नहीं",
|
||||
"No book content or reading data": "कोई पुस्तक सामग्री या पठन डेटा नहीं",
|
||||
"Share anonymous data": "गुमनाम डेटा साझा करें",
|
||||
"Not now": "अभी नहीं",
|
||||
"You can change this anytime in Settings.": "आप इसे कभी भी सेटिंग्स में बदल सकते हैं।"
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "Nem importáltunk új szótárakat",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Olvassa a könyveket az eredeti mappáikból, ahelyett, hogy a könyvtárba másolná őket. Lemezterületet takarít meg; a felhő automatikus feltöltése továbbra is működik, ha engedélyezve van.",
|
||||
"This folder is an external library. Books here are always read in place.": "Ez a mappa egy külső könyvtár. Az itt található könyvek mindig a saját helyükön nyílnak meg.",
|
||||
"Swipe to Paginate": "Lapozás húzással"
|
||||
"Swipe to Paginate": "Lapozás húzással",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Oszd meg az anonim használati adatokat, hogy megérthessük, hogyan használják a Readestet, és továbbfejleszthessük.",
|
||||
"Anonymous, aggregated feature usage": "Anonim, összesített funkcióhasználat",
|
||||
"No personal information": "Nincs személyes információ",
|
||||
"No book content or reading data": "Nincs könyvtartalom vagy olvasási adat",
|
||||
"Share anonymous data": "Anonim adatok megosztása",
|
||||
"Not now": "Most nem",
|
||||
"You can change this anytime in Settings.": "Ezt bármikor megváltoztathatod a Beállításokban."
|
||||
}
|
||||
|
||||
@@ -1616,5 +1616,12 @@
|
||||
"No new dictionaries were imported": "Tidak ada kamus baru yang diimpor",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Baca buku dari folder aslinya alih-alih menyalinnya ke perpustakaan. Menghemat ruang disk; unggah otomatis ke awan tetap berfungsi jika diaktifkan.",
|
||||
"This folder is an external library. Books here are always read in place.": "Folder ini adalah pustaka eksternal. Buku di sini selalu dibaca di tempat aslinya.",
|
||||
"Swipe to Paginate": "Geser untuk Pindah Halaman"
|
||||
"Swipe to Paginate": "Geser untuk Pindah Halaman",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Bagikan data penggunaan anonim agar kami dapat memahami bagaimana Readest digunakan dan meningkatkannya.",
|
||||
"Anonymous, aggregated feature usage": "Penggunaan fitur anonim dan agregat",
|
||||
"No personal information": "Tidak ada informasi pribadi",
|
||||
"No book content or reading data": "Tidak ada konten buku atau data baca",
|
||||
"Share anonymous data": "Bagikan data anonim",
|
||||
"Not now": "Nanti saja",
|
||||
"You can change this anytime in Settings.": "Anda dapat mengubahnya kapan saja di Pengaturan."
|
||||
}
|
||||
|
||||
@@ -1678,5 +1678,12 @@
|
||||
"No new dictionaries were imported": "Nessun nuovo dizionario importato",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Legge i libri dalle loro cartelle originali invece di copiarli nella libreria. Risparmia spazio su disco; il caricamento automatico sul cloud continua a funzionare se attivato.",
|
||||
"This folder is an external library. Books here are always read in place.": "Questa cartella è una libreria esterna. I libri vengono sempre letti dalla loro posizione originale.",
|
||||
"Swipe to Paginate": "Scorri per Sfogliare"
|
||||
"Swipe to Paginate": "Scorri per Sfogliare",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Condividi dati di utilizzo anonimi per aiutarci a capire come viene usato Readest e a migliorarlo.",
|
||||
"Anonymous, aggregated feature usage": "Utilizzo delle funzionalità anonimo e aggregato",
|
||||
"No personal information": "Nessuna informazione personale",
|
||||
"No book content or reading data": "Nessun contenuto dei libri o dato di lettura",
|
||||
"Share anonymous data": "Condividi dati anonimi",
|
||||
"Not now": "Non ora",
|
||||
"You can change this anytime in Settings.": "Puoi modificarlo in qualsiasi momento dalle Impostazioni."
|
||||
}
|
||||
|
||||
@@ -1616,5 +1616,12 @@
|
||||
"No new dictionaries were imported": "新しい辞書は読み込まれませんでした",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "ライブラリにコピーせず、元のフォルダから直接書籍を読み込みます。ディスク容量を節約でき、自動アップロードが有効ならクラウド同期も引き続き機能します。",
|
||||
"This folder is an external library. Books here are always read in place.": "このフォルダは外部ライブラリです。ここの書籍は常に元の場所から読み込まれます。",
|
||||
"Swipe to Paginate": "スワイプでページ送り"
|
||||
"Swipe to Paginate": "スワイプでページ送り",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Readestがどのように使われているかを把握し、改善するために匿名の使用データを共有してください。",
|
||||
"Anonymous, aggregated feature usage": "匿名で集計された機能の使用状況",
|
||||
"No personal information": "個人情報は含まれません",
|
||||
"No book content or reading data": "書籍の内容や読書データは含まれません",
|
||||
"Share anonymous data": "匿名データを共有",
|
||||
"Not now": "後で",
|
||||
"You can change this anytime in Settings.": "この設定はいつでも設定画面から変更できます。"
|
||||
}
|
||||
|
||||
@@ -1616,5 +1616,12 @@
|
||||
"No new dictionaries were imported": "가져온 새 사전이 없습니다",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "도서를 라이브러리에 복사하지 않고 원래 폴더에서 직접 읽어옵니다. 디스크 공간을 절약하며, 자동 업로드가 켜져 있으면 클라우드 동기화도 그대로 작동합니다.",
|
||||
"This folder is an external library. Books here are always read in place.": "이 폴더는 외부 라이브러리입니다. 여기 도서는 항상 원래 위치에서 읽힙니다.",
|
||||
"Swipe to Paginate": "스와이프로 페이지 넘기기"
|
||||
"Swipe to Paginate": "스와이프로 페이지 넘기기",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Readest이 어떻게 사용되는지 파악하고 개선할 수 있도록 익명 사용 데이터를 공유해 주세요.",
|
||||
"Anonymous, aggregated feature usage": "익명으로 집계된 기능 사용량",
|
||||
"No personal information": "개인 정보 없음",
|
||||
"No book content or reading data": "도서 내용이나 독서 데이터 없음",
|
||||
"Share anonymous data": "익명 데이터 공유",
|
||||
"Not now": "나중에",
|
||||
"You can change this anytime in Settings.": "설정에서 언제든지 변경할 수 있습니다."
|
||||
}
|
||||
|
||||
@@ -1616,5 +1616,12 @@
|
||||
"No new dictionaries were imported": "Tiada kamus baharu diimport",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Baca buku dari folder asalnya tanpa menyalinnya ke perpustakaan. Menjimatkan ruang cakera; muat naik automatik ke awan masih berfungsi jika diaktifkan.",
|
||||
"This folder is an external library. Books here are always read in place.": "Folder ini ialah perpustakaan luaran. Buku di sini sentiasa dibaca di lokasi asalnya.",
|
||||
"Swipe to Paginate": "Leret untuk Tukar Halaman"
|
||||
"Swipe to Paginate": "Leret untuk Tukar Halaman",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Kongsi data penggunaan tanpa nama supaya kami dapat memahami cara Readest digunakan dan menambah baiknya.",
|
||||
"Anonymous, aggregated feature usage": "Penggunaan ciri tanpa nama dan teragregat",
|
||||
"No personal information": "Tiada maklumat peribadi",
|
||||
"No book content or reading data": "Tiada kandungan buku atau data bacaan",
|
||||
"Share anonymous data": "Kongsi data tanpa nama",
|
||||
"Not now": "Bukan sekarang",
|
||||
"You can change this anytime in Settings.": "Anda boleh menukar ini pada bila-bila masa dalam Tetapan."
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "Er zijn geen nieuwe woordenboeken geïmporteerd",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Lees boeken vanuit hun oorspronkelijke mappen in plaats van ze naar de bibliotheek te kopiëren. Bespaart schijfruimte; automatische uploads naar de cloud blijven werken als ze ingeschakeld zijn.",
|
||||
"This folder is an external library. Books here are always read in place.": "Deze map is een externe bibliotheek. De boeken hier worden altijd op hun oorspronkelijke locatie gelezen.",
|
||||
"Swipe to Paginate": "Vegen om door te bladeren"
|
||||
"Swipe to Paginate": "Vegen om door te bladeren",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Deel anonieme gebruiksgegevens zodat we kunnen begrijpen hoe Readest wordt gebruikt en het kunnen verbeteren.",
|
||||
"Anonymous, aggregated feature usage": "Anoniem, geaggregeerd functiegebruik",
|
||||
"No personal information": "Geen persoonlijke informatie",
|
||||
"No book content or reading data": "Geen boekinhoud of leesgegevens",
|
||||
"Share anonymous data": "Anonieme gegevens delen",
|
||||
"Not now": "Niet nu",
|
||||
"You can change this anytime in Settings.": "Je kunt dit op elk moment wijzigen in Instellingen."
|
||||
}
|
||||
|
||||
@@ -1709,5 +1709,12 @@
|
||||
"No new dictionaries were imported": "Nie zaimportowano żadnych nowych słowników",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Odczytuj książki z ich pierwotnych folderów zamiast kopiować je do biblioteki. Oszczędza miejsce na dysku; automatyczne przesyłanie do chmury nadal działa, jeśli jest włączone.",
|
||||
"This folder is an external library. Books here are always read in place.": "Ten folder jest zewnętrzną biblioteką. Książki są w nim zawsze otwierane w ich pierwotnej lokalizacji.",
|
||||
"Swipe to Paginate": "Przesuń, aby przewinąć stronę"
|
||||
"Swipe to Paginate": "Przesuń, aby przewinąć stronę",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Udostępnij anonimowe dane o użytkowaniu, abyśmy mogli zrozumieć, jak Readest jest używany, i go ulepszać.",
|
||||
"Anonymous, aggregated feature usage": "Anonimowe, zagregowane użycie funkcji",
|
||||
"No personal information": "Bez danych osobowych",
|
||||
"No book content or reading data": "Bez treści książek i danych o czytaniu",
|
||||
"Share anonymous data": "Udostępnij anonimowe dane",
|
||||
"Not now": "Nie teraz",
|
||||
"You can change this anytime in Settings.": "Możesz to zmienić w dowolnej chwili w Ustawieniach."
|
||||
}
|
||||
|
||||
@@ -1678,5 +1678,12 @@
|
||||
"No new dictionaries were imported": "Nenhum dicionário novo foi importado",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Lê os livros a partir das pastas originais em vez de copiá-los para a biblioteca. Economiza espaço em disco; o envio automático para a nuvem continua funcionando se estiver ativado.",
|
||||
"This folder is an external library. Books here are always read in place.": "Esta pasta é uma biblioteca externa. Os livros aqui são sempre lidos no local original.",
|
||||
"Swipe to Paginate": "Deslizar para Virar Página"
|
||||
"Swipe to Paginate": "Deslizar para Virar Página",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Compartilhe dados de uso anônimos para que possamos entender como o Readest é usado e melhorá-lo.",
|
||||
"Anonymous, aggregated feature usage": "Uso de recursos anônimo e agregado",
|
||||
"No personal information": "Sem informações pessoais",
|
||||
"No book content or reading data": "Sem conteúdo de livros ou dados de leitura",
|
||||
"Share anonymous data": "Compartilhar dados anônimos",
|
||||
"Not now": "Agora não",
|
||||
"You can change this anytime in Settings.": "Você pode alterar isso a qualquer momento em Configurações."
|
||||
}
|
||||
|
||||
@@ -1678,5 +1678,12 @@
|
||||
"No new dictionaries were imported": "Nenhum dicionário novo foi importado",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Lê os livros a partir das pastas originais em vez de os copiar para a biblioteca. Poupa espaço em disco; o envio automático para a nuvem continua a funcionar se estiver ativado.",
|
||||
"This folder is an external library. Books here are always read in place.": "Esta pasta é uma biblioteca externa. Os livros aqui são sempre lidos no local original.",
|
||||
"Swipe to Paginate": "Deslizar para Mudar de Página"
|
||||
"Swipe to Paginate": "Deslizar para Mudar de Página",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Partilhe dados de utilização anónimos para podermos perceber como o Readest é utilizado e melhorá-lo.",
|
||||
"Anonymous, aggregated feature usage": "Utilização de funcionalidades anónima e agregada",
|
||||
"No personal information": "Sem informações pessoais",
|
||||
"No book content or reading data": "Sem conteúdo de livros ou dados de leitura",
|
||||
"Share anonymous data": "Partilhar dados anónimos",
|
||||
"Not now": "Agora não",
|
||||
"You can change this anytime in Settings.": "Pode alterar isto a qualquer momento nas Definições."
|
||||
}
|
||||
|
||||
@@ -1678,5 +1678,12 @@
|
||||
"No new dictionaries were imported": "Nu au fost importate dicționare noi",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Citește cărțile din folderele lor originale în loc să le copieze în bibliotecă. Economisește spațiu pe disc; încărcarea automată în cloud funcționează în continuare dacă este activată.",
|
||||
"This folder is an external library. Books here are always read in place.": "Acest folder este o bibliotecă externă. Cărțile de aici sunt întotdeauna citite din locația originală.",
|
||||
"Swipe to Paginate": "Glisare pentru a Schimba Pagina"
|
||||
"Swipe to Paginate": "Glisare pentru a Schimba Pagina",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Partajează date anonime de utilizare pentru a ne ajuta să înțelegem cum este folosit Readest și să-l îmbunătățim.",
|
||||
"Anonymous, aggregated feature usage": "Utilizare anonimă și agregată a funcțiilor",
|
||||
"No personal information": "Fără informații personale",
|
||||
"No book content or reading data": "Fără conținut din cărți sau date despre citire",
|
||||
"Share anonymous data": "Partajează date anonime",
|
||||
"Not now": "Nu acum",
|
||||
"You can change this anytime in Settings.": "Poți modifica această opțiune oricând din Setări."
|
||||
}
|
||||
|
||||
@@ -1709,5 +1709,12 @@
|
||||
"No new dictionaries were imported": "Новые словари не были импортированы",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Читать книги из их исходных папок вместо копирования в библиотеку. Экономит место на диске; автозагрузка в облако по-прежнему работает, если включена.",
|
||||
"This folder is an external library. Books here are always read in place.": "Эта папка — внешняя библиотека. Книги в ней всегда открываются по исходному пути.",
|
||||
"Swipe to Paginate": "Смахивание для перелистывания"
|
||||
"Swipe to Paginate": "Смахивание для перелистывания",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Поделитесь анонимными данными об использовании, чтобы мы могли понять, как используется Readest, и улучшить его.",
|
||||
"Anonymous, aggregated feature usage": "Анонимное, агрегированное использование функций",
|
||||
"No personal information": "Без личной информации",
|
||||
"No book content or reading data": "Без содержимого книг и данных о чтении",
|
||||
"Share anonymous data": "Поделиться анонимными данными",
|
||||
"Not now": "Не сейчас",
|
||||
"You can change this anytime in Settings.": "Это можно изменить в любое время в Настройках."
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "නව ශබ්දකෝෂ ආයාත කර නැත",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "පොත් පුස්තකාලයට පිටපත් කරනවාට වඩා ඒවායේ මුල් ෆෝල්ඩරවලින් කියවන්න. තැටි ඉඩ ඉතිරි කරයි; සක්රිය නම් වලාකුළු ස්වයංක්රීය උඩුගත කිරීම තවමත් ක්රියා කරයි.",
|
||||
"This folder is an external library. Books here are always read in place.": "මෙම ෆෝල්ඩරය බාහිර පුස්තකාලයකි. මෙහි ඇති පොත් සැමවිටම ඔවුන්ගේ මුල් ස්ථානයේ සිට කියවනු ලැබේ.",
|
||||
"Swipe to Paginate": "පිටු මාරුවට ස්වයිප් කරන්න"
|
||||
"Swipe to Paginate": "පිටු මාරුවට ස්වයිප් කරන්න",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Readest භාවිතය වටහාගෙන එය වඩාත් යහපත් කිරීමට අපට හැකි වන පරිදි අනාමික භාවිත දත්ත බෙදාගන්න.",
|
||||
"Anonymous, aggregated feature usage": "අනාමික, ඒකරාශී වූ විශේෂාංග භාවිතය",
|
||||
"No personal information": "පුද්ගලික තොරතුරු නැත",
|
||||
"No book content or reading data": "පොත් අන්තර්ගතය හෝ කියවීමේ දත්ත නැත",
|
||||
"Share anonymous data": "අනාමික දත්ත බෙදාගන්න",
|
||||
"Not now": "දැන් නොවේ",
|
||||
"You can change this anytime in Settings.": "ඔබට මෙය ඕනෑම විටක සැකසුම් තුළ වෙනස් කළ හැකිය."
|
||||
}
|
||||
|
||||
@@ -1709,5 +1709,12 @@
|
||||
"No new dictionaries were imported": "Novi slovarji niso bili uvoženi",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Berite knjige iz njihovih izvirnih map, namesto da bi jih kopirali v knjižnico. Prihrani prostor na disku; samodejno nalaganje v oblak še naprej deluje, če je omogočeno.",
|
||||
"This folder is an external library. Books here are always read in place.": "Ta mapa je zunanja knjižnica. Knjige v njej se vedno berejo na izvirnem mestu.",
|
||||
"Swipe to Paginate": "Drsanje za listanje"
|
||||
"Swipe to Paginate": "Drsanje za listanje",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Delite anonimne podatke o uporabi, da bomo razumeli, kako se Readest uporablja, in ga lahko izboljšali.",
|
||||
"Anonymous, aggregated feature usage": "Anonimna, združena uporaba funkcij",
|
||||
"No personal information": "Brez osebnih podatkov",
|
||||
"No book content or reading data": "Brez vsebine knjig ali bralnih podatkov",
|
||||
"Share anonymous data": "Deli anonimne podatke",
|
||||
"Not now": "Ne zdaj",
|
||||
"You can change this anytime in Settings.": "To lahko kadar koli spremenite v Nastavitvah."
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "Inga nya ordböcker importerades",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Läs böcker direkt från deras ursprungsmappar i stället för att kopiera dem till biblioteket. Sparar diskutrymme; automatisk uppladdning till molnet fungerar fortfarande om den är aktiverad.",
|
||||
"This folder is an external library. Books here are always read in place.": "Den här mappen är ett externt bibliotek. Böckerna här läses alltid från sin ursprungsplats.",
|
||||
"Swipe to Paginate": "Svep för att Bläddra"
|
||||
"Swipe to Paginate": "Svep för att Bläddra",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Dela anonyma användningsdata så att vi kan förstå hur Readest används och göra det bättre.",
|
||||
"Anonymous, aggregated feature usage": "Anonym, aggregerad funktionsanvändning",
|
||||
"No personal information": "Ingen personlig information",
|
||||
"No book content or reading data": "Inget bokinnehåll eller läsdata",
|
||||
"Share anonymous data": "Dela anonyma data",
|
||||
"Not now": "Inte nu",
|
||||
"You can change this anytime in Settings.": "Du kan ändra detta när som helst i Inställningar."
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "புதிய அகராதிகள் எதுவும் இறக்குமதி செய்யப்படவில்லை",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "புத்தகங்களை நூலகத்திற்கு நகலெடுக்காமல், அவற்றின் அசல் கோப்புறைகளில் இருந்தே வாசிக்கவும். வட்டக் கொள்ளளவை சேமிக்கும்; இயக்கப்பட்டிருந்தால் கிளவுட் தானியங்கு பதிவேற்றம் இன்னும் வேலை செய்யும்.",
|
||||
"This folder is an external library. Books here are always read in place.": "இந்த கோப்புறை ஒரு வெளிப்புற நூலகம். இங்குள்ள புத்தகங்கள் எப்போதும் அவற்றின் அசல் இடத்திலேயே வாசிக்கப்படும்.",
|
||||
"Swipe to Paginate": "பக்கம் மாற்ற ஸ்வைப் செய்யவும்"
|
||||
"Swipe to Paginate": "பக்கம் மாற்ற ஸ்வைப் செய்யவும்",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Readest எவ்வாறு பயன்படுத்தப்படுகிறது என்பதைப் புரிந்து கொள்ளவும் அதை மேம்படுத்தவும் அநாமதேய பயன்பாட்டுத் தரவைப் பகிரவும்.",
|
||||
"Anonymous, aggregated feature usage": "அநாமதேய, தொகுக்கப்பட்ட அம்சப் பயன்பாடு",
|
||||
"No personal information": "தனிப்பட்ட தகவல்கள் இல்லை",
|
||||
"No book content or reading data": "புத்தக உள்ளடக்கம் அல்லது வாசிப்புத் தரவு இல்லை",
|
||||
"Share anonymous data": "அநாமதேய தரவைப் பகிர்",
|
||||
"Not now": "இப்போது வேண்டாம்",
|
||||
"You can change this anytime in Settings.": "அமைப்புகளில் எப்போது வேண்டுமானாலும் இதை மாற்றலாம்."
|
||||
}
|
||||
|
||||
@@ -1616,5 +1616,12 @@
|
||||
"No new dictionaries were imported": "ไม่มีพจนานุกรมใหม่ถูกนำเข้า",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "อ่านหนังสือจากโฟลเดอร์ต้นฉบับโดยตรงแทนการคัดลอกเข้าคลังหนังสือ ช่วยประหยัดพื้นที่ดิสก์ การอัปโหลดอัตโนมัติไปยังคลาวด์ยังคงทำงานหากเปิดใช้งานอยู่",
|
||||
"This folder is an external library. Books here are always read in place.": "โฟลเดอร์นี้เป็นคลังหนังสือภายนอก หนังสือในนี้จะถูกเปิดอ่านจากตำแหน่งเดิมเสมอ",
|
||||
"Swipe to Paginate": "ปัดเพื่อเปลี่ยนหน้า"
|
||||
"Swipe to Paginate": "ปัดเพื่อเปลี่ยนหน้า",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "แชร์ข้อมูลการใช้งานแบบไม่ระบุตัวตนเพื่อให้เราเข้าใจว่ามีการใช้ Readest อย่างไรและปรับปรุงให้ดียิ่งขึ้น",
|
||||
"Anonymous, aggregated feature usage": "การใช้คุณสมบัติแบบไม่ระบุตัวตนและรวมข้อมูล",
|
||||
"No personal information": "ไม่มีข้อมูลส่วนบุคคล",
|
||||
"No book content or reading data": "ไม่มีเนื้อหาหนังสือหรือข้อมูลการอ่าน",
|
||||
"Share anonymous data": "แชร์ข้อมูลแบบไม่ระบุตัวตน",
|
||||
"Not now": "ไม่ใช่ตอนนี้",
|
||||
"You can change this anytime in Settings.": "คุณสามารถเปลี่ยนแปลงสิ่งนี้ได้ทุกเมื่อในการตั้งค่า"
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "Yeni sözlük içe aktarılmadı",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Kitapları kitaplığa kopyalamak yerine orijinal klasörlerinden okuyun. Disk alanından tasarruf sağlar; etkinleştirilmişse bulut otomatik yüklemesi yine de çalışır.",
|
||||
"This folder is an external library. Books here are always read in place.": "Bu klasör harici bir kitaplıktır. İçindeki kitaplar her zaman bulundukları yerden okunur.",
|
||||
"Swipe to Paginate": "Sayfa Çevirmek için Kaydır"
|
||||
"Swipe to Paginate": "Sayfa Çevirmek için Kaydır",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Readest'in nasıl kullanıldığını anlayıp geliştirebilmemiz için anonim kullanım verilerini paylaşın.",
|
||||
"Anonymous, aggregated feature usage": "Anonim, toplu özellik kullanımı",
|
||||
"No personal information": "Kişisel bilgi yok",
|
||||
"No book content or reading data": "Kitap içeriği veya okuma verisi yok",
|
||||
"Share anonymous data": "Anonim veri paylaş",
|
||||
"Not now": "Şimdi değil",
|
||||
"You can change this anytime in Settings.": "Bunu istediğiniz zaman Ayarlar'dan değiştirebilirsiniz."
|
||||
}
|
||||
|
||||
@@ -1709,5 +1709,12 @@
|
||||
"No new dictionaries were imported": "Нові словники не імпортовано",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Читайте книги з їхніх початкових тек, замість копіювання до бібліотеки. Заощаджує місце на диску; автозавантаження до хмари продовжує працювати, якщо ввімкнено.",
|
||||
"This folder is an external library. Books here are always read in place.": "Ця тека — зовнішня бібліотека. Книги в ній завжди читаються з початкового розташування.",
|
||||
"Swipe to Paginate": "Гортання для перегортання"
|
||||
"Swipe to Paginate": "Гортання для перегортання",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Поділіться анонімними даними використання, щоб ми могли зрозуміти, як використовується Readest, і покращити його.",
|
||||
"Anonymous, aggregated feature usage": "Анонімне, агреговане використання функцій",
|
||||
"No personal information": "Жодної особистої інформації",
|
||||
"No book content or reading data": "Жодного вмісту книг чи даних читання",
|
||||
"Share anonymous data": "Поділитися анонімними даними",
|
||||
"Not now": "Не зараз",
|
||||
"You can change this anytime in Settings.": "Ви можете змінити це будь-коли в Налаштуваннях."
|
||||
}
|
||||
|
||||
@@ -1647,5 +1647,12 @@
|
||||
"No new dictionaries were imported": "Yangi lug‘atlar import qilinmadi",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Kitoblarni kutubxonaga ko‘chirish o‘rniga ularning asl papkalaridan o‘qing. Disk joyini tejaydi; yoqilgan bo‘lsa, bulutga avtomatik yuklash hamon ishlaydi.",
|
||||
"This folder is an external library. Books here are always read in place.": "Bu papka tashqi kutubxonadir. Bu yerdagi kitoblar har doim o‘z joyidan o‘qiladi.",
|
||||
"Swipe to Paginate": "Sahifani aylantirish uchun suring"
|
||||
"Swipe to Paginate": "Sahifani aylantirish uchun suring",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Readest qanday ishlatilishini tushunish va uni yaxshilash uchun anonim foydalanish maʼlumotlarini ulashing.",
|
||||
"Anonymous, aggregated feature usage": "Anonim, jamlangan funksiya foydalanishi",
|
||||
"No personal information": "Shaxsiy maʼlumotlar yoʻq",
|
||||
"No book content or reading data": "Kitob mazmuni yoki oʻqish maʼlumotlari yoʻq",
|
||||
"Share anonymous data": "Anonim maʼlumotlarni ulashish",
|
||||
"Not now": "Hozir emas",
|
||||
"You can change this anytime in Settings.": "Buni xohlagan vaqtda Sozlamalarda oʻzgartirishingiz mumkin."
|
||||
}
|
||||
|
||||
@@ -1616,5 +1616,12 @@
|
||||
"No new dictionaries were imported": "Không có từ điển mới nào được nhập",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "Đọc sách trực tiếp từ thư mục gốc thay vì sao chép vào thư viện. Tiết kiệm dung lượng đĩa; tự động tải lên đám mây vẫn hoạt động nếu được bật.",
|
||||
"This folder is an external library. Books here are always read in place.": "Thư mục này là thư viện ngoài. Sách trong đây luôn được đọc tại vị trí gốc.",
|
||||
"Swipe to Paginate": "Vuốt để Sang Trang"
|
||||
"Swipe to Paginate": "Vuốt để Sang Trang",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "Chia sẻ dữ liệu sử dụng ẩn danh để chúng tôi có thể hiểu cách Readest được sử dụng và cải thiện nó.",
|
||||
"Anonymous, aggregated feature usage": "Sử dụng tính năng ẩn danh và tổng hợp",
|
||||
"No personal information": "Không có thông tin cá nhân",
|
||||
"No book content or reading data": "Không có nội dung sách hoặc dữ liệu đọc",
|
||||
"Share anonymous data": "Chia sẻ dữ liệu ẩn danh",
|
||||
"Not now": "Để sau",
|
||||
"You can change this anytime in Settings.": "Bạn có thể thay đổi tùy chọn này bất cứ lúc nào trong Cài đặt."
|
||||
}
|
||||
|
||||
@@ -1616,5 +1616,12 @@
|
||||
"No new dictionaries were imported": "未导入任何新词典",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "直接从图书的原始文件夹读取,无需复制到书库。可节省磁盘空间;若启用了自动上传,云端同步仍正常工作。",
|
||||
"This folder is an external library. Books here are always read in place.": "此文件夹是外部书库,其中的图书始终在原位置读取。",
|
||||
"Swipe to Paginate": "滑动翻页"
|
||||
"Swipe to Paginate": "滑动翻页",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "分享匿名使用数据,帮助我们了解 Readest 的使用情况并加以改进。",
|
||||
"Anonymous, aggregated feature usage": "匿名、聚合的功能使用数据",
|
||||
"No personal information": "不包含个人信息",
|
||||
"No book content or reading data": "不包含书籍内容或阅读数据",
|
||||
"Share anonymous data": "分享匿名数据",
|
||||
"Not now": "暂不",
|
||||
"You can change this anytime in Settings.": "您可以随时在“设置”中更改此选项。"
|
||||
}
|
||||
|
||||
@@ -1616,5 +1616,12 @@
|
||||
"No new dictionaries were imported": "未匯入任何新字典",
|
||||
"Read books from their original folders instead of copying them into the library. Saves disk space; cloud auto-upload still works if enabled.": "直接從原始資料夾讀取書籍,不複製到書庫。可節省磁碟空間;若已啟用自動上傳,雲端同步仍正常運作。",
|
||||
"This folder is an external library. Books here are always read in place.": "此資料夾是外部書庫,其中的書籍一律從原位置讀取。",
|
||||
"Swipe to Paginate": "滑動翻頁"
|
||||
"Swipe to Paginate": "滑動翻頁",
|
||||
"Share anonymous usage data so we can understand how Readest is used and make it better.": "分享匿名使用資料,協助我們了解 Readest 的使用情形並加以改進。",
|
||||
"Anonymous, aggregated feature usage": "匿名、聚合的功能使用資料",
|
||||
"No personal information": "不包含個人資訊",
|
||||
"No book content or reading data": "不包含書籍內容或閱讀資料",
|
||||
"Share anonymous data": "分享匿名資料",
|
||||
"Not now": "暫不",
|
||||
"You can change this anytime in Settings.": "您可以隨時在「設定」中變更此項目。"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
|
||||
vi.mock('posthog-js', () => ({
|
||||
default: {
|
||||
capture: vi.fn(),
|
||||
opt_in_capturing: vi.fn(),
|
||||
opt_out_capturing: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import posthog from 'posthog-js';
|
||||
import {
|
||||
getTelemetryDecision,
|
||||
hasOptedOutTelemetry,
|
||||
optInTelemetry,
|
||||
optOutTelemetry,
|
||||
rollIntoTelemetryPromptBucket,
|
||||
setTelemetryDecision,
|
||||
TELEMETRY_DECISION_KEY,
|
||||
TELEMETRY_OPT_OUT_KEY,
|
||||
TELEMETRY_PROMPT_BUCKET_RATE,
|
||||
} from '@/utils/telemetry';
|
||||
|
||||
describe('telemetry decision storage', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns null when no decision is stored', () => {
|
||||
expect(getTelemetryDecision()).toBeNull();
|
||||
});
|
||||
|
||||
it('round-trips the three valid decisions', () => {
|
||||
setTelemetryDecision('opt-in');
|
||||
expect(getTelemetryDecision()).toBe('opt-in');
|
||||
setTelemetryDecision('opt-out');
|
||||
expect(getTelemetryDecision()).toBe('opt-out');
|
||||
setTelemetryDecision('pending');
|
||||
expect(getTelemetryDecision()).toBe('pending');
|
||||
});
|
||||
|
||||
it('ignores garbage values written directly to the key', () => {
|
||||
localStorage.setItem(TELEMETRY_DECISION_KEY, 'something-else');
|
||||
expect(getTelemetryDecision()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('optInTelemetry / optOutTelemetry', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('opt-in records opt-in decision, clears opt-out flag, and calls posthog', () => {
|
||||
optInTelemetry();
|
||||
expect(localStorage.getItem(TELEMETRY_OPT_OUT_KEY)).toBe('false');
|
||||
expect(getTelemetryDecision()).toBe('opt-in');
|
||||
expect(hasOptedOutTelemetry()).toBe(false);
|
||||
expect(posthog.opt_in_capturing).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('opt-out records opt-out decision, sets opt-out flag, and calls posthog', () => {
|
||||
optOutTelemetry();
|
||||
expect(localStorage.getItem(TELEMETRY_OPT_OUT_KEY)).toBe('true');
|
||||
expect(getTelemetryDecision()).toBe('opt-out');
|
||||
expect(hasOptedOutTelemetry()).toBe(true);
|
||||
expect(posthog.opt_out_capturing).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe('rollIntoTelemetryPromptBucket', () => {
|
||||
it('is true when the rng falls under the bucket rate', () => {
|
||||
expect(rollIntoTelemetryPromptBucket(() => 0)).toBe(true);
|
||||
expect(rollIntoTelemetryPromptBucket(() => TELEMETRY_PROMPT_BUCKET_RATE - 1e-9)).toBe(true);
|
||||
});
|
||||
|
||||
it('is false at or above the bucket rate', () => {
|
||||
expect(rollIntoTelemetryPromptBucket(() => TELEMETRY_PROMPT_BUCKET_RATE)).toBe(false);
|
||||
expect(rollIntoTelemetryPromptBucket(() => 0.99)).toBe(false);
|
||||
});
|
||||
|
||||
it('places roughly 10% of uniform draws into the bucket', () => {
|
||||
const n = 10000;
|
||||
let inBucket = 0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (rollIntoTelemetryPromptBucket(() => i / n)) inBucket++;
|
||||
}
|
||||
// Deterministic uniform sweep: floor(n * rate) = 1000.
|
||||
expect(inBucket).toBe(Math.floor(n * TELEMETRY_PROMPT_BUCKET_RATE));
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import '@/utils/polyfill';
|
||||
import posthog from 'posthog-js';
|
||||
import i18n from '@/i18n/i18n';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { IconContext } from 'react-icons';
|
||||
import { AuthProvider } from '@/context/AuthContext';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
@@ -18,17 +19,86 @@ import { useEinkMode } from '@/hooks/useEinkMode';
|
||||
import { getLocale } from '@/utils/misc';
|
||||
import { getDirFromUILanguage } from '@/utils/rtl';
|
||||
import { getAndroidPatchedViewportContent } from '@/utils/viewport';
|
||||
import {
|
||||
getTelemetryDecision,
|
||||
rollIntoTelemetryPromptBucket,
|
||||
setTelemetryDecision,
|
||||
TELEMETRY_OPT_OUT_KEY,
|
||||
} from '@/utils/telemetry';
|
||||
import { SETTINGS_FILENAME } from '@/services/constants';
|
||||
import type { AppService } from '@/types/system';
|
||||
import type { SystemSettings } from '@/types/settings';
|
||||
import { DropdownProvider } from '@/context/DropdownContext';
|
||||
import { CommandPaletteProvider, CommandPalette } from '@/components/command-palette';
|
||||
import AtmosphereOverlay from '@/components/AtmosphereOverlay';
|
||||
import AppLockScreen from '@/components/AppLockScreen';
|
||||
import AppLockDialog from '@/components/settings/AppLockDialog';
|
||||
import PassphrasePrompt from '@/components/PassphrasePrompt';
|
||||
import TelemetryConsentDialog from '@/components/TelemetryConsentDialog';
|
||||
import { upgradeToKeychainIfAvailable } from '@/libs/crypto/passphrase';
|
||||
import { cryptoSession } from '@/libs/crypto/session';
|
||||
import { useAppLockStore } from '@/store/appLockStore';
|
||||
import { initSettingsSync } from '@/services/sync/replicaSettingsSync';
|
||||
|
||||
// One-time, on first launch after this feature ships, decide how to handle
|
||||
// PostHog telemetry for the current install:
|
||||
// - Existing user (settings file already on disk): preserve their current
|
||||
// `telemetryEnabled` setting and persist a matching decision so we don't
|
||||
// reconsider on every boot.
|
||||
// - New user: silently opt 90% out and skip the prompt; show the consent
|
||||
// dialog to the remaining 10%. Until the dialog is resolved their state
|
||||
// is `pending` (opt-out at the PostHog layer).
|
||||
// If a decision has already been recorded, this is a no-op.
|
||||
const finalizeTelemetryDecision = ({
|
||||
appService,
|
||||
settings,
|
||||
isNewUser,
|
||||
onShowPrompt,
|
||||
}: {
|
||||
appService: AppService;
|
||||
settings: SystemSettings;
|
||||
isNewUser: boolean;
|
||||
onShowPrompt: () => void;
|
||||
}) => {
|
||||
const existing = getTelemetryDecision();
|
||||
if (existing === 'pending') {
|
||||
onShowPrompt();
|
||||
return;
|
||||
}
|
||||
if (existing !== null) return;
|
||||
|
||||
if (!isNewUser) {
|
||||
// Existing user: don't change anything they had set. Sync PostHog to
|
||||
// their saved preference and record the decision so we stop checking.
|
||||
if (settings.telemetryEnabled) {
|
||||
localStorage.setItem(TELEMETRY_OPT_OUT_KEY, 'false');
|
||||
posthog.opt_in_capturing();
|
||||
setTelemetryDecision('opt-in');
|
||||
} else {
|
||||
localStorage.setItem(TELEMETRY_OPT_OUT_KEY, 'true');
|
||||
posthog.opt_out_capturing();
|
||||
setTelemetryDecision('opt-out');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Brand-new user. Default to opt-out for privacy; ask only the prompt
|
||||
// bucket so the rest get a friction-free first launch.
|
||||
if (rollIntoTelemetryPromptBucket()) {
|
||||
setTelemetryDecision('pending');
|
||||
onShowPrompt();
|
||||
} else {
|
||||
localStorage.setItem(TELEMETRY_OPT_OUT_KEY, 'true');
|
||||
posthog.opt_out_capturing();
|
||||
setTelemetryDecision('opt-out');
|
||||
// Persist the off-by-default to the settings file directly. The settings
|
||||
// store isn't seeded yet at this point in boot, so saveSysSettings would
|
||||
// write a malformed partial object — write through appService instead.
|
||||
settings.telemetryEnabled = false;
|
||||
void appService.saveSettings(settings);
|
||||
}
|
||||
};
|
||||
|
||||
const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { applyUILanguage } = useSettingsStore();
|
||||
@@ -40,6 +110,7 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
initialize: initializeAppLock,
|
||||
} = useAppLockStore();
|
||||
const iconSize = useDefaultIconSize();
|
||||
const [showTelemetryConsent, setShowTelemetryConsent] = useState(false);
|
||||
useSafeAreaInsets(); // Initialize safe area insets
|
||||
|
||||
useEffect(() => {
|
||||
@@ -66,8 +137,16 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
loadDataTheme();
|
||||
if (appService) {
|
||||
initSystemThemeListener(appService);
|
||||
appService.loadSettings().then((settings) => {
|
||||
const hadSettingsFilePromise = appService.exists(SETTINGS_FILENAME, 'Settings');
|
||||
appService.loadSettings().then(async (settings) => {
|
||||
const globalViewSettings = settings.globalViewSettings;
|
||||
const hadSettingsFile = await hadSettingsFilePromise.catch(() => false);
|
||||
finalizeTelemetryDecision({
|
||||
appService,
|
||||
settings,
|
||||
isNewUser: !hadSettingsFile,
|
||||
onShowPrompt: () => setShowTelemetryConsent(true),
|
||||
});
|
||||
applyUILanguage(globalViewSettings.uiLanguage);
|
||||
// Seed the customTextureStore with the disk-loaded textures (preserving
|
||||
// their saved ids) so the boot-time applyBackgroundTexture below can
|
||||
@@ -158,6 +237,10 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
<PassphrasePrompt />
|
||||
</div>
|
||||
<AppLockDialog />
|
||||
<TelemetryConsentDialog
|
||||
open={showTelemetryConsent}
|
||||
onClose={() => setShowTelemetryConsent(false)}
|
||||
/>
|
||||
{showAppLockScreen && <AppLockScreen />}
|
||||
</CommandPaletteProvider>
|
||||
</DropdownProvider>
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
'use client';
|
||||
|
||||
import { LuCheck, LuChartLine, LuX } from 'react-icons/lu';
|
||||
|
||||
import ModalPortal from '@/components/ModalPortal';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { optInTelemetry, optOutTelemetry } from '@/utils/telemetry';
|
||||
|
||||
interface TelemetryConsentDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* First-launch consent prompt asking new users whether to share anonymous
|
||||
* usage data. Shown to a small fraction of new users; the rest are opted out
|
||||
* silently.
|
||||
*/
|
||||
export default function TelemetryConsentDialog({ open, onClose }: TelemetryConsentDialogProps) {
|
||||
const _ = useTranslation();
|
||||
const { envConfig } = useEnv();
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
// Persist `telemetryEnabled` via the settings store if it has already
|
||||
// been seeded by the library/reader page; otherwise go straight through
|
||||
// appService.loadSettings + saveSettings so we don't overwrite the on-disk
|
||||
// file with a near-empty store snapshot.
|
||||
const persistTelemetryEnabled = async (value: boolean) => {
|
||||
const store = useSettingsStore.getState();
|
||||
if (store.settings && typeof store.settings.version === 'number') {
|
||||
const next = { ...store.settings, telemetryEnabled: value };
|
||||
store.setSettings(next);
|
||||
await store.saveSettings(envConfig, next);
|
||||
} else {
|
||||
const appService = await envConfig.getAppService();
|
||||
const settings = await appService.loadSettings();
|
||||
settings.telemetryEnabled = value;
|
||||
await appService.saveSettings(settings);
|
||||
}
|
||||
};
|
||||
|
||||
const accept = async () => {
|
||||
optInTelemetry();
|
||||
await persistTelemetryEnabled(true);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const decline = async () => {
|
||||
optOutTelemetry();
|
||||
await persistTelemetryEnabled(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalPortal>
|
||||
<dialog className='modal modal-open'>
|
||||
<div className='modal-box bg-base-100 w-[min(420px,calc(100vw-2rem))] rounded-2xl p-0'>
|
||||
<div className='border-base-content/10 flex flex-col items-center gap-3 border-b px-6 pb-5 pt-7 text-center'>
|
||||
<div
|
||||
className='eink-bordered bg-base-200 text-base-content/80 flex h-12 w-12 items-center justify-center rounded-full'
|
||||
aria-hidden='true'
|
||||
>
|
||||
<LuChartLine size={22} strokeWidth={1.75} />
|
||||
</div>
|
||||
<h3 className='text-base-content text-base font-semibold tracking-tight'>
|
||||
{_('Help improve Readest')}
|
||||
</h3>
|
||||
<p className='text-base-content/65 text-[13px] leading-relaxed'>
|
||||
{_(
|
||||
'Share anonymous usage data so we can understand how Readest is used and make it better.',
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul className='space-y-2.5 px-6 py-5'>
|
||||
<ConsentRow kind='positive' label={_('Anonymous, aggregated feature usage')} />
|
||||
<ConsentRow kind='negative' label={_('No personal information')} />
|
||||
<ConsentRow kind='negative' label={_('No book content or reading data')} />
|
||||
</ul>
|
||||
|
||||
<div className='border-base-content/10 flex flex-col gap-2 border-t px-6 py-4'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={accept}
|
||||
className='btn btn-contrast h-10 min-h-0 rounded-xl text-sm font-medium'
|
||||
>
|
||||
{_('Share anonymous data')}
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
onClick={decline}
|
||||
className='eink-bordered text-base-content hover:bg-base-200 h-10 rounded-xl border border-transparent text-sm font-medium transition-colors'
|
||||
>
|
||||
{_('Not now')}
|
||||
</button>
|
||||
<p className='text-base-content/55 pt-1 text-center text-[11px]'>
|
||||
{_('You can change this anytime in Settings.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
</ModalPortal>
|
||||
);
|
||||
}
|
||||
|
||||
function ConsentRow({ kind, label }: { kind: 'positive' | 'negative'; label: string }) {
|
||||
const isPositive = kind === 'positive';
|
||||
// Positive: filled disc. We deliberately skip `eink-bordered` here so the
|
||||
// eink override that paints `bg-base-content` solid wins, keeping the row
|
||||
// visually distinct from the negative (outlined) rows on e-paper.
|
||||
// Negative: outlined disc — `eink-bordered` flips to base-100 + 1px border
|
||||
// under eink, and `border-base-content/15` carries the boundary on color
|
||||
// themes.
|
||||
return (
|
||||
<li className='flex items-center gap-3'>
|
||||
<span
|
||||
className={
|
||||
'flex h-6 w-6 shrink-0 items-center justify-center rounded-full ' +
|
||||
(isPositive
|
||||
? 'bg-base-content text-base-100'
|
||||
: 'eink-bordered border-base-content/15 text-base-content/70 border')
|
||||
}
|
||||
aria-hidden='true'
|
||||
>
|
||||
{isPositive ? <LuCheck size={14} strokeWidth={2.5} /> : <LuX size={14} strokeWidth={2.5} />}
|
||||
</span>
|
||||
<span className='text-base-content/85 text-[13px] leading-snug'>{label}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@@ -3,12 +3,20 @@
|
||||
import posthog from 'posthog-js';
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { PostHogProvider } from 'posthog-js/react';
|
||||
import { TELEMETRY_OPT_OUT_KEY } from '@/utils/telemetry';
|
||||
import { TELEMETRY_DECISION_KEY, TELEMETRY_OPT_OUT_KEY } from '@/utils/telemetry';
|
||||
import { getAppVersion } from '@/utils/version';
|
||||
|
||||
const shouldDisablePostHog = () => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
return localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === 'true';
|
||||
// Returns true if PostHog should be opted-out at boot time, before settings
|
||||
// have loaded. Honors any explicit decision the user has made; otherwise
|
||||
// returns true (opt-out by default) so brand-new users never ping PostHog
|
||||
// before Providers can finalize the decision. Existing users whose settings
|
||||
// have telemetry enabled are re-opted-in once Providers loads their settings.
|
||||
const shouldOptOutAtBoot = () => {
|
||||
if (typeof window === 'undefined') return true;
|
||||
const decision = localStorage.getItem(TELEMETRY_DECISION_KEY);
|
||||
if (decision === 'opt-in') return false;
|
||||
if (decision === 'opt-out' || decision === 'pending') return true;
|
||||
return localStorage.getItem(TELEMETRY_OPT_OUT_KEY) !== 'false';
|
||||
};
|
||||
|
||||
const posthogUrl =
|
||||
@@ -19,13 +27,12 @@ const posthogKey =
|
||||
atob(process.env['NEXT_PUBLIC_DEFAULT_POSTHOG_KEY_BASE64']!);
|
||||
|
||||
if (typeof window !== 'undefined' && process.env['NODE_ENV'] === 'production' && posthogKey) {
|
||||
if (!shouldDisablePostHog()) {
|
||||
posthog.init(posthogKey, {
|
||||
api_host: posthogUrl,
|
||||
person_profiles: 'always',
|
||||
autocapture: false,
|
||||
});
|
||||
}
|
||||
posthog.init(posthogKey, {
|
||||
api_host: posthogUrl,
|
||||
person_profiles: 'always',
|
||||
autocapture: false,
|
||||
opt_out_capturing_by_default: shouldOptOutAtBoot(),
|
||||
});
|
||||
}
|
||||
export const CSPostHogProvider = ({ children }: { children: ReactNode }) => {
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
import posthog from 'posthog-js';
|
||||
|
||||
export const TELEMETRY_OPT_OUT_KEY = 'readest-telemetry-opt-out';
|
||||
export const TELEMETRY_DECISION_KEY = 'readest-telemetry-decision';
|
||||
|
||||
export type TelemetryDecision = 'opt-in' | 'opt-out' | 'pending';
|
||||
|
||||
/** Fraction of new users shown the consent prompt; the rest are opted out silently. */
|
||||
export const TELEMETRY_PROMPT_BUCKET_RATE = 0.1;
|
||||
|
||||
export const hasOptedOutTelemetry = () => {
|
||||
return localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === 'true';
|
||||
};
|
||||
|
||||
export const getTelemetryDecision = (): TelemetryDecision | null => {
|
||||
if (typeof window === 'undefined') return null;
|
||||
const value = localStorage.getItem(TELEMETRY_DECISION_KEY);
|
||||
if (value === 'opt-in' || value === 'opt-out' || value === 'pending') return value;
|
||||
return null;
|
||||
};
|
||||
|
||||
export const setTelemetryDecision = (decision: TelemetryDecision) => {
|
||||
localStorage.setItem(TELEMETRY_DECISION_KEY, decision);
|
||||
};
|
||||
|
||||
/** Returns true with probability TELEMETRY_PROMPT_BUCKET_RATE. */
|
||||
export const rollIntoTelemetryPromptBucket = (rng: () => number = Math.random) => {
|
||||
return rng() < TELEMETRY_PROMPT_BUCKET_RATE;
|
||||
};
|
||||
|
||||
export const captureEvent = (event: string, properties?: Record<string, unknown>) => {
|
||||
if (!hasOptedOutTelemetry()) {
|
||||
posthog.capture(event, properties);
|
||||
@@ -14,9 +36,11 @@ export const captureEvent = (event: string, properties?: Record<string, unknown>
|
||||
|
||||
export const optInTelemetry = () => {
|
||||
localStorage.setItem(TELEMETRY_OPT_OUT_KEY, 'false');
|
||||
setTelemetryDecision('opt-in');
|
||||
posthog.opt_in_capturing();
|
||||
};
|
||||
export const optOutTelemetry = () => {
|
||||
localStorage.setItem(TELEMETRY_OPT_OUT_KEY, 'true');
|
||||
setTelemetryDecision('opt-out');
|
||||
posthog.opt_out_capturing();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user