diff --git a/CHANGELOG.md b/CHANGELOG.md index 7475ea16..7e759f9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for touch-swipe and mouse-wheel gestures on the search engine icon to switch search engines when they are hidden ([@prem-k-r](https://github.com/prem-k-r)) ([#145](https://github.com/prem-k-r/MaterialYouNewTab/pull/145)) +- Added WebDAV backup and restore support, including manual cloud backup and restore for MYNT settings. ### Improved diff --git a/index.html b/index.html index ba338bb7..9072c6b2 100644 --- a/index.html +++ b/index.html @@ -41,6 +41,7 @@ + @@ -1829,7 +1830,6 @@

Material You New Tab

-
- @@ -1858,6 +1857,58 @@

Material You New Tab

+
+
+
+ Cloud Backup + +
+ +
+
+
+
WebDAV Sync
+ + + +
+ + +
+
+ +
+
+
+
+
Enable automatic backup
+
Back up settings to WebDAV on a schedule
+
+ +
+ +
+ +
+
+
+
+
+
@@ -1954,4 +2005,4 @@

Material You New Tab

- \ No newline at end of file + diff --git a/locales/ar_SA.js b/locales/ar_SA.js index 9a035e02..2b85a8b4 100644 --- a/locales/ar_SA.js +++ b/locales/ar_SA.js @@ -225,5 +225,25 @@ const ar_SA = { invalidBackup: "تم اختيار ملف نسخ احتياطي غير صالح.", deleteBookmark: 'هل أنت متأكد أنك تريد حذف الإشارة المرجعية "{title}"؟', UnsupportedBrowser: "الإشارات المرجعية غير مدعومة في متصفحك.", - resetShortcutsPrompt: "سيتم حذف جميع الاختصارات المحفوظة وإعادتها إلى الإعدادات الافتراضية. هل تريد المتابعة؟" -}; \ No newline at end of file + resetShortcutsPrompt: "سيتم حذف جميع الاختصارات المحفوظة وإعادتها إلى الإعدادات الافتراضية. هل تريد المتابعة؟", + "cloudBackupSectionTitle": "نسخ احتياطي سحابي", + "webdavTitle": "مزامنة WebDAV", + "webdavBackupText": "نسخ احتياطي إلى WebDAV", + "webdavRestoreText": "استعادة الأحدث", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "اسم المستخدم (اختياري)", + "webdavPasswordPlaceholder": "كلمة المرور أو كلمة مرور التطبيق (اختياري)", + "cloudAutoBackupText": "تفعيل النسخ الاحتياطي التلقائي", + "cloudIntervalPlaceholder": "ساعات", + "cloudIntervalHours": "ساعات", + "saveBackupConfig": "حفظ إعدادات النسخ الاحتياطي", + "backupConfigSaved": "تم حفظ إعدادات النسخ الاحتياطي.", + "webdavMissingConfig": "يرجى ملء رابط مجلد WebDAV أولاً.", + "webdavPermissionDenied": "لم يتم منح إذن المضيف لهذا الخادم WebDAV.", + "webdavBackupCompleted": "تم رفع النسخة الاحتياطية إلى WebDAV بنجاح!", + "webdavDownloadFailed": "فشل تنزيل النسخة الاحتياطية من WebDAV: ", + "webdavUploadFailed": "فشل رفع النسخة الاحتياطية إلى WebDAV: ", + "webdavRestoreConfirm": "هل تريد استعادة الإعدادات من أحدث نسخة احتياطية على WebDAV؟ سيتم استبدال بياناتك المحلية الحالية.", + "webdavLatestPointerMissing": "لم يتم العثور على سجل لأحدث نسخة احتياطية في مجلد MYNT." +}; diff --git a/locales/az.js b/locales/az.js index ae03d4af..7f15e835 100644 --- a/locales/az.js +++ b/locales/az.js @@ -132,4 +132,24 @@ const az = { // "invalidBackup": "Invalid backup file selected.", // "deleteBookmark": "Are you sure you want to delete the bookmark \"{title}\"?", // Do not translate {title} // "UnsupportedBrowser": "Bookmarks are not supported in your browser", -}; \ No newline at end of file + "cloudBackupSectionTitle": "Bulud ehtiyat nüsxəsi", + "webdavTitle": "WebDAV sinxronizasiyası", + "webdavBackupText": "WebDAV-a yedəklə", + "webdavRestoreText": "Ən sonuncunu bərpa et", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "İstifadəçi adı (istəyə bağlı)", + "webdavPasswordPlaceholder": "Parol və ya tətbiq parolu (istəyə bağlı)", + "cloudAutoBackupText": "Avtomatik ehtiyat nüsxəni aktiv et", + "cloudIntervalPlaceholder": "Saat", + "cloudIntervalHours": "Saat", + "saveBackupConfig": "Ehtiyat nüsxə ayarlarını saxla", + "backupConfigSaved": "Ehtiyat nüsxə ayarları saxlanıldı.", + "webdavMissingConfig": "Əvvəlcə WebDAV qovluğunun URL-ni doldurun.", + "webdavPermissionDenied": "Bu WebDAV serveri üçün host icazəsi verilmədi.", + "webdavBackupCompleted": "Ehtiyat nüsxə uğurla WebDAV-a yükləndi!", + "webdavDownloadFailed": "WebDAV-dan ehtiyat nüsxəni endirmək alınmadı: ", + "webdavUploadFailed": "Ehtiyat nüsxəni WebDAV-a yükləmək alınmadı: ", + "webdavRestoreConfirm": "Ayarlar ən son WebDAV ehtiyat nüsxəsindən bərpa olunsun? Cari lokal məlumatlarınız üzərinə yazılacaq.", + "webdavLatestPointerMissing": "MYNT qovluğunda ən son ehtiyat nüsxə qeydi tapılmadı." +}; diff --git a/locales/bn.js b/locales/bn.js index 9202cdb5..0d9c789f 100644 --- a/locales/bn.js +++ b/locales/bn.js @@ -210,5 +210,25 @@ const bn = { "invalidBackup": "অবৈধ ব্যাকআপ ফাইল নির্বাচিত হয়েছে।", "deleteBookmark": "আপনি কি নিশ্চিত যে আপনি \"{title}\" বুকমার্কটি মুছে ফেলতে চান?", "UnsupportedBrowser": "আপনার ব্রাউজারে বুকমার্ক সমর্থিত নয়।", - "resetShortcutsPrompt": "সব সংরক্ষিত শর্টকাট মুছে ফেলা হবে এবং ডিফল্ট অবস্থায় পুনরায় সেট করা হবে। আপনি কি চালিয়ে যেতে চান?" + "resetShortcutsPrompt": "সব সংরক্ষিত শর্টকাট মুছে ফেলা হবে এবং ডিফল্ট অবস্থায় পুনরায় সেট করা হবে। আপনি কি চালিয়ে যেতে চান?", + "cloudBackupSectionTitle": "ক্লাউড ব্যাকআপ", + "webdavTitle": "WebDAV সিঙ্ক", + "webdavBackupText": "WebDAV-এ ব্যাকআপ", + "webdavRestoreText": "সর্বশেষ পুনরুদ্ধার করুন", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "ব্যবহারকারীর নাম (ঐচ্ছিক)", + "webdavPasswordPlaceholder": "পাসওয়ার্ড বা অ্যাপ পাসওয়ার্ড (ঐচ্ছিক)", + "cloudAutoBackupText": "স্বয়ংক্রিয় ব্যাকআপ চালু করুন", + "cloudIntervalPlaceholder": "ঘণ্টা", + "cloudIntervalHours": "ঘণ্টা", + "saveBackupConfig": "ব্যাকআপ কনফিগারেশন সংরক্ষণ করুন", + "backupConfigSaved": "ব্যাকআপ কনফিগারেশন সংরক্ষিত হয়েছে।", + "webdavMissingConfig": "আগে WebDAV ফোল্ডারের URL পূরণ করুন।", + "webdavPermissionDenied": "এই WebDAV সার্ভারের জন্য হোস্ট অনুমতি দেওয়া হয়নি।", + "webdavBackupCompleted": "সফলভাবে WebDAV-এ ব্যাকআপ আপলোড হয়েছে!", + "webdavDownloadFailed": "WebDAV থেকে ব্যাকআপ ডাউনলোড করা যায়নি: ", + "webdavUploadFailed": "WebDAV-এ ব্যাকআপ আপলোড করা যায়নি: ", + "webdavRestoreConfirm": "সর্বশেষ WebDAV ব্যাকআপ থেকে সেটিংস পুনরুদ্ধার করবেন? বর্তমান লোকাল ডেটা ওভাররাইট হবে।", + "webdavLatestPointerMissing": "MYNT ফোল্ডারে সর্বশেষ ব্যাকআপের রেকর্ড পাওয়া যায়নি।" }; diff --git a/locales/cs.js b/locales/cs.js index 89cf8e94..a680c1a1 100644 --- a/locales/cs.js +++ b/locales/cs.js @@ -210,4 +210,24 @@ const cs = { "deleteBookmark": "Opravdu si přejete smazat záložku „{title}“?", "UnsupportedBrowser": "Ve vašem prohlížeči nejsou záložky podporované", "resetShortcutsPrompt": "Budou odstraněny všechny uložené zkratky a následně se obnoví výchozí sada zkratek. Přejete si pokračovat?", + "cloudBackupSectionTitle": "Cloud záloha", + "webdavTitle": "Synchronizace WebDAV", + "webdavBackupText": "Zálohovat do WebDAV", + "webdavRestoreText": "Obnovit nejnovější", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Uživatelské jméno (volitelné)", + "webdavPasswordPlaceholder": "Heslo nebo heslo aplikace (volitelné)", + "cloudAutoBackupText": "Povolit automatické zálohování", + "cloudIntervalPlaceholder": "Hodiny", + "cloudIntervalHours": "Hodiny", + "saveBackupConfig": "Uložit nastavení zálohy", + "backupConfigSaved": "Nastavení zálohy bylo uloženo.", + "webdavMissingConfig": "Nejprve vyplňte URL složky WebDAV.", + "webdavPermissionDenied": "Pro tento server WebDAV nebylo uděleno oprávnění hostitele.", + "webdavBackupCompleted": "Záloha byla úspěšně nahrána na WebDAV!", + "webdavDownloadFailed": "Stažení zálohy z WebDAV se nezdařilo: ", + "webdavUploadFailed": "Nahrání zálohy na WebDAV se nezdařilo: ", + "webdavRestoreConfirm": "Obnovit nastavení z nejnovější zálohy WebDAV? Vaše aktuální místní data budou přepsána.", + "webdavLatestPointerMissing": "Ve složce MYNT nebyl nalezen záznam o nejnovější záloze." }; diff --git a/locales/de.js b/locales/de.js index c2c7d0af..051500f5 100644 --- a/locales/de.js +++ b/locales/de.js @@ -194,5 +194,25 @@ const de = { "invalidBackup": "Ungültige Sicherungsdatei ausgewählt.", "deleteBookmark": "Sind Sie sicher, dass Sie das Lesezeichen „{title}“ löschen wollen?", "UnsupportedBrowser": "Lesezeichen werden von Ihrem Browser nicht unterstützt.", - "resetShortcutsPrompt": "Alle gespeicherten Verknüpfungen werden gelöscht und auf die Standardwerte zurückgesetzt. Möchten Sie fortfahren?" + "resetShortcutsPrompt": "Alle gespeicherten Verknüpfungen werden gelöscht und auf die Standardwerte zurückgesetzt. Möchten Sie fortfahren?", + "cloudBackupSectionTitle": "Cloud-Backup", + "webdavTitle": "WebDAV-Synchronisierung", + "webdavBackupText": "Auf WebDAV sichern", + "webdavRestoreText": "Neueste Sicherung wiederherstellen", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Benutzername (optional)", + "webdavPasswordPlaceholder": "Passwort oder App-Passwort (optional)", + "cloudAutoBackupText": "Automatische Sicherung aktivieren", + "cloudIntervalPlaceholder": "Stunden", + "cloudIntervalHours": "Stunden", + "saveBackupConfig": "Backup-Konfiguration speichern", + "backupConfigSaved": "Backup-Konfiguration gespeichert.", + "webdavMissingConfig": "Bitte zuerst die URL des WebDAV-Ordners eingeben.", + "webdavPermissionDenied": "Für diesen WebDAV-Server wurde keine Host-Berechtigung erteilt.", + "webdavBackupCompleted": "Backup erfolgreich zu WebDAV hochgeladen!", + "webdavDownloadFailed": "WebDAV-Backup konnte nicht heruntergeladen werden: ", + "webdavUploadFailed": "WebDAV-Backup konnte nicht hochgeladen werden: ", + "webdavRestoreConfirm": "Einstellungen aus dem neuesten WebDAV-Backup wiederherstellen? Ihre aktuellen lokalen Daten werden überschrieben.", + "webdavLatestPointerMissing": "Im MYNT-Ordner wurde kein Eintrag für das neueste Backup gefunden." }; diff --git a/locales/el.js b/locales/el.js index a31a6a84..a8f85ea2 100644 --- a/locales/el.js +++ b/locales/el.js @@ -194,5 +194,25 @@ const el = { "invalidBackup": "Επιλέχθηκε μη έγκυρο αρχείο αντιγράφου ασφαλείας.", "deleteBookmark": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον σελιδοδείκτη \"{title}\";", "UnsupportedBrowser": "Οι σελιδοδείκτες δεν υποστηρίζονται στον περιηγητή σας.", - "resetShortcutsPrompt": "Όλες οι αποθηκευμένες συντομεύσεις θα διαγραφούν και θα επανέλθουν στις προεπιλογές. Θέλετε να συνεχίσετε;" + "resetShortcutsPrompt": "Όλες οι αποθηκευμένες συντομεύσεις θα διαγραφούν και θα επανέλθουν στις προεπιλογές. Θέλετε να συνεχίσετε;", + "cloudBackupSectionTitle": "Αντίγραφο ασφαλείας cloud", + "webdavTitle": "Συγχρονισμός WebDAV", + "webdavBackupText": "Δημιουργία αντιγράφου στο WebDAV", + "webdavRestoreText": "Επαναφορά τελευταίου", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Όνομα χρήστη (προαιρετικό)", + "webdavPasswordPlaceholder": "Κωδικός ή κωδικός εφαρμογής (προαιρετικό)", + "cloudAutoBackupText": "Ενεργοποίηση αυτόματου αντιγράφου ασφαλείας", + "cloudIntervalPlaceholder": "Ώρες", + "cloudIntervalHours": "Ώρες", + "saveBackupConfig": "Αποθήκευση ρυθμίσεων αντιγράφου ασφαλείας", + "backupConfigSaved": "Οι ρυθμίσεις αντιγράφου ασφαλείας αποθηκεύτηκαν.", + "webdavMissingConfig": "Συμπληρώστε πρώτα το URL του φακέλου WebDAV.", + "webdavPermissionDenied": "Δεν δόθηκε άδεια host για αυτόν τον διακομιστή WebDAV.", + "webdavBackupCompleted": "Το αντίγραφο ασφαλείας μεταφορτώθηκε επιτυχώς στο WebDAV!", + "webdavDownloadFailed": "Αποτυχία λήψης αντιγράφου ασφαλείας από το WebDAV: ", + "webdavUploadFailed": "Αποτυχία μεταφόρτωσης αντιγράφου ασφαλείας στο WebDAV: ", + "webdavRestoreConfirm": "Επαναφορά ρυθμίσεων από το πιο πρόσφατο αντίγραφο ασφαλείας WebDAV; Τα τρέχοντα τοπικά δεδομένα θα αντικατασταθούν.", + "webdavLatestPointerMissing": "Δεν βρέθηκε εγγραφή του πιο πρόσφατου αντιγράφου ασφαλείας στον φάκελο MYNT." }; diff --git a/locales/en.js b/locales/en.js index c45a7cc7..594ec115 100644 --- a/locales/en.js +++ b/locales/en.js @@ -206,7 +206,40 @@ const en = { "restorecompleted": "Restore completed successfully!", "restorefailed": "Restore failed: ", "invalidBackup": "Invalid backup file selected.", + "webdavTitle": "WebDAV Sync", + "webdavBackupText": "Backup to WebDAV", + "webdavRestoreText": "Restore from WebDAV", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "webdavFileNamePlaceholder": "mynt-backup.json", + "webdavUsernamePlaceholder": "Username (optional)", + "webdavPasswordPlaceholder": "Password or app password (optional)", + "webdavMissingConfig": "Please fill in the WebDAV URL and file name first.", + "webdavPermissionDenied": "Host permission was not granted for this WebDAV server.", + "webdavBackupCompleted": "Backup uploaded to WebDAV successfully!", + "webdavDownloadFailed": "Failed to download backup from WebDAV: ", + "webdavUploadFailed": "Failed to upload backup to WebDAV: ", + "webdavRestoreConfirm": "Restore settings from WebDAV? Your current local data will be overwritten.", "deleteBookmark": "Are you sure you want to delete the bookmark \"{title}\"?", // Do not translate {title} "UnsupportedBrowser": "Bookmarks are not supported in your browser.", - "resetShortcutsPrompt": "All saved shortcuts will be deleted and reset to default. Do you want to continue?" -}; + "resetShortcutsPrompt": "All saved shortcuts will be deleted and reset to default. Do you want to continue?", + "cloudBackupSectionTitle": "Cloud Backup", + "webdavTitle": "WebDAV Sync", + "webdavBackupText": "Backup to WebDAV", + "webdavRestoreText": "Restore Latest", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Username (optional)", + "webdavPasswordPlaceholder": "Password or app password (optional)", + "cloudAutoBackupText": "Enable automatic backup", + "cloudIntervalPlaceholder": "Hours", + "cloudIntervalHours": "Hours", + "saveBackupConfig": "Save Backup Config", + "backupConfigSaved": "Backup configuration saved.", + "webdavMissingConfig": "Please fill in the WebDAV folder URL first.", + "webdavPermissionDenied": "Host permission was not granted for this WebDAV server.", + "webdavBackupCompleted": "Backup uploaded to WebDAV successfully!", + "webdavDownloadFailed": "Failed to download backup from WebDAV: ", + "webdavUploadFailed": "Failed to upload backup to WebDAV: ", + "webdavRestoreConfirm": "Restore settings from the latest WebDAV backup? Your current local data will be overwritten.", + "webdavLatestPointerMissing": "No latest backup record was found in the MYNT folder." +}; \ No newline at end of file diff --git a/locales/es.js b/locales/es.js index ea9ff245..c0873b09 100644 --- a/locales/es.js +++ b/locales/es.js @@ -208,4 +208,24 @@ const es = { "deleteBookmark": "¿Estás seguro de que deseas eliminar el marcador \"{title}\"?", "UnsupportedBrowser": "Los marcadores no son compatibles con tu navegador", "resetShortcutsPrompt": "Todos los marcadores guardados serán eliminados y restablecidos ¿Deseas continuar?", + "cloudBackupSectionTitle": "Copia en la nube", + "webdavTitle": "Sincronización WebDAV", + "webdavBackupText": "Respaldar en WebDAV", + "webdavRestoreText": "Restaurar el más reciente", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Nombre de usuario (opcional)", + "webdavPasswordPlaceholder": "Contraseña o contraseña de aplicación (opcional)", + "cloudAutoBackupText": "Activar copia automática", + "cloudIntervalPlaceholder": "Horas", + "cloudIntervalHours": "Horas", + "saveBackupConfig": "Guardar configuración de copia", + "backupConfigSaved": "Configuración de copia guardada.", + "webdavMissingConfig": "Completa primero la URL de la carpeta WebDAV.", + "webdavPermissionDenied": "No se concedió permiso de host para este servidor WebDAV.", + "webdavBackupCompleted": "¡Copia subida a WebDAV correctamente!", + "webdavDownloadFailed": "Error al descargar la copia desde WebDAV: ", + "webdavUploadFailed": "Error al subir la copia a WebDAV: ", + "webdavRestoreConfirm": "¿Restaurar la configuración desde la copia WebDAV más reciente? Tus datos locales actuales se sobrescribirán.", + "webdavLatestPointerMissing": "No se encontró un registro de la copia más reciente en la carpeta MYNT." }; diff --git a/locales/fa.js b/locales/fa.js index ea46aee6..aaa5f143 100644 --- a/locales/fa.js +++ b/locales/fa.js @@ -220,5 +220,25 @@ const fa = { invalidBackup: "فایل پشتیبان نامعتبر انتخاب شده است.", deleteBookmark: 'آیا مطمئن هستید که می‌خواهید نشانک "{title}" را حذف کنید؟', UnsupportedBrowser: "نشانک‌ها در مرورگر شما پشتیبانی نمی‌شوند.", - resetShortcutsPrompt: "همه میانبرهای ذخیره‌شده حذف و به حالت پیش‌فرض بازنشانی خواهند شد. آیا می‌خواهید ادامه دهید؟" + resetShortcutsPrompt: "همه میانبرهای ذخیره‌شده حذف و به حالت پیش‌فرض بازنشانی خواهند شد. آیا می‌خواهید ادامه دهید؟", + "cloudBackupSectionTitle": "پشتیبان‌گیری ابری", + "webdavTitle": "همگام‌سازی WebDAV", + "webdavBackupText": "پشتیبان‌گیری در WebDAV", + "webdavRestoreText": "بازیابی آخرین نسخه", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "نام کاربری (اختیاری)", + "webdavPasswordPlaceholder": "رمز عبور یا رمز عبور برنامه (اختیاری)", + "cloudAutoBackupText": "فعال‌سازی پشتیبان‌گیری خودکار", + "cloudIntervalPlaceholder": "ساعت", + "cloudIntervalHours": "ساعت", + "saveBackupConfig": "ذخیره تنظیمات پشتیبان", + "backupConfigSaved": "تنظیمات پشتیبان ذخیره شد.", + "webdavMissingConfig": "ابتدا URL پوشه WebDAV را وارد کنید.", + "webdavPermissionDenied": "مجوز میزبان برای این سرور WebDAV داده نشده است.", + "webdavBackupCompleted": "پشتیبان با موفقیت در WebDAV بارگذاری شد!", + "webdavDownloadFailed": "دانلود پشتیبان از WebDAV ناموفق بود: ", + "webdavUploadFailed": "بارگذاری پشتیبان در WebDAV ناموفق بود: ", + "webdavRestoreConfirm": "تنظیمات از جدیدترین پشتیبان WebDAV بازیابی شود؟ داده‌های محلی فعلی شما بازنویسی می‌شوند.", + "webdavLatestPointerMissing": "رکورد جدیدترین پشتیبان در پوشه MYNT پیدا نشد." }; diff --git a/locales/fr.js b/locales/fr.js index 6eed63b9..4b768e54 100644 --- a/locales/fr.js +++ b/locales/fr.js @@ -198,5 +198,25 @@ const fr = { "invalidBackup": "Fichier de sauvegarde invalide", "deleteBookmark": "Êtes-vous sûr de vouloir supprimer le favori \"{title}\"?", "UnsupportedBrowser": "Les favoris ne sont pas supportés dans votre navigateur.", - "resetShortcutsPrompt": "Tous les raccourcis enregistrés seront supprimés et réinitialisés par défaut. Voulez-vous continuer?" -}; \ No newline at end of file + "resetShortcutsPrompt": "Tous les raccourcis enregistrés seront supprimés et réinitialisés par défaut. Voulez-vous continuer?", + "cloudBackupSectionTitle": "Sauvegarde cloud", + "webdavTitle": "Synchronisation WebDAV", + "webdavBackupText": "Sauvegarder sur WebDAV", + "webdavRestoreText": "Restaurer la plus récente", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Nom d'utilisateur (optionnel)", + "webdavPasswordPlaceholder": "Mot de passe ou mot de passe d’application (optionnel)", + "cloudAutoBackupText": "Activer la sauvegarde automatique", + "cloudIntervalPlaceholder": "Heures", + "cloudIntervalHours": "Heures", + "saveBackupConfig": "Enregistrer la configuration de sauvegarde", + "backupConfigSaved": "Configuration de sauvegarde enregistrée.", + "webdavMissingConfig": "Veuillez d’abord remplir l’URL du dossier WebDAV.", + "webdavPermissionDenied": "L’autorisation d’hôte n’a pas été accordée pour ce serveur WebDAV.", + "webdavBackupCompleted": "Sauvegarde envoyée vers WebDAV avec succès !", + "webdavDownloadFailed": "Échec du téléchargement depuis WebDAV : ", + "webdavUploadFailed": "Échec de l’envoi vers WebDAV : ", + "webdavRestoreConfirm": "Restaurer les paramètres depuis la sauvegarde WebDAV la plus récente ? Vos données locales actuelles seront écrasées.", + "webdavLatestPointerMissing": "Aucune trace de la sauvegarde la plus récente n’a été trouvée dans le dossier MYNT." +}; diff --git a/locales/hi.js b/locales/hi.js index 1c137350..f438e38e 100644 --- a/locales/hi.js +++ b/locales/hi.js @@ -210,5 +210,25 @@ const hi = { "invalidBackup": "अमान्य बैकअप फ़ाइल चयनित है।", "deleteBookmark": "क्या आप \"{title}\" बुकमार्क डिलीट करना चाहते हैं?", "UnsupportedBrowser": "आपके ब्राउज़र में बुकमार्क समर्थित नहीं हैं।", - "resetShortcutsPrompt": "सभी सहेजे गए शॉर्टकट हटा दिए जाएंगे और डिफ़ॉल्ट पर रीसेट हो जाएंगे। क्या आप जारी रखना चाहते हैं?" + "resetShortcutsPrompt": "सभी सहेजे गए शॉर्टकट हटा दिए जाएंगे और डिफ़ॉल्ट पर रीसेट हो जाएंगे। क्या आप जारी रखना चाहते हैं?", + "cloudBackupSectionTitle": "क्लाउड बैकअप", + "webdavTitle": "WebDAV सिंक", + "webdavBackupText": "WebDAV में बैकअप", + "webdavRestoreText": "नवीनतम पुनर्स्थापित करें", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "उपयोगकर्ता नाम (वैकल्पिक)", + "webdavPasswordPlaceholder": "पासवर्ड या ऐप पासवर्ड (वैकल्पिक)", + "cloudAutoBackupText": "स्वचालित बैकअप सक्षम करें", + "cloudIntervalPlaceholder": "घंटे", + "cloudIntervalHours": "घंटे", + "saveBackupConfig": "बैकअप कॉन्फ़िगरेशन सहेजें", + "backupConfigSaved": "बैकअप कॉन्फ़िगरेशन सहेजा गया।", + "webdavMissingConfig": "पहले WebDAV फ़ोल्डर URL भरें।", + "webdavPermissionDenied": "इस WebDAV सर्वर के लिए होस्ट अनुमति नहीं दी गई।", + "webdavBackupCompleted": "बैकअप सफलतापूर्वक WebDAV पर अपलोड हो गया!", + "webdavDownloadFailed": "WebDAV से बैकअप डाउनलोड नहीं हो सका: ", + "webdavUploadFailed": "WebDAV पर बैकअप अपलोड नहीं हो सका: ", + "webdavRestoreConfirm": "क्या नवीनतम WebDAV बैकअप से सेटिंग्स पुनर्स्थापित करनी हैं? आपका वर्तमान स्थानीय डेटा अधिलेखित हो जाएगा।", + "webdavLatestPointerMissing": "MYNT फ़ोल्डर में नवीनतम बैकअप का रिकॉर्ड नहीं मिला।" }; diff --git a/locales/hu.js b/locales/hu.js index bb34ed21..19b75517 100644 --- a/locales/hu.js +++ b/locales/hu.js @@ -212,5 +212,25 @@ const hu = { "invalidBackup": "A választott mentési fájl érvénytelen.", "deleteBookmark": "Biztos, hogy törölni szeretné a könyvjelzőt '{title}'?", "UnsupportedBrowser": "A könyvjelzők nem támogatottak a böngészőjében", - "resetShortcutsPrompt": "Az összes mentett parancsikon törlésre kerül és visszaállításra kerülnek az alapértelmezett beállítások. Szeretné folytatni?" + "resetShortcutsPrompt": "Az összes mentett parancsikon törlésre kerül és visszaállításra kerülnek az alapértelmezett beállítások. Szeretné folytatni?", + "cloudBackupSectionTitle": "Felhőmentés", + "webdavTitle": "WebDAV szinkronizálás", + "webdavBackupText": "Mentés WebDAV-ra", + "webdavRestoreText": "Legutóbbi visszaállítása", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Felhasználónév (opcionális)", + "webdavPasswordPlaceholder": "Jelszó vagy alkalmazásjelszó (opcionális)", + "cloudAutoBackupText": "Automatikus mentés engedélyezése", + "cloudIntervalPlaceholder": "Óra", + "cloudIntervalHours": "Óra", + "saveBackupConfig": "Mentési beállítások mentése", + "backupConfigSaved": "A mentési beállítások elmentve.", + "webdavMissingConfig": "Először töltse ki a WebDAV mappa URL-jét.", + "webdavPermissionDenied": "Ehhez a WebDAV szerverhez nem kapott hosztengedélyt.", + "webdavBackupCompleted": "A mentés sikeresen feltöltve a WebDAV-ra!", + "webdavDownloadFailed": "A mentés letöltése a WebDAV-ról nem sikerült: ", + "webdavUploadFailed": "A mentés feltöltése a WebDAV-ra nem sikerült: ", + "webdavRestoreConfirm": "Visszaállítja a beállításokat a legutóbbi WebDAV mentésből? A jelenlegi helyi adatok felülíródnak.", + "webdavLatestPointerMissing": "Nem található a legutóbbi mentés bejegyzése a MYNT mappában." }; diff --git a/locales/idn.js b/locales/idn.js index a8bbb8f8..2855ef3d 100644 --- a/locales/idn.js +++ b/locales/idn.js @@ -207,5 +207,25 @@ const idn = { "invalidBackup": "File cadangan yang dipilih tidak valid", "deleteBookmark": "Apakah Anda yakin untuk menghapus markah buku \"{title}\"?", "UnsupportedBrowser": "Markah buku tidak didukung oleh browser Anda", - "resetShortcutsPrompt": "Semua pintasan yang tersimpan akan terhapus dan diatur ke default. Apakah Anda ingin melanjutkannya?" + "resetShortcutsPrompt": "Semua pintasan yang tersimpan akan terhapus dan diatur ke default. Apakah Anda ingin melanjutkannya?", + "cloudBackupSectionTitle": "Cadangan cloud", + "webdavTitle": "Sinkronisasi WebDAV", + "webdavBackupText": "Cadangkan ke WebDAV", + "webdavRestoreText": "Pulihkan yang terbaru", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Nama pengguna (opsional)", + "webdavPasswordPlaceholder": "Kata sandi atau kata sandi aplikasi (opsional)", + "cloudAutoBackupText": "Aktifkan cadangan otomatis", + "cloudIntervalPlaceholder": "Jam", + "cloudIntervalHours": "Jam", + "saveBackupConfig": "Simpan konfigurasi cadangan", + "backupConfigSaved": "Konfigurasi cadangan disimpan.", + "webdavMissingConfig": "Isi URL folder WebDAV terlebih dahulu.", + "webdavPermissionDenied": "Izin host belum diberikan untuk server WebDAV ini.", + "webdavBackupCompleted": "Cadangan berhasil diunggah ke WebDAV!", + "webdavDownloadFailed": "Gagal mengunduh cadangan dari WebDAV: ", + "webdavUploadFailed": "Gagal mengunggah cadangan ke WebDAV: ", + "webdavRestoreConfirm": "Pulihkan pengaturan dari cadangan WebDAV terbaru? Data lokal Anda saat ini akan ditimpa.", + "webdavLatestPointerMissing": "Catatan cadangan terbaru tidak ditemukan di folder MYNT." }; diff --git a/locales/it.js b/locales/it.js index cfe15046..ee366cfc 100644 --- a/locales/it.js +++ b/locales/it.js @@ -185,4 +185,24 @@ const it = { "invalidBackup": "File di backup non valido", "deleteBookmark": "Sei sicuro di voler eliminare il segnalibro", "UnsupportedBrowser": "I segnalibri non sono supportati nel tuo browser \"{title}\"?", -}; \ No newline at end of file + "cloudBackupSectionTitle": "Backup cloud", + "webdavTitle": "Sincronizzazione WebDAV", + "webdavBackupText": "Esegui backup su WebDAV", + "webdavRestoreText": "Ripristina il più recente", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Nome utente (opzionale)", + "webdavPasswordPlaceholder": "Password o password app (opzionale)", + "cloudAutoBackupText": "Attiva backup automatico", + "cloudIntervalPlaceholder": "Ore", + "cloudIntervalHours": "Ore", + "saveBackupConfig": "Salva configurazione backup", + "backupConfigSaved": "Configurazione backup salvata.", + "webdavMissingConfig": "Compila prima l'URL della cartella WebDAV.", + "webdavPermissionDenied": "L'autorizzazione host non è stata concessa per questo server WebDAV.", + "webdavBackupCompleted": "Backup caricato su WebDAV con successo!", + "webdavDownloadFailed": "Download del backup da WebDAV non riuscito: ", + "webdavUploadFailed": "Caricamento del backup su WebDAV non riuscito: ", + "webdavRestoreConfirm": "Ripristinare le impostazioni dal backup WebDAV più recente? I dati locali correnti verranno sovrascritti.", + "webdavLatestPointerMissing": "Nessun record del backup più recente trovato nella cartella MYNT." +}; diff --git a/locales/ja.js b/locales/ja.js index 15869472..3d240e61 100644 --- a/locales/ja.js +++ b/locales/ja.js @@ -199,5 +199,25 @@ const ja = { "invalidBackup": "このバックアップファイルは無効です。", "deleteBookmark": "ブックマーク「{title}」を削除しますか?", "UnsupportedBrowser": "お使いのブラウザではブックマークはサポートされていません", - "resetShortcutsPrompt": "すべての保存済みショートカットが削除されデフォルトにリセットされます。続行しますか?" + "resetShortcutsPrompt": "すべての保存済みショートカットが削除されデフォルトにリセットされます。続行しますか?", + "cloudBackupSectionTitle": "クラウドバックアップ", + "webdavTitle": "WebDAV 同期", + "webdavBackupText": "WebDAV にバックアップ", + "webdavRestoreText": "最新を復元", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "ユーザー名(任意)", + "webdavPasswordPlaceholder": "パスワードまたはアプリパスワード(任意)", + "cloudAutoBackupText": "自動バックアップを有効にする", + "cloudIntervalPlaceholder": "時間", + "cloudIntervalHours": "時間", + "saveBackupConfig": "バックアップ設定を保存", + "backupConfigSaved": "バックアップ設定を保存しました。", + "webdavMissingConfig": "最初に WebDAV フォルダーの URL を入力してください。", + "webdavPermissionDenied": "この WebDAV サーバーのホスト権限が付与されていません。", + "webdavBackupCompleted": "バックアップを WebDAV に正常にアップロードしました。", + "webdavDownloadFailed": "WebDAV からバックアップをダウンロードできませんでした: ", + "webdavUploadFailed": "WebDAV へのバックアップのアップロードに失敗しました: ", + "webdavRestoreConfirm": "最新の WebDAV バックアップから設定を復元しますか?現在のローカルデータは上書きされます。", + "webdavLatestPointerMissing": "MYNT フォルダーに最新バックアップの記録が見つかりませんでした。" }; diff --git a/locales/ko.js b/locales/ko.js index db42307f..50553232 100644 --- a/locales/ko.js +++ b/locales/ko.js @@ -194,5 +194,25 @@ const ko = { "invalidBackup": "잘못된 백업 파일이에요.", "deleteBookmark": "\"{title}\" 즐겨찾기를 삭제할까요?", "UnsupportedBrowser": "이 브라우저에서는 즐겨찾기를 지원하지 않아요.", - "resetShortcutsPrompt": "저장된 모든 바로가기가 삭제되고 기본값으로 초기화돼요. 계속할까요?" + "resetShortcutsPrompt": "저장된 모든 바로가기가 삭제되고 기본값으로 초기화돼요. 계속할까요?", + "cloudBackupSectionTitle": "클라우드 백업", + "webdavTitle": "WebDAV 동기화", + "webdavBackupText": "WebDAV에 백업", + "webdavRestoreText": "최신 백업 복원", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "사용자 이름(선택 사항)", + "webdavPasswordPlaceholder": "비밀번호 또는 앱 비밀번호(선택 사항)", + "cloudAutoBackupText": "자동 백업 사용", + "cloudIntervalPlaceholder": "시간", + "cloudIntervalHours": "시간", + "saveBackupConfig": "백업 설정 저장", + "backupConfigSaved": "백업 설정이 저장되었습니다.", + "webdavMissingConfig": "먼저 WebDAV 폴더 URL을 입력하세요.", + "webdavPermissionDenied": "이 WebDAV 서버에 대한 호스트 권한이 부여되지 않았습니다.", + "webdavBackupCompleted": "백업이 WebDAV에 성공적으로 업로드되었습니다!", + "webdavDownloadFailed": "WebDAV에서 백업을 다운로드하지 못했습니다: ", + "webdavUploadFailed": "WebDAV에 백업을 업로드하지 못했습니다: ", + "webdavRestoreConfirm": "가장 최근 WebDAV 백업에서 설정을 복원하시겠습니까? 현재 로컬 데이터가 덮어써집니다.", + "webdavLatestPointerMissing": "MYNT 폴더에서 최신 백업 기록을 찾을 수 없습니다." }; diff --git a/locales/mr.js b/locales/mr.js index f5a8254a..5e515a90 100644 --- a/locales/mr.js +++ b/locales/mr.js @@ -158,4 +158,24 @@ const mr = { "invalidBackup": "अवैध बॅकअप फाइल निवडली आहे.", "deleteBookmark": "तुम्हाला \"{title}\" बुकमार्क डिलीट करायचा आहे का?", "UnsupportedBrowser": "तुमच्या ब्राउझरमध्ये बुकमार्क्सला समर्थन नाही.", + "cloudBackupSectionTitle": "क्लाउड बॅकअप", + "webdavTitle": "WebDAV समक्रमण", + "webdavBackupText": "WebDAV वर बॅकअप", + "webdavRestoreText": "सर्वात अलीकडचे पुनर्स्थापित करा", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "वापरकर्ता नाव (पर्यायी)", + "webdavPasswordPlaceholder": "पासवर्ड किंवा अॅप पासवर्ड (पर्यायी)", + "cloudAutoBackupText": "स्वयंचलित बॅकअप सक्षम करा", + "cloudIntervalPlaceholder": "तास", + "cloudIntervalHours": "तास", + "saveBackupConfig": "बॅकअप संरचना जतन करा", + "backupConfigSaved": "बॅकअप संरचना जतन केली.", + "webdavMissingConfig": "कृपया आधी WebDAV फोल्डर URL भरा.", + "webdavPermissionDenied": "या WebDAV सर्व्हरसाठी होस्ट परवानगी दिली गेली नाही.", + "webdavBackupCompleted": "बॅकअप यशस्वीरीत्या WebDAV वर अपलोड झाला!", + "webdavDownloadFailed": "WebDAV वरून बॅकअप डाउनलोड करता आला नाही: ", + "webdavUploadFailed": "WebDAV वर बॅकअप अपलोड करता आला नाही: ", + "webdavRestoreConfirm": "सर्वात अलीकडील WebDAV बॅकअपमधून सेटिंग्ज पुनर्स्थापित करायच्या? तुमचा सध्याचा स्थानिक डेटा अधिलेखित होईल.", + "webdavLatestPointerMissing": "MYNT फोल्डरमध्ये सर्वात अलीकडील बॅकअपची नोंद सापडली नाही." }; diff --git a/locales/np.js b/locales/np.js index d5a6180c..235fdddf 100644 --- a/locales/np.js +++ b/locales/np.js @@ -132,5 +132,25 @@ const np = { "restorefailed": "ब्याकअप असफल भयो: ", "invalidBackup": "अवैध ब्याकअप फाइल", "deleteBookmark": "के तपाइँ निश्चित रूपमा \"{title}\" बुकमार्क मेटाउन चाहनुहुन्छ?", - "UnsupportedBrowser": "बुकमार्कहरू तपाईंको ब्राउजरमा समर्थित छैनन्" + "UnsupportedBrowser": "बुकमार्कहरू तपाईंको ब्राउजरमा समर्थित छैनन्", + "cloudBackupSectionTitle": "क्लाउड ब्याकअप", + "webdavTitle": "WebDAV सिङ्क", + "webdavBackupText": "WebDAV मा ब्याकअप", + "webdavRestoreText": "नवीनतम पुनर्स्थापना", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "प्रयोगकर्ता नाम (वैकल्पिक)", + "webdavPasswordPlaceholder": "पासवर्ड वा एप पासवर्ड (वैकल्पिक)", + "cloudAutoBackupText": "स्वचालित ब्याकअप सक्षम गर्नुहोस्", + "cloudIntervalPlaceholder": "घण्टा", + "cloudIntervalHours": "घण्टा", + "saveBackupConfig": "ब्याकअप कन्फिगरेसन बचत गर्नुहोस्", + "backupConfigSaved": "ब्याकअप कन्फिगरेसन बचत भयो।", + "webdavMissingConfig": "पहिले WebDAV फोल्डर URL भर्नुहोस्।", + "webdavPermissionDenied": "यस WebDAV सर्भरका लागि होस्ट अनुमति दिइएको छैन।", + "webdavBackupCompleted": "ब्याकअप सफलतापूर्वक WebDAV मा अपलोड भयो!", + "webdavDownloadFailed": "WebDAV बाट ब्याकअप डाउनलोड गर्न सकिएन: ", + "webdavUploadFailed": "WebDAV मा ब्याकअप अपलोड गर्न सकिएन: ", + "webdavRestoreConfirm": "सबैभन्दा नयाँ WebDAV ब्याकअपबाट सेटिङहरू पुनर्स्थापना गर्ने? हालको स्थानीय डेटा अधिलेखित हुनेछ।", + "webdavLatestPointerMissing": "MYNT फोल्डरमा सबैभन्दा नयाँ ब्याकअपको रेकर्ड फेला परेन।" }; diff --git a/locales/pl.js b/locales/pl.js index 7812e2f1..6895dbd9 100644 --- a/locales/pl.js +++ b/locales/pl.js @@ -194,5 +194,25 @@ const pl = { "invalidBackup": "Wybrano nieprawidłowy plik kopii zapasowej.", "deleteBookmark": "Czy na pewno chcesz usunąć zakładkę \"{title}\"?", "UnsupportedBrowser": "Zakładki nie są obsługiwane w twojej przeglądarce.", - "resetShortcutsPrompt": "Wszystkie zapisane skróty zostaną usunięte i zresetowane do wartości domyślnych. Czy chcesz kontynuować?" + "resetShortcutsPrompt": "Wszystkie zapisane skróty zostaną usunięte i zresetowane do wartości domyślnych. Czy chcesz kontynuować?", + "cloudBackupSectionTitle": "Kopia w chmurze", + "webdavTitle": "Synchronizacja WebDAV", + "webdavBackupText": "Utwórz kopię w WebDAV", + "webdavRestoreText": "Przywróć najnowszą", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Nazwa użytkownika (opcjonalnie)", + "webdavPasswordPlaceholder": "Hasło lub hasło aplikacji (opcjonalnie)", + "cloudAutoBackupText": "Włącz automatyczne kopie", + "cloudIntervalPlaceholder": "Godziny", + "cloudIntervalHours": "Godziny", + "saveBackupConfig": "Zapisz konfigurację kopii", + "backupConfigSaved": "Konfiguracja kopii została zapisana.", + "webdavMissingConfig": "Najpierw uzupełnij adres URL folderu WebDAV.", + "webdavPermissionDenied": "Nie przyznano uprawnienia hosta dla tego serwera WebDAV.", + "webdavBackupCompleted": "Kopia została pomyślnie wysłana do WebDAV!", + "webdavDownloadFailed": "Nie udało się pobrać kopii z WebDAV: ", + "webdavUploadFailed": "Nie udało się wysłać kopii do WebDAV: ", + "webdavRestoreConfirm": "Przywrócić ustawienia z najnowszej kopii WebDAV? Obecne lokalne dane zostaną nadpisane.", + "webdavLatestPointerMissing": "Nie znaleziono wpisu najnowszej kopii w folderze MYNT." }; diff --git a/locales/pt.js b/locales/pt.js index 31fd6e79..dea4e6a6 100644 --- a/locales/pt.js +++ b/locales/pt.js @@ -192,4 +192,24 @@ const pt = { "invalidBackup": "Arquivo de backup inválido", "deleteBookmark": "Você tem certeza de que deseja excluir o favorito \"{title}\"?", "UnsupportedBrowser": "Bookmarks não são suportados no seu navegador.", + "cloudBackupSectionTitle": "Backup na nuvem", + "webdavTitle": "Sincronização WebDAV", + "webdavBackupText": "Fazer backup no WebDAV", + "webdavRestoreText": "Restaurar backup mais recente", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Nome de usuário (opcional)", + "webdavPasswordPlaceholder": "Senha ou senha de app (opcional)", + "cloudAutoBackupText": "Ativar backup automático", + "cloudIntervalPlaceholder": "Horas", + "cloudIntervalHours": "Horas", + "saveBackupConfig": "Salvar configuração de backup", + "backupConfigSaved": "Configuração de backup salva.", + "webdavMissingConfig": "Preencha primeiro a URL da pasta WebDAV.", + "webdavPermissionDenied": "A permissão de host não foi concedida para este servidor WebDAV.", + "webdavBackupCompleted": "Backup enviado para o WebDAV com sucesso!", + "webdavDownloadFailed": "Falha ao baixar o backup do WebDAV: ", + "webdavUploadFailed": "Falha ao enviar o backup para o WebDAV: ", + "webdavRestoreConfirm": "Restaurar as configurações do backup WebDAV mais recente? Seus dados locais atuais serão substituídos.", + "webdavLatestPointerMissing": "Nenhum registro do backup mais recente foi encontrado na pasta MYNT." }; diff --git a/locales/ru.js b/locales/ru.js index 636f2790..a262ff06 100644 --- a/locales/ru.js +++ b/locales/ru.js @@ -207,5 +207,25 @@ const ru = { "invalidBackup": "Недопустимый файл резервной копии.", "deleteBookmark": "Вы уверены, что хотите удалить эту закладку \"{title}\"?", "UnsupportedBrowser": "Закладки не поддерживаются в вашем браузере.", - "resetShortcutsPrompt": "Все сохранённые ярлыки будут удалены, и их значение будет восстановлено по умолчанию. Хотите продолжить?" + "resetShortcutsPrompt": "Все сохранённые ярлыки будут удалены, и их значение будет восстановлено по умолчанию. Хотите продолжить?", + "cloudBackupSectionTitle": "Облачное резервное копирование", + "webdavTitle": "Синхронизация WebDAV", + "webdavBackupText": "Сохранить в WebDAV", + "webdavRestoreText": "Восстановить последнее", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Имя пользователя (необязательно)", + "webdavPasswordPlaceholder": "Пароль или пароль приложения (необязательно)", + "cloudAutoBackupText": "Включить автоматическое резервное копирование", + "cloudIntervalPlaceholder": "Часы", + "cloudIntervalHours": "Часы", + "saveBackupConfig": "Сохранить настройки резервного копирования", + "backupConfigSaved": "Настройки резервного копирования сохранены.", + "webdavMissingConfig": "Сначала заполните URL папки WebDAV.", + "webdavPermissionDenied": "Для этого сервера WebDAV не выдано разрешение хоста.", + "webdavBackupCompleted": "Резервная копия успешно загружена в WebDAV!", + "webdavDownloadFailed": "Не удалось скачать резервную копию из WebDAV: ", + "webdavUploadFailed": "Не удалось загрузить резервную копию в WebDAV: ", + "webdavRestoreConfirm": "Восстановить настройки из последней резервной копии WebDAV? Текущие локальные данные будут перезаписаны.", + "webdavLatestPointerMissing": "В папке MYNT не найдена запись о последней резервной копии." }; diff --git a/locales/sl.js b/locales/sl.js index e716ffde..4cafcdbb 100644 --- a/locales/sl.js +++ b/locales/sl.js @@ -132,4 +132,24 @@ const sl = { "invalidBackup": "Neveljavna datoteka za varnostno kopijo", "deleteBookmark": "Ali ste prepričani, da želite odstraniti zaznamek \"{title}\"?", // "UnsupportedBrowser": "Bookmarks are not supported in your browser", + "cloudBackupSectionTitle": "Varnostna kopija v oblaku", + "webdavTitle": "Sinhronizacija WebDAV", + "webdavBackupText": "Varnostno kopiraj v WebDAV", + "webdavRestoreText": "Obnovi najnovejše", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Uporabniško ime (neobvezno)", + "webdavPasswordPlaceholder": "Geslo ali geslo aplikacije (neobvezno)", + "cloudAutoBackupText": "Omogoči samodejno varnostno kopiranje", + "cloudIntervalPlaceholder": "Ure", + "cloudIntervalHours": "Ure", + "saveBackupConfig": "Shrani nastavitve varnostne kopije", + "backupConfigSaved": "Nastavitve varnostne kopije so shranjene.", + "webdavMissingConfig": "Najprej izpolnite URL mape WebDAV.", + "webdavPermissionDenied": "Dovoljenje gostitelja za ta strežnik WebDAV ni bilo odobreno.", + "webdavBackupCompleted": "Varnostna kopija je bila uspešno naložena v WebDAV!", + "webdavDownloadFailed": "Prenos varnostne kopije iz WebDAV ni uspel: ", + "webdavUploadFailed": "Nalaganje varnostne kopije v WebDAV ni uspelo: ", + "webdavRestoreConfirm": "Obnoviti nastavitve iz najnovejše varnostne kopije WebDAV? Trenutni lokalni podatki bodo prepisani.", + "webdavLatestPointerMissing": "V mapi MYNT ni bilo najdene evidence o najnovejši varnostni kopiji." }; diff --git a/locales/ta.js b/locales/ta.js index 21758713..d508bca6 100644 --- a/locales/ta.js +++ b/locales/ta.js @@ -195,4 +195,24 @@ const ta = { "deleteBookmark": "\"{title}\"என்ற புத்தக்குறியை நீக்க விரும்புகிறீர்களா?", "UnsupportedBrowser": "உங்கள் உலாவியில் புத்தக்குறிகள் ஆதரிக்கப்படவில்லை.", "resetShortcutsPrompt": "சேமிக்கப்பட்ட அனைத்து குறுக்குவழிகளும் நீக்கப்பட்டு இயல்புநிலைக்கு மீட்டமைக்கப்படும். நீங்கள் தொடர விரும்புகிறீர்களா?", + "cloudBackupSectionTitle": "மேக காப்புப்பிரதி", + "webdavTitle": "WebDAV ஒத்திசைவு", + "webdavBackupText": "WebDAV இல் காப்புப்பிரதி எடுக்க", + "webdavRestoreText": "சமீபத்தியதை மீட்டெடு", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "பயனர் பெயர் (விருப்பம்)", + "webdavPasswordPlaceholder": "கடவுச்சொல் அல்லது ஆப் கடவுச்சொல் (விருப்பம்)", + "cloudAutoBackupText": "தானியங்கி காப்புப்பிரதியை இயக்கு", + "cloudIntervalPlaceholder": "மணிநேரம்", + "cloudIntervalHours": "மணிநேரம்", + "saveBackupConfig": "காப்புப்பிரதி அமைப்பை சேமிக்க", + "backupConfigSaved": "காப்புப்பிரதி அமைப்பு சேமிக்கப்பட்டது.", + "webdavMissingConfig": "முதலில் WebDAV கோப்புறை URL ஐ நிரப்பவும்.", + "webdavPermissionDenied": "இந்த WebDAV சேவையகத்திற்கு ஹோஸ்ட் அனுமதி வழங்கப்படவில்லை.", + "webdavBackupCompleted": "காப்புப்பிரதி வெற்றிகரமாக WebDAV இல் பதிவேற்றப்பட்டது!", + "webdavDownloadFailed": "WebDAV இலிருந்து காப்புப்பிரதியை பதிவிறக்க முடியவில்லை: ", + "webdavUploadFailed": "WebDAV இல் காப்புப்பிரதியை பதிவேற்ற முடியவில்லை: ", + "webdavRestoreConfirm": "சமீபத்திய WebDAV காப்புப்பிரதியிலிருந்து அமைப்புகளை மீட்டெடுக்கவா? உங்கள் தற்போதைய உள்ளூர் தரவு மேலெழுதப்படும்.", + "webdavLatestPointerMissing": "MYNT கோப்புறையில் சமீபத்திய காப்புப்பிரதி பதிவு கிடைக்கவில்லை." }; diff --git a/locales/th.js b/locales/th.js index 721e58e9..b1eb4325 100644 --- a/locales/th.js +++ b/locales/th.js @@ -193,5 +193,25 @@ const th = { "invalidBackup": "เลือกไฟล์สำรองข้อมูลไม่ถูกต้อง", "deleteBookmark": "คุณแน่ใจหรือไม่ว่าต้องการลบบุ๊กมาร์ก \"{title}\"?", "UnsupportedBrowser": "เบราว์เซอร์ของคุณไม่รองรับบุ๊กมาร์ก", - "resetShortcutsPrompt": "คำสั่งลัดทั้งหมดที่บันทึกไว้จะถูกลบและรีเซ็ตเป็นค่าเริ่มต้น คุณต้องการดำเนินการต่อหรือไม่?" + "resetShortcutsPrompt": "คำสั่งลัดทั้งหมดที่บันทึกไว้จะถูกลบและรีเซ็ตเป็นค่าเริ่มต้น คุณต้องการดำเนินการต่อหรือไม่?", + "cloudBackupSectionTitle": "สำรองข้อมูลบนคลาวด์", + "webdavTitle": "ซิงก์ WebDAV", + "webdavBackupText": "สำรองไปยัง WebDAV", + "webdavRestoreText": "กู้คืนล่าสุด", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "ชื่อผู้ใช้ (ไม่บังคับ)", + "webdavPasswordPlaceholder": "รหัสผ่านหรือรหัสผ่านแอป (ไม่บังคับ)", + "cloudAutoBackupText": "เปิดใช้การสำรองข้อมูลอัตโนมัติ", + "cloudIntervalPlaceholder": "ชั่วโมง", + "cloudIntervalHours": "ชั่วโมง", + "saveBackupConfig": "บันทึกการตั้งค่าการสำรองข้อมูล", + "backupConfigSaved": "บันทึกการตั้งค่าการสำรองข้อมูลแล้ว", + "webdavMissingConfig": "กรุณากรอก URL โฟลเดอร์ WebDAV ก่อน", + "webdavPermissionDenied": "ยังไม่ได้รับสิทธิ์โฮสต์สำหรับเซิร์ฟเวอร์ WebDAV นี้", + "webdavBackupCompleted": "อัปโหลดข้อมูลสำรองไปยัง WebDAV สำเร็จ!", + "webdavDownloadFailed": "ดาวน์โหลดข้อมูลสำรองจาก WebDAV ไม่สำเร็จ: ", + "webdavUploadFailed": "อัปโหลดข้อมูลสำรองไปยัง WebDAV ไม่สำเร็จ: ", + "webdavRestoreConfirm": "กู้คืนการตั้งค่าจากข้อมูลสำรอง WebDAV ล่าสุดหรือไม่? ข้อมูลภายในเครื่องปัจจุบันจะถูกเขียนทับ", + "webdavLatestPointerMissing": "ไม่พบบันทึกข้อมูลสำรองล่าสุดในโฟลเดอร์ MYNT" }; diff --git a/locales/tr.js b/locales/tr.js index 25aa3f9f..31f82012 100644 --- a/locales/tr.js +++ b/locales/tr.js @@ -141,4 +141,24 @@ const tr = { "invalidBackup": "Geçersiz yedekleme dosyası seçildi.", "deleteBookmark": "\"{title}\" yer imini silmek istediğinizden emin misiniz?", "UnsupportedBrowser": "Yer imleri tarayıcınızda desteklenmiyor.", + "cloudBackupSectionTitle": "Bulut yedekleme", + "webdavTitle": "WebDAV senkronizasyonu", + "webdavBackupText": "WebDAV'a yedekle", + "webdavRestoreText": "En yeniyi geri yükle", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Kullanıcı adı (isteğe bağlı)", + "webdavPasswordPlaceholder": "Parola veya uygulama parolası (isteğe bağlı)", + "cloudAutoBackupText": "Otomatik yedeklemeyi etkinleştir", + "cloudIntervalPlaceholder": "Saat", + "cloudIntervalHours": "Saat", + "saveBackupConfig": "Yedekleme yapılandırmasını kaydet", + "backupConfigSaved": "Yedekleme yapılandırması kaydedildi.", + "webdavMissingConfig": "Önce WebDAV klasörü URL'sini doldurun.", + "webdavPermissionDenied": "Bu WebDAV sunucusu için host izni verilmedi.", + "webdavBackupCompleted": "Yedek başarıyla WebDAV'a yüklendi!", + "webdavDownloadFailed": "Yedek WebDAV'dan indirilemedi: ", + "webdavUploadFailed": "Yedek WebDAV'a yüklenemedi: ", + "webdavRestoreConfirm": "Ayarlar en son WebDAV yedeğinden geri yüklensin mi? Geçerli yerel verilerinizin üzerine yazılacak.", + "webdavLatestPointerMissing": "MYNT klasöründe en son yedeğe ait kayıt bulunamadı." }; diff --git a/locales/uk.js b/locales/uk.js index 6722a6dd..26c96906 100644 --- a/locales/uk.js +++ b/locales/uk.js @@ -208,5 +208,25 @@ const uk = { "invalidBackup": "Вибрано недійсний файл резервної копії.", "deleteBookmark": "Ви впевнені, що хочете видалити закладку \"{title}\"?", "UnsupportedBrowser": "Закладки не підтримуються у вашому браузері.", - "resetShortcutsPrompt": "Усі збережені ярлики будуть видалені та скинуті до заводських налаштувань. Чи хочете ви продовжити?" + "resetShortcutsPrompt": "Усі збережені ярлики будуть видалені та скинуті до заводських налаштувань. Чи хочете ви продовжити?", + "cloudBackupSectionTitle": "Хмарне резервне копіювання", + "webdavTitle": "Синхронізація WebDAV", + "webdavBackupText": "Зберегти у WebDAV", + "webdavRestoreText": "Відновити останню", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Ім’я користувача (необов’язково)", + "webdavPasswordPlaceholder": "Пароль або пароль застосунку (необов’язково)", + "cloudAutoBackupText": "Увімкнути автоматичне резервне копіювання", + "cloudIntervalPlaceholder": "Години", + "cloudIntervalHours": "Години", + "saveBackupConfig": "Зберегти налаштування резервного копіювання", + "backupConfigSaved": "Налаштування резервного копіювання збережено.", + "webdavMissingConfig": "Спочатку заповніть URL папки WebDAV.", + "webdavPermissionDenied": "Для цього сервера WebDAV не надано дозвіл хоста.", + "webdavBackupCompleted": "Резервну копію успішно завантажено до WebDAV!", + "webdavDownloadFailed": "Не вдалося завантажити резервну копію з WebDAV: ", + "webdavUploadFailed": "Не вдалося завантажити резервну копію до WebDAV: ", + "webdavRestoreConfirm": "Відновити налаштування з останньої резервної копії WebDAV? Поточні локальні дані буде перезаписано.", + "webdavLatestPointerMissing": "У папці MYNT не знайдено запису про останню резервну копію." }; diff --git a/locales/ur.js b/locales/ur.js index a50482c0..9f9c470f 100644 --- a/locales/ur.js +++ b/locales/ur.js @@ -115,5 +115,25 @@ const ur = { "ProxyDisclaimer": "تمام پراکسی فیچرز ڈیفالٹ طور پر بند ہیں۔\n\nاگر آپ سرچ تجاویز اور CORS بائی پاس پراکسی کو فعال کرتے ہیں تو بہتر رازداری کے لیے اپنی پراکسی کو ہوسٹ کرنا سختی سے مشورہ دیا جاتا ہے۔\n\nڈیفالٹ طور پر پراکسی https://mynt-proxy.rhythmcorehq.com پر سیٹ ہوگی، اس کا مطلب ہے کہ آپ کا سارا ڈیٹا اس سروس کے ذریعے گزرے گا، جو رازداری کے مسائل پیدا کر سکتا ہے۔", "failedbackup": "بیک اپ ناکام ہوگیا: ", "restorecompleted": "بحالی کامیابی سے مکمل ہوگئی!", - "restorefailed": "بحالی ناکام ہوگئی: " + "restorefailed": "بحالی ناکام ہوگئی: ", + "cloudBackupSectionTitle": "کلاؤڈ بیک اپ", + "webdavTitle": "WebDAV ہم آہنگی", + "webdavBackupText": "WebDAV پر بیک اپ", + "webdavRestoreText": "تازہ ترین بحال کریں", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "صارف نام (اختیاری)", + "webdavPasswordPlaceholder": "پاس ورڈ یا ایپ پاس ورڈ (اختیاری)", + "cloudAutoBackupText": "خودکار بیک اپ فعال کریں", + "cloudIntervalPlaceholder": "گھنٹے", + "cloudIntervalHours": "گھنٹے", + "saveBackupConfig": "بیک اپ کی ترتیب محفوظ کریں", + "backupConfigSaved": "بیک اپ کی ترتیب محفوظ ہو گئی۔", + "webdavMissingConfig": "پہلے WebDAV فولڈر کا URL درج کریں۔", + "webdavPermissionDenied": "اس WebDAV سرور کے لیے ہوسٹ اجازت نہیں دی گئی۔", + "webdavBackupCompleted": "بیک اپ کامیابی سے WebDAV پر اپ لوڈ ہو گیا!", + "webdavDownloadFailed": "WebDAV سے بیک اپ ڈاؤن لوڈ نہیں ہو سکا: ", + "webdavUploadFailed": "WebDAV پر بیک اپ اپ لوڈ نہیں ہو سکا: ", + "webdavRestoreConfirm": "کیا تازہ ترین WebDAV بیک اپ سے سیٹنگز بحال کی جائیں؟ آپ کا موجودہ مقامی ڈیٹا اووررائٹ ہو جائے گا۔", + "webdavLatestPointerMissing": "MYNT فولڈر میں تازہ ترین بیک اپ کا ریکارڈ نہیں ملا۔" }; diff --git a/locales/uz.js b/locales/uz.js index 1856d86b..7caf3065 100644 --- a/locales/uz.js +++ b/locales/uz.js @@ -209,5 +209,25 @@ const uz = { "deleteBookmark": "\"{title}\" xatcho'pini o'chirmoqchimisiz?", "UnsupportedBrowser": "Xatcho'plar brauzeringizda qo'llab-quvvatlanmaydi.", - "resetShortcutsPrompt": "Barcha saqlangan yorliqlar o'chiriladi va standart holatga qaytariladi. Davom etilsinmi?" + "resetShortcutsPrompt": "Barcha saqlangan yorliqlar o'chiriladi va standart holatga qaytariladi. Davom etilsinmi?", + "cloudBackupSectionTitle": "Bulut zaxira nusxasi", + "webdavTitle": "WebDAV sinxronlash", + "webdavBackupText": "WebDAV ga zaxiralash", + "webdavRestoreText": "Eng so‘nggisini tiklash", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Foydalanuvchi nomi (ixtiyoriy)", + "webdavPasswordPlaceholder": "Parol yoki ilova paroli (ixtiyoriy)", + "cloudAutoBackupText": "Avtomatik zaxiralashni yoqish", + "cloudIntervalPlaceholder": "Soat", + "cloudIntervalHours": "Soat", + "saveBackupConfig": "Zaxira sozlamalarini saqlash", + "backupConfigSaved": "Zaxira sozlamalari saqlandi.", + "webdavMissingConfig": "Avval WebDAV papkasi URL manzilini kiriting.", + "webdavPermissionDenied": "Ushbu WebDAV serveri uchun host ruxsati berilmadi.", + "webdavBackupCompleted": "Zaxira muvaffaqiyatli WebDAV ga yuklandi!", + "webdavDownloadFailed": "WebDAV dan zaxirani yuklab bo‘lmadi: ", + "webdavUploadFailed": "Zaxirani WebDAV ga yuklab bo‘lmadi: ", + "webdavRestoreConfirm": "Sozlamalar eng so‘nggi WebDAV zaxirasidan tiklansinmi? Joriy mahalliy ma’lumotlaringiz ustiga yoziladi.", + "webdavLatestPointerMissing": "MYNT papkasida eng so‘nggi zaxira yozuvi topilmadi." }; diff --git a/locales/vi.js b/locales/vi.js index cd01ae18..ea40a4ca 100644 --- a/locales/vi.js +++ b/locales/vi.js @@ -134,4 +134,24 @@ const vi = { "invalidBackup": "Tệp sao lưu không hợp lệ", "deleteBookmark": "Bạn có chắc chắn muốn xóa dấu trang \"{title}\" không?", "UnsupportedBrowser": "Dấu trang không được hỗ trợ trong trình duyệt của bạn", + "cloudBackupSectionTitle": "Sao lưu đám mây", + "webdavTitle": "Đồng bộ WebDAV", + "webdavBackupText": "Sao lưu lên WebDAV", + "webdavRestoreText": "Khôi phục bản mới nhất", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "Tên người dùng (tùy chọn)", + "webdavPasswordPlaceholder": "Mật khẩu hoặc mật khẩu ứng dụng (tùy chọn)", + "cloudAutoBackupText": "Bật sao lưu tự động", + "cloudIntervalPlaceholder": "Giờ", + "cloudIntervalHours": "Giờ", + "saveBackupConfig": "Lưu cấu hình sao lưu", + "backupConfigSaved": "Đã lưu cấu hình sao lưu.", + "webdavMissingConfig": "Vui lòng nhập URL thư mục WebDAV trước.", + "webdavPermissionDenied": "Chưa được cấp quyền host cho máy chủ WebDAV này.", + "webdavBackupCompleted": "Đã tải bản sao lưu lên WebDAV thành công!", + "webdavDownloadFailed": "Không thể tải bản sao lưu từ WebDAV: ", + "webdavUploadFailed": "Không thể tải bản sao lưu lên WebDAV: ", + "webdavRestoreConfirm": "Khôi phục cài đặt từ bản sao lưu WebDAV mới nhất? Dữ liệu cục bộ hiện tại sẽ bị ghi đè.", + "webdavLatestPointerMissing": "Không tìm thấy bản ghi của bản sao lưu mới nhất trong thư mục MYNT." }; diff --git a/locales/zh.js b/locales/zh.js index 1a63062c..c69c577b 100644 --- a/locales/zh.js +++ b/locales/zh.js @@ -192,5 +192,25 @@ const zh = { "invalidBackup": "无效的备份文件", "deleteBookmark": "确定要删除书签“{title}”吗?", "UnsupportedBrowser": "您的浏览器不支持书签功能", - "resetShortcutsPrompt": "所有快捷方式将重置为默认值,是否继续?" -}; + "resetShortcutsPrompt": "所有快捷方式将重置为默认值,是否继续?", + "cloudBackupSectionTitle": "云备份", + "webdavTitle": "WebDAV 同步", + "webdavBackupText": "备份到 WebDAV", + "webdavRestoreText": "恢复最新备份", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "用户名(可选)", + "webdavPasswordPlaceholder": "密码或应用专用密码(可选)", + "cloudAutoBackupText": "启用自动备份", + "cloudIntervalPlaceholder": "小时", + "cloudIntervalHours": "小时", + "saveBackupConfig": "保存备份配置", + "backupConfigSaved": "备份配置已保存。", + "webdavMissingConfig": "请先填写 WebDAV 文件夹 URL。", + "webdavPermissionDenied": "未授予此 WebDAV 服务器的主机权限。", + "webdavBackupCompleted": "已成功上传备份到 WebDAV!", + "webdavDownloadFailed": "从 WebDAV 下载备份失败:", + "webdavUploadFailed": "上传备份到 WebDAV 失败:", + "webdavRestoreConfirm": "要从最新的 WebDAV 备份恢复设置吗?当前本地数据将被覆盖。", + "webdavLatestPointerMissing": "在 MYNT 文件夹中没有找到最新备份记录。" +}; \ No newline at end of file diff --git a/locales/zh_TW.js b/locales/zh_TW.js index 6a689a1a..d62bf174 100644 --- a/locales/zh_TW.js +++ b/locales/zh_TW.js @@ -192,4 +192,24 @@ const zh_TW = { "invalidBackup": "選擇的備份檔案無效。", "deleteBookmark": "您確定要刪除書籤 \"{title}\" 嗎?", "UnsupportedBrowser": "您的瀏覽器不支援書籤功能", -}; + "cloudBackupSectionTitle": "雲端備份", + "webdavTitle": "WebDAV 同步", + "webdavBackupText": "備份到 WebDAV", + "webdavRestoreText": "還原最新備份", + "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", + "cloudPrefixPlaceholder": "MYNT_Backup", + "webdavUsernamePlaceholder": "使用者名稱(選填)", + "webdavPasswordPlaceholder": "密碼或應用程式密碼(選填)", + "cloudAutoBackupText": "啟用自動備份", + "cloudIntervalPlaceholder": "小時", + "cloudIntervalHours": "小時", + "saveBackupConfig": "儲存備份設定", + "backupConfigSaved": "備份設定已儲存。", + "webdavMissingConfig": "請先填寫 WebDAV 資料夾 URL。", + "webdavPermissionDenied": "尚未授予此 WebDAV 伺服器的主機權限。", + "webdavBackupCompleted": "已成功上傳備份到 WebDAV!", + "webdavDownloadFailed": "從 WebDAV 下載備份失敗:", + "webdavUploadFailed": "上傳備份到 WebDAV 失敗:", + "webdavRestoreConfirm": "要從最新的 WebDAV 備份還原設定嗎?目前的本機資料將會被覆寫。", + "webdavLatestPointerMissing": "在 MYNT 資料夾中找不到最新備份記錄。" +}; \ No newline at end of file diff --git a/manifest(firefox).json b/manifest(firefox).json index 6959b54e..a4440ad5 100644 --- a/manifest(firefox).json +++ b/manifest(firefox).json @@ -5,12 +5,17 @@ "description": "A Simple New Tab (browser's home page) inspired by Google's 'Material You' design.", "permissions": [ "search", + "downloads", "bookmarks", "https://www.google.com/complete/search?client=*", "https://duckduckgo.com/ac/?q=*", "https://search.brave.com/api/suggest?q=*", "https://*.wikipedia.org/w/api.php?action=opensearch&search=*" ], + "optional_permissions": [ + "http://*/*", + "https://*/*" + ], "background": { "scripts": ["scripts/background.js"] }, diff --git a/manifest.json b/manifest.json index 1cbee8d4..402d3ba2 100644 --- a/manifest.json +++ b/manifest.json @@ -3,9 +3,11 @@ "name": "MYNT: Material You New Tab", "version": "3.3.7", "description": "A Simple New Tab (browser's home page) inspired by Google's 'Material You' design.", - "permissions": ["search"], + "permissions": ["search", "downloads"], "optional_permissions": ["bookmarks", "favicon"], "optional_host_permissions": [ + "http://*/*", + "https://*/*", "https://www.google.com/complete/search?client=*", "https://duckduckgo.com/ac/?q=*", "https://search.brave.com/api/suggest?q=*", diff --git a/scripts/backup-restore.js b/scripts/backup-restore.js index d7af8214..d2164590 100644 --- a/scripts/backup-restore.js +++ b/scripts/backup-restore.js @@ -6,190 +6,504 @@ * If not, see . */ -// -------------------- Backup-Restore Settings ---------------------- -document.getElementById("backupBtn").addEventListener("click", backupData); -document.getElementById("restoreBtn").addEventListener("click", () => document.getElementById("fileInput").click()); -document.getElementById("fileInput").addEventListener("change", validateAndRestoreData); +const LOCAL_BACKUP_FILE_PREFIX = "MYNT_Backup"; +const LOCAL_BACKUP_FOLDER = "MYNT_Backups"; +const AUTO_BACKUP_MIN_INTERVAL_HOURS = 1; +const CLOUD_BACKUP_FOLDER = "MYNT"; + +const STORAGE_KEYS = { + cloudAutoEnabled: "cloudAutoBackupEnabled", + cloudIntervalHours: "cloudAutoBackupIntervalHours", + cloudLastAutoBackupAt: "cloudLastAutoBackupAt", + webdavUrl: "webdavUrl", + webdavUsername: "webdavUsername", + webdavPassword: "webdavPassword" +}; + +const storageFallbackTexts = { + backupModuleTitle: "Cloud Backup", + webdavTitle: "WebDAV Sync", + webdavBackupText: "Backup to WebDAV", + webdavRestoreText: "Restore Latest", + webdavUrlPlaceholder: "https://example.com/remote.php/dav/files/username/backups", + webdavUsernamePlaceholder: "Username (optional)", + webdavPasswordPlaceholder: "Password or app password (optional)", + webdavMissingConfig: "Please fill in the WebDAV folder URL first.", + webdavPermissionDenied: "Host permission was not granted for this WebDAV server.", + webdavBackupCompleted: "Backup uploaded to WebDAV successfully!", + webdavDownloadFailed: "Failed to download backup from WebDAV: ", + webdavUploadFailed: "Failed to upload backup to WebDAV: ", + webdavRestoreConfirm: "Restore settings from the latest WebDAV backup? Your current local data will be overwritten.", + webdavLatestPointerMissing: "No latest backup record was found in the MYNT folder.", + cloudAutoBackupText: "Enable automatic backup", + cloudAutoBackupHint: "Back up settings to WebDAV on a schedule", + cloudIntervalPlaceholder: "Hours", + cloudIntervalHours: "Hours", + cloudAutoBackupInvalidInterval: "Automatic backup interval must be at least 1 hour.", + saveBackupConfig: "Save Backup Config", + backupConfigSaved: "Backup configuration saved." +}; + +let autoBackupTimerId = null; + +const backupButton = document.getElementById("backupBtn"); +const restoreButton = document.getElementById("restoreBtn"); +const fileInput = document.getElementById("fileInput"); +const webdavBackupButton = document.getElementById("webdavBackupBtn"); +const webdavRestoreButton = document.getElementById("webdavRestoreBtn"); +const webdavUrlInput = document.getElementById("webdavUrlInput"); +const webdavUsernameInput = document.getElementById("webdavUsernameInput"); +const webdavPasswordInput = document.getElementById("webdavPasswordInput"); +const cloudAutoBackupCheckbox = document.getElementById("cloudAutoBackupCheckbox"); +const cloudIntervalInput = document.getElementById("cloudIntervalInput"); +const saveBackupConfigBtn = document.getElementById("saveBackupConfigBtn"); +const resetbtn = document.getElementById("resetsettings"); + +applyStorageTexts(); +loadWebDAVSettings(); +setupEventListeners(); +setupAutoBackupScheduler(); + +function setupEventListeners() { + backupButton.addEventListener("click", backupData); + restoreButton.addEventListener("click", () => fileInput.click()); + fileInput.addEventListener("change", validateAndRestoreData); + + webdavBackupButton.addEventListener("click", () => uploadSnapshotToWebDAV({ + isAutomatic: false, + requestPermissionInteractively: true + })); + webdavRestoreButton.addEventListener("click", restoreFromWebDAV); + saveBackupConfigBtn.addEventListener("click", saveBackupConfig); + + resetbtn.addEventListener("click", async () => { + const confirmationMessage = translations[currentLanguage]?.confirmRestore || translations.en.confirmRestore; + if (await confirmPrompt(confirmationMessage)) { + await snapshotService.clearAllStoredData(); + location.reload(); + } + }); +} -// Backup data from localStorage and IndexedDB async function backupData() { try { - const backup = { localStorage: {}, indexedDB: {} }; + const backup = await snapshotService.createSnapshot(); + await downloadBackupFile(backup, buildLocalBackupFileName()); + } catch (error) { + await alertPrompt(t("failedbackup") + getErrorMessage(error)); + } +} + +async function validateAndRestoreData(event) { + const file = event.target.files[0]; + if (!file) { + return; + } + + try { + const backup = JSON.parse(await file.text()); + await restoreSnapshotAndReload(backup); + } catch (error) { + await alertPrompt(t("restorefailed") + getErrorMessage(error)); + } finally { + fileInput.value = ""; + } +} + +async function uploadSnapshotToWebDAV(options = {}) { + const isAutomatic = options.isAutomatic === true; + const requestPermissionInteractively = options.requestPermissionInteractively === true; - // Backup localStorage - for (let key in localStorage) { - if (localStorage.hasOwnProperty(key)) { - backup.localStorage[key] = localStorage.getItem(key); + try { + const config = getWebDAVConfig(); + if (!config) { + if (!isAutomatic) { + await alertPrompt(t("webdavMissingConfig")); } + return false; } - // Backup IndexedDB (ImageDB) - backup.indexedDB = await backupIndexedDB(); - - // Generate filename with current date (format: DDMMYYYY) - const date = new Date(); - const formattedDate = `${String(date.getDate()).padStart(2, "0")}${String(date.getMonth() + 1).padStart(2, "0")}${date.getFullYear()}`; - const fileName = `MYNT_Backup_${formattedDate}.json`; + const hasPermission = await ensureHostPermission(config.requestOrigin, { + interactive: requestPermissionInteractively + }); + if (!hasPermission) { + if (!isAutomatic) { + await alertPrompt(t("webdavPermissionDenied")); + } + return false; + } - // Create and download the backup file - const blob = new Blob([JSON.stringify(backup, null, 2)], { type: "application/json" }); - const link = document.createElement("a"); - link.href = URL.createObjectURL(blob); - link.download = fileName; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); + const snapshot = await snapshotService.createSnapshot(); + const fileName = buildRemoteBackupFileName(); + const headers = buildWebDAVHeaders(config); + const folderUrl = buildProviderFolderUrl(config.baseUrl, CLOUD_BACKUP_FOLDER); + + await ensureWebDAVFolder(folderUrl, headers); + await putJson(buildRemoteFileUrl(folderUrl, fileName), snapshot, headers); + await putJson(buildRemoteFileUrl(folderUrl, buildLatestPointerFileName()), { + latestFileName: fileName, + backedUpAt: new Date().toISOString() + }, headers); + + if (isAutomatic) { + localStorage.setItem(STORAGE_KEYS.cloudLastAutoBackupAt, String(Date.now())); + } else { + await alertPrompt(t("webdavBackupCompleted")); + } - console.log("Backup completed successfully!"); + return true; } catch (error) { - await alertPrompt((translations[currentLanguage]?.failedbackup || translations["en"].failedbackup) + error.message); + if (isAutomatic) { + console.error("Automatic WebDAV backup failed:", error); + } else { + await alertPrompt(t("webdavUploadFailed") + getErrorMessage(error)); + } + return false; } } -// Validate and restore data from a backup file -async function validateAndRestoreData(event) { - const file = event.target.files[0]; - if (!file) return; +async function restoreFromWebDAV() { + try { + const config = getWebDAVConfig(); + if (!config) { + await alertPrompt(t("webdavMissingConfig")); + return; + } - const reader = new FileReader(); - reader.onload = async (e) => { - try { - const backup = JSON.parse(e.target.result); + const hasPermission = await ensureHostPermission(config.requestOrigin, { + interactive: true + }); + if (!hasPermission) { + await alertPrompt(t("webdavPermissionDenied")); + return; + } - // Validate the structure of the JSON file - if (!isValidBackupFile(backup)) { - await alertPrompt(translations[currentLanguage]?.invalidBackup || translations["en"].invalidBackup); - return; - } + if (!(await confirmPrompt(t("webdavRestoreConfirm")))) { + return; + } - await restoreData(backup); + const headers = buildWebDAVHeaders(config); + const folderUrl = buildProviderFolderUrl(config.baseUrl, CLOUD_BACKUP_FOLDER); + const latestResponse = await fetch(buildRemoteFileUrl(folderUrl, buildLatestPointerFileName()), { + method: "GET", + headers + }); - await alertPrompt(translations[currentLanguage]?.restorecompleted || translations["en"].restorecompleted); - location.reload(); - } catch (error) { - await alertPrompt(translations[currentLanguage]?.restorefailed || translations["en"].restorefailed + error.message); + if (latestResponse.status === 404) { + await alertPrompt(t("webdavLatestPointerMissing")); + return; } - }; - reader.readAsText(file); + + if (!latestResponse.ok) { + throw new Error(`${latestResponse.status} ${latestResponse.statusText}`); + } + + const latestPointer = await latestResponse.json(); + const backupResponse = await fetch(buildRemoteFileUrl(folderUrl, latestPointer.latestFileName), { + method: "GET", + headers + }); + + if (!backupResponse.ok) { + throw new Error(`${backupResponse.status} ${backupResponse.statusText}`); + } + + await restoreSnapshotAndReload(await backupResponse.json()); + } catch (error) { + await alertPrompt(t("webdavDownloadFailed") + getErrorMessage(error)); + } } -function isValidBackupFile(backup) { - // Check if localStorage and indexedDB exist and are objects - return !(typeof backup.localStorage !== "object" || typeof backup.indexedDB !== "object"); +function applyStorageTexts() { + document.getElementById("cloudBackupSectionTitle").textContent = t("backupModuleTitle"); + document.getElementById("webdavTitle").textContent = t("webdavTitle"); + document.getElementById("webdavBackupText").textContent = t("webdavBackupText"); + document.getElementById("webdavRestoreText").textContent = t("webdavRestoreText"); + document.getElementById("cloudAutoBackupText").textContent = t("cloudAutoBackupText"); + document.getElementById("cloudAutoBackupHint").textContent = t("cloudAutoBackupHint"); + document.getElementById("cloudIntervalUnitLabel").textContent = t("cloudIntervalHours"); + saveBackupConfigBtn.textContent = t("saveBackupConfig"); + webdavUrlInput.placeholder = t("webdavUrlPlaceholder"); + webdavUsernameInput.placeholder = t("webdavUsernamePlaceholder"); + webdavPasswordInput.placeholder = t("webdavPasswordPlaceholder"); + cloudIntervalInput.placeholder = t("cloudIntervalPlaceholder"); } -// Backup IndexedDB: Extract data from ImageDB -> backgroundImages -async function backupIndexedDB() { - const db = await openDatabase(); - return new Promise((resolve, reject) => { - const transaction = db.transaction(storeName, "readonly"); - const store = transaction.objectStore(storeName); - const data = {}; +function loadWebDAVSettings() { + webdavUrlInput.value = localStorage.getItem(STORAGE_KEYS.webdavUrl) || ""; + webdavUsernameInput.value = localStorage.getItem(STORAGE_KEYS.webdavUsername) || ""; + webdavPasswordInput.value = localStorage.getItem(STORAGE_KEYS.webdavPassword) || ""; + cloudAutoBackupCheckbox.checked = (localStorage.getItem(STORAGE_KEYS.cloudAutoEnabled) + ?? localStorage.getItem("webdavAutoBackupEnabled")) === "true"; + cloudIntervalInput.value = localStorage.getItem(STORAGE_KEYS.cloudIntervalHours) + || localStorage.getItem("webdavAutoBackupIntervalHours") + || "24"; +} - store.getAllKeys().onsuccess = (keysEvent) => { - const keys = keysEvent.target.result; +async function saveBackupConfig() { + const config = getWebDAVConfig(); + if (!config) { + await alertPrompt(t("webdavMissingConfig")); + return; + } - if (!keys.length) { - resolve({}); - return; - } + const intervalHours = getAutoBackupIntervalHours(); + if (intervalHours < AUTO_BACKUP_MIN_INTERVAL_HOURS) { + await alertPrompt(t("cloudAutoBackupInvalidInterval")); + cloudIntervalInput.value = "1"; + return; + } - let pending = keys.length; - keys.forEach(key => { - store.get(key).onsuccess = (getEvent) => { - const value = getEvent.target.result; - if (value instanceof Blob) { - // Convert Blob to Base64 for JSON compatibility - const reader = new FileReader(); - reader.onload = () => { - data[key] = { blob: reader.result, isBlob: true }; - if (--pending === 0) resolve(data); - }; - reader.readAsDataURL(value); - } else { - data[key] = value; - if (--pending === 0) resolve(data); - } - }; - }); - }; + localStorage.setItem(STORAGE_KEYS.webdavUrl, config.baseUrl); + localStorage.setItem(STORAGE_KEYS.webdavUsername, webdavUsernameInput.value); + localStorage.setItem(STORAGE_KEYS.webdavPassword, webdavPasswordInput.value); + localStorage.setItem(STORAGE_KEYS.cloudAutoEnabled, String(cloudAutoBackupCheckbox.checked)); + localStorage.setItem(STORAGE_KEYS.cloudIntervalHours, String(intervalHours)); - transaction.onerror = () => reject(transaction.error); - }); + setupAutoBackupScheduler(); + await alertPrompt(t("backupConfigSaved")); } -// Restore IndexedDB: Clear and repopulate ImageDB -> backgroundImages -async function restoreIndexedDB(data) { - const db = await openDatabase(); - return new Promise((resolve, reject) => { - const transaction = db.transaction(storeName, "readwrite"); - const store = transaction.objectStore(storeName); +function getWebDAVConfig() { + const baseUrl = webdavUrlInput.value.trim().replace(/\/+$/, ""); + if (!baseUrl) { + return null; + } - store.clear(); - const entries = Object.entries(data); - let pending = entries.length; + return { + baseUrl, + username: webdavUsernameInput.value, + password: webdavPasswordInput.value, + requestOrigin: new URL(baseUrl).origin + "/*" + }; +} - if (pending === 0) { - resolve(); // If no data to restore, resolve immediately - return; - } +function setupAutoBackupScheduler() { + if (autoBackupTimerId) { + clearInterval(autoBackupTimerId); + autoBackupTimerId = null; + } - entries.forEach(([key, value]) => { - if (value.isBlob) { - // Convert Base64 back to Blob - const blob = base64ToBlob(value.blob); - store.put(blob, key); - } else { - store.put(value, key); - } + if (localStorage.getItem(STORAGE_KEYS.cloudAutoEnabled) !== "true") { + return; + } - if (--pending === 0) resolve(); - }); + const intervalHours = Number(localStorage.getItem(STORAGE_KEYS.cloudIntervalHours) || "24"); + if (intervalHours < AUTO_BACKUP_MIN_INTERVAL_HOURS) { + return; + } + + maybeRunAutomaticBackup(intervalHours); + autoBackupTimerId = setInterval(() => { + maybeRunAutomaticBackup(intervalHours); + }, intervalHours * 60 * 60 * 1000); +} + +async function maybeRunAutomaticBackup(intervalHours) { + const lastAutoBackupAt = Number(localStorage.getItem(STORAGE_KEYS.cloudLastAutoBackupAt) || "0"); + const intervalMs = intervalHours * 60 * 60 * 1000; - transaction.onerror = () => reject(transaction.error); + if (Date.now() - lastAutoBackupAt < intervalMs) { + return; + } + + const success = await uploadSnapshotToWebDAV({ + isAutomatic: true, + requestPermissionInteractively: false }); + if (success) { + localStorage.setItem(STORAGE_KEYS.cloudLastAutoBackupAt, String(Date.now())); + } } -// Restore data for both localStorage and IndexedDB -async function restoreData(backup) { - // Clear localStorage before restoring - localStorage.clear(); +function getAutoBackupIntervalHours() { + const parsedValue = parseInt(cloudIntervalInput.value, 10); + return Number.isNaN(parsedValue) ? 24 : parsedValue; +} - // Restore localStorage from backup - if (backup.localStorage) { - Object.keys(backup.localStorage).forEach(key => { - localStorage.setItem(key, backup.localStorage[key]); - }); +function buildWebDAVHeaders(config) { + const headers = { "Content-Type": "application/json" }; + if (config.username || config.password) { + headers.Authorization = `Basic ${encodeBasicAuth(config.username, config.password)}`; } + return headers; +} + +async function ensureHostPermission(originPattern, options = {}) { + const interactive = options.interactive === true; + const chromePermissions = typeof chrome !== "undefined" ? chrome.permissions : null; + const browserPermissions = typeof browser !== "undefined" ? browser.permissions : null; + const permissionsApi = chromePermissions || browserPermissions; - // Restore IndexedDB from backup - if (backup.indexedDB) { - await restoreIndexedDB(backup.indexedDB); + if (!permissionsApi || typeof permissionsApi.contains !== "function" || typeof permissionsApi.request !== "function") { + return true; } + + const permissionRequest = { origins: [originPattern] }; + if (interactive) { + return chromePermissions + ? callChromePermissionApi("request", permissionRequest) + : permissionsApi.request(permissionRequest); + } + + return chromePermissions + ? callChromePermissionApi("contains", permissionRequest) + : permissionsApi.contains(permissionRequest); } -// Helper: Convert Base64 string to Blob -function base64ToBlob(base64) { - const [metadata, data] = base64.split(","); - const mime = metadata.match(/:(.*?);/)[1]; - const binary = atob(data); - const array = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) { - array[i] = binary.charCodeAt(i); +async function putJson(url, payload, headers) { + const response = await fetch(url, { + method: "PUT", + headers, + body: JSON.stringify(payload, null, 2) + }); + + if (!response.ok) { + throw new Error(`${response.status} ${response.statusText}`); } - return new Blob([array], { type: mime }); } -// ------------------- Reset Settings ---------------------------- -const resetbtn = document.getElementById("resetsettings"); +async function ensureWebDAVFolder(folderUrl, headers) { + const response = await fetch(folderUrl, { + method: "MKCOL", + headers + }); + + if (response.ok || response.status === 405 || response.status === 301 || response.status === 302) { + return; + } + + throw new Error(`Failed to prepare backup folder: ${response.status} ${response.statusText}`); +} + +function buildProviderFolderUrl(baseUrl, folderName) { + const normalizedBaseUrl = baseUrl.replace(/\/+$/, ""); + const parsedUrl = new URL(normalizedBaseUrl); + const pathSegments = parsedUrl.pathname.split("/").filter(Boolean).map((segment) => { + try { + return decodeURIComponent(segment).toLowerCase(); + } catch { + return segment.toLowerCase(); + } + }); + + if (pathSegments[pathSegments.length - 1] === folderName.toLowerCase()) { + return normalizedBaseUrl; + } + + return `${normalizedBaseUrl}/${folderName}`; +} + +function buildRemoteFileUrl(baseUrl, fileName) { + return `${baseUrl}/${encodeURIComponent(fileName)}`; +} + +function buildLocalBackupFileName() { + return `${LOCAL_BACKUP_FOLDER}/${LOCAL_BACKUP_FILE_PREFIX}_${buildTimestampPart()}.json`; +} + +function buildRemoteBackupFileName() { + return `${LOCAL_BACKUP_FILE_PREFIX}_${buildTimestampPart()}.json`; +} -// Clear localStorage and reload the page -resetbtn.addEventListener("click", async () => { - const confirmationMessage = translations[currentLanguage]?.confirmRestore || translations["en"].confirmRestore; +function buildLatestPointerFileName() { + return `${LOCAL_BACKUP_FILE_PREFIX}_latest.json`; +} + +function buildTimestampPart() { + const date = new Date(); + return [ + date.getFullYear(), + String(date.getMonth() + 1).padStart(2, "0"), + String(date.getDate()).padStart(2, "0") + ].join("") + "_" + [ + String(date.getHours()).padStart(2, "0"), + String(date.getMinutes()).padStart(2, "0"), + String(date.getSeconds()).padStart(2, "0") + ].join(""); +} + +async function downloadBackupFile(backup, fileName) { + const blob = new Blob([JSON.stringify(backup, null, 2)], { type: "application/json" }); + const objectUrl = URL.createObjectURL(blob); + + try { + const browserDownloads = typeof browser !== "undefined" ? browser.downloads : null; + if (browserDownloads?.download) { + await browserDownloads.download({ url: objectUrl, filename: fileName, saveAs: false, conflictAction: "uniquify" }); + return; + } + + const chromeDownloads = typeof chrome !== "undefined" ? chrome.downloads : null; + if (chromeDownloads?.download) { + await new Promise((resolve, reject) => { + chromeDownloads.download({ url: objectUrl, filename: fileName, saveAs: false, conflictAction: "uniquify" }, (downloadId) => { + if (chrome.runtime.lastError) { + reject(new Error(chrome.runtime.lastError.message)); + return; + } - if (await confirmPrompt(confirmationMessage)) { - localStorage.clear(); - location.reload(); + resolve(downloadId); + }); + }); + return; + } + + const link = document.createElement("a"); + link.href = objectUrl; + link.download = fileName.split("/").pop(); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } finally { + URL.revokeObjectURL(objectUrl); } -}); +} +async function restoreSnapshotAndReload(snapshot) { + const normalizedSnapshot = snapshotService.normalizeSnapshot(snapshot); + if (!snapshotService.validateSnapshot(normalizedSnapshot)) { + await alertPrompt(t("invalidBackup")); + return; + } + + await snapshotService.restoreSnapshot(normalizedSnapshot); + await alertPrompt(t("restorecompleted")); + location.reload(); +} + +function t(key) { + return translations[currentLanguage]?.[key] + || translations.en?.[key] + || storageFallbackTexts[key] + || key; +} + +function encodeBasicAuth(username, password) { + const credentials = `${username}:${password}`; + const bytes = new TextEncoder().encode(credentials); + let binary = ""; + bytes.forEach((byte) => { + binary += String.fromCharCode(byte); + }); + return btoa(binary); +} + +function callChromePermissionApi(method, permissionRequest) { + return new Promise((resolve, reject) => { + chrome.permissions[method](permissionRequest, (result) => { + if (chrome.runtime.lastError) { + reject(new Error(chrome.runtime.lastError.message)); + return; + } + resolve(result); + }); + }); +} + +function getErrorMessage(error) { + if (error instanceof Error && error.message) { + return error.message; + } + return String(error); +} diff --git a/scripts/languages.js b/scripts/languages.js index 9458c128..d101787d 100644 --- a/scripts/languages.js +++ b/scripts/languages.js @@ -236,7 +236,12 @@ function applyLanguage(lang) { "searchSectionTitle", "weatherSectionTitle", "appearanceSectionTitle", - "settingsSectionTitle" + "settingsSectionTitle", + "cloudBackupSectionTitle", + "webdavTitle", + "webdavBackupText", + "webdavRestoreText", + "cloudAutoBackupText" ]; // Specific mapping for placeholders @@ -247,7 +252,11 @@ function applyLanguage(lang) { { id: "todoInput", key: "todoPlaceholder" }, { id: "bookmarkSearch", key: "bookmarkSearch" }, { id: "editBookmarkName", key: "editBookmarkName" }, - { id: "editBookmarkURL", key: "editBookmarkURL" } + { id: "editBookmarkURL", key: "editBookmarkURL" }, + { id: "webdavUrlInput", key: "webdavUrlPlaceholder" }, + { id: "webdavUsernameInput", key: "webdavUsernamePlaceholder" }, + { id: "webdavPasswordInput", key: "webdavPasswordPlaceholder" }, + { id: "cloudIntervalInput", key: "cloudIntervalPlaceholder" } ]; // Mapping of elements and their different translation keys @@ -273,6 +282,8 @@ function applyLanguage(lang) { { id: "editBookmarkNameLabel", key: "editBookmarkName" }, { id: "editBookmarkURLLabel", key: "editBookmarkURL" }, { id: "shortcutsSectionTitle", key: "shortcutsText" }, + { id: "saveBackupConfigBtn", key: "saveBackupConfig" }, + { id: "cloudIntervalUnitLabel", key: "cloudIntervalHours" } ]; // Function to apply translations diff --git a/scripts/snapshot-service.js b/scripts/snapshot-service.js new file mode 100644 index 00000000..40e0d747 --- /dev/null +++ b/scripts/snapshot-service.js @@ -0,0 +1,278 @@ +/* + * Material You NewTab + * Copyright (c) 2023-2025 XengShi + * Licensed under the GNU General Public License v3.0 (GPL-3.0) + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +(function initializeSnapshotService() { + const SNAPSHOT_SCHEMA_VERSION = 1; + const SNAPSHOT_DATABASES = [ + { + dbName: "ImageDB", + version: 1, + stores: ["backgroundImages"] + } + ]; + + async function createSnapshot(options = {}) { + const mode = options.mode || "full"; + return { + schemaVersion: SNAPSHOT_SCHEMA_VERSION, + snapshotType: mode, + exportedAt: new Date().toISOString(), + app: "MYNT", + localStorage: collectLocalStorage(mode), + indexedDB: await collectIndexedDB() + }; + } + + function validateSnapshot(snapshot) { + return !!snapshot + && typeof snapshot === "object" + && typeof snapshot.localStorage === "object" + && typeof snapshot.indexedDB === "object"; + } + + function normalizeSnapshot(snapshot) { + if (!snapshot || typeof snapshot !== "object") { + return snapshot; + } + + if (snapshot.localStorage && snapshot.indexedDB) { + return snapshot; + } + + return { + schemaVersion: SNAPSHOT_SCHEMA_VERSION, + snapshotType: "legacy", + exportedAt: snapshot.exportedAt || new Date().toISOString(), + app: "MYNT", + localStorage: snapshot.localStorage || {}, + indexedDB: snapshot.indexedDB || {} + }; + } + + async function restoreSnapshot(snapshot) { + const normalizedSnapshot = normalizeSnapshot(snapshot); + + if (!validateSnapshot(normalizedSnapshot)) { + throw new Error("Invalid snapshot format."); + } + + localStorage.clear(); + Object.keys(normalizedSnapshot.localStorage).forEach((key) => { + localStorage.setItem(key, normalizedSnapshot.localStorage[key]); + }); + + await restoreIndexedDB(normalizedSnapshot.indexedDB); + } + + async function clearAllStoredData() { + localStorage.clear(); + for (const databaseConfig of SNAPSHOT_DATABASES) { + await clearDatabase(databaseConfig); + } + } + + function collectLocalStorage(mode) { + const data = {}; + + for (let key in localStorage) { + if (!localStorage.hasOwnProperty(key)) { + continue; + } + + if (mode === "settings" && isTransientLocalStorageKey(key)) { + continue; + } + + data[key] = localStorage.getItem(key); + } + + return data; + } + + async function collectIndexedDB() { + const databaseSnapshots = {}; + + for (const databaseConfig of SNAPSHOT_DATABASES) { + databaseSnapshots[databaseConfig.dbName] = await exportDatabase(databaseConfig); + } + + return databaseSnapshots; + } + + async function exportDatabase(databaseConfig) { + const db = await openDatabase(databaseConfig); + const stores = {}; + + for (const storeName of databaseConfig.stores) { + stores[storeName] = await exportObjectStore(db, storeName); + } + + db.close(); + return stores; + } + + async function exportObjectStore(db, storeName) { + return new Promise((resolve, reject) => { + const transaction = db.transaction(storeName, "readonly"); + const store = transaction.objectStore(storeName); + const data = {}; + + store.getAllKeys().onsuccess = (keysEvent) => { + const keys = keysEvent.target.result; + + if (!keys.length) { + resolve({}); + return; + } + + let pending = keys.length; + keys.forEach((key) => { + store.get(key).onsuccess = async (getEvent) => { + try { + data[key] = await serializeIndexedDBValue(getEvent.target.result); + pending -= 1; + if (pending === 0) { + resolve(data); + } + } catch (error) { + reject(error); + } + }; + }); + }; + + transaction.onerror = () => reject(transaction.error); + }); + } + + async function restoreIndexedDB(indexedDBSnapshot) { + for (const databaseConfig of SNAPSHOT_DATABASES) { + const databaseSnapshot = indexedDBSnapshot?.[databaseConfig.dbName]; + await restoreDatabase(databaseConfig, databaseSnapshot || {}); + } + } + + async function restoreDatabase(databaseConfig, databaseSnapshot) { + const db = await openDatabase(databaseConfig); + + for (const storeName of databaseConfig.stores) { + await restoreObjectStore(db, storeName, databaseSnapshot[storeName] || {}); + } + + db.close(); + } + + async function clearDatabase(databaseConfig) { + const db = await openDatabase(databaseConfig); + + for (const storeName of databaseConfig.stores) { + await restoreObjectStore(db, storeName, {}); + } + + db.close(); + } + + async function restoreObjectStore(db, storeName, storeData) { + return new Promise((resolve, reject) => { + const transaction = db.transaction(storeName, "readwrite"); + const store = transaction.objectStore(storeName); + const entries = Object.entries(storeData); + + store.clear(); + entries.forEach(([key, value]) => { + store.put(deserializeIndexedDBValue(value), key); + }); + + transaction.oncomplete = () => resolve(); + transaction.onerror = () => reject(transaction.error); + }); + } + + function openDatabase(databaseConfig) { + return new Promise((resolve, reject) => { + const request = indexedDB.open(databaseConfig.dbName, databaseConfig.version); + + request.onupgradeneeded = (event) => { + const db = event.target.result; + databaseConfig.stores.forEach((storeName) => { + if (!db.objectStoreNames.contains(storeName)) { + db.createObjectStore(storeName); + } + }); + }; + + request.onsuccess = (event) => resolve(event.target.result); + request.onerror = () => reject(new Error("Database error.")); + }); + } + + function isTransientLocalStorageKey(key) { + return key.startsWith("quotes_") + || key === "quotes_metadata_timestamp" + || key === "weatherParsedData" + || key === "weatherParsedTime" + || key === "weatherParsedLocation" + || key === "weatherParsedLang"; + } + + function serializeIndexedDBValue(value) { + if (value instanceof Blob) { + return blobToDataUrl(value).then((dataUrl) => ({ + serializedType: "blob", + value: dataUrl, + mimeType: value.type + })); + } + + return Promise.resolve({ + serializedType: "plain", + value: value + }); + } + + function deserializeIndexedDBValue(serializedValue) { + if (!serializedValue || typeof serializedValue !== "object") { + return serializedValue; + } + + if (serializedValue.serializedType === "blob") { + return dataUrlToBlob(serializedValue.value, serializedValue.mimeType); + } + + return serializedValue.value; + } + + function blobToDataUrl(blob) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = () => reject(new Error("Failed to serialize blob.")); + reader.readAsDataURL(blob); + }); + } + + function dataUrlToBlob(dataUrl, mimeType) { + const parts = dataUrl.split(","); + const data = atob(parts[1]); + const bytes = new Uint8Array(data.length); + + for (let index = 0; index < data.length; index += 1) { + bytes[index] = data.charCodeAt(index); + } + + return new Blob([bytes], { type: mimeType || "application/octet-stream" }); + } + + window.snapshotService = { + createSnapshot, + restoreSnapshot, + validateSnapshot, + normalizeSnapshot, + clearAllStoredData + }; +})(); diff --git a/style.css b/style.css index 07a94cda..2e4dc826 100644 --- a/style.css +++ b/style.css @@ -4016,6 +4016,233 @@ option:checked { color: #fffffff5; } +.backupModuleSection { + width: 100%; + margin-top: 18px; + display: grid; + gap: 12px; +} + +.backupModuleTitle { + font-size: 0.96rem; + color: var(--darkerColor-blue); + font-weight: 600; +} + +.cloudMethodButtons { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; +} + +.backupMethodPrimaryBtn, +.backupMethodChoiceBtn, +.backupMethodSwitchBtn { + border: none; + border-radius: 18px; + padding: 11px 14px; + cursor: pointer; + background-color: var(--bg-color-blue); + color: var(--darkerColor-blue); + font-size: 0.9rem; +} + +.backupMethodPrimaryBtn { + width: 100%; +} + +.cloudMethodActiveBar { + display: none; + grid-template-columns: 1fr auto; + align-items: center; + gap: 12px; +} + +.cloudMethodActiveBar.active { + display: grid; +} + +.cloudMethodCurrent { + color: var(--darkerColor-blue); + font-size: 0.9rem; + display: flex; + gap: 6px; + align-items: center; +} + +.cloudMethodInlinePicker { + background-color: var(--lightColor-blue); + border-radius: 22px; + padding: 16px; + display: none; + box-shadow: 0 18px 48px rgba(0, 0, 0, 0.08); +} + +.cloudMethodInlinePicker.active { + display: grid; + gap: 14px; +} + +.cloudMethodInlineTitle { + font-size: 1rem; + font-weight: 600; + color: var(--darkerColor-blue); +} + +.backupMethodPanel { + display: none !important; +} + +.backupMethodPanel.active { + display: grid !important; +} + +.cloudBackupSharedPanel { + display: grid; + gap: 10px; + padding-top: 4px; +} + +.webdavSettingsSection { + width: 100%; + display: grid; + gap: 10px; +} + +.webdavSettingsTitle { + font-size: 0.92rem; + color: var(--darkerColor-blue); + font-weight: 600; +} + +.webdavInput { + width: 100%; + border: none; + border-radius: 18px; + padding: 11px 15px; + background-color: var(--bg-color-blue); + color: var(--darkerColor-blue); + font-size: 0.9rem; + outline: none; +} + +.webdavInput::placeholder { + color: color-mix(in srgb, var(--darkerColor-blue) 64%, transparent); +} + +.webdavActionContainer { + margin-top: 2px; +} + +.cloudProviderStatusRow { + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + gap: 10px; + padding: 4px 6px; + color: var(--darkerColor-blue); + font-size: 0.9rem; +} + +.cloudProviderStatusLabel { + opacity: 0.78; +} + +.cloudProviderStatusValue { + justify-self: end; +} + +.backupConfigSaveBtn { + margin-top: 4px; + margin-bottom: 0; +} + +.webdavAutoRow { + display: grid; + grid-template-columns: minmax(0, 1fr) 130px; + gap: 10px; + align-items: stretch; +} + +.webdavAutoCard { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + padding: 14px 16px; + border-radius: 20px; + background-color: var(--bg-color-blue); + color: var(--darkerColor-blue); + min-width: 0; +} + +.webdavAutoCard .texts { + min-width: 0; +} + +.webdavAutoCard .bigText { + font-size: 0.95rem; +} + +.webdavAutoCard .infoText { + font-size: 0.82rem; + line-height: 1.35; + color: color-mix(in srgb, var(--darkerColor-blue) 76%, transparent); +} + +.webdavAutoCard .switch { + margin-inline-start: 0; + flex-shrink: 0; +} + +.webdavIntervalField { + display: grid; + align-content: center; + gap: 8px; + padding: 12px 14px; + border-radius: 20px; + background-color: var(--bg-color-blue); + color: var(--darkerColor-blue); +} + +.webdavIntervalLabel { + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.02em; + opacity: 0.78; +} + +.webdavIntervalInput { + text-align: center; + padding: 10px 12px; + background-color: color-mix(in srgb, var(--accentLightTint-blue) 38%, transparent); + font-weight: 600; +} + +.webdavIntervalGroup { + display: grid; + grid-template-columns: 72px 1fr; + gap: 8px; + align-items: center; +} + +.webdavIntervalUnitLabel { + font-size: 0.78rem; + color: inherit; + padding-inline: 0; +} + +.cloudPlaceholderPanel { + background-color: var(--bg-color-blue); + border-radius: 18px; + padding: 14px 16px; +} + +.cloudPlaceholderText { + font-size: 0.9rem; + color: var(--darkerColor-blue); +} + /* */ #colorPicker { display: none; @@ -4442,6 +4669,16 @@ body[data-bg="wallpaper"] .dropdown-content { :root { --gap: 1rem; } + .webdavAutoRow { + grid-template-columns: 1fr; + } + .webdavIntervalField { + grid-template-columns: auto 88px; + align-items: center; + } + .webdavIntervalLabel { + font-size: 0.82rem; + } #shortcuts-section { position: fixed; bottom: 62px; @@ -4908,6 +5145,53 @@ body[data-bg="wallpaper"] .dropdown-content { background-color: var(--darkColor-dark); } +.black-theme .webdavSettingsTitle { + color: #fff; +} + +.black-theme .backupModuleTitle { + color: #fff; +} + +.black-theme .cloudMethodCurrent, +.black-theme .webdavIntervalUnitLabel, +.black-theme .cloudPlaceholderText { + color: #fff; +} + +.black-theme .cloudMethodInlineTitle { + color: #fff; +} + +.black-theme .webdavInput { + background-color: var(--darkColor-dark); + color: #fff; +} + +.black-theme .cloudMethodInlinePicker, +.black-theme .backupMethodChoiceBtn, +.black-theme .backupMethodSwitchBtn, +.black-theme .backupMethodPrimaryBtn, +.black-theme .cloudPlaceholderPanel, +.black-theme .webdavAutoCard, +.black-theme .webdavIntervalField { + background-color: var(--darkColor-dark); + color: #fff; +} + +.black-theme .webdavAutoCard .infoText, +.black-theme .webdavIntervalLabel { + color: #d0d0d0; +} + +.black-theme .webdavIntervalInput { + background-color: var(--bg-color-dark); +} + +.black-theme .webdavInput::placeholder { + color: #a8a8a8; +} + .black-theme .backupRestoreBtn:hover, .black-theme .uploadButton:hover, .black-theme .randomButton:hover,