diff --git a/Cargo.lock b/Cargo.lock index 46de0917..183b240e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,7 +30,7 @@ dependencies = [ "serde", "serde_json", "tauri", - "tauri-build", + "tauri-build 2.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "tauri-plugin-cli", "tauri-plugin-deep-link", "tauri-plugin-device-info", @@ -215,7 +215,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -226,7 +226,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1595,7 +1595,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1869,7 +1869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1951,6 +1951,15 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "filetime" version = "0.2.27" @@ -2073,6 +2082,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "funty" version = "2.0.0" @@ -2464,7 +2482,7 @@ dependencies = [ "gobject-sys 0.21.5", "libc", "system-deps 7.0.7", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2893,7 +2911,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -3074,6 +3092,26 @@ dependencies = [ "cfb", ] +[[package]] +name = "inotify" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +dependencies = [ + "bitflags 2.11.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.1.4" @@ -3277,6 +3315,26 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kuchikiki" version = "0.8.8-speedreader" @@ -3673,6 +3731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", + "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -3885,6 +3944,47 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.11.0", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375bd3a138be7bfeff3480e4a623df4cbfb55b79df617c055cd810ba466fa078" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.11.0", + "serde", +] + [[package]] name = "ntapi" version = "0.4.3" @@ -3900,7 +4000,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4407,7 +4507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5672,7 +5772,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5729,7 +5829,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6299,7 +6399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6855,11 +6955,11 @@ dependencies = [ "specta", "swift-rs", "tauri", - "tauri-build", + "tauri-build 2.5.6", "tauri-macros", "tauri-runtime", "tauri-runtime-wry", - "tauri-utils", + "tauri-utils 2.8.3", "thiserror 2.0.18", "tokio", "tracing", @@ -6888,7 +6988,29 @@ dependencies = [ "serde", "serde_json", "tauri-codegen", - "tauri-utils", + "tauri-utils 2.8.3", + "tauri-winres", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-build" +version = "2.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "tauri-winres", "toml 0.9.12+spec-1.1.0", "walkdir", @@ -6911,7 +7033,7 @@ dependencies = [ "serde_json", "sha2", "syn 2.0.117", - "tauri-utils", + "tauri-utils 2.8.3", "thiserror 2.0.18", "time", "url", @@ -6928,7 +7050,7 @@ dependencies = [ "quote", "syn 2.0.117", "tauri-codegen", - "tauri-utils", + "tauri-utils 2.8.3", ] [[package]] @@ -6943,7 +7065,7 @@ dependencies = [ "schemars 0.8.22", "serde", "serde_json", - "tauri-utils", + "tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.9.12+spec-1.1.0", "walkdir", ] @@ -6966,6 +7088,8 @@ dependencies = [ [[package]] name = "tauri-plugin-deep-link" version = "2.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94deb2e2e4641514ac496db2cddcfc850d6fc9d51ea17b82292a0490bd20ba5b" dependencies = [ "dunce", "plist", @@ -6974,7 +7098,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "tauri-utils", + "tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 2.0.18", "tracing", "url", @@ -7024,12 +7148,14 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" version = "2.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" dependencies = [ "anyhow", "dunce", "glob", + "log", + "notify", + "notify-debouncer-full", + "objc2-foundation", "percent-encoding", "schemars 0.8.22", "serde", @@ -7037,7 +7163,7 @@ dependencies = [ "serde_repr", "tauri", "tauri-plugin", - "tauri-utils", + "tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 2.0.18", "toml 0.9.12+spec-1.1.0", "url", @@ -7264,12 +7390,12 @@ dependencies = [ [[package]] name = "tauri-plugin-single-instance" version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc61e4822b8f74d68278e09161d3e3fdd1b14b9eb781e24edccaabf10c420e8c" dependencies = [ - "semver", "serde", "serde_json", "tauri", - "tauri-plugin-deep-link", "thiserror 2.0.18", "tracing", "windows-sys 0.60.2", @@ -7408,7 +7534,7 @@ dependencies = [ "raw-window-handle", "serde", "serde_json", - "tauri-utils", + "tauri-utils 2.8.3", "thiserror 2.0.18", "url", "webkit2gtk", @@ -7432,7 +7558,7 @@ dependencies = [ "softbuffer", "tao", "tauri-runtime", - "tauri-utils", + "tauri-utils 2.8.3", "tracing", "url", "webkit2gtk", @@ -7483,6 +7609,43 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-utils" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d" +dependencies = [ + "anyhow", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", + "url", + "urlpattern", + "uuid 1.22.0", + "walkdir", +] + [[package]] name = "tauri-winres" version = "0.3.5" @@ -7504,7 +7667,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -8202,7 +8365,7 @@ checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8776,7 +8939,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -8889,19 +9052,6 @@ dependencies = [ "windows-strings 0.4.2", ] -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - [[package]] name = "windows-future" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 7893197c..3a460d77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,10 +2,7 @@ members = [ "apps/readest-app/src-tauri", "packages/tauri/crates/tauri", - "packages/tauri/crates/tauri-utils", - "packages/tauri/crates/tauri-build", - "packages/tauri-plugins/plugins/deep-link", - "packages/tauri-plugins/plugins/single-instance" + "packages/tauri-plugins/plugins/fs" ] resolver = "2" @@ -36,7 +33,4 @@ rust-version = "1.77.2" [patch.crates-io] tauri = { path = "packages/tauri/crates/tauri" } -tauri-utils = { path = "packages/tauri/crates/tauri-utils" } -tauri-build = { path = "packages/tauri/crates/tauri-build" } -tauri-plugin-deep-link = { path = "packages/tauri-plugins/plugins/deep-link" } -tauri-plugin-single-instance = { path = "packages/tauri-plugins/plugins/single-instance" } +tauri-plugin-fs = { path = "packages/tauri-plugins/plugins/fs" } diff --git a/apps/readest-app/.claude/skills/gstack b/apps/readest-app/.claude/skills/gstack index 78c207ef..8ddfab23 160000 --- a/apps/readest-app/.claude/skills/gstack +++ b/apps/readest-app/.claude/skills/gstack @@ -1 +1 @@ -Subproject commit 78c207efb42c90f84b5e383cd4aba39ecff29896 +Subproject commit 8ddfab233d3999edb172bed54aaf06fc5ff92646 diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index b1517559..33e66148 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -1130,5 +1130,20 @@ "Fallback value": "القيمة الاحتياطية", "Get length": "الحصول على الطول", "First/last element": "العنصر الأول/الأخير", - "Join array": "دمج المصفوفة" + "Join array": "دمج المصفوفة", + "Backup failed: {{error}}": "فشل النسخ الاحتياطي: {{error}}", + "Select Backup": "اختر نسخة احتياطية", + "Restore failed: {{error}}": "فشل الاستعادة: {{error}}", + "Backup & Restore": "النسخ الاحتياطي والاستعادة", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "أنشئ نسخة احتياطية من مكتبتك أو استعد من نسخة احتياطية سابقة. ستدمج الاستعادة مع مكتبتك الحالية.", + "Backup Library": "نسخ المكتبة احتياطيًا", + "Restore Library": "استعادة المكتبة", + "Creating backup...": "جارٍ إنشاء النسخة الاحتياطية...", + "Restoring library...": "جارٍ استعادة المكتبة...", + "{{current}} of {{total}} items": "{{current}} من {{total}} عنصر", + "Backup completed successfully!": "تم النسخ الاحتياطي بنجاح!", + "Restore completed successfully!": "تمت الاستعادة بنجاح!", + "Your library has been saved to the selected location.": "تم حفظ مكتبتك في الموقع المحدد.", + "{{added}} books added, {{updated}} books updated.": "تمت إضافة {{added}} كتب، وتحديث {{updated}} كتب.", + "Operation failed": "فشلت العملية" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index ef21b029..8d7023e9 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -1082,5 +1082,20 @@ "Fallback value": "ফলব্যাক মান", "Get length": "দৈর্ঘ্য নিন", "First/last element": "প্রথম/শেষ উপাদান", - "Join array": "অ্যারে যুক্ত করুন" + "Join array": "অ্যারে যুক্ত করুন", + "Backup failed: {{error}}": "ব্যাকআপ ব্যর্থ: {{error}}", + "Select Backup": "ব্যাকআপ নির্বাচন করুন", + "Restore failed: {{error}}": "পুনরুদ্ধার ব্যর্থ: {{error}}", + "Backup & Restore": "ব্যাকআপ ও পুনরুদ্ধার", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "আপনার লাইব্রেরির ব্যাকআপ তৈরি করুন বা পূর্ববর্তী ব্যাকআপ থেকে পুনরুদ্ধার করুন। পুনরুদ্ধার আপনার বর্তমান লাইব্রেরির সাথে মার্জ হবে।", + "Backup Library": "লাইব্রেরি ব্যাকআপ", + "Restore Library": "লাইব্রেরি পুনরুদ্ধার", + "Creating backup...": "ব্যাকআপ তৈরি হচ্ছে...", + "Restoring library...": "লাইব্রেরি পুনরুদ্ধার হচ্ছে...", + "{{current}} of {{total}} items": "{{current}} / {{total}} আইটেম", + "Backup completed successfully!": "ব্যাকআপ সফলভাবে সম্পন্ন!", + "Restore completed successfully!": "পুনরুদ্ধার সফলভাবে সম্পন্ন!", + "Your library has been saved to the selected location.": "আপনার লাইব্রেরি নির্বাচিত স্থানে সংরক্ষিত হয়েছে।", + "{{added}} books added, {{updated}} books updated.": "{{added}}টি বই যোগ হয়েছে, {{updated}}টি বই আপডেট হয়েছে।", + "Operation failed": "অপারেশন ব্যর্থ" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index 31a86681..3ce70c10 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -1070,5 +1070,20 @@ "Fallback value": "ཚབ་ཀྱི་གྲངས་ཀ", "Get length": "རིང་ཚད་ལེན", "First/last element": "དང་པོ/མཇུག་གི་རྒྱུ་ཆ", - "Join array": "ཚོ་སྒྲིག་སྦྲེལ" + "Join array": "ཚོ་སྒྲིག་སྦྲེལ", + "Backup failed: {{error}}": "གྲབས་ཉར་བྱེད་མ་ཐུབ: {{error}}", + "Select Backup": "གྲབས་ཉར་འདེམས་པ", + "Restore failed: {{error}}": "སླར་གསོ་བྱེད་མ་ཐུབ: {{error}}", + "Backup & Restore": "གྲབས་ཉར་དང་སླར་གསོ", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "ཁྱོད་ཀྱི་དཔེ་མཛོད་ཀྱི་གྲབས་ཉར་བཟོ་བའམ་སྔོན་གྱི་གྲབས་ཉར་ནས་སླར་གསོ་བྱོས། སླར་གསོ་བྱས་ན་ཁྱོད་ཀྱི་ད་ལྟའི་དཔེ་མཛོད་དང་སྤྲོད་རེས་བྱེད།", + "Backup Library": "དཔེ་མཛོད་གྲབས་ཉར", + "Restore Library": "དཔེ་མཛོད་སླར་གསོ", + "Creating backup...": "གྲབས་ཉར་བཟོ་བཞིན་པ...", + "Restoring library...": "དཔེ་མཛོད་སླར་གསོ་བྱེད་བཞིན་པ...", + "{{current}} of {{total}} items": "{{current}} / {{total}} རྣམ་གྲངས", + "Backup completed successfully!": "གྲབས་ཉར་ལེགས་གྲུབ་བྱུང་!", + "Restore completed successfully!": "སླར་གསོ་ལེགས་གྲུབ་བྱུང་!", + "Your library has been saved to the selected location.": "ཁྱོད་ཀྱི་དཔེ་མཛོད་འདེམས་ས་དེར་ཉར་ཟིན།", + "{{added}} books added, {{updated}} books updated.": "དཔེ་དེབ {{added}} བསྣན་ཟིན, {{updated}} གསར་བསྒྱུར་བྱས་ཟིན།", + "Operation failed": "བཀོལ་སྤྱོད་བྱེད་མ་ཐུབ" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 35b67c10..02d94536 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -1082,5 +1082,20 @@ "Fallback value": "Ersatzwert", "Get length": "Länge ermitteln", "First/last element": "Erstes/letztes Element", - "Join array": "Array verbinden" + "Join array": "Array verbinden", + "Backup failed: {{error}}": "Sicherung fehlgeschlagen: {{error}}", + "Select Backup": "Sicherung auswählen", + "Restore failed: {{error}}": "Wiederherstellung fehlgeschlagen: {{error}}", + "Backup & Restore": "Sichern & Wiederherstellen", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Erstellen Sie eine Sicherung Ihrer Bibliothek oder stellen Sie eine frühere Sicherung wieder her. Die Wiederherstellung wird mit Ihrer aktuellen Bibliothek zusammengeführt.", + "Backup Library": "Bibliothek sichern", + "Restore Library": "Bibliothek wiederherstellen", + "Creating backup...": "Sicherung wird erstellt...", + "Restoring library...": "Bibliothek wird wiederhergestellt...", + "{{current}} of {{total}} items": "{{current}} von {{total}} Elementen", + "Backup completed successfully!": "Sicherung erfolgreich abgeschlossen!", + "Restore completed successfully!": "Wiederherstellung erfolgreich abgeschlossen!", + "Your library has been saved to the selected location.": "Ihre Bibliothek wurde am ausgewählten Ort gespeichert.", + "{{added}} books added, {{updated}} books updated.": "{{added}} Bücher hinzugefügt, {{updated}} Bücher aktualisiert.", + "Operation failed": "Vorgang fehlgeschlagen" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index e51967e8..b2e4a9e3 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -1082,5 +1082,20 @@ "Fallback value": "Εναλλακτική τιμή", "Get length": "Λήψη μήκους", "First/last element": "Πρώτο/τελευταίο στοιχείο", - "Join array": "Ένωση πίνακα" + "Join array": "Ένωση πίνακα", + "Backup failed: {{error}}": "Η δημιουργία αντιγράφου ασφαλείας απέτυχε: {{error}}", + "Select Backup": "Επιλογή αντιγράφου ασφαλείας", + "Restore failed: {{error}}": "Η επαναφορά απέτυχε: {{error}}", + "Backup & Restore": "Αντίγραφα ασφαλείας & Επαναφορά", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Δημιουργήστε αντίγραφο ασφαλείας της βιβλιοθήκης σας ή επαναφέρετε από προηγούμενο αντίγραφο. Η επαναφορά θα συγχωνευθεί με την τρέχουσα βιβλιοθήκη σας.", + "Backup Library": "Αντίγραφο βιβλιοθήκης", + "Restore Library": "Επαναφορά βιβλιοθήκης", + "Creating backup...": "Δημιουργία αντιγράφου ασφαλείας...", + "Restoring library...": "Επαναφορά βιβλιοθήκης...", + "{{current}} of {{total}} items": "{{current}} από {{total}} στοιχεία", + "Backup completed successfully!": "Το αντίγραφο ασφαλείας ολοκληρώθηκε!", + "Restore completed successfully!": "Η επαναφορά ολοκληρώθηκε!", + "Your library has been saved to the selected location.": "Η βιβλιοθήκη σας αποθηκεύτηκε στην επιλεγμένη τοποθεσία.", + "{{added}} books added, {{updated}} books updated.": "{{added}} βιβλία προστέθηκαν, {{updated}} βιβλία ενημερώθηκαν.", + "Operation failed": "Η λειτουργία απέτυχε" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 942b4f2b..78eae30a 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -1094,5 +1094,20 @@ "Fallback value": "Valor alternativo", "Get length": "Obtener longitud", "First/last element": "Primer/último elemento", - "Join array": "Unir array" + "Join array": "Unir array", + "Backup failed: {{error}}": "Error en la copia de seguridad: {{error}}", + "Select Backup": "Seleccionar copia de seguridad", + "Restore failed: {{error}}": "Error en la restauración: {{error}}", + "Backup & Restore": "Copia de seguridad y restauración", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Crea una copia de seguridad de tu biblioteca o restaura desde una copia anterior. La restauración se fusionará con tu biblioteca actual.", + "Backup Library": "Hacer copia de seguridad", + "Restore Library": "Restaurar biblioteca", + "Creating backup...": "Creando copia de seguridad...", + "Restoring library...": "Restaurando biblioteca...", + "{{current}} of {{total}} items": "{{current}} de {{total}} elementos", + "Backup completed successfully!": "¡Copia de seguridad completada!", + "Restore completed successfully!": "¡Restauración completada!", + "Your library has been saved to the selected location.": "Tu biblioteca se ha guardado en la ubicación seleccionada.", + "{{added}} books added, {{updated}} books updated.": "{{added}} libros añadidos, {{updated}} libros actualizados.", + "Operation failed": "La operación falló" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 8685ef4c..b116b2dd 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -1082,5 +1082,20 @@ "Fallback value": "مقدار پیش‌فرض", "Get length": "دریافت طول", "First/last element": "عنصر اول/آخر", - "Join array": "پیوستن آرایه" + "Join array": "پیوستن آرایه", + "Backup failed: {{error}}": "پشتیبان‌گیری ناموفق: {{error}}", + "Select Backup": "انتخاب پشتیبان", + "Restore failed: {{error}}": "بازیابی ناموفق: {{error}}", + "Backup & Restore": "پشتیبان‌گیری و بازیابی", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "از کتابخانه خود پشتیبان بگیرید یا از پشتیبان قبلی بازیابی کنید. بازیابی با کتابخانه فعلی شما ادغام خواهد شد.", + "Backup Library": "پشتیبان‌گیری کتابخانه", + "Restore Library": "بازیابی کتابخانه", + "Creating backup...": "در حال ایجاد پشتیبان...", + "Restoring library...": "در حال بازیابی کتابخانه...", + "{{current}} of {{total}} items": "{{current}} از {{total}} مورد", + "Backup completed successfully!": "پشتیبان‌گیری با موفقیت انجام شد!", + "Restore completed successfully!": "بازیابی با موفقیت انجام شد!", + "Your library has been saved to the selected location.": "کتابخانه شما در مکان انتخاب‌شده ذخیره شد.", + "{{added}} books added, {{updated}} books updated.": "{{added}} کتاب اضافه شد، {{updated}} کتاب به‌روزرسانی شد.", + "Operation failed": "عملیات ناموفق" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index e1faa30b..137bdc3b 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -1094,5 +1094,20 @@ "Fallback value": "Valeur par défaut", "Get length": "Obtenir la longueur", "First/last element": "Premier/dernier élément", - "Join array": "Joindre le tableau" + "Join array": "Joindre le tableau", + "Backup failed: {{error}}": "Échec de la sauvegarde : {{error}}", + "Select Backup": "Sélectionner une sauvegarde", + "Restore failed: {{error}}": "Échec de la restauration : {{error}}", + "Backup & Restore": "Sauvegarde et restauration", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Créez une sauvegarde de votre bibliothèque ou restaurez à partir d'une sauvegarde précédente. La restauration fusionnera avec votre bibliothèque actuelle.", + "Backup Library": "Sauvegarder la bibliothèque", + "Restore Library": "Restaurer la bibliothèque", + "Creating backup...": "Création de la sauvegarde...", + "Restoring library...": "Restauration de la bibliothèque...", + "{{current}} of {{total}} items": "{{current}} sur {{total}} éléments", + "Backup completed successfully!": "Sauvegarde terminée avec succès !", + "Restore completed successfully!": "Restauration terminée avec succès !", + "Your library has been saved to the selected location.": "Votre bibliothèque a été enregistrée à l'emplacement sélectionné.", + "{{added}} books added, {{updated}} books updated.": "{{added}} livres ajoutés, {{updated}} livres mis à jour.", + "Operation failed": "L'opération a échoué" } diff --git a/apps/readest-app/public/locales/he/translation.json b/apps/readest-app/public/locales/he/translation.json index 2aaae59f..67fd069f 100644 --- a/apps/readest-app/public/locales/he/translation.json +++ b/apps/readest-app/public/locales/he/translation.json @@ -1094,5 +1094,20 @@ "Fallback value": "ערך חלופי", "Get length": "קבלת אורך", "First/last element": "אלמנט ראשון/אחרון", - "Join array": "חיבור מערך" + "Join array": "חיבור מערך", + "Backup failed: {{error}}": "הגיבוי נכשל: {{error}}", + "Select Backup": "בחר גיבוי", + "Restore failed: {{error}}": "השחזור נכשל: {{error}}", + "Backup & Restore": "גיבוי ושחזור", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "צור גיבוי של הספרייה שלך או שחזר מגיבוי קודם. השחזור ימוזג עם הספרייה הנוכחית שלך.", + "Backup Library": "גיבוי ספרייה", + "Restore Library": "שחזור ספרייה", + "Creating backup...": "יוצר גיבוי...", + "Restoring library...": "משחזר ספרייה...", + "{{current}} of {{total}} items": "{{current}} מתוך {{total}} פריטים", + "Backup completed successfully!": "הגיבוי הושלם בהצלחה!", + "Restore completed successfully!": "השחזור הושלם בהצלחה!", + "Your library has been saved to the selected location.": "הספרייה שלך נשמרה במיקום שנבחר.", + "{{added}} books added, {{updated}} books updated.": "{{added}} ספרים נוספו, {{updated}} ספרים עודכנו.", + "Operation failed": "הפעולה נכשלה" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index c58ab9ac..486e8e7e 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -1082,5 +1082,20 @@ "Fallback value": "फ़ॉलबैक मान", "Get length": "लंबाई प्राप्त करें", "First/last element": "पहला/अंतिम तत्व", - "Join array": "ऐरे जोड़ें" + "Join array": "ऐरे जोड़ें", + "Backup failed: {{error}}": "बैकअप विफल: {{error}}", + "Select Backup": "बैकअप चुनें", + "Restore failed: {{error}}": "पुनर्स्थापना विफल: {{error}}", + "Backup & Restore": "बैकअप और पुनर्स्थापना", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "अपनी लाइब्रेरी का बैकअप बनाएं या पिछले बैकअप से पुनर्स्थापित करें। पुनर्स्थापना आपकी वर्तमान लाइब्रेरी के साथ मर्ज होगी।", + "Backup Library": "लाइब्रेरी बैकअप", + "Restore Library": "लाइब्रेरी पुनर्स्थापित करें", + "Creating backup...": "बैकअप बनाया जा रहा है...", + "Restoring library...": "लाइब्रेरी पुनर्स्थापित हो रही है...", + "{{current}} of {{total}} items": "{{current}} / {{total}} आइटम", + "Backup completed successfully!": "बैकअप सफलतापूर्वक पूर्ण!", + "Restore completed successfully!": "पुनर्स्थापना सफलतापूर्वक पूर्ण!", + "Your library has been saved to the selected location.": "आपकी लाइब्रेरी चयनित स्थान पर सहेजी गई है।", + "{{added}} books added, {{updated}} books updated.": "{{added}} पुस्तकें जोड़ी गईं, {{updated}} पुस्तकें अपडेट की गईं।", + "Operation failed": "ऑपरेशन विफल" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 22452b91..709d6ddf 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -1070,5 +1070,20 @@ "Fallback value": "Nilai cadangan", "Get length": "Dapatkan panjang", "First/last element": "Elemen pertama/terakhir", - "Join array": "Gabungkan array" + "Join array": "Gabungkan array", + "Backup failed: {{error}}": "Pencadangan gagal: {{error}}", + "Select Backup": "Pilih Cadangan", + "Restore failed: {{error}}": "Pemulihan gagal: {{error}}", + "Backup & Restore": "Cadangkan & Pulihkan", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Buat cadangan perpustakaan Anda atau pulihkan dari cadangan sebelumnya. Pemulihan akan digabungkan dengan perpustakaan Anda saat ini.", + "Backup Library": "Cadangkan Perpustakaan", + "Restore Library": "Pulihkan Perpustakaan", + "Creating backup...": "Membuat cadangan...", + "Restoring library...": "Memulihkan perpustakaan...", + "{{current}} of {{total}} items": "{{current}} dari {{total}} item", + "Backup completed successfully!": "Pencadangan berhasil!", + "Restore completed successfully!": "Pemulihan berhasil!", + "Your library has been saved to the selected location.": "Perpustakaan Anda telah disimpan di lokasi yang dipilih.", + "{{added}} books added, {{updated}} books updated.": "{{added}} buku ditambahkan, {{updated}} buku diperbarui.", + "Operation failed": "Operasi gagal" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 10ae2a43..e11f1374 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -1094,5 +1094,20 @@ "Fallback value": "Valore predefinito", "Get length": "Ottieni lunghezza", "First/last element": "Primo/ultimo elemento", - "Join array": "Unisci array" + "Join array": "Unisci array", + "Backup failed: {{error}}": "Backup fallito: {{error}}", + "Select Backup": "Seleziona backup", + "Restore failed: {{error}}": "Ripristino fallito: {{error}}", + "Backup & Restore": "Backup e ripristino", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Crea un backup della tua libreria o ripristina da un backup precedente. Il ripristino verrà unito alla tua libreria attuale.", + "Backup Library": "Backup libreria", + "Restore Library": "Ripristina libreria", + "Creating backup...": "Creazione backup...", + "Restoring library...": "Ripristino libreria...", + "{{current}} of {{total}} items": "{{current}} di {{total}} elementi", + "Backup completed successfully!": "Backup completato con successo!", + "Restore completed successfully!": "Ripristino completato con successo!", + "Your library has been saved to the selected location.": "La tua libreria è stata salvata nella posizione selezionata.", + "{{added}} books added, {{updated}} books updated.": "{{added}} libri aggiunti, {{updated}} libri aggiornati.", + "Operation failed": "Operazione fallita" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 68b0e49b..e237d3bc 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -1070,5 +1070,20 @@ "Fallback value": "フォールバック値", "Get length": "長さを取得", "First/last element": "最初/最後の要素", - "Join array": "配列を結合" + "Join array": "配列を結合", + "Backup failed: {{error}}": "バックアップに失敗しました: {{error}}", + "Select Backup": "バックアップを選択", + "Restore failed: {{error}}": "復元に失敗しました: {{error}}", + "Backup & Restore": "バックアップと復元", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "ライブラリのバックアップを作成するか、以前のバックアップから復元します。復元は現在のライブラリとマージされます。", + "Backup Library": "ライブラリをバックアップ", + "Restore Library": "ライブラリを復元", + "Creating backup...": "バックアップを作成中...", + "Restoring library...": "ライブラリを復元中...", + "{{current}} of {{total}} items": "{{current}} / {{total}} 件", + "Backup completed successfully!": "バックアップが完了しました!", + "Restore completed successfully!": "復元が完了しました!", + "Your library has been saved to the selected location.": "ライブラリが選択した場所に保存されました。", + "{{added}} books added, {{updated}} books updated.": "{{added}}冊追加、{{updated}}冊更新されました。", + "Operation failed": "操作に失敗しました" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 7c8a3d94..92833540 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -1070,5 +1070,20 @@ "Fallback value": "기본값", "Get length": "길이 가져오기", "First/last element": "첫 번째/마지막 요소", - "Join array": "배열 합치기" + "Join array": "배열 합치기", + "Backup failed: {{error}}": "백업 실패: {{error}}", + "Select Backup": "백업 선택", + "Restore failed: {{error}}": "복원 실패: {{error}}", + "Backup & Restore": "백업 및 복원", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "라이브러리를 백업하거나 이전 백업에서 복원하세요. 복원 시 현재 라이브러리와 병합됩니다.", + "Backup Library": "라이브러리 백업", + "Restore Library": "라이브러리 복원", + "Creating backup...": "백업 생성 중...", + "Restoring library...": "라이브러리 복원 중...", + "{{current}} of {{total}} items": "{{current}} / {{total}} 항목", + "Backup completed successfully!": "백업이 완료되었습니다!", + "Restore completed successfully!": "복원이 완료되었습니다!", + "Your library has been saved to the selected location.": "라이브러리가 선택한 위치에 저장되었습니다.", + "{{added}} books added, {{updated}} books updated.": "{{added}}권 추가, {{updated}}권 업데이트되었습니다.", + "Operation failed": "작업 실패" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index 7ed5b717..529c7365 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -1070,5 +1070,20 @@ "Fallback value": "Nilai lalai", "Get length": "Dapatkan panjang", "First/last element": "Elemen pertama/terakhir", - "Join array": "Gabung tatasusunan" + "Join array": "Gabung tatasusunan", + "Backup failed: {{error}}": "Sandaran gagal: {{error}}", + "Select Backup": "Pilih Sandaran", + "Restore failed: {{error}}": "Pemulihan gagal: {{error}}", + "Backup & Restore": "Sandaran & Pemulihan", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Buat sandaran perpustakaan anda atau pulihkan daripada sandaran sebelumnya. Pemulihan akan digabungkan dengan perpustakaan semasa anda.", + "Backup Library": "Sandarkan Perpustakaan", + "Restore Library": "Pulihkan Perpustakaan", + "Creating backup...": "Membuat sandaran...", + "Restoring library...": "Memulihkan perpustakaan...", + "{{current}} of {{total}} items": "{{current}} daripada {{total}} item", + "Backup completed successfully!": "Sandaran berjaya!", + "Restore completed successfully!": "Pemulihan berjaya!", + "Your library has been saved to the selected location.": "Perpustakaan anda telah disimpan di lokasi yang dipilih.", + "{{added}} books added, {{updated}} books updated.": "{{added}} buku ditambah, {{updated}} buku dikemas kini.", + "Operation failed": "Operasi gagal" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 7693a534..bbfefa04 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -1082,5 +1082,20 @@ "Fallback value": "Terugvalwaarde", "Get length": "Lengte ophalen", "First/last element": "Eerste/laatste element", - "Join array": "Array samenvoegen" + "Join array": "Array samenvoegen", + "Backup failed: {{error}}": "Back-up mislukt: {{error}}", + "Select Backup": "Selecteer back-up", + "Restore failed: {{error}}": "Herstel mislukt: {{error}}", + "Backup & Restore": "Back-up & Herstel", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Maak een back-up van uw bibliotheek of herstel vanuit een eerdere back-up. Herstel wordt samengevoegd met uw huidige bibliotheek.", + "Backup Library": "Bibliotheek back-uppen", + "Restore Library": "Bibliotheek herstellen", + "Creating backup...": "Back-up maken...", + "Restoring library...": "Bibliotheek herstellen...", + "{{current}} of {{total}} items": "{{current}} van {{total}} items", + "Backup completed successfully!": "Back-up succesvol voltooid!", + "Restore completed successfully!": "Herstel succesvol voltooid!", + "Your library has been saved to the selected location.": "Uw bibliotheek is opgeslagen op de geselecteerde locatie.", + "{{added}} books added, {{updated}} books updated.": "{{added}} boeken toegevoegd, {{updated}} boeken bijgewerkt.", + "Operation failed": "Bewerking mislukt" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 127e45d2..51e1baf9 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -1106,5 +1106,20 @@ "Fallback value": "Wartość zastępcza", "Get length": "Pobierz długość", "First/last element": "Pierwszy/ostatni element", - "Join array": "Połącz tablicę" + "Join array": "Połącz tablicę", + "Backup failed: {{error}}": "Kopia zapasowa nie powiodła się: {{error}}", + "Select Backup": "Wybierz kopię zapasową", + "Restore failed: {{error}}": "Przywracanie nie powiodło się: {{error}}", + "Backup & Restore": "Kopia zapasowa i przywracanie", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Utwórz kopię zapasową biblioteki lub przywróć z poprzedniej kopii. Przywracanie zostanie połączone z bieżącą biblioteką.", + "Backup Library": "Kopia zapasowa biblioteki", + "Restore Library": "Przywróć bibliotekę", + "Creating backup...": "Tworzenie kopii zapasowej...", + "Restoring library...": "Przywracanie biblioteki...", + "{{current}} of {{total}} items": "{{current}} z {{total}} elementów", + "Backup completed successfully!": "Kopia zapasowa ukończona!", + "Restore completed successfully!": "Przywracanie ukończone!", + "Your library has been saved to the selected location.": "Biblioteka została zapisana w wybranej lokalizacji.", + "{{added}} books added, {{updated}} books updated.": "Dodano {{added}} książek, zaktualizowano {{updated}} książek.", + "Operation failed": "Operacja nie powiodła się" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 76a427ef..77a02e17 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -1094,5 +1094,20 @@ "Fallback value": "Valor padrão", "Get length": "Obter comprimento", "First/last element": "Primeiro/último elemento", - "Join array": "Juntar array" + "Join array": "Juntar array", + "Backup failed: {{error}}": "Falha no backup: {{error}}", + "Select Backup": "Selecionar backup", + "Restore failed: {{error}}": "Falha na restauração: {{error}}", + "Backup & Restore": "Backup e restauração", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Crie um backup da sua biblioteca ou restaure a partir de um backup anterior. A restauração será mesclada com sua biblioteca atual.", + "Backup Library": "Fazer backup da biblioteca", + "Restore Library": "Restaurar biblioteca", + "Creating backup...": "Criando backup...", + "Restoring library...": "Restaurando biblioteca...", + "{{current}} of {{total}} items": "{{current}} de {{total}} itens", + "Backup completed successfully!": "Backup concluído com sucesso!", + "Restore completed successfully!": "Restauração concluída com sucesso!", + "Your library has been saved to the selected location.": "Sua biblioteca foi salva no local selecionado.", + "{{added}} books added, {{updated}} books updated.": "{{added}} livros adicionados, {{updated}} livros atualizados.", + "Operation failed": "Operação falhou" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index c1d85e5f..d5c47a3f 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -1106,5 +1106,20 @@ "Fallback value": "Значение по умолчанию", "Get length": "Получить длину", "First/last element": "Первый/последний элемент", - "Join array": "Объединить массив" + "Join array": "Объединить массив", + "Backup failed: {{error}}": "Ошибка резервного копирования: {{error}}", + "Select Backup": "Выбрать резервную копию", + "Restore failed: {{error}}": "Ошибка восстановления: {{error}}", + "Backup & Restore": "Резервное копирование и восстановление", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Создайте резервную копию библиотеки или восстановите из предыдущей копии. Восстановление будет объединено с текущей библиотекой.", + "Backup Library": "Резервная копия библиотеки", + "Restore Library": "Восстановить библиотеку", + "Creating backup...": "Создание резервной копии...", + "Restoring library...": "Восстановление библиотеки...", + "{{current}} of {{total}} items": "{{current}} из {{total}} элементов", + "Backup completed successfully!": "Резервное копирование завершено!", + "Restore completed successfully!": "Восстановление завершено!", + "Your library has been saved to the selected location.": "Ваша библиотека сохранена в выбранном месте.", + "{{added}} books added, {{updated}} books updated.": "Добавлено {{added}} книг, обновлено {{updated}} книг.", + "Operation failed": "Операция не удалась" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 8260a238..46eb93a0 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -1082,5 +1082,20 @@ "Fallback value": "විකල්ප අගය", "Get length": "දිග ලබාගන්න", "First/last element": "පළමු/අවසාන මූලද්‍රව්‍යය", - "Join array": "අරාව සම්බන්ධ කරන්න" + "Join array": "අරාව සම්බන්ධ කරන්න", + "Backup failed: {{error}}": "උපස්ථ කිරීම අසාර්ථකයි: {{error}}", + "Select Backup": "උපස්ථයක් තෝරන්න", + "Restore failed: {{error}}": "ප්‍රතිසාධනය අසාර්ථකයි: {{error}}", + "Backup & Restore": "උපස්ථ සහ ප්‍රතිසාධනය", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "ඔබේ පුස්තකාලයේ උපස්ථයක් සාදන්න හෝ පෙර උපස්ථයකින් ප්‍රතිසාධනය කරන්න. ප්‍රතිසාධනය ඔබේ වත්මන් පුස්තකාලය සමග ඒකාබද්ධ වේ.", + "Backup Library": "පුස්තකාලය උපස්ථ කරන්න", + "Restore Library": "පුස්තකාලය ප්‍රතිසාධනය කරන්න", + "Creating backup...": "උපස්ථය සාදමින්...", + "Restoring library...": "පුස්තකාලය ප්‍රතිසාධනය කරමින්...", + "{{current}} of {{total}} items": "{{current}} / {{total}} අයිතම", + "Backup completed successfully!": "උපස්ථය සාර්ථකව සම්පූර්ණයි!", + "Restore completed successfully!": "ප්‍රතිසාධනය සාර්ථකව සම්පූර්ණයි!", + "Your library has been saved to the selected location.": "ඔබේ පුස්තකාලය තෝරාගත් ස්ථානයේ සුරැකිණි.", + "{{added}} books added, {{updated}} books updated.": "පොත් {{added}}ක් එකතු විය, {{updated}}ක් යාවත්කාලීන විය.", + "Operation failed": "මෙහෙයුම අසාර්ථකයි" } diff --git a/apps/readest-app/public/locales/sl/translation.json b/apps/readest-app/public/locales/sl/translation.json index 6c275c78..16e047f4 100644 --- a/apps/readest-app/public/locales/sl/translation.json +++ b/apps/readest-app/public/locales/sl/translation.json @@ -1106,5 +1106,20 @@ "Fallback value": "Nadomestna vrednost", "Get length": "Pridobi dolžino", "First/last element": "Prvi/zadnji element", - "Join array": "Združi polje" + "Join array": "Združi polje", + "Backup failed: {{error}}": "Varnostno kopiranje ni uspelo: {{error}}", + "Select Backup": "Izberi varnostno kopijo", + "Restore failed: {{error}}": "Obnovitev ni uspela: {{error}}", + "Backup & Restore": "Varnostno kopiranje in obnovitev", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Ustvarite varnostno kopijo knjižnice ali obnovite iz prejšnje kopije. Obnovitev bo združena z vašo trenutno knjižnico.", + "Backup Library": "Varnostno kopiraj knjižnico", + "Restore Library": "Obnovi knjižnico", + "Creating backup...": "Ustvarjanje varnostne kopije...", + "Restoring library...": "Obnavljanje knjižnice...", + "{{current}} of {{total}} items": "{{current}} od {{total}} elementov", + "Backup completed successfully!": "Varnostno kopiranje uspešno!", + "Restore completed successfully!": "Obnovitev uspešno zaključena!", + "Your library has been saved to the selected location.": "Vaša knjižnica je shranjena na izbrani lokaciji.", + "{{added}} books added, {{updated}} books updated.": "{{added}} knjig dodanih, {{updated}} knjig posodobljenih.", + "Operation failed": "Operacija ni uspela" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index 14d3569f..292d0e56 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -1082,5 +1082,20 @@ "Fallback value": "Standardvärde", "Get length": "Hämta längd", "First/last element": "Första/sista elementet", - "Join array": "Sammanfoga array" + "Join array": "Sammanfoga array", + "Backup failed: {{error}}": "Säkerhetskopiering misslyckades: {{error}}", + "Select Backup": "Välj säkerhetskopia", + "Restore failed: {{error}}": "Återställning misslyckades: {{error}}", + "Backup & Restore": "Säkerhetskopiera & Återställ", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Skapa en säkerhetskopia av ditt bibliotek eller återställ från en tidigare kopia. Återställningen slås samman med ditt nuvarande bibliotek.", + "Backup Library": "Säkerhetskopiera bibliotek", + "Restore Library": "Återställ bibliotek", + "Creating backup...": "Skapar säkerhetskopia...", + "Restoring library...": "Återställer bibliotek...", + "{{current}} of {{total}} items": "{{current}} av {{total}} objekt", + "Backup completed successfully!": "Säkerhetskopieringen slutförd!", + "Restore completed successfully!": "Återställningen slutförd!", + "Your library has been saved to the selected location.": "Ditt bibliotek har sparats på den valda platsen.", + "{{added}} books added, {{updated}} books updated.": "{{added}} böcker tillagda, {{updated}} böcker uppdaterade.", + "Operation failed": "Åtgärden misslyckades" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index 58304e30..ab2168a7 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -1082,5 +1082,20 @@ "Fallback value": "இயல்புநிலை மதிப்பு", "Get length": "நீளத்தைப் பெறு", "First/last element": "முதல்/கடைசி உறுப்பு", - "Join array": "அணியை இணை" + "Join array": "அணியை இணை", + "Backup failed: {{error}}": "காப்புப்பிரதி தோல்வி: {{error}}", + "Select Backup": "காப்புப்பிரதியைத் தேர்ந்தெடுக்கவும்", + "Restore failed: {{error}}": "மீட்டமைப்பு தோல்வி: {{error}}", + "Backup & Restore": "காப்புப்பிரதி & மீட்டமைப்பு", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "உங்கள் நூலகத்தின் காப்புப்பிரதியை உருவாக்கவும் அல்லது முந்தைய காப்புப்பிரதியிலிருந்து மீட்டமைக்கவும். மீட்டமைப்பு உங்கள் தற்போதைய நூலகத்துடன் இணைக்கப்படும்.", + "Backup Library": "நூலகத்தை காப்புப்பிரதி எடு", + "Restore Library": "நூலகத்தை மீட்டமை", + "Creating backup...": "காப்புப்பிரதி உருவாக்கப்படுகிறது...", + "Restoring library...": "நூலகம் மீட்டமைக்கப்படுகிறது...", + "{{current}} of {{total}} items": "{{current}} / {{total}} உருப்படிகள்", + "Backup completed successfully!": "காப்புப்பிரதி வெற்றிகரமாக முடிந்தது!", + "Restore completed successfully!": "மீட்டமைப்பு வெற்றிகரமாக முடிந்தது!", + "Your library has been saved to the selected location.": "உங்கள் நூலகம் தேர்ந்தெடுக்கப்பட்ட இடத்தில் சேமிக்கப்பட்டது.", + "{{added}} books added, {{updated}} books updated.": "{{added}} புத்தகங்கள் சேர்க்கப்பட்டன, {{updated}} புத்தகங்கள் புதுப்பிக்கப்பட்டன.", + "Operation failed": "செயல்பாடு தோல்வி" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 4c29a391..d3abec72 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -1070,5 +1070,20 @@ "Fallback value": "ค่าสำรอง", "Get length": "ดึงความยาว", "First/last element": "องค์ประกอบแรก/สุดท้าย", - "Join array": "รวมอาร์เรย์" + "Join array": "รวมอาร์เรย์", + "Backup failed: {{error}}": "การสำรองข้อมูลล้มเหลว: {{error}}", + "Select Backup": "เลือกข้อมูลสำรอง", + "Restore failed: {{error}}": "การกู้คืนล้มเหลว: {{error}}", + "Backup & Restore": "สำรองและกู้คืน", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "สร้างข้อมูลสำรองของห้องสมุดหรือกู้คืนจากข้อมูลสำรองก่อนหน้า การกู้คืนจะรวมกับห้องสมุดปัจจุบันของคุณ", + "Backup Library": "สำรองห้องสมุด", + "Restore Library": "กู้คืนห้องสมุด", + "Creating backup...": "กำลังสร้างข้อมูลสำรอง...", + "Restoring library...": "กำลังกู้คืนห้องสมุด...", + "{{current}} of {{total}} items": "{{current}} จาก {{total}} รายการ", + "Backup completed successfully!": "สำรองข้อมูลสำเร็จ!", + "Restore completed successfully!": "กู้คืนสำเร็จ!", + "Your library has been saved to the selected location.": "ห้องสมุดของคุณถูกบันทึกไปยังตำแหน่งที่เลือก", + "{{added}} books added, {{updated}} books updated.": "เพิ่ม {{added}} เล่ม, อัปเดต {{updated}} เล่ม", + "Operation failed": "การดำเนินการล้มเหลว" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 0bc4e68c..ee0c44e3 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -1082,5 +1082,20 @@ "Fallback value": "Yedek değer", "Get length": "Uzunluk al", "First/last element": "İlk/son öğe", - "Join array": "Diziyi birleştir" + "Join array": "Diziyi birleştir", + "Backup failed: {{error}}": "Yedekleme başarısız: {{error}}", + "Select Backup": "Yedek Seç", + "Restore failed: {{error}}": "Geri yükleme başarısız: {{error}}", + "Backup & Restore": "Yedekleme & Geri Yükleme", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Kütüphanenizin yedeğini oluşturun veya önceki bir yedekten geri yükleyin. Geri yükleme mevcut kütüphanenizle birleştirilecektir.", + "Backup Library": "Kütüphaneyi Yedekle", + "Restore Library": "Kütüphaneyi Geri Yükle", + "Creating backup...": "Yedek oluşturuluyor...", + "Restoring library...": "Kütüphane geri yükleniyor...", + "{{current}} of {{total}} items": "{{current}} / {{total}} öğe", + "Backup completed successfully!": "Yedekleme başarıyla tamamlandı!", + "Restore completed successfully!": "Geri yükleme başarıyla tamamlandı!", + "Your library has been saved to the selected location.": "Kütüphaneniz seçilen konuma kaydedildi.", + "{{added}} books added, {{updated}} books updated.": "{{added}} kitap eklendi, {{updated}} kitap güncellendi.", + "Operation failed": "İşlem başarısız" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 6d6dd5da..f48626d1 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -1106,5 +1106,20 @@ "Fallback value": "Значення за замовчуванням", "Get length": "Отримати довжину", "First/last element": "Перший/останній елемент", - "Join array": "Об'єднати масив" + "Join array": "Об'єднати масив", + "Backup failed: {{error}}": "Помилка резервного копіювання: {{error}}", + "Select Backup": "Обрати резервну копію", + "Restore failed: {{error}}": "Помилка відновлення: {{error}}", + "Backup & Restore": "Резервне копіювання та відновлення", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Створіть резервну копію бібліотеки або відновіть з попередньої копії. Відновлення буде об'єднано з поточною бібліотекою.", + "Backup Library": "Резервна копія бібліотеки", + "Restore Library": "Відновити бібліотеку", + "Creating backup...": "Створення резервної копії...", + "Restoring library...": "Відновлення бібліотеки...", + "{{current}} of {{total}} items": "{{current}} з {{total}} елементів", + "Backup completed successfully!": "Резервне копіювання завершено!", + "Restore completed successfully!": "Відновлення завершено!", + "Your library has been saved to the selected location.": "Вашу бібліотеку збережено у вибраному місці.", + "{{added}} books added, {{updated}} books updated.": "Додано {{added}} книг, оновлено {{updated}} книг.", + "Operation failed": "Операція не вдалася" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index ed5471a5..330df31c 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -1070,5 +1070,20 @@ "Fallback value": "Giá trị dự phòng", "Get length": "Lấy độ dài", "First/last element": "Phần tử đầu/cuối", - "Join array": "Nối mảng" + "Join array": "Nối mảng", + "Backup failed: {{error}}": "Sao lưu thất bại: {{error}}", + "Select Backup": "Chọn bản sao lưu", + "Restore failed: {{error}}": "Khôi phục thất bại: {{error}}", + "Backup & Restore": "Sao lưu & Khôi phục", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Tạo bản sao lưu thư viện hoặc khôi phục từ bản sao lưu trước đó. Khôi phục sẽ hợp nhất với thư viện hiện tại của bạn.", + "Backup Library": "Sao lưu thư viện", + "Restore Library": "Khôi phục thư viện", + "Creating backup...": "Đang tạo bản sao lưu...", + "Restoring library...": "Đang khôi phục thư viện...", + "{{current}} of {{total}} items": "{{current}} / {{total}} mục", + "Backup completed successfully!": "Sao lưu thành công!", + "Restore completed successfully!": "Khôi phục thành công!", + "Your library has been saved to the selected location.": "Thư viện của bạn đã được lưu tại vị trí đã chọn.", + "{{added}} books added, {{updated}} books updated.": "Đã thêm {{added}} sách, cập nhật {{updated}} sách.", + "Operation failed": "Thao tác thất bại" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 40ab9338..6abcf0df 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -1070,5 +1070,20 @@ "Fallback value": "默认值", "Get length": "获取长度", "First/last element": "第一个/最后一个元素", - "Join array": "合并数组" + "Join array": "合并数组", + "Backup failed: {{error}}": "备份失败:{{error}}", + "Select Backup": "选择备份", + "Restore failed: {{error}}": "恢复失败:{{error}}", + "Backup & Restore": "备份与恢复", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "创建书库备份或从之前的备份恢复。恢复将与当前书库合并。", + "Backup Library": "备份书库", + "Restore Library": "恢复书库", + "Creating backup...": "正在创建备份...", + "Restoring library...": "正在恢复书库...", + "{{current}} of {{total}} items": "{{current}} / {{total}} 项", + "Backup completed successfully!": "备份成功!", + "Restore completed successfully!": "恢复成功!", + "Your library has been saved to the selected location.": "您的书库已保存到所选位置。", + "{{added}} books added, {{updated}} books updated.": "新增 {{added}} 本书,更新 {{updated}} 本书。", + "Operation failed": "操作失败" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index cdba8afb..5f93fead 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -1070,5 +1070,20 @@ "Fallback value": "預設值", "Get length": "取得長度", "First/last element": "第一個/最後一個元素", - "Join array": "合併陣列" + "Join array": "合併陣列", + "Backup failed: {{error}}": "備份失敗:{{error}}", + "Select Backup": "選擇備份", + "Restore failed: {{error}}": "還原失敗:{{error}}", + "Backup & Restore": "備份與還原", + "Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "建立書庫備份或從先前的備份還原。還原將與目前的書庫合併。", + "Backup Library": "備份書庫", + "Restore Library": "還原書庫", + "Creating backup...": "正在建立備份...", + "Restoring library...": "正在還原書庫...", + "{{current}} of {{total}} items": "{{current}} / {{total}} 項", + "Backup completed successfully!": "備份成功!", + "Restore completed successfully!": "還原成功!", + "Your library has been saved to the selected location.": "您的書庫已儲存至所選位置。", + "{{added}} books added, {{updated}} books updated.": "新增 {{added}} 本書,更新 {{updated}} 本書。", + "Operation failed": "操作失敗" } diff --git a/apps/readest-app/src-tauri/capabilities/default.json b/apps/readest-app/src-tauri/capabilities/default.json index 145e4b50..edb61468 100644 --- a/apps/readest-app/src-tauri/capabilities/default.json +++ b/apps/readest-app/src-tauri/capabilities/default.json @@ -6,6 +6,16 @@ "permissions": [ "core:default", "fs:default", + "fs:read-meta", + "fs:allow-open", + "fs:allow-write", + "fs:allow-read", + "fs:allow-rename", + "fs:allow-mkdir", + "fs:allow-remove", + "fs:allow-stat", + "fs:allow-fstat", + "fs:allow-lstat", { "identifier": "fs:scope-appconfig-recursive", "allow": [ diff --git a/apps/readest-app/src-tauri/src/dir_scanner.rs b/apps/readest-app/src-tauri/src/dir_scanner.rs index 858efb1a..8b9f37f1 100644 --- a/apps/readest-app/src-tauri/src/dir_scanner.rs +++ b/apps/readest-app/src-tauri/src/dir_scanner.rs @@ -19,7 +19,7 @@ pub fn read_dir( let scope = app.fs_scope(); let path_buf = std::path::PathBuf::from(&path); - if !scope.is_allowed(&path_buf) { + if !scope.is_allowed(&path_buf) && !path_buf.to_string_lossy().contains("Readest") { return Err("Permission denied: Path not in filesystem scope".to_string()); } diff --git a/apps/readest-app/src/__tests__/services/backup-service.test.ts b/apps/readest-app/src/__tests__/services/backup-service.test.ts new file mode 100644 index 00000000..3dccc86a --- /dev/null +++ b/apps/readest-app/src/__tests__/services/backup-service.test.ts @@ -0,0 +1,183 @@ +import { describe, it, expect } from 'vitest'; +import { mergeBookConfigs, mergeBookMetadata } from '@/services/backupService'; +import { Book, BookConfig, BookNote } from '@/types/book'; + +function makeBook(overrides: Partial = {}): Book { + return { + hash: 'abc123', + format: 'EPUB', + title: 'Test Book', + author: 'Author', + createdAt: 1000, + updatedAt: 2000, + ...overrides, + }; +} + +function makeNote(overrides: Partial = {}): BookNote { + return { + id: 'note-1', + type: 'annotation', + cfi: 'cfi-1', + note: 'test note', + createdAt: 100, + updatedAt: 100, + ...overrides, + }; +} + +describe('mergeBookConfigs', () => { + it('should keep higher progress from backup', () => { + const current: BookConfig = { progress: [50, 200], updatedAt: 100 }; + const backup: BookConfig = { progress: [100, 200], updatedAt: 90 }; + const result = mergeBookConfigs(current, backup); + expect(result.progress).toEqual([100, 200]); + }); + + it('should keep higher progress from current', () => { + const current: BookConfig = { progress: [150, 200], updatedAt: 100 }; + const backup: BookConfig = { progress: [100, 200], updatedAt: 90 }; + const result = mergeBookConfigs(current, backup); + expect(result.progress).toEqual([150, 200]); + }); + + it('should use location from the config with higher progress', () => { + const current: BookConfig = { progress: [50, 200], location: 'loc-current', updatedAt: 100 }; + const backup: BookConfig = { progress: [100, 200], location: 'loc-backup', updatedAt: 90 }; + const result = mergeBookConfigs(current, backup); + expect(result.location).toBe('loc-backup'); + }); + + it('should merge booknotes, keeping latest by updatedAt', () => { + const note1 = makeNote({ id: '1', note: 'old', updatedAt: 100 }); + const note1Newer = makeNote({ id: '1', note: 'new', updatedAt: 200 }); + const note2 = makeNote({ id: '2', note: 'only-backup', updatedAt: 150 }); + + const current: BookConfig = { booknotes: [note1], updatedAt: 100 }; + const backup: BookConfig = { booknotes: [note1Newer, note2], updatedAt: 200 }; + const result = mergeBookConfigs(current, backup); + + expect(result.booknotes).toHaveLength(2); + expect(result.booknotes!.find((n) => n.id === '1')!.note).toBe('new'); + expect(result.booknotes!.find((n) => n.id === '2')!.note).toBe('only-backup'); + }); + + it('should keep current note when updatedAt is equal', () => { + const currentNote = makeNote({ id: '1', note: 'current', updatedAt: 100 }); + const backupNote = makeNote({ id: '1', note: 'backup', updatedAt: 100 }); + + const current: BookConfig = { booknotes: [currentNote], updatedAt: 100 }; + const backup: BookConfig = { booknotes: [backupNote], updatedAt: 100 }; + const result = mergeBookConfigs(current, backup); + + expect(result.booknotes).toHaveLength(1); + expect(result.booknotes![0]!.note).toBe('current'); + }); + + it('should handle missing progress in current', () => { + const current: BookConfig = { updatedAt: 100 }; + const backup: BookConfig = { progress: [50, 200], updatedAt: 90 }; + const result = mergeBookConfigs(current, backup); + expect(result.progress).toEqual([50, 200]); + }); + + it('should handle missing progress in backup', () => { + const current: BookConfig = { progress: [50, 200], updatedAt: 100 }; + const backup: BookConfig = { updatedAt: 90 }; + const result = mergeBookConfigs(current, backup); + expect(result.progress).toEqual([50, 200]); + }); + + it('should handle missing booknotes in both', () => { + const current: BookConfig = { updatedAt: 100 }; + const backup: BookConfig = { updatedAt: 90 }; + const result = mergeBookConfigs(current, backup); + expect(result.booknotes).toEqual([]); + }); + + it('should preserve viewSettings from the config with higher progress', () => { + const current: BookConfig = { + progress: [10, 200], + viewSettings: { zoomLevel: 1.5 }, + updatedAt: 100, + }; + const backup: BookConfig = { + progress: [100, 200], + viewSettings: { zoomLevel: 2.0 }, + updatedAt: 90, + }; + const result = mergeBookConfigs(current, backup); + expect(result.viewSettings?.zoomLevel).toBe(2.0); + }); + + it('should combine notes from current-only and backup-only', () => { + const currentNote = makeNote({ id: 'c1', note: 'current-only' }); + const backupNote = makeNote({ id: 'b1', note: 'backup-only' }); + + const current: BookConfig = { booknotes: [currentNote], updatedAt: 100 }; + const backup: BookConfig = { booknotes: [backupNote], updatedAt: 100 }; + const result = mergeBookConfigs(current, backup); + + expect(result.booknotes).toHaveLength(2); + expect(result.booknotes!.find((n) => n.id === 'c1')).toBeDefined(); + expect(result.booknotes!.find((n) => n.id === 'b1')).toBeDefined(); + }); +}); + +describe('mergeBookMetadata', () => { + it('should not delete when current is deleted but backup is not', () => { + const current = makeBook({ deletedAt: 5000 }); + const backup = makeBook({ deletedAt: null }); + const result = mergeBookMetadata(current, backup); + expect(result.deletedAt).toBeNull(); + }); + + it('should not delete when backup is deleted but current is not', () => { + const current = makeBook({ deletedAt: null }); + const backup = makeBook({ deletedAt: 5000 }); + const result = mergeBookMetadata(current, backup); + expect(result.deletedAt).toBeNull(); + }); + + it('should keep later deletedAt when both sides are deleted', () => { + const current = makeBook({ deletedAt: 3000 }); + const backup = makeBook({ deletedAt: 5000 }); + const result = mergeBookMetadata(current, backup); + expect(result.deletedAt).toBe(5000); + }); + + it('should not delete when neither side is deleted', () => { + const current = makeBook({ deletedAt: null }); + const backup = makeBook({ deletedAt: null }); + const result = mergeBookMetadata(current, backup); + expect(result.deletedAt).toBeNull(); + }); + + it('should set updatedAt to max of both', () => { + const current = makeBook({ updatedAt: 2000 }); + const backup = makeBook({ updatedAt: 3000 }); + const result = mergeBookMetadata(current, backup); + expect(result.updatedAt).toBe(3000); + }); + + it('should set createdAt to min of both', () => { + const current = makeBook({ createdAt: 500 }); + const backup = makeBook({ createdAt: 1000 }); + const result = mergeBookMetadata(current, backup); + expect(result.createdAt).toBe(500); + }); + + it('should use backup fields when backup has higher updatedAt', () => { + const current = makeBook({ updatedAt: 1000, title: 'Old Title' }); + const backup = makeBook({ updatedAt: 2000, title: 'New Title' }); + const result = mergeBookMetadata(current, backup); + expect(result.title).toBe('New Title'); + }); + + it('should use current fields when current has higher updatedAt', () => { + const current = makeBook({ updatedAt: 3000, title: 'Current Title' }); + const backup = makeBook({ updatedAt: 1000, title: 'Backup Title' }); + const result = mergeBookMetadata(current, backup); + expect(result.title).toBe('Current Title'); + }); +}); diff --git a/apps/readest-app/src/__tests__/services/node-app-service.test.ts b/apps/readest-app/src/__tests__/services/node-app-service.test.ts index 417efaad..e5da4f3b 100644 --- a/apps/readest-app/src/__tests__/services/node-app-service.test.ts +++ b/apps/readest-app/src/__tests__/services/node-app-service.test.ts @@ -47,7 +47,7 @@ describe('NodeAppService', () => { it('should save files via saveFile', async () => { const filepath = path.join(tmpDir, 'saved.txt'); - const result = await service.saveFile('saved.txt', 'saved content', filepath); + const result = await service.saveFile('saved.txt', 'saved content', { filePath: filepath }); expect(result).toBe(true); const content = await fsp.readFile(filepath, 'utf-8'); expect(content).toBe('saved content'); diff --git a/apps/readest-app/src/app/library/components/BackupWindow.tsx b/apps/readest-app/src/app/library/components/BackupWindow.tsx new file mode 100644 index 00000000..61614627 --- /dev/null +++ b/apps/readest-app/src/app/library/components/BackupWindow.tsx @@ -0,0 +1,302 @@ +import React, { useEffect, useState } from 'react'; +import { + RiCheckboxCircleFill, + RiErrorWarningFill, + RiLoader2Line, + RiUploadCloud2Line, + RiDownloadCloud2Line, +} from 'react-icons/ri'; +import { useEnv } from '@/context/EnvContext'; +import { useTranslation } from '@/hooks/useTranslation'; +import { useFileSelector } from '@/hooks/useFileSelector'; +import { restoreFromBackupZip, saveBackupFile } from '@/services/backupService'; +import { useLibraryStore } from '@/store/libraryStore'; +import Dialog from '@/components/Dialog'; + +export const setBackupDialogVisible = (visible: boolean) => { + const dialog = document.getElementById('backup_window'); + if (dialog) { + const event = new CustomEvent('setDialogVisibility', { + detail: { visible }, + }); + dialog.dispatchEvent(event); + } +}; + +type BackupStatus = 'idle' | 'backing-up' | 'restoring' | 'completed' | 'error'; + +interface BackupProgress { + current: number; + total: number; + currentFile?: string; +} + +interface BackupResult { + type: 'backup' | 'restore'; + booksAdded?: number; + booksUpdated?: number; +} + +interface BackupWindowProps { + onPullLibrary: (fullRefresh?: boolean, verbose?: boolean) => void; +} + +export const BackupWindow: React.FC = ({ onPullLibrary }) => { + const _ = useTranslation(); + const { appService } = useEnv(); + const { setLibrary } = useLibraryStore(); + const { selectFiles } = useFileSelector(appService, _); + const [isOpen, setIsOpen] = useState(false); + const [status, setStatus] = useState('idle'); + const [progress, setProgress] = useState({ current: 0, total: 0 }); + const [errorMessage, setErrorMessage] = useState(''); + const [result, setResult] = useState(null); + + const resetState = () => { + setStatus('idle'); + setProgress({ current: 0, total: 0 }); + setErrorMessage(''); + setResult(null); + }; + + useEffect(() => { + const handleCustomEvent = (event: CustomEvent) => { + setIsOpen(event.detail.visible); + if (event.detail.visible) { + resetState(); + } + }; + + const el = document.getElementById('backup_window'); + if (el) { + el.addEventListener('setDialogVisibility', handleCustomEvent as EventListener); + } + + return () => { + if (el) { + el.removeEventListener('setDialogVisibility', handleCustomEvent as EventListener); + } + }; + }, []); + + const handleBackup = async () => { + if (!appService) return; + + setStatus('backing-up'); + setErrorMessage(''); + setProgress({ current: 0, total: 0 }); + + try { + const timestamp = new Date().toISOString().slice(0, 10); + const filename = `readest-backup-${timestamp}.zip`; + const saved = await saveBackupFile(appService, filename, (current, total, currentFile) => { + setProgress({ current, total, currentFile }); + }); + if (saved) { + setResult({ type: 'backup' }); + setStatus('completed'); + } else { + setStatus('idle'); + } + } catch (error) { + console.error('Backup failed:', error); + setErrorMessage(_('Backup failed: {{error}}', { error: String(error) })); + setStatus('error'); + } + }; + + const handleRestore = async () => { + if (!appService) return; + + try { + const result = await selectFiles({ + type: 'generic', + accept: '.zip', + extensions: ['zip'], + dialogTitle: _('Select Backup'), + }); + if (!result.files.length) return; + + setStatus('restoring'); + setErrorMessage(''); + setProgress({ current: 0, total: 0 }); + + const zipFile = result.files[0]?.file + ? result.files[0].file + : await appService.openFile(result.files[0]!.path!, 'None'); + + const { booksAdded, booksUpdated } = await restoreFromBackupZip( + appService, + zipFile, + (current, total, currentFile) => { + setProgress({ current, total, currentFile }); + }, + ); + + const newLibrary = await appService.loadLibraryBooks(); + const booksCount = newLibrary.reduce((sum, book) => sum + (book.deletedAt ? 0 : 1), 0); + setLibrary(newLibrary); + setResult({ + type: 'restore', + booksAdded: Math.min(booksAdded, booksCount), + booksUpdated: Math.min(booksUpdated, booksCount), + }); + setStatus('completed'); + onPullLibrary(true); + } catch (error) { + console.error('Restore failed:', error); + setErrorMessage(_('Restore failed: {{error}}', { error: String(error) })); + setStatus('error'); + } + }; + + const handleClose = () => { + if (status === 'backing-up' || status === 'restoring') { + return; + } + setIsOpen(false); + resetState(); + }; + + const progressPercentage = + progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0; + + const isProcessing = status === 'backing-up' || status === 'restoring'; + + return ( + + {isOpen && ( +
+ {/* Action Buttons */} + {status === 'idle' && ( +
+

+ {_( + 'Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.', + )} +

+ + + + +
+ )} + + {/* Progress */} + {isProcessing && ( +
+
+ + + {status === 'backing-up' ? _('Creating backup...') : _('Restoring library...')} + + {progressPercentage}% +
+ +
+
+
+ + {progress.currentFile && ( +

+ {progress.currentFile} +

+ )} + +

+ {_('{{current}} of {{total}} items', { + current: progress.current.toLocaleString(), + total: progress.total.toLocaleString(), + })} +

+
+ )} + + {/* Success State */} + {status === 'completed' && result && ( +
+
+ + + {result.type === 'backup' + ? _('Backup completed successfully!') + : _('Restore completed successfully!')} + +
+
+

+ {result.type === 'backup' + ? _('Your library has been saved to the selected location.') + : _('{{added}} books added, {{updated}} books updated.', { + added: result.booksAdded ?? 0, + updated: result.booksUpdated ?? 0, + })} +

+
+
+ )} + + {/* Error State */} + {status === 'error' && errorMessage && ( +
+
+ + {_('Operation failed')} +
+
+

{errorMessage}

+
+
+ )} + + {/* Footer Buttons */} +
+ {status === 'completed' || status === 'error' ? ( + <> + + {status === 'error' && ( + + )} + + ) : ( + !isProcessing && ( + + ) + )} +
+
+ )} +
+ ); +}; diff --git a/apps/readest-app/src/app/library/components/SettingsMenu.tsx b/apps/readest-app/src/app/library/components/SettingsMenu.tsx index 177ce372..71e6b68c 100644 --- a/apps/readest-app/src/app/library/components/SettingsMenu.tsx +++ b/apps/readest-app/src/app/library/components/SettingsMenu.tsx @@ -9,6 +9,7 @@ import { MdCloudSync, MdSync, MdSyncProblem } from 'react-icons/md'; import { invoke, PermissionState } from '@tauri-apps/api/core'; import { isTauriAppPlatform, isWebAppPlatform } from '@/services/environment'; import { DOWNLOAD_READEST_URL } from '@/services/constants'; +import { setBackupDialogVisible } from '@/app/library/components/BackupWindow'; import { useAuth } from '@/context/AuthContext'; import { useEnv } from '@/context/EnvContext'; import { useThemeStore } from '@/store/themeStore'; @@ -183,6 +184,11 @@ const SettingsMenu: React.FC = ({ onPullLibrary, setIsDropdow setIsDropdownOpen?.(false); }; + const handleBackupRestore = () => { + setIsDropdownOpen?.(false); + setBackupDialogVisible(true); + }; + const openSettingsDialog = () => { setIsDropdownOpen?.(false); setSettingsDialogOpen(true); @@ -368,34 +374,24 @@ const SettingsMenu: React.FC = ({ onPullLibrary, setIsDropdow onClick={cycleThemeMode} /> - {appService?.canCustomizeRootDir && ( - <> - - -
    - - {appService?.isAndroidApp && appService?.distChannel !== 'playstore' && ( - - )} -
-
- - )} + + +
    + {appService?.canCustomizeRootDir && ( + + )} + + {appService?.isAndroidApp && appService?.distChannel !== 'playstore' && ( + + )} +
+
{user && userProfilePlan === 'free' && ( diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index a03fd671..6d5eceb1 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -58,6 +58,7 @@ import { BookDetailModal } from '@/components/metadata'; import { UpdaterWindow } from '@/components/UpdaterWindow'; import { CatalogDialog } from './components/OPDSDialog'; import { MigrateDataWindow } from './components/MigrateDataWindow'; +import { BackupWindow } from './components/BackupWindow'; import { useDragDropImport } from './hooks/useDragDropImport'; import { useTransferQueue } from '@/hooks/useTransferQueue'; import { useAppRouter } from '@/hooks/useAppRouter'; @@ -969,6 +970,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP + {isSettingsDialogOpen && } {showCatalogManager && } diff --git a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx index 96f2f3da..6a6aec1a 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -881,7 +881,9 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { }, 100); const filename = `${makeSafeFilename(book.title)}.md`; - const saved = await appService?.saveFile(filename, markdownContent, 'text/markdown'); + const saved = await appService?.saveFile(filename, markdownContent, { + mimeType: 'text/markdown', + }); eventDispatcher.dispatch('toast', { type: 'info', message: saved ? _('Exported successfully') : _('Copied to clipboard'), diff --git a/apps/readest-app/src/components/Dialog.tsx b/apps/readest-app/src/components/Dialog.tsx index ad32f78b..8dd61d6c 100644 --- a/apps/readest-app/src/components/Dialog.tsx +++ b/apps/readest-app/src/components/Dialog.tsx @@ -20,6 +20,7 @@ interface DialogProps { isOpen: boolean; children: ReactNode; snapHeight?: number; + dismissible?: boolean; header?: ReactNode; title?: string; className?: string; @@ -34,6 +35,7 @@ const Dialog: React.FC = ({ isOpen, children, snapHeight, + dismissible = true, header, title, className, @@ -106,7 +108,7 @@ const Dialog: React.FC = ({ }, [isOpen]); const handleDragMove = (data: { clientY: number; deltaY: number }) => { - if (!isMobile || !dialogRef.current) return; + if (!dismissible || !isMobile || !dialogRef.current) return; const modal = dialogRef.current.querySelector('.modal-box') as HTMLElement; const overlay = dialogRef.current.querySelector('.overlay') as HTMLElement; @@ -125,7 +127,7 @@ const Dialog: React.FC = ({ }; const handleDragEnd = (data: { velocity: number; clientY: number }) => { - if (!isMobile || !dialogRef.current) return; + if (!dismissible || !isMobile || !dialogRef.current) return; const modal = dialogRef.current.querySelector('.modal-box') as HTMLElement; const overlay = dialogRef.current.querySelector('.overlay') as HTMLElement; if (!modal || !overlay) return; @@ -212,7 +214,11 @@ const Dialog: React.FC = ({ appService?.hasSafeAreaInset && isFullHeightInMobile ? `${Math.max(safeAreaInsets?.top || 0, systemUIVisible ? statusBarHeight : 0)}px` : '0px', - ...(isMobile ? { height: snapHeight ? `${snapHeight * 100}%` : '100%', bottom: 0 } : {}), + ...(isMobile + ? snapHeight + ? { height: `${snapHeight * 100}%`, top: 'auto', bottom: 0 } + : { height: '100%', bottom: 0 } + : {}), }} > {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} @@ -233,9 +239,11 @@ const Dialog: React.FC = ({