From 3e3dd7db41eaf0bb66c5eb157953cd7b86936eac Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 8 Oct 2026 13:34:18 +0200 Subject: [PATCH 01/11] feat(fuse,core,cli,gui): move another computer's backup to this one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user switching from a Windows PC to Linux had two half answers: adopting the old device makes this machine back up as it, and restoring its folders keeps them in the old device's tree. Neither puts the data under this machine's own device. Device roots are separate shares on the main volume, so each top-level folder of the other device can move under this device's root in one server-side move. Nothing is uploaded again, and the other device stays registered, without those folders. - core: `Request::MigrateDevice { uid }` and `Db::sync_folder_set_share` - fuse: `Core::migrate_device` moves every folder, suffixes a name already taken here with the other device's name, keeps going past a failed folder, and re-points sync folders that already tracked a moved folder at the new share - cli: `pdfs devices migrate UID [--yes]` confirms, moves, then runs the ordinary restore picker - gui: "Move to This Computer…" on another computer's row, followed by the restore picker - i18n: translate the new strings in every catalog - docs: CLI.md, USER_GUIDE.md, RECOVERY.md, ARCHITECTURE.md, changelog --- crates/pdfs-cli/src/main.rs | 27 ++++ crates/pdfs-core/src/control.rs | 10 ++ crates/pdfs-core/src/db/sync.rs | 11 ++ crates/pdfs-core/src/db/tests.rs | 22 +++ crates/pdfs-fuse/src/control.rs | 6 +- crates/pdfs-fuse/src/devices.rs | 168 ++++++++++++++++++-- crates/pdfs-gui/src/app/pages/devices.rs | 65 ++++++++ docs/ARCHITECTURE.md | 6 + docs/CHANGELOG.md | 7 + docs/CLI.md | 1 + docs/RECOVERY.md | 23 +++ docs/USER_GUIDE.md | 3 + po/de.po | 189 +++++++++++++--------- po/en_GB.po | 188 +++++++++++++--------- po/es.po | 189 +++++++++++++--------- po/fr.po | 189 +++++++++++++--------- po/it.po | 189 +++++++++++++--------- po/nl.po | 188 +++++++++++++--------- po/pdfs.pot | 183 +++++++++++++--------- po/pl.po | 190 ++++++++++++++--------- po/pt_BR.po | 189 +++++++++++++--------- 21 files changed, 1349 insertions(+), 694 deletions(-) diff --git a/crates/pdfs-cli/src/main.rs b/crates/pdfs-cli/src/main.rs index 2e629da..bdca897 100644 --- a/crates/pdfs-cli/src/main.rs +++ b/crates/pdfs-cli/src/main.rs @@ -628,6 +628,15 @@ enum DeviceCmd { #[arg(long, conflicts_with = "uid")] clear: bool, }, + /// Move another computer's folders into this computer's backup and sync + /// them here. The other computer stays registered, without those folders. + Migrate { + /// Device uid (from `devices list`). + uid: String, + /// Skip the confirmation and accept every proposed local path. + #[arg(long)] + yes: bool, + }, } #[derive(Subcommand)] @@ -967,6 +976,24 @@ fn cmd_devices(action: DeviceCmd) -> Result<()> { let uid = if clear { None } else { uid }; ok_or_bail(control_request(CtlRequest::AdoptDevice { uid })?)? } + DeviceCmd::Migrate { uid, yes } => { + if !yes { + println!( + "Every folder that device backs up moves into this computer's backup.\n\ + The device stays registered, without those folders. Stop Proton Drive\n\ + on that computer first, or it sees its folders disappear." + ); + confirm("Move them here?")?; + } + let response = control_request(CtlRequest::MigrateDevice { uid })?; + // A script gets the one reply and runs `sync restore` itself. + if emit_json(&response)? { + return Ok(()); + } + ok_or_bail(response)?; + // The folders are this device's now, so the plain restore offers them. + cmd_sync_restore(yes, None)? + } } Ok(()) } diff --git a/crates/pdfs-core/src/control.rs b/crates/pdfs-core/src/control.rs index 770c5d4..de904d0 100644 --- a/crates/pdfs-core/src/control.rs +++ b/crates/pdfs-core/src/control.rs @@ -446,6 +446,13 @@ pub enum Request { /// of registering a duplicate. `uid: None` clears the pin. /// Replies with [`Response::Ok`]. AdoptDevice { uid: Option }, + /// Move every folder another device backs up into this machine's own + /// device, as server-side moves on the main volume. The other device stays + /// registered, without those folders. Sync folders here that already track + /// one of them follow it to its new share. Nothing is downloaded: the moved + /// folders are then offered by [`Request::ListRestorableFolders`]. + /// Replies with [`Response::Ok`]. + MigrateDevice { uid: String }, // ---- device folder sync ---------------------------------- /// Add a local folder to this machine's device, uploading its tree and @@ -3082,6 +3089,9 @@ mod tests { uid: Some("dev-1".into()), }, Request::AdoptDevice { uid: None }, + Request::MigrateDevice { + uid: "dev-2".into(), + }, Request::ListRestorableFolders, Request::RestoreSyncFolders { items: vec![RestoreItem { diff --git a/crates/pdfs-core/src/db/sync.rs b/crates/pdfs-core/src/db/sync.rs index 404a990..af93b07 100644 --- a/crates/pdfs-core/src/db/sync.rs +++ b/crates/pdfs-core/src/db/sync.rs @@ -139,6 +139,17 @@ impl Db { Ok(()) } + /// Re-point a synced folder at the share that now holds its remote folder, + /// after the folder moved to another device without changing its uid. + pub fn sync_folder_set_share(&self, id: i64, share_id: &str) -> Result<()> { + let conn = self.conn.lock(); + conn.execute( + "UPDATE sync_folder SET remote_share_id = ?2 WHERE id = ?1", + params![id, share_id], + )?; + Ok(()) + } + /// Queue (or, with `None`, withdraw) a mode the folder should move to once it /// is safe to switch. pub fn sync_folder_set_pending_mode(&self, id: i64, mode: Option<&str>) -> Result<()> { diff --git a/crates/pdfs-core/src/db/tests.rs b/crates/pdfs-core/src/db/tests.rs index 559e560..6991ba5 100644 --- a/crates/pdfs-core/src/db/tests.rs +++ b/crates/pdfs-core/src/db/tests.rs @@ -4940,6 +4940,28 @@ fn pending_mode_is_queued_until_the_mode_is_reached() { assert_eq!(folder.pending_mode, None); } +#[test] +fn moved_sync_folder_points_at_its_new_share() { + let db = Db::open_in_memory().unwrap(); + let id = db + .sync_folder_add("/tmp/pdfs-moved-share", "vol~folder", "windows-share") + .unwrap(); + let other = db + .sync_folder_add("/tmp/pdfs-unmoved-share", "vol~other", "windows-share") + .unwrap(); + + db.sync_folder_set_share(id, "linux-share").unwrap(); + + let folder = db.sync_folder_get(id).unwrap().unwrap(); + assert_eq!(folder.remote_share_id, "linux-share"); + // A move keeps the uid, so nothing but the share changes. + assert_eq!(folder.remote_uid, "vol~folder"); + assert_eq!( + db.sync_folder_get(other).unwrap().unwrap().remote_share_id, + "windows-share" + ); +} + #[test] fn mode_commit_clears_only_the_intent_it_satisfies() { let db = Db::open_in_memory().unwrap(); diff --git a/crates/pdfs-fuse/src/control.rs b/crates/pdfs-fuse/src/control.rs index f0a5bfe..f308493 100644 --- a/crates/pdfs-fuse/src/control.rs +++ b/crates/pdfs-fuse/src/control.rs @@ -369,7 +369,7 @@ fn touched_topics(request: &CtlRequest) -> &'static [Topic] { RefreshScope::Photos { .. } => &[], }, R::RenameDevice { .. } | R::DeleteDevice { .. } => &[Topic::Devices], - R::AdoptDevice { .. } => &[Topic::Devices, Topic::Locations], + R::AdoptDevice { .. } | R::MigrateDevice { .. } => &[Topic::Devices, Topic::Locations], R::AddSyncFolder { .. } | R::RemoveSyncFolder { .. } | R::SetSyncFolderMode { .. } @@ -1297,6 +1297,10 @@ fn handle_control_conn(core: &Core, username: &str, mountpoint: &Path, stream: U Ok(message) => CtlResponse::Ok { message }, Err(e) => CtlResponse::error(e), }, + Ok(CtlRequest::MigrateDevice { uid }) => match core.migrate_device(&uid) { + Ok(message) => CtlResponse::Ok { message }, + Err(e) => CtlResponse::error(e), + }, Ok(CtlRequest::AddSyncFolder { local_path }) => { // Registering the device and uploading a folder tree far outlasts the // socket read timeout, so ack immediately and work on a background diff --git a/crates/pdfs-fuse/src/devices.rs b/crates/pdfs-fuse/src/devices.rs index 79c131e..ea1fa78 100644 --- a/crates/pdfs-fuse/src/devices.rs +++ b/crates/pdfs-fuse/src/devices.rs @@ -19,6 +19,7 @@ use pdfs_core::control::{ }; use pdfs_core::db::{StoredDevice, StoredSyncFolder}; use pdfs_core::mounts::MountMode; +use pdfs_core::profile::PROFILE_DIR_NAME; use pdfs_core::{CoreError, CoreResult}; use proton_drive_rs::proton_sdk::ids::{DeviceUid, NodeUid}; use proton_drive_rs::{DeviceType, Node}; @@ -114,6 +115,22 @@ pub(crate) fn set_adopted_device_uid(uid: Option<&str>) -> CoreResult<()> { .map_err(|e| CoreError::internal(format!("write config: {e}"))) } +/// The name a folder moved in from `device` takes when `name` is already +/// taken under this device's root, or `None` when it can keep its own. +fn migrated_name(name: &str, device: &str, taken: &[String]) -> Option { + let free = |candidate: &str| !taken.iter().any(|t| t == candidate); + if free(name) { + return None; + } + let base = format!("{name} ({device})"); + if free(&base) { + return Some(base); + } + (2..) + .map(|n| format!("{base} {n}")) + .find(|candidate| free(candidate)) +} + impl Core { // ---- devices ---------------------------------------------------------- @@ -260,6 +277,107 @@ impl Core { }) } + /// Move every folder another device backs up into this machine's own + /// device, so this machine carries on backing them up as itself. The other + /// device stays registered, without those folders. + /// + /// Device roots all live on the main volume, so each folder is one + /// server-side move: nothing is downloaded or uploaded, and no quota is + /// used twice. A folder whose name is taken here keeps its content under + /// " ()". A failed folder does not stop the others, and a + /// second run picks up whatever is still left on the other device. + /// + /// Nothing is synced to this disk here. The moved folders are now this + /// device's, so the ordinary restore offers them. Only folders that were + /// already synced here from the other device are touched: their uid + /// survives the move but their share does not. + pub(crate) fn migrate_device(&self, uid: &str) -> CoreResult { + let source = self.restore_source(Some(uid))?; + let target = self.ensure_device()?; + if source.uid == target.uid { + return Err(CoreError::invalid(format!( + "{} is this computer's own device", + source.name + ))); + } + let bad_root = |d: &StoredDevice| { + parse_uid(&d.root_uid) + .ok_or_else(|| CoreError::internal(format!("bad device root uid: {}", d.root_uid))) + }; + let (source_root, target_root) = (bad_root(&source)?, bad_root(&target)?); + if source_root.volume_id != target_root.volume_id { + return Err(CoreError::invalid( + "the two devices are on different Proton Drive volumes; copy it instead", + )); + } + + let mut taken: Vec = self + .device_root_nodes(&target_root)? + .into_iter() + .filter(|n| !n.trashed) + .map(|n| n.name) + .collect(); + let synced = self + .db + .sync_folder_list() + .map_err(|e| CoreError::internal(format!("db: {e:?}")))?; + + let mut moved = 0usize; + let mut renamed: Vec = Vec::new(); + let mut left: Vec = Vec::new(); + let mut failed: Vec = Vec::new(); + for node in self.device_root_nodes(&source_root)? { + if node.trashed || node.name == PROFILE_DIR_NAME { + continue; + } + // A device root only ever holds folders (B68); anything else was + // put there by hand and is left for the user to deal with. + if !node.is_folder() { + left.push(node.name); + continue; + } + let new_name = migrated_name(&node.name, &source.name, &taken); + if let Err(e) = self.move_rename_remote(&node.uid, &target_root, new_name.as_deref()) { + warn!(name = %node.name, error = %e, "migrate: could not move folder"); + failed.push(format!("{}: {e}", node.name)); + continue; + } + let landed = new_name.clone().unwrap_or_else(|| node.name.clone()); + if new_name.is_some() { + renamed.push(format!("{} -> {landed}", node.name)); + } + taken.push(landed); + moved += 1; + + let uid = node.uid.to_string(); + for folder in synced.iter().filter(|f| f.remote_uid == uid) { + if let Err(e) = self.db.sync_folder_set_share(folder.id, &target.share_id) { + warn!(id = folder.id, error = ?e, "migrate: could not re-point sync folder"); + } + self.sync_now(Some(folder.id)); + } + } + self.invalidate_children_of(&source_root); + self.invalidate_children_of(&target_root); + + let mut message = format!("moved {moved} folder(s) from {}", source.name); + if !renamed.is_empty() { + message.push_str(&format!("; renamed: {}", renamed.join(", "))); + } + if !left.is_empty() { + message.push_str(&format!("; left behind: {}", left.join(", "))); + } + if !failed.is_empty() { + message.push_str(&format!("; failed: {}", failed.join(", "))); + } + info!(from = %source.name, to = %target.name, moved, failed = failed.len(), + "device migrated"); + if moved == 0 && !failed.is_empty() { + return Err(CoreError::conflict(message)); + } + Ok(message) + } + // ---- device folder sync ------------------------- /// Auto-register (or recover) this machine as a Proton Drive Device, caching @@ -360,21 +478,25 @@ impl Core { root_uid: &NodeUid, name: &str, ) -> CoreResult> { + Ok(self + .device_root_nodes(root_uid)? + .into_iter() + .find(|n| n.is_folder() && !n.trashed && n.name == name) + .map(|n| n.uid)) + } + + /// Everything directly under a device root, trashed nodes included. + fn device_root_nodes(&self, root_uid: &NodeUid) -> CoreResult> { let uids = self .rt .block_on(self.drive.enumerate_folder_children_node_uids(root_uid)) .map_err(|e| CoreError::from_api(&e, "list device root"))?; if uids.is_empty() { - return Ok(None); + return Ok(Vec::new()); } - let nodes = self - .rt + self.rt .block_on(self.drive.enumerate_nodes(&uids)) - .map_err(|e| CoreError::from_api(&e, "resolve device root children"))?; - Ok(nodes - .into_iter() - .find(|n| n.is_folder() && !n.trashed && n.name == name) - .map(|n| n.uid)) + .map_err(|e| CoreError::from_api(&e, "resolve device root children")) } /// Add a local folder to this device's sync set: register the device if @@ -1107,10 +1229,38 @@ mod tests { use pdfs_core::db::StoredSyncFolder; use super::{ - ApplicableMountMode, commit_after_teardown, pending_intent_matches, + ApplicableMountMode, commit_after_teardown, migrated_name, pending_intent_matches, restore_snapshot_matches, select_mode_transition, }; + #[test] + fn migrated_folder_keeps_its_name_when_it_is_free() { + let taken = vec!["Pictures".to_string()]; + assert_eq!(migrated_name("Documents", "DESKTOP-1", &taken), None); + } + + #[test] + fn migrated_folder_is_named_after_its_device_on_a_clash() { + let taken = vec!["Documents".to_string()]; + assert_eq!( + migrated_name("Documents", "DESKTOP-1", &taken).as_deref(), + Some("Documents (DESKTOP-1)") + ); + } + + #[test] + fn migrated_folder_is_numbered_when_the_device_name_is_taken_too() { + let taken = vec![ + "Documents".to_string(), + "Documents (DESKTOP-1)".to_string(), + "Documents (DESKTOP-1) 2".to_string(), + ]; + assert_eq!( + migrated_name("Documents", "DESKTOP-1", &taken).as_deref(), + Some("Documents (DESKTOP-1) 3") + ); + } + #[test] fn unknown_mode_cannot_select_a_destructive_transition() { let mut unmounts = 0; diff --git a/crates/pdfs-gui/src/app/pages/devices.rs b/crates/pdfs-gui/src/app/pages/devices.rs index d12d56d..14e26d7 100644 --- a/crates/pdfs-gui/src/app/pages/devices.rs +++ b/crates/pdfs-gui/src/app/pages/devices.rs @@ -439,6 +439,10 @@ fn other_computer_row(ui: &Rc, dev: &DeviceInfo) -> adw::ActionRow { menu.item(&gettext("Restore to This Computer…"), move || { prompt_restore_folders(&ui_c, Some((uid_c.clone(), name_c.clone()))) }); + let (ui_c, uid_c, name_c) = (ui.clone(), uid.clone(), name.clone()); + menu.item(&gettext("Move to This Computer…"), move || { + prompt_migrate_device(&ui_c, &uid_c, &name_c) + }); menu.section(); let (ui_c, uid_c, name_c) = (ui.clone(), uid.clone(), name.clone()); menu.item(&gettext("Rename…"), move || { @@ -1096,6 +1100,67 @@ pub(crate) fn prompt_adopt_device(ui: &Rc, uid: &str, name: &str) { dialog.present(win.as_ref()); } +/// Ask before moving another computer's folders into this computer's backup, +/// then offer them for syncing here. +/// +/// Destructive-looking on purpose: the other computer keeps its device but +/// loses its folders, and a copy of Proton Drive still running there would +/// watch them vanish. +pub(crate) fn prompt_migrate_device(ui: &Rc, uid: &str, name: &str) { + let win = ui_window(ui); + let dialog = adw::AlertDialog::builder() + .heading(gettext("Move Backup to This Computer?")) + // Translators: {name} is the name of the other computer whose folders move to this one. + .body(gettext_f( + "The folders backed up by “{name}” move into this computer's backup in Proton Drive, and you choose where they go on this computer. “{name}” stays in your account, without those folders.\n\nStop Proton Drive on “{name}” first. Otherwise it sees its folders disappear.", + &[("name", name)], + )) + .build(); + dialog.add_response("cancel", &gettext("Cancel")); + dialog.add_response("move", &gettext("Move Here")); + dialog.set_response_appearance("move", adw::ResponseAppearance::Destructive); + dialog.set_default_response(Some("cancel")); + dialog.set_close_response("cancel"); + let ui = ui.clone(); + let uid = uid.to_string(); + // Translators: {name} is the name of the computer whose folders moved to this one. + let done = gettext_f("Moved the backup of “{name}”", &[("name", name)]); + dialog.connect_response(None, move |_, resp| { + if resp != "move" { + return; + } + ui.busy_begin(); + let rx = spawn_request( + ui.dirs.control_socket(), + Request::MigrateDevice { uid: uid.clone() }, + ); + let (ui, done) = (ui.clone(), done.clone()); + glib::spawn_future_local(async move { + let result = rx.recv().await; + ui.busy_end(); + let failed = gettext("Couldn't move that backup"); + match result { + Ok(Ok(Response::Ok { .. })) => { + reload_sync_pages(&ui); + toast(&ui, &done); + // The folders are this computer's now, so its own restore + // picker is where the user chooses their local paths. + prompt_restore_folders(&ui, None); + } + Ok(Ok(Response::Error { message, kind })) => { + toast_failure(&ui, &failed, &message, kind) + } + _ => toast_error( + &ui, + &failed, + &gettext("The Proton Drive service didn't respond."), + ), + } + }); + }); + dialog.present(win.as_ref()); +} + /// Run a mutation raised from Computers or Locations and reload the page on success. pub(crate) fn run_devices_mutation(ui: &Rc, req: Request, done: &str, failed: &str) { ui.busy_begin(); diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a0ea309..ff44614 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -377,6 +377,12 @@ Restore lists the device's folders, proposes a local path from the profile, and adds a synced folder bound to the remote uid. The download is an ordinary pass against an empty baseline. The user side is in [RECOVERY.md](RECOVERY.md). +`Core::migrate_device` moves another device's top-level folders under this machine's device root. +Device roots are separate shares on the main volume, so each folder is one server-side move that +keeps its uid. A name already taken here gets the other device's name as a suffix. A synced folder +that already tracked one of them keeps its `remote_uid` and only has its `remote_share_id` +re-pointed. The moved folders are then offered by the ordinary restore. + --- ## 8. Control protocol diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cdf8744..6a42c1e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -11,6 +11,13 @@ scratch (user data in `staging/` and `recovery/` is never touched by this). ## [Unreleased] +### Added +- **Move another computer's backup to this one.** **Move to This Computer…** on the Computers page, + or `pdfs devices migrate UID`, moves every folder another computer backs up, for example an old + Windows PC, into this computer's backup on Proton Drive. Then it offers them for syncing here. The + folders move on Proton Drive itself, so nothing is uploaded again. The other computer stays in the + account, without those folders. + ## [3.1.2] — 2026-10-08 Schema: **42**, unchanged. diff --git a/docs/CLI.md b/docs/CLI.md index 2720856..2c33d9a 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -103,6 +103,7 @@ commands are for scripts and for cases where the mount is not available. | `pdfs devices rename UID NAME` | Rename a computer. | | `pdfs devices rm UID` | Remove a computer and its backup from the account. | | `pdfs devices adopt UID` | Make this machine continue another computer's backup, for example after a reinstall. `--clear` returns to matching by hostname. | +| `pdfs devices migrate UID [--yes]` | Move another computer's folders into this computer's backup, then restore them here like `pdfs sync restore`. The other computer stays registered, without those folders. `--yes` skips the confirmation and accepts every proposed path. See [RECOVERY.md](RECOVERY.md). | ## Trash diff --git a/docs/RECOVERY.md b/docs/RECOVERY.md index 9a009b8..8d0b47c 100644 --- a/docs/RECOVERY.md +++ b/docs/RECOVERY.md @@ -133,6 +133,29 @@ pdfs sync restore --device UID The folders stay part of the other computer's backup and sync in both directions. This machine does not take over that computer's backup. +### Moving another computer's backup here + +To switch computers for good, for example from a Windows PC to this Linux machine, move the old +computer's folders into this computer's backup instead. Use **Move to This Computer…** on that +computer's row, or: + +```bash +pdfs devices list # find the old computer's UID +pdfs devices migrate UID +``` + +Each folder moves on Proton Drive itself, so nothing is uploaded again and no storage is used +twice. A folder whose name this computer already uses gets the old computer's name added, for +example `Documents (DESKTOP-1)`. The restore picker then opens, as in +[Step 3](#step-3-restore-the-folders), so you choose where each folder goes here. + +The old computer stays in your account, without those folders. Unlike +[adoption](#step-2-continue-the-old-computers-backup), this machine keeps its own name and backup. + +> [!WARNING] +> Stop Proton Drive on the old computer first. A client still running there sees its folders +> disappear, and may upload them again as new. + ## 3. How the restore works This section is for the curious and for anyone debugging a restore. diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 7848828..0e4a37b 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -278,6 +278,9 @@ Proton Drive keeps earlier versions of every file. **Details → Versions…** i **Restore to This Computer…** downloads its folders here. - **Continue This Backup Here…** makes this computer take over another computer's backup, for example after reinstalling or replacing a machine. See [RECOVERY.md](RECOVERY.md). +- **Move to This Computer…** moves another computer's folders into this computer's backup, for + example when you switch from Windows to Linux, and then offers them for syncing here. The other + computer stays in the list, without those folders. ## Search launcher diff --git a/po/de.po b/po/de.po index d7f77f7..bdccd1d 100644 --- a/po/de.po +++ b/po/de.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 2.1.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 10:15+0200\n" +"POT-Creation-Date: 2026-10-08 13:23+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: German\n" @@ -90,7 +90,7 @@ msgstr "Von mir geteilt" #: crates/pdfs-gui/src/app/main.rs:776 #: crates/pdfs-gui/src/app/pages/devices.rs:140 -#: crates/pdfs-gui/src/app/pages/devices.rs:481 +#: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "Computer" @@ -266,11 +266,12 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1892 #: crates/pdfs-gui/src/app/pages/browser.rs:1968 #: crates/pdfs-gui/src/app/pages/browser.rs:2381 -#: crates/pdfs-gui/src/app/pages/devices.rs:788 -#: crates/pdfs-gui/src/app/pages/devices.rs:941 -#: crates/pdfs-gui/src/app/pages/devices.rs:979 -#: crates/pdfs-gui/src/app/pages/devices.rs:1031 -#: crates/pdfs-gui/src/app/pages/devices.rs:1075 +#: crates/pdfs-gui/src/app/pages/devices.rs:792 +#: crates/pdfs-gui/src/app/pages/devices.rs:945 +#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1035 +#: crates/pdfs-gui/src/app/pages/devices.rs:1079 +#: crates/pdfs-gui/src/app/pages/devices.rs:1119 #: crates/pdfs-gui/src/app/pages/locations.rs:809 #: crates/pdfs-gui/src/app/pages/locations.rs:1013 #: crates/pdfs-gui/src/app/pages/locations.rs:1488 @@ -316,7 +317,7 @@ msgstr "Drive durchsuchen" #: crates/pdfs-gui/src/app/main.rs:1388 #: crates/pdfs-gui/src/app/pages/browser.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:554 +#: crates/pdfs-gui/src/app/pages/devices.rs:558 #: crates/pdfs-gui/src/app/pages/shared.rs:586 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:526 #: crates/pdfs-gui/src/app/pages/trash.rs:246 @@ -358,7 +359,7 @@ msgstr "Raster / Liste" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1871 #: crates/pdfs-gui/src/app/pages/browser.rs:1893 -#: crates/pdfs-gui/src/app/pages/devices.rs:980 +#: crates/pdfs-gui/src/app/pages/devices.rs:984 #: crates/pdfs-gui/src/app/widgets/details.rs:159 msgid "Rename" msgstr "Umbenennen" @@ -686,7 +687,7 @@ msgid "Couldn't add a sync folder" msgstr "Synchronisierungsordner konnte nicht hinzugefügt werden" #: crates/pdfs-gui/src/app/pages/activity_text.rs:58 -#: crates/pdfs-gui/src/app/pages/devices.rs:823 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Couldn't restore folders" msgstr "Ordner konnten nicht wiederhergestellt werden" @@ -1266,10 +1267,11 @@ msgstr "Alben konnten nicht geladen werden" #: crates/pdfs-gui/src/app/pages/browser.rs:1848 #: crates/pdfs-gui/src/app/pages/browser.rs:2315 #: crates/pdfs-gui/src/app/pages/browser.rs:2491 -#: crates/pdfs-gui/src/app/pages/devices.rs:694 -#: crates/pdfs-gui/src/app/pages/devices.rs:734 -#: crates/pdfs-gui/src/app/pages/devices.rs:884 -#: crates/pdfs-gui/src/app/pages/devices.rs:1120 +#: crates/pdfs-gui/src/app/pages/devices.rs:698 +#: crates/pdfs-gui/src/app/pages/devices.rs:738 +#: crates/pdfs-gui/src/app/pages/devices.rs:888 +#: crates/pdfs-gui/src/app/pages/devices.rs:1156 +#: crates/pdfs-gui/src/app/pages/devices.rs:1185 #: crates/pdfs-gui/src/app/pages/duplicates.rs:220 #: crates/pdfs-gui/src/app/pages/locations.rs:536 #: crates/pdfs-gui/src/app/pages/locations.rs:783 @@ -1301,7 +1303,7 @@ msgstr "Der Proton-Drive-Dienst hat nicht geantwortet." #: crates/pdfs-gui/src/app/pages/albums.rs:206 #: crates/pdfs-gui/src/app/pages/browser.rs:840 -#: crates/pdfs-gui/src/app/pages/devices.rs:530 +#: crates/pdfs-gui/src/app/pages/devices.rs:534 #: crates/pdfs-gui/src/app/pages/photos.rs:3190 #: crates/pdfs-gui/src/app/pages/shared.rs:541 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474 @@ -1315,7 +1317,7 @@ msgstr "Öffnen" #: crates/pdfs-gui/src/app/pages/albums.rs:212 #: crates/pdfs-gui/src/app/pages/browser.rs:882 #: crates/pdfs-gui/src/app/pages/devices.rs:406 -#: crates/pdfs-gui/src/app/pages/devices.rs:444 +#: crates/pdfs-gui/src/app/pages/devices.rs:448 msgid "Rename…" msgstr "Umbenennen …" @@ -1583,7 +1585,7 @@ msgstr "Symbolgröße" #. Translators: {n} is how many items are selected: files and folders in My Files, photos here. #: crates/pdfs-gui/src/app/pages/browser.rs:538 #: crates/pdfs-gui/src/app/pages/browser.rs:925 -#: crates/pdfs-gui/src/app/pages/devices.rs:539 +#: crates/pdfs-gui/src/app/pages/devices.rs:543 #: crates/pdfs-gui/src/app/pages/photos.rs:1988 #: crates/pdfs-gui/src/app/pages/shared.rs:565 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:507 @@ -1741,7 +1743,7 @@ msgid "New Folder…" msgstr "Neuer Ordner …" #: crates/pdfs-gui/src/app/pages/browser.rs:953 -#: crates/pdfs-gui/src/app/pages/devices.rs:550 +#: crates/pdfs-gui/src/app/pages/devices.rs:554 #: crates/pdfs-gui/src/app/pages/shared.rs:582 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:522 #: crates/pdfs-gui/src/app/pages/trash.rs:242 @@ -1886,7 +1888,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1875 -#: crates/pdfs-gui/src/app/pages/devices.rs:973 +#: crates/pdfs-gui/src/app/pages/devices.rs:977 msgid "New name" msgstr "Neuer Name" @@ -1916,6 +1918,7 @@ msgid "Choose the folder to move into." msgstr "Den Zielordner auswählen." #: crates/pdfs-gui/src/app/pages/browser.rs:1969 +#: crates/pdfs-gui/src/app/pages/devices.rs:1120 msgid "Move Here" msgstr "Hierher verschieben" @@ -2147,99 +2150,103 @@ msgstr "Dateien durchsuchen" msgid "Restore to This Computer…" msgstr "Auf diesem Computer wiederherstellen …" -#: crates/pdfs-gui/src/app/pages/devices.rs:451 +#: crates/pdfs-gui/src/app/pages/devices.rs:443 +msgid "Move to This Computer…" +msgstr "Auf diesen Computer verschieben …" + +#: crates/pdfs-gui/src/app/pages/devices.rs:455 msgid "Continue This Backup Here…" msgstr "Diese Sicherung hier fortsetzen …" -#: crates/pdfs-gui/src/app/pages/devices.rs:456 +#: crates/pdfs-gui/src/app/pages/devices.rs:460 #: crates/pdfs-gui/src/app/pages/shared.rs:895 msgid "Remove…" msgstr "Entfernen …" -#: crates/pdfs-gui/src/app/pages/devices.rs:567 +#: crates/pdfs-gui/src/app/pages/devices.rs:571 #, rust-format msgid "{platform} · last backup {time}" msgstr "{platform} · letzte Sicherung {time}" #. Translators: {platform} is the device's platform, such as "Linux". -#: crates/pdfs-gui/src/app/pages/devices.rs:571 +#: crates/pdfs-gui/src/app/pages/devices.rs:575 #, rust-format msgid "{platform} · no backups yet" msgstr "{platform} · noch keine Sicherungen" -#: crates/pdfs-gui/src/app/pages/devices.rs:587 +#: crates/pdfs-gui/src/app/pages/devices.rs:591 msgid "No folders backed up yet" msgstr "Noch keine Ordner gesichert" -#: crates/pdfs-gui/src/app/pages/devices.rs:590 +#: crates/pdfs-gui/src/app/pages/devices.rs:594 #, rust-format msgid "{n} folder backed up" msgid_plural "{n} folders backed up" msgstr[0] "{n} Ordner gesichert" msgstr[1] "{n} Ordner gesichert" -#: crates/pdfs-gui/src/app/pages/devices.rs:603 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 #, rust-format msgid "{folders} · {n} needs attention" msgid_plural "{folders} · {n} need attention" msgstr[0] "{folders} · {n} erfordert Aufmerksamkeit" msgstr[1] "{folders} · {n} erfordern Aufmerksamkeit" -#: crates/pdfs-gui/src/app/pages/devices.rs:614 +#: crates/pdfs-gui/src/app/pages/devices.rs:618 msgid "syncing…" msgstr "wird synchronisiert …" -#: crates/pdfs-gui/src/app/pages/devices.rs:615 +#: crates/pdfs-gui/src/app/pages/devices.rs:619 msgid "sync error" msgstr "Synchronisierungsfehler" -#: crates/pdfs-gui/src/app/pages/devices.rs:616 +#: crates/pdfs-gui/src/app/pages/devices.rs:620 msgid "needs attention" msgstr "erfordert Aufmerksamkeit" -#: crates/pdfs-gui/src/app/pages/devices.rs:617 +#: crates/pdfs-gui/src/app/pages/devices.rs:621 msgid "up to date" msgstr "aktuell" -#: crates/pdfs-gui/src/app/pages/devices.rs:629 +#: crates/pdfs-gui/src/app/pages/devices.rs:633 msgid "checking for changes…" msgstr "wird auf Änderungen geprüft …" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:636 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "wird auf Änderungen geprüft – {done} von {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:644 +#: crates/pdfs-gui/src/app/pages/devices.rs:648 #, rust-format msgid "syncing {done} of {total}" msgstr "{done} von {total} wird synchronisiert" -#: crates/pdfs-gui/src/app/pages/devices.rs:650 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "{file} wird synchronisiert – {done} von {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:662 +#: crates/pdfs-gui/src/app/pages/devices.rs:666 msgid "Add Folder to Sync" msgstr "Ordner zur Synchronisierung hinzufügen" -#: crates/pdfs-gui/src/app/pages/devices.rs:686 +#: crates/pdfs-gui/src/app/pages/devices.rs:690 #: crates/pdfs-gui/src/app/pages/locations.rs:1593 msgid "Syncing folder…" msgstr "Ordner wird synchronisiert …" -#: crates/pdfs-gui/src/app/pages/devices.rs:689 #: crates/pdfs-gui/src/app/pages/devices.rs:693 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Couldn't add folder" msgstr "Ordner konnte nicht hinzugefügt werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:727 -#: crates/pdfs-gui/src/app/pages/devices.rs:733 +#: crates/pdfs-gui/src/app/pages/devices.rs:731 +#: crates/pdfs-gui/src/app/pages/devices.rs:737 msgid "Couldn't list folders to restore" msgstr "Wiederherstellbare Ordner konnten nicht aufgelistet werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:759 +#: crates/pdfs-gui/src/app/pages/devices.rs:763 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2247,19 +2254,19 @@ msgstr "" "Die Ordner ankreuzen, die mit diesem Rechner synchronisiert werden sollen, " "und für jeden den Speicherort anpassen." -#: crates/pdfs-gui/src/app/pages/devices.rs:782 +#: crates/pdfs-gui/src/app/pages/devices.rs:786 msgid "Restore Folders" msgstr "Ordner wiederherstellen" -#: crates/pdfs-gui/src/app/pages/devices.rs:789 +#: crates/pdfs-gui/src/app/pages/devices.rs:793 msgid "Restore" msgstr "Wiederherstellen" -#: crates/pdfs-gui/src/app/pages/devices.rs:822 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 msgid "Restoring folders…" msgstr "Ordner werden wiederhergestellt …" -#: crates/pdfs-gui/src/app/pages/devices.rs:834 +#: crates/pdfs-gui/src/app/pages/devices.rs:838 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2268,37 +2275,37 @@ msgstr "" "Diese Ordner werden von „{name}“ gesichert. Wiederhergestellte Ordner werden " "weiterhin mit der Kopie dieses Computers synchronisiert." -#: crates/pdfs-gui/src/app/pages/devices.rs:837 +#: crates/pdfs-gui/src/app/pages/devices.rs:841 msgid "These folders are backed up under this computer in Proton Drive." msgstr "Diese Ordner werden in Proton Drive unter diesem Computer gesichert." -#: crates/pdfs-gui/src/app/pages/devices.rs:846 +#: crates/pdfs-gui/src/app/pages/devices.rs:850 #, rust-format msgid "Nothing to restore — “{name}”'s folders are all synced here." msgstr "" "Nichts wiederherzustellen – alle Ordner von „{name}“ werden bereits hier " "synchronisiert." -#: crates/pdfs-gui/src/app/pages/devices.rs:849 +#: crates/pdfs-gui/src/app/pages/devices.rs:853 msgid "Nothing to restore — this computer's folders are all synced here." msgstr "" "Nichts wiederherzustellen – alle Ordner dieses Computers werden bereits hier " "synchronisiert." -#: crates/pdfs-gui/src/app/pages/devices.rs:873 +#: crates/pdfs-gui/src/app/pages/devices.rs:877 msgid "Folder will switch to online only" msgstr "Ordner wird auf „Nur online“ umgestellt" -#: crates/pdfs-gui/src/app/pages/devices.rs:875 +#: crates/pdfs-gui/src/app/pages/devices.rs:879 msgid "Folder will download and stay synced" msgstr "Ordner wird heruntergeladen und bleibt synchronisiert" -#: crates/pdfs-gui/src/app/pages/devices.rs:879 #: crates/pdfs-gui/src/app/pages/devices.rs:883 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 msgid "Couldn't change mode" msgstr "Modus konnte nicht geändert werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:915 +#: crates/pdfs-gui/src/app/pages/devices.rs:919 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2316,7 +2323,7 @@ msgstr "" "Ordner auf „Synchronisiert“ umstellen und vor dem Entfernen warten, bis der " "Download abgeschlossen ist." -#: crates/pdfs-gui/src/app/pages/devices.rs:921 +#: crates/pdfs-gui/src/app/pages/devices.rs:925 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2330,68 +2337,68 @@ msgstr "" "mehr synchronisiert. Es kann zusätzlich ausgewählt werden, die Kopie in " "Proton Drive zu löschen." -#: crates/pdfs-gui/src/app/pages/devices.rs:926 +#: crates/pdfs-gui/src/app/pages/devices.rs:930 msgid "Stop Syncing Folder?" msgstr "Synchronisierung des Ordners beenden?" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:935 msgid "Also delete from Proton Drive" msgstr "Auch aus Proton Drive löschen" -#: crates/pdfs-gui/src/app/pages/devices.rs:933 +#: crates/pdfs-gui/src/app/pages/devices.rs:937 msgid "Deletes the only copy of these files." msgstr "Löscht die einzige Kopie dieser Dateien." -#: crates/pdfs-gui/src/app/pages/devices.rs:935 +#: crates/pdfs-gui/src/app/pages/devices.rs:939 msgid "The local copy is unaffected." msgstr "Die lokale Kopie bleibt unverändert." -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:946 msgid "Stop Syncing" msgstr "Synchronisierung beenden" -#: crates/pdfs-gui/src/app/pages/devices.rs:955 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 msgid "Stopped syncing folder" msgstr "Synchronisierung des Ordners beendet" -#: crates/pdfs-gui/src/app/pages/devices.rs:956 +#: crates/pdfs-gui/src/app/pages/devices.rs:960 msgid "Couldn't stop syncing the folder" msgstr "Synchronisierung des Ordners konnte nicht beendet werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:967 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Rename Computer" msgstr "Computer umbenennen" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:969 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 #, rust-format msgid "Rename “{name}”." msgstr "„{name}“ umbenennen." -#: crates/pdfs-gui/src/app/pages/devices.rs:994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1006 +#: crates/pdfs-gui/src/app/pages/devices.rs:998 +#: crates/pdfs-gui/src/app/pages/devices.rs:1010 msgid "Couldn't rename the computer" msgstr "Der Computer konnte nicht umbenannt werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:995 +#: crates/pdfs-gui/src/app/pages/devices.rs:999 msgid "A name is required." msgstr "Ein Name ist erforderlich." -#: crates/pdfs-gui/src/app/pages/devices.rs:1005 +#: crates/pdfs-gui/src/app/pages/devices.rs:1009 msgid "Computer renamed" msgstr "Computer umbenannt" #. Translators: {name} is the computer's name, which the user must type. -#: crates/pdfs-gui/src/app/pages/devices.rs:1018 +#: crates/pdfs-gui/src/app/pages/devices.rs:1022 #, rust-format msgid "Type “{name}” to confirm" msgstr "Zur Bestätigung „{name}“ eingeben" -#: crates/pdfs-gui/src/app/pages/devices.rs:1023 +#: crates/pdfs-gui/src/app/pages/devices.rs:1027 msgid "Remove Computer?" msgstr "Computer entfernen?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1026 +#: crates/pdfs-gui/src/app/pages/devices.rs:1030 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2406,25 +2413,25 @@ msgstr "" "gelöscht. Die Dateien auf dem Computer selbst bleiben unberührt – dies kann " "jedoch von hier aus nicht rückgängig gemacht werden." -#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1036 #: crates/pdfs-gui/src/app/pages/shared.rs:1028 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:529 msgid "Remove" msgstr "Entfernen" -#: crates/pdfs-gui/src/app/pages/devices.rs:1049 +#: crates/pdfs-gui/src/app/pages/devices.rs:1053 msgid "Computer removed" msgstr "Computer entfernt" -#: crates/pdfs-gui/src/app/pages/devices.rs:1050 +#: crates/pdfs-gui/src/app/pages/devices.rs:1054 msgid "Couldn't remove the computer" msgstr "Der Computer konnte nicht entfernt werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:1068 +#: crates/pdfs-gui/src/app/pages/devices.rs:1072 msgid "Continue This Backup Here?" msgstr "Diese Sicherung hier fortsetzen?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1071 +#: crates/pdfs-gui/src/app/pages/devices.rs:1075 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2443,20 +2450,50 @@ msgstr "" "werden nicht verschoben. Mit „Auf diesem Computer wiederherstellen“ lässt " "sich danach zurückholen, was „{name}“ synchronisiert hat." -#: crates/pdfs-gui/src/app/pages/devices.rs:1076 +#: crates/pdfs-gui/src/app/pages/devices.rs:1080 msgid "Continue Here" msgstr "Hier fortsetzen" #. Translators: {name} is the name of the computer whose backup this one took over. -#: crates/pdfs-gui/src/app/pages/devices.rs:1083 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 #, rust-format msgid "Now backing up as “{name}”" msgstr "Sicherung jetzt als „{name}“" -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1096 msgid "Couldn't continue that backup here" msgstr "Diese Sicherung konnte hier nicht fortgesetzt werden" +#: crates/pdfs-gui/src/app/pages/devices.rs:1112 +msgid "Move Backup to This Computer?" +msgstr "Sicherung auf diesen Computer verschieben?" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1115 +#, rust-format +msgid "" +"The folders backed up by “{name}” move into this computer's backup in Proton " +"Drive, and you choose where they go on this computer. “{name}” stays in your " +"account, without those folders.\n" +"\n" +"Stop Proton Drive on “{name}” first. Otherwise it sees its folders disappear." +msgstr "" +"Die von „{name}“ gesicherten Ordner werden in Proton Drive in die Sicherung " +"dieses Computers verschoben, und Sie wählen, wo sie auf diesem Computer " +"liegen. „{name}“ bleibt in Ihrem Konto, ohne diese Ordner.\n" +"\n" +"Beenden Sie Proton Drive zuerst auf „{name}“. Sonst sieht es seine Ordner " +"verschwinden." + +#. Translators: {name} is the name of the computer whose folders moved to this one. +#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#, rust-format +msgid "Moved the backup of “{name}”" +msgstr "Sicherung von „{name}“ verschoben" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1141 +msgid "Couldn't move that backup" +msgstr "Diese Sicherung konnte nicht verschoben werden" + #: crates/pdfs-gui/src/app/pages/duplicates.rs:81 msgctxt "duplicates" msgid "Exact" diff --git a/po/en_GB.po b/po/en_GB.po index 7e06e81..2492f94 100644 --- a/po/en_GB.po +++ b/po/en_GB.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 2.1.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 10:15+0200\n" +"POT-Creation-Date: 2026-10-08 13:23+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: English (United Kingdom)\n" @@ -90,7 +90,7 @@ msgstr "Shared by me" #: crates/pdfs-gui/src/app/main.rs:776 #: crates/pdfs-gui/src/app/pages/devices.rs:140 -#: crates/pdfs-gui/src/app/pages/devices.rs:481 +#: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "Computers" @@ -262,11 +262,12 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1892 #: crates/pdfs-gui/src/app/pages/browser.rs:1968 #: crates/pdfs-gui/src/app/pages/browser.rs:2381 -#: crates/pdfs-gui/src/app/pages/devices.rs:788 -#: crates/pdfs-gui/src/app/pages/devices.rs:941 -#: crates/pdfs-gui/src/app/pages/devices.rs:979 -#: crates/pdfs-gui/src/app/pages/devices.rs:1031 -#: crates/pdfs-gui/src/app/pages/devices.rs:1075 +#: crates/pdfs-gui/src/app/pages/devices.rs:792 +#: crates/pdfs-gui/src/app/pages/devices.rs:945 +#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1035 +#: crates/pdfs-gui/src/app/pages/devices.rs:1079 +#: crates/pdfs-gui/src/app/pages/devices.rs:1119 #: crates/pdfs-gui/src/app/pages/locations.rs:809 #: crates/pdfs-gui/src/app/pages/locations.rs:1013 #: crates/pdfs-gui/src/app/pages/locations.rs:1488 @@ -312,7 +313,7 @@ msgstr "Search Drive" #: crates/pdfs-gui/src/app/main.rs:1388 #: crates/pdfs-gui/src/app/pages/browser.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:554 +#: crates/pdfs-gui/src/app/pages/devices.rs:558 #: crates/pdfs-gui/src/app/pages/shared.rs:586 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:526 #: crates/pdfs-gui/src/app/pages/trash.rs:246 @@ -354,7 +355,7 @@ msgstr "Grid / list" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1871 #: crates/pdfs-gui/src/app/pages/browser.rs:1893 -#: crates/pdfs-gui/src/app/pages/devices.rs:980 +#: crates/pdfs-gui/src/app/pages/devices.rs:984 #: crates/pdfs-gui/src/app/widgets/details.rs:159 msgid "Rename" msgstr "Rename" @@ -678,7 +679,7 @@ msgid "Couldn't add a sync folder" msgstr "Couldn't add a sync folder" #: crates/pdfs-gui/src/app/pages/activity_text.rs:58 -#: crates/pdfs-gui/src/app/pages/devices.rs:823 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Couldn't restore folders" msgstr "Couldn't restore folders" @@ -1254,10 +1255,11 @@ msgstr "Couldn't load albums" #: crates/pdfs-gui/src/app/pages/browser.rs:1848 #: crates/pdfs-gui/src/app/pages/browser.rs:2315 #: crates/pdfs-gui/src/app/pages/browser.rs:2491 -#: crates/pdfs-gui/src/app/pages/devices.rs:694 -#: crates/pdfs-gui/src/app/pages/devices.rs:734 -#: crates/pdfs-gui/src/app/pages/devices.rs:884 -#: crates/pdfs-gui/src/app/pages/devices.rs:1120 +#: crates/pdfs-gui/src/app/pages/devices.rs:698 +#: crates/pdfs-gui/src/app/pages/devices.rs:738 +#: crates/pdfs-gui/src/app/pages/devices.rs:888 +#: crates/pdfs-gui/src/app/pages/devices.rs:1156 +#: crates/pdfs-gui/src/app/pages/devices.rs:1185 #: crates/pdfs-gui/src/app/pages/duplicates.rs:220 #: crates/pdfs-gui/src/app/pages/locations.rs:536 #: crates/pdfs-gui/src/app/pages/locations.rs:783 @@ -1289,7 +1291,7 @@ msgstr "The Proton Drive service didn't respond." #: crates/pdfs-gui/src/app/pages/albums.rs:206 #: crates/pdfs-gui/src/app/pages/browser.rs:840 -#: crates/pdfs-gui/src/app/pages/devices.rs:530 +#: crates/pdfs-gui/src/app/pages/devices.rs:534 #: crates/pdfs-gui/src/app/pages/photos.rs:3190 #: crates/pdfs-gui/src/app/pages/shared.rs:541 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474 @@ -1303,7 +1305,7 @@ msgstr "Open" #: crates/pdfs-gui/src/app/pages/albums.rs:212 #: crates/pdfs-gui/src/app/pages/browser.rs:882 #: crates/pdfs-gui/src/app/pages/devices.rs:406 -#: crates/pdfs-gui/src/app/pages/devices.rs:444 +#: crates/pdfs-gui/src/app/pages/devices.rs:448 msgid "Rename…" msgstr "Rename…" @@ -1571,7 +1573,7 @@ msgstr "Icon size" #. Translators: {n} is how many items are selected: files and folders in My Files, photos here. #: crates/pdfs-gui/src/app/pages/browser.rs:538 #: crates/pdfs-gui/src/app/pages/browser.rs:925 -#: crates/pdfs-gui/src/app/pages/devices.rs:539 +#: crates/pdfs-gui/src/app/pages/devices.rs:543 #: crates/pdfs-gui/src/app/pages/photos.rs:1988 #: crates/pdfs-gui/src/app/pages/shared.rs:565 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:507 @@ -1725,7 +1727,7 @@ msgid "New Folder…" msgstr "New Folder…" #: crates/pdfs-gui/src/app/pages/browser.rs:953 -#: crates/pdfs-gui/src/app/pages/devices.rs:550 +#: crates/pdfs-gui/src/app/pages/devices.rs:554 #: crates/pdfs-gui/src/app/pages/shared.rs:582 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:522 #: crates/pdfs-gui/src/app/pages/trash.rs:242 @@ -1867,7 +1869,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1875 -#: crates/pdfs-gui/src/app/pages/devices.rs:973 +#: crates/pdfs-gui/src/app/pages/devices.rs:977 msgid "New name" msgstr "New name" @@ -1897,6 +1899,7 @@ msgid "Choose the folder to move into." msgstr "Choose the folder to move into." #: crates/pdfs-gui/src/app/pages/browser.rs:1969 +#: crates/pdfs-gui/src/app/pages/devices.rs:1120 msgid "Move Here" msgstr "Move Here" @@ -2124,99 +2127,103 @@ msgstr "Browse Files" msgid "Restore to This Computer…" msgstr "Restore to This Computer…" -#: crates/pdfs-gui/src/app/pages/devices.rs:451 +#: crates/pdfs-gui/src/app/pages/devices.rs:443 +msgid "Move to This Computer…" +msgstr "Move to This Computer…" + +#: crates/pdfs-gui/src/app/pages/devices.rs:455 msgid "Continue This Backup Here…" msgstr "Continue This Backup Here…" -#: crates/pdfs-gui/src/app/pages/devices.rs:456 +#: crates/pdfs-gui/src/app/pages/devices.rs:460 #: crates/pdfs-gui/src/app/pages/shared.rs:895 msgid "Remove…" msgstr "Remove…" -#: crates/pdfs-gui/src/app/pages/devices.rs:567 +#: crates/pdfs-gui/src/app/pages/devices.rs:571 #, rust-format msgid "{platform} · last backup {time}" msgstr "{platform} · last backup {time}" #. Translators: {platform} is the device's platform, such as "Linux". -#: crates/pdfs-gui/src/app/pages/devices.rs:571 +#: crates/pdfs-gui/src/app/pages/devices.rs:575 #, rust-format msgid "{platform} · no backups yet" msgstr "{platform} · no backups yet" -#: crates/pdfs-gui/src/app/pages/devices.rs:587 +#: crates/pdfs-gui/src/app/pages/devices.rs:591 msgid "No folders backed up yet" msgstr "No folders backed up yet" -#: crates/pdfs-gui/src/app/pages/devices.rs:590 +#: crates/pdfs-gui/src/app/pages/devices.rs:594 #, rust-format msgid "{n} folder backed up" msgid_plural "{n} folders backed up" msgstr[0] "{n} folder backed up" msgstr[1] "{n} folders backed up" -#: crates/pdfs-gui/src/app/pages/devices.rs:603 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 #, rust-format msgid "{folders} · {n} needs attention" msgid_plural "{folders} · {n} need attention" msgstr[0] "{folders} · {n} needs attention" msgstr[1] "{folders} · {n} need attention" -#: crates/pdfs-gui/src/app/pages/devices.rs:614 +#: crates/pdfs-gui/src/app/pages/devices.rs:618 msgid "syncing…" msgstr "syncing…" -#: crates/pdfs-gui/src/app/pages/devices.rs:615 +#: crates/pdfs-gui/src/app/pages/devices.rs:619 msgid "sync error" msgstr "sync error" -#: crates/pdfs-gui/src/app/pages/devices.rs:616 +#: crates/pdfs-gui/src/app/pages/devices.rs:620 msgid "needs attention" msgstr "needs attention" -#: crates/pdfs-gui/src/app/pages/devices.rs:617 +#: crates/pdfs-gui/src/app/pages/devices.rs:621 msgid "up to date" msgstr "up to date" -#: crates/pdfs-gui/src/app/pages/devices.rs:629 +#: crates/pdfs-gui/src/app/pages/devices.rs:633 msgid "checking for changes…" msgstr "checking for changes…" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:636 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "checking for changes — {done} of {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:644 +#: crates/pdfs-gui/src/app/pages/devices.rs:648 #, rust-format msgid "syncing {done} of {total}" msgstr "syncing {done} of {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:650 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "syncing {file} — {done} of {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:662 +#: crates/pdfs-gui/src/app/pages/devices.rs:666 msgid "Add Folder to Sync" msgstr "Add Folder to Sync" -#: crates/pdfs-gui/src/app/pages/devices.rs:686 +#: crates/pdfs-gui/src/app/pages/devices.rs:690 #: crates/pdfs-gui/src/app/pages/locations.rs:1593 msgid "Syncing folder…" msgstr "Syncing folder…" -#: crates/pdfs-gui/src/app/pages/devices.rs:689 #: crates/pdfs-gui/src/app/pages/devices.rs:693 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Couldn't add folder" msgstr "Couldn't add folder" -#: crates/pdfs-gui/src/app/pages/devices.rs:727 -#: crates/pdfs-gui/src/app/pages/devices.rs:733 +#: crates/pdfs-gui/src/app/pages/devices.rs:731 +#: crates/pdfs-gui/src/app/pages/devices.rs:737 msgid "Couldn't list folders to restore" msgstr "Couldn't list folders to restore" -#: crates/pdfs-gui/src/app/pages/devices.rs:759 +#: crates/pdfs-gui/src/app/pages/devices.rs:763 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2224,19 +2231,19 @@ msgstr "" "Tick the folders to sync to this machine, and adjust where each one should " "live." -#: crates/pdfs-gui/src/app/pages/devices.rs:782 +#: crates/pdfs-gui/src/app/pages/devices.rs:786 msgid "Restore Folders" msgstr "Restore Folders" -#: crates/pdfs-gui/src/app/pages/devices.rs:789 +#: crates/pdfs-gui/src/app/pages/devices.rs:793 msgid "Restore" msgstr "Restore" -#: crates/pdfs-gui/src/app/pages/devices.rs:822 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 msgid "Restoring folders…" msgstr "Restoring folders…" -#: crates/pdfs-gui/src/app/pages/devices.rs:834 +#: crates/pdfs-gui/src/app/pages/devices.rs:838 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2245,33 +2252,33 @@ msgstr "" "These folders are backed up from “{name}”. Restored folders keep syncing " "with that computer's copy." -#: crates/pdfs-gui/src/app/pages/devices.rs:837 +#: crates/pdfs-gui/src/app/pages/devices.rs:841 msgid "These folders are backed up under this computer in Proton Drive." msgstr "These folders are backed up under this computer in Proton Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:846 +#: crates/pdfs-gui/src/app/pages/devices.rs:850 #, rust-format msgid "Nothing to restore — “{name}”'s folders are all synced here." msgstr "Nothing to restore — “{name}”'s folders are all synced here." -#: crates/pdfs-gui/src/app/pages/devices.rs:849 +#: crates/pdfs-gui/src/app/pages/devices.rs:853 msgid "Nothing to restore — this computer's folders are all synced here." msgstr "Nothing to restore — this computer's folders are all synced here." -#: crates/pdfs-gui/src/app/pages/devices.rs:873 +#: crates/pdfs-gui/src/app/pages/devices.rs:877 msgid "Folder will switch to online only" msgstr "Folder will switch to online only" -#: crates/pdfs-gui/src/app/pages/devices.rs:875 +#: crates/pdfs-gui/src/app/pages/devices.rs:879 msgid "Folder will download and stay synced" msgstr "Folder will download and stay synced" -#: crates/pdfs-gui/src/app/pages/devices.rs:879 #: crates/pdfs-gui/src/app/pages/devices.rs:883 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 msgid "Couldn't change mode" msgstr "Couldn't change mode" -#: crates/pdfs-gui/src/app/pages/devices.rs:915 +#: crates/pdfs-gui/src/app/pages/devices.rs:919 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2288,7 +2295,7 @@ msgstr "" "copy, cancel, switch the folder to Synced, and wait for the download to " "finish before removing it." -#: crates/pdfs-gui/src/app/pages/devices.rs:921 +#: crates/pdfs-gui/src/app/pages/devices.rs:925 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2301,68 +2308,68 @@ msgstr "" "The local files stay on this disk and simply stop being synced. Choose " "whether to also delete the copy in Proton Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:926 +#: crates/pdfs-gui/src/app/pages/devices.rs:930 msgid "Stop Syncing Folder?" msgstr "Stop Syncing Folder?" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:935 msgid "Also delete from Proton Drive" msgstr "Also delete from Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:933 +#: crates/pdfs-gui/src/app/pages/devices.rs:937 msgid "Deletes the only copy of these files." msgstr "Deletes the only copy of these files." -#: crates/pdfs-gui/src/app/pages/devices.rs:935 +#: crates/pdfs-gui/src/app/pages/devices.rs:939 msgid "The local copy is unaffected." msgstr "The local copy is unaffected." -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:946 msgid "Stop Syncing" msgstr "Stop Syncing" -#: crates/pdfs-gui/src/app/pages/devices.rs:955 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 msgid "Stopped syncing folder" msgstr "Stopped syncing folder" -#: crates/pdfs-gui/src/app/pages/devices.rs:956 +#: crates/pdfs-gui/src/app/pages/devices.rs:960 msgid "Couldn't stop syncing the folder" msgstr "Couldn't stop syncing the folder" -#: crates/pdfs-gui/src/app/pages/devices.rs:967 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Rename Computer" msgstr "Rename Computer" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:969 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 #, rust-format msgid "Rename “{name}”." msgstr "Rename “{name}”." -#: crates/pdfs-gui/src/app/pages/devices.rs:994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1006 +#: crates/pdfs-gui/src/app/pages/devices.rs:998 +#: crates/pdfs-gui/src/app/pages/devices.rs:1010 msgid "Couldn't rename the computer" msgstr "Couldn't rename the computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:995 +#: crates/pdfs-gui/src/app/pages/devices.rs:999 msgid "A name is required." msgstr "A name is required." -#: crates/pdfs-gui/src/app/pages/devices.rs:1005 +#: crates/pdfs-gui/src/app/pages/devices.rs:1009 msgid "Computer renamed" msgstr "Computer renamed" #. Translators: {name} is the computer's name, which the user must type. -#: crates/pdfs-gui/src/app/pages/devices.rs:1018 +#: crates/pdfs-gui/src/app/pages/devices.rs:1022 #, rust-format msgid "Type “{name}” to confirm" msgstr "Type “{name}” to confirm" -#: crates/pdfs-gui/src/app/pages/devices.rs:1023 +#: crates/pdfs-gui/src/app/pages/devices.rs:1027 msgid "Remove Computer?" msgstr "Remove Computer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1026 +#: crates/pdfs-gui/src/app/pages/devices.rs:1030 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2377,25 +2384,25 @@ msgstr "" "on that computer itself are not touched — but this cannot be undone from " "here." -#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1036 #: crates/pdfs-gui/src/app/pages/shared.rs:1028 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:529 msgid "Remove" msgstr "Remove" -#: crates/pdfs-gui/src/app/pages/devices.rs:1049 +#: crates/pdfs-gui/src/app/pages/devices.rs:1053 msgid "Computer removed" msgstr "Computer removed" -#: crates/pdfs-gui/src/app/pages/devices.rs:1050 +#: crates/pdfs-gui/src/app/pages/devices.rs:1054 msgid "Couldn't remove the computer" msgstr "Couldn't remove the computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1068 +#: crates/pdfs-gui/src/app/pages/devices.rs:1072 msgid "Continue This Backup Here?" msgstr "Continue This Backup Here?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1071 +#: crates/pdfs-gui/src/app/pages/devices.rs:1075 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2414,20 +2421,49 @@ msgstr "" "“Restore to This Computer” afterwards to bring back what “{name}” was " "syncing." -#: crates/pdfs-gui/src/app/pages/devices.rs:1076 +#: crates/pdfs-gui/src/app/pages/devices.rs:1080 msgid "Continue Here" msgstr "Continue Here" #. Translators: {name} is the name of the computer whose backup this one took over. -#: crates/pdfs-gui/src/app/pages/devices.rs:1083 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 #, rust-format msgid "Now backing up as “{name}”" msgstr "Now backing up as “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1096 msgid "Couldn't continue that backup here" msgstr "Couldn't continue that backup here" +#: crates/pdfs-gui/src/app/pages/devices.rs:1112 +msgid "Move Backup to This Computer?" +msgstr "Move Backup to This Computer?" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1115 +#, rust-format +msgid "" +"The folders backed up by “{name}” move into this computer's backup in Proton " +"Drive, and you choose where they go on this computer. “{name}” stays in your " +"account, without those folders.\n" +"\n" +"Stop Proton Drive on “{name}” first. Otherwise it sees its folders disappear." +msgstr "" +"The folders backed up by “{name}” move into this computer's backup in Proton " +"Drive, and you choose where they go on this computer. “{name}” stays in your " +"account, without those folders.\n" +"\n" +"Stop Proton Drive on “{name}” first. Otherwise it sees its folders disappear." + +#. Translators: {name} is the name of the computer whose folders moved to this one. +#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#, rust-format +msgid "Moved the backup of “{name}”" +msgstr "Moved the backup of “{name}”" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1141 +msgid "Couldn't move that backup" +msgstr "Couldn't move that backup" + #: crates/pdfs-gui/src/app/pages/duplicates.rs:81 msgctxt "duplicates" msgid "Exact" diff --git a/po/es.po b/po/es.po index 19081eb..9a50e78 100644 --- a/po/es.po +++ b/po/es.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 2.1.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 10:15+0200\n" +"POT-Creation-Date: 2026-10-08 13:23+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: Spanish\n" @@ -90,7 +90,7 @@ msgstr "Compartido por mí" #: crates/pdfs-gui/src/app/main.rs:776 #: crates/pdfs-gui/src/app/pages/devices.rs:140 -#: crates/pdfs-gui/src/app/pages/devices.rs:481 +#: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "Equipos" @@ -264,11 +264,12 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1892 #: crates/pdfs-gui/src/app/pages/browser.rs:1968 #: crates/pdfs-gui/src/app/pages/browser.rs:2381 -#: crates/pdfs-gui/src/app/pages/devices.rs:788 -#: crates/pdfs-gui/src/app/pages/devices.rs:941 -#: crates/pdfs-gui/src/app/pages/devices.rs:979 -#: crates/pdfs-gui/src/app/pages/devices.rs:1031 -#: crates/pdfs-gui/src/app/pages/devices.rs:1075 +#: crates/pdfs-gui/src/app/pages/devices.rs:792 +#: crates/pdfs-gui/src/app/pages/devices.rs:945 +#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1035 +#: crates/pdfs-gui/src/app/pages/devices.rs:1079 +#: crates/pdfs-gui/src/app/pages/devices.rs:1119 #: crates/pdfs-gui/src/app/pages/locations.rs:809 #: crates/pdfs-gui/src/app/pages/locations.rs:1013 #: crates/pdfs-gui/src/app/pages/locations.rs:1488 @@ -314,7 +315,7 @@ msgstr "Buscar en Drive" #: crates/pdfs-gui/src/app/main.rs:1388 #: crates/pdfs-gui/src/app/pages/browser.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:554 +#: crates/pdfs-gui/src/app/pages/devices.rs:558 #: crates/pdfs-gui/src/app/pages/shared.rs:586 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:526 #: crates/pdfs-gui/src/app/pages/trash.rs:246 @@ -356,7 +357,7 @@ msgstr "Cuadrícula / lista" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1871 #: crates/pdfs-gui/src/app/pages/browser.rs:1893 -#: crates/pdfs-gui/src/app/pages/devices.rs:980 +#: crates/pdfs-gui/src/app/pages/devices.rs:984 #: crates/pdfs-gui/src/app/widgets/details.rs:159 msgid "Rename" msgstr "Renombrar" @@ -683,7 +684,7 @@ msgid "Couldn't add a sync folder" msgstr "No se pudo añadir una carpeta sincronizada" #: crates/pdfs-gui/src/app/pages/activity_text.rs:58 -#: crates/pdfs-gui/src/app/pages/devices.rs:823 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Couldn't restore folders" msgstr "No se pudieron restaurar las carpetas" @@ -1260,10 +1261,11 @@ msgstr "No se pudieron cargar los álbumes" #: crates/pdfs-gui/src/app/pages/browser.rs:1848 #: crates/pdfs-gui/src/app/pages/browser.rs:2315 #: crates/pdfs-gui/src/app/pages/browser.rs:2491 -#: crates/pdfs-gui/src/app/pages/devices.rs:694 -#: crates/pdfs-gui/src/app/pages/devices.rs:734 -#: crates/pdfs-gui/src/app/pages/devices.rs:884 -#: crates/pdfs-gui/src/app/pages/devices.rs:1120 +#: crates/pdfs-gui/src/app/pages/devices.rs:698 +#: crates/pdfs-gui/src/app/pages/devices.rs:738 +#: crates/pdfs-gui/src/app/pages/devices.rs:888 +#: crates/pdfs-gui/src/app/pages/devices.rs:1156 +#: crates/pdfs-gui/src/app/pages/devices.rs:1185 #: crates/pdfs-gui/src/app/pages/duplicates.rs:220 #: crates/pdfs-gui/src/app/pages/locations.rs:536 #: crates/pdfs-gui/src/app/pages/locations.rs:783 @@ -1295,7 +1297,7 @@ msgstr "El servicio de Proton Drive no respondió." #: crates/pdfs-gui/src/app/pages/albums.rs:206 #: crates/pdfs-gui/src/app/pages/browser.rs:840 -#: crates/pdfs-gui/src/app/pages/devices.rs:530 +#: crates/pdfs-gui/src/app/pages/devices.rs:534 #: crates/pdfs-gui/src/app/pages/photos.rs:3190 #: crates/pdfs-gui/src/app/pages/shared.rs:541 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474 @@ -1309,7 +1311,7 @@ msgstr "Abrir" #: crates/pdfs-gui/src/app/pages/albums.rs:212 #: crates/pdfs-gui/src/app/pages/browser.rs:882 #: crates/pdfs-gui/src/app/pages/devices.rs:406 -#: crates/pdfs-gui/src/app/pages/devices.rs:444 +#: crates/pdfs-gui/src/app/pages/devices.rs:448 msgid "Rename…" msgstr "Renombrar…" @@ -1577,7 +1579,7 @@ msgstr "Tamaño de los iconos" #. Translators: {n} is how many items are selected: files and folders in My Files, photos here. #: crates/pdfs-gui/src/app/pages/browser.rs:538 #: crates/pdfs-gui/src/app/pages/browser.rs:925 -#: crates/pdfs-gui/src/app/pages/devices.rs:539 +#: crates/pdfs-gui/src/app/pages/devices.rs:543 #: crates/pdfs-gui/src/app/pages/photos.rs:1988 #: crates/pdfs-gui/src/app/pages/shared.rs:565 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:507 @@ -1732,7 +1734,7 @@ msgid "New Folder…" msgstr "Carpeta nueva…" #: crates/pdfs-gui/src/app/pages/browser.rs:953 -#: crates/pdfs-gui/src/app/pages/devices.rs:550 +#: crates/pdfs-gui/src/app/pages/devices.rs:554 #: crates/pdfs-gui/src/app/pages/shared.rs:582 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:522 #: crates/pdfs-gui/src/app/pages/trash.rs:242 @@ -1878,7 +1880,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1875 -#: crates/pdfs-gui/src/app/pages/devices.rs:973 +#: crates/pdfs-gui/src/app/pages/devices.rs:977 msgid "New name" msgstr "Nombre nuevo" @@ -1908,6 +1910,7 @@ msgid "Choose the folder to move into." msgstr "Elija la carpeta de destino." #: crates/pdfs-gui/src/app/pages/browser.rs:1969 +#: crates/pdfs-gui/src/app/pages/devices.rs:1120 msgid "Move Here" msgstr "Mover aquí" @@ -2136,99 +2139,103 @@ msgstr "Examinar archivos" msgid "Restore to This Computer…" msgstr "Restaurar en este equipo…" -#: crates/pdfs-gui/src/app/pages/devices.rs:451 +#: crates/pdfs-gui/src/app/pages/devices.rs:443 +msgid "Move to This Computer…" +msgstr "Mover a este equipo…" + +#: crates/pdfs-gui/src/app/pages/devices.rs:455 msgid "Continue This Backup Here…" msgstr "Continuar esta copia de seguridad aquí…" -#: crates/pdfs-gui/src/app/pages/devices.rs:456 +#: crates/pdfs-gui/src/app/pages/devices.rs:460 #: crates/pdfs-gui/src/app/pages/shared.rs:895 msgid "Remove…" msgstr "Quitar…" -#: crates/pdfs-gui/src/app/pages/devices.rs:567 +#: crates/pdfs-gui/src/app/pages/devices.rs:571 #, rust-format msgid "{platform} · last backup {time}" msgstr "{platform} · última copia de seguridad {time}" #. Translators: {platform} is the device's platform, such as "Linux". -#: crates/pdfs-gui/src/app/pages/devices.rs:571 +#: crates/pdfs-gui/src/app/pages/devices.rs:575 #, rust-format msgid "{platform} · no backups yet" msgstr "{platform} · aún no hay copias de seguridad" -#: crates/pdfs-gui/src/app/pages/devices.rs:587 +#: crates/pdfs-gui/src/app/pages/devices.rs:591 msgid "No folders backed up yet" msgstr "Aún no hay carpetas con copia de seguridad" -#: crates/pdfs-gui/src/app/pages/devices.rs:590 +#: crates/pdfs-gui/src/app/pages/devices.rs:594 #, rust-format msgid "{n} folder backed up" msgid_plural "{n} folders backed up" msgstr[0] "{n} carpeta con copia de seguridad" msgstr[1] "{n} carpetas con copia de seguridad" -#: crates/pdfs-gui/src/app/pages/devices.rs:603 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 #, rust-format msgid "{folders} · {n} needs attention" msgid_plural "{folders} · {n} need attention" msgstr[0] "{folders} · {n} requiere atención" msgstr[1] "{folders} · {n} requieren atención" -#: crates/pdfs-gui/src/app/pages/devices.rs:614 +#: crates/pdfs-gui/src/app/pages/devices.rs:618 msgid "syncing…" msgstr "sincronizando…" -#: crates/pdfs-gui/src/app/pages/devices.rs:615 +#: crates/pdfs-gui/src/app/pages/devices.rs:619 msgid "sync error" msgstr "error de sincronización" -#: crates/pdfs-gui/src/app/pages/devices.rs:616 +#: crates/pdfs-gui/src/app/pages/devices.rs:620 msgid "needs attention" msgstr "requiere atención" -#: crates/pdfs-gui/src/app/pages/devices.rs:617 +#: crates/pdfs-gui/src/app/pages/devices.rs:621 msgid "up to date" msgstr "actualizado" -#: crates/pdfs-gui/src/app/pages/devices.rs:629 +#: crates/pdfs-gui/src/app/pages/devices.rs:633 msgid "checking for changes…" msgstr "buscando cambios…" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:636 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "buscando cambios: {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:644 +#: crates/pdfs-gui/src/app/pages/devices.rs:648 #, rust-format msgid "syncing {done} of {total}" msgstr "sincronizando {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:650 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "sincronizando {file}: {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:662 +#: crates/pdfs-gui/src/app/pages/devices.rs:666 msgid "Add Folder to Sync" msgstr "Añadir carpeta para sincronizar" -#: crates/pdfs-gui/src/app/pages/devices.rs:686 +#: crates/pdfs-gui/src/app/pages/devices.rs:690 #: crates/pdfs-gui/src/app/pages/locations.rs:1593 msgid "Syncing folder…" msgstr "Sincronizando la carpeta…" -#: crates/pdfs-gui/src/app/pages/devices.rs:689 #: crates/pdfs-gui/src/app/pages/devices.rs:693 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Couldn't add folder" msgstr "No se pudo añadir la carpeta" -#: crates/pdfs-gui/src/app/pages/devices.rs:727 -#: crates/pdfs-gui/src/app/pages/devices.rs:733 +#: crates/pdfs-gui/src/app/pages/devices.rs:731 +#: crates/pdfs-gui/src/app/pages/devices.rs:737 msgid "Couldn't list folders to restore" msgstr "No se pudieron listar las carpetas para restaurar" -#: crates/pdfs-gui/src/app/pages/devices.rs:759 +#: crates/pdfs-gui/src/app/pages/devices.rs:763 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2236,19 +2243,19 @@ msgstr "" "Marque las carpetas que sincronizar con esta máquina y ajuste dónde debe " "estar cada una." -#: crates/pdfs-gui/src/app/pages/devices.rs:782 +#: crates/pdfs-gui/src/app/pages/devices.rs:786 msgid "Restore Folders" msgstr "Restaurar carpetas" -#: crates/pdfs-gui/src/app/pages/devices.rs:789 +#: crates/pdfs-gui/src/app/pages/devices.rs:793 msgid "Restore" msgstr "Restaurar" -#: crates/pdfs-gui/src/app/pages/devices.rs:822 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 msgid "Restoring folders…" msgstr "Restaurando carpetas…" -#: crates/pdfs-gui/src/app/pages/devices.rs:834 +#: crates/pdfs-gui/src/app/pages/devices.rs:838 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2257,36 +2264,36 @@ msgstr "" "Estas carpetas tienen copia de seguridad desde “{name}”. Las carpetas " "restauradas siguen sincronizándose con la copia de ese equipo." -#: crates/pdfs-gui/src/app/pages/devices.rs:837 +#: crates/pdfs-gui/src/app/pages/devices.rs:841 msgid "These folders are backed up under this computer in Proton Drive." msgstr "" "Estas carpetas tienen copia de seguridad bajo este equipo en Proton Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:846 +#: crates/pdfs-gui/src/app/pages/devices.rs:850 #, rust-format msgid "Nothing to restore — “{name}”'s folders are all synced here." msgstr "" "Nada que restaurar: todas las carpetas de “{name}” ya se sincronizan aquí." -#: crates/pdfs-gui/src/app/pages/devices.rs:849 +#: crates/pdfs-gui/src/app/pages/devices.rs:853 msgid "Nothing to restore — this computer's folders are all synced here." msgstr "" "Nada que restaurar: todas las carpetas de este equipo ya se sincronizan aquí." -#: crates/pdfs-gui/src/app/pages/devices.rs:873 +#: crates/pdfs-gui/src/app/pages/devices.rs:877 msgid "Folder will switch to online only" msgstr "La carpeta pasará a solo en línea" -#: crates/pdfs-gui/src/app/pages/devices.rs:875 +#: crates/pdfs-gui/src/app/pages/devices.rs:879 msgid "Folder will download and stay synced" msgstr "La carpeta se descargará y permanecerá sincronizada" -#: crates/pdfs-gui/src/app/pages/devices.rs:879 #: crates/pdfs-gui/src/app/pages/devices.rs:883 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 msgid "Couldn't change mode" msgstr "No se pudo cambiar el modo" -#: crates/pdfs-gui/src/app/pages/devices.rs:915 +#: crates/pdfs-gui/src/app/pages/devices.rs:919 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2303,7 +2310,7 @@ msgstr "" "Para conservar una copia local, cancele, cambie la carpeta a Sincronizada y " "espere a que termine la descarga antes de quitarla." -#: crates/pdfs-gui/src/app/pages/devices.rs:921 +#: crates/pdfs-gui/src/app/pages/devices.rs:925 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2316,68 +2323,68 @@ msgstr "" "Los archivos locales permanecen en este disco y simplemente dejan de " "sincronizarse. Elija si quiere eliminar también la copia en Proton Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:926 +#: crates/pdfs-gui/src/app/pages/devices.rs:930 msgid "Stop Syncing Folder?" msgstr "¿Dejar de sincronizar la carpeta?" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:935 msgid "Also delete from Proton Drive" msgstr "Eliminar también de Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:933 +#: crates/pdfs-gui/src/app/pages/devices.rs:937 msgid "Deletes the only copy of these files." msgstr "Elimina la única copia de estos archivos." -#: crates/pdfs-gui/src/app/pages/devices.rs:935 +#: crates/pdfs-gui/src/app/pages/devices.rs:939 msgid "The local copy is unaffected." msgstr "La copia local no se ve afectada." -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:946 msgid "Stop Syncing" msgstr "Dejar de sincronizar" -#: crates/pdfs-gui/src/app/pages/devices.rs:955 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 msgid "Stopped syncing folder" msgstr "Se dejó de sincronizar la carpeta" -#: crates/pdfs-gui/src/app/pages/devices.rs:956 +#: crates/pdfs-gui/src/app/pages/devices.rs:960 msgid "Couldn't stop syncing the folder" msgstr "No se pudo dejar de sincronizar la carpeta" -#: crates/pdfs-gui/src/app/pages/devices.rs:967 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Rename Computer" msgstr "Renombrar equipo" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:969 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 #, rust-format msgid "Rename “{name}”." msgstr "Renombrar “{name}”." -#: crates/pdfs-gui/src/app/pages/devices.rs:994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1006 +#: crates/pdfs-gui/src/app/pages/devices.rs:998 +#: crates/pdfs-gui/src/app/pages/devices.rs:1010 msgid "Couldn't rename the computer" msgstr "No se pudo renombrar el equipo" -#: crates/pdfs-gui/src/app/pages/devices.rs:995 +#: crates/pdfs-gui/src/app/pages/devices.rs:999 msgid "A name is required." msgstr "Se necesita un nombre." -#: crates/pdfs-gui/src/app/pages/devices.rs:1005 +#: crates/pdfs-gui/src/app/pages/devices.rs:1009 msgid "Computer renamed" msgstr "Equipo renombrado" #. Translators: {name} is the computer's name, which the user must type. -#: crates/pdfs-gui/src/app/pages/devices.rs:1018 +#: crates/pdfs-gui/src/app/pages/devices.rs:1022 #, rust-format msgid "Type “{name}” to confirm" msgstr "Escriba “{name}” para confirmar" -#: crates/pdfs-gui/src/app/pages/devices.rs:1023 +#: crates/pdfs-gui/src/app/pages/devices.rs:1027 msgid "Remove Computer?" msgstr "¿Quitar el equipo?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1026 +#: crates/pdfs-gui/src/app/pages/devices.rs:1030 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2391,25 +2398,25 @@ msgstr "" "Todo lo que respaldó en Proton Drive se eliminará con él. Los archivos de " "ese equipo no se tocan, pero esto no se puede deshacer desde aquí." -#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1036 #: crates/pdfs-gui/src/app/pages/shared.rs:1028 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:529 msgid "Remove" msgstr "Quitar" -#: crates/pdfs-gui/src/app/pages/devices.rs:1049 +#: crates/pdfs-gui/src/app/pages/devices.rs:1053 msgid "Computer removed" msgstr "Equipo quitado" -#: crates/pdfs-gui/src/app/pages/devices.rs:1050 +#: crates/pdfs-gui/src/app/pages/devices.rs:1054 msgid "Couldn't remove the computer" msgstr "No se pudo quitar el equipo" -#: crates/pdfs-gui/src/app/pages/devices.rs:1068 +#: crates/pdfs-gui/src/app/pages/devices.rs:1072 msgid "Continue This Backup Here?" msgstr "¿Continuar esta copia de seguridad aquí?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1071 +#: crates/pdfs-gui/src/app/pages/devices.rs:1075 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2428,20 +2435,50 @@ msgstr "" "no se mueven. Después, use “Restaurar en este equipo” para recuperar lo que " "“{name}” sincronizaba." -#: crates/pdfs-gui/src/app/pages/devices.rs:1076 +#: crates/pdfs-gui/src/app/pages/devices.rs:1080 msgid "Continue Here" msgstr "Continuar aquí" #. Translators: {name} is the name of the computer whose backup this one took over. -#: crates/pdfs-gui/src/app/pages/devices.rs:1083 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 #, rust-format msgid "Now backing up as “{name}”" msgstr "Ahora se hace copia de seguridad como “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1096 msgid "Couldn't continue that backup here" msgstr "No se pudo continuar esa copia de seguridad aquí" +#: crates/pdfs-gui/src/app/pages/devices.rs:1112 +msgid "Move Backup to This Computer?" +msgstr "¿Mover la copia de seguridad a este equipo?" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1115 +#, rust-format +msgid "" +"The folders backed up by “{name}” move into this computer's backup in Proton " +"Drive, and you choose where they go on this computer. “{name}” stays in your " +"account, without those folders.\n" +"\n" +"Stop Proton Drive on “{name}” first. Otherwise it sees its folders disappear." +msgstr "" +"Las carpetas de las que “{name}” hace copia de seguridad pasan a la copia de " +"seguridad de este equipo en Proton Drive, y tú eliges dónde van en este " +"equipo. “{name}” sigue en tu cuenta, sin esas carpetas.\n" +"\n" +"Cierra primero Proton Drive en “{name}”. Si no, verá desaparecer sus " +"carpetas." + +#. Translators: {name} is the name of the computer whose folders moved to this one. +#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#, rust-format +msgid "Moved the backup of “{name}”" +msgstr "Se movió la copia de seguridad de “{name}”" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1141 +msgid "Couldn't move that backup" +msgstr "No se pudo mover esa copia de seguridad" + #: crates/pdfs-gui/src/app/pages/duplicates.rs:81 msgctxt "duplicates" msgid "Exact" diff --git a/po/fr.po b/po/fr.po index f42fea6..47cf36d 100644 --- a/po/fr.po +++ b/po/fr.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 2.1.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 10:15+0200\n" +"POT-Creation-Date: 2026-10-08 13:23+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: French\n" @@ -90,7 +90,7 @@ msgstr "Partagés par moi" #: crates/pdfs-gui/src/app/main.rs:776 #: crates/pdfs-gui/src/app/pages/devices.rs:140 -#: crates/pdfs-gui/src/app/pages/devices.rs:481 +#: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "Ordinateurs" @@ -266,11 +266,12 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1892 #: crates/pdfs-gui/src/app/pages/browser.rs:1968 #: crates/pdfs-gui/src/app/pages/browser.rs:2381 -#: crates/pdfs-gui/src/app/pages/devices.rs:788 -#: crates/pdfs-gui/src/app/pages/devices.rs:941 -#: crates/pdfs-gui/src/app/pages/devices.rs:979 -#: crates/pdfs-gui/src/app/pages/devices.rs:1031 -#: crates/pdfs-gui/src/app/pages/devices.rs:1075 +#: crates/pdfs-gui/src/app/pages/devices.rs:792 +#: crates/pdfs-gui/src/app/pages/devices.rs:945 +#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1035 +#: crates/pdfs-gui/src/app/pages/devices.rs:1079 +#: crates/pdfs-gui/src/app/pages/devices.rs:1119 #: crates/pdfs-gui/src/app/pages/locations.rs:809 #: crates/pdfs-gui/src/app/pages/locations.rs:1013 #: crates/pdfs-gui/src/app/pages/locations.rs:1488 @@ -316,7 +317,7 @@ msgstr "Rechercher dans Drive" #: crates/pdfs-gui/src/app/main.rs:1388 #: crates/pdfs-gui/src/app/pages/browser.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:554 +#: crates/pdfs-gui/src/app/pages/devices.rs:558 #: crates/pdfs-gui/src/app/pages/shared.rs:586 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:526 #: crates/pdfs-gui/src/app/pages/trash.rs:246 @@ -358,7 +359,7 @@ msgstr "Grille / liste" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1871 #: crates/pdfs-gui/src/app/pages/browser.rs:1893 -#: crates/pdfs-gui/src/app/pages/devices.rs:980 +#: crates/pdfs-gui/src/app/pages/devices.rs:984 #: crates/pdfs-gui/src/app/widgets/details.rs:159 msgid "Rename" msgstr "Renommer" @@ -686,7 +687,7 @@ msgid "Couldn't add a sync folder" msgstr "Impossible d’ajouter un dossier synchronisé" #: crates/pdfs-gui/src/app/pages/activity_text.rs:58 -#: crates/pdfs-gui/src/app/pages/devices.rs:823 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Couldn't restore folders" msgstr "Impossible de restaurer les dossiers" @@ -1271,10 +1272,11 @@ msgstr "Impossible de charger les albums" #: crates/pdfs-gui/src/app/pages/browser.rs:1848 #: crates/pdfs-gui/src/app/pages/browser.rs:2315 #: crates/pdfs-gui/src/app/pages/browser.rs:2491 -#: crates/pdfs-gui/src/app/pages/devices.rs:694 -#: crates/pdfs-gui/src/app/pages/devices.rs:734 -#: crates/pdfs-gui/src/app/pages/devices.rs:884 -#: crates/pdfs-gui/src/app/pages/devices.rs:1120 +#: crates/pdfs-gui/src/app/pages/devices.rs:698 +#: crates/pdfs-gui/src/app/pages/devices.rs:738 +#: crates/pdfs-gui/src/app/pages/devices.rs:888 +#: crates/pdfs-gui/src/app/pages/devices.rs:1156 +#: crates/pdfs-gui/src/app/pages/devices.rs:1185 #: crates/pdfs-gui/src/app/pages/duplicates.rs:220 #: crates/pdfs-gui/src/app/pages/locations.rs:536 #: crates/pdfs-gui/src/app/pages/locations.rs:783 @@ -1306,7 +1308,7 @@ msgstr "Le service Proton Drive n’a pas répondu." #: crates/pdfs-gui/src/app/pages/albums.rs:206 #: crates/pdfs-gui/src/app/pages/browser.rs:840 -#: crates/pdfs-gui/src/app/pages/devices.rs:530 +#: crates/pdfs-gui/src/app/pages/devices.rs:534 #: crates/pdfs-gui/src/app/pages/photos.rs:3190 #: crates/pdfs-gui/src/app/pages/shared.rs:541 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474 @@ -1320,7 +1322,7 @@ msgstr "Ouvrir" #: crates/pdfs-gui/src/app/pages/albums.rs:212 #: crates/pdfs-gui/src/app/pages/browser.rs:882 #: crates/pdfs-gui/src/app/pages/devices.rs:406 -#: crates/pdfs-gui/src/app/pages/devices.rs:444 +#: crates/pdfs-gui/src/app/pages/devices.rs:448 msgid "Rename…" msgstr "Renommer…" @@ -1588,7 +1590,7 @@ msgstr "Taille des icônes" #. Translators: {n} is how many items are selected: files and folders in My Files, photos here. #: crates/pdfs-gui/src/app/pages/browser.rs:538 #: crates/pdfs-gui/src/app/pages/browser.rs:925 -#: crates/pdfs-gui/src/app/pages/devices.rs:539 +#: crates/pdfs-gui/src/app/pages/devices.rs:543 #: crates/pdfs-gui/src/app/pages/photos.rs:1988 #: crates/pdfs-gui/src/app/pages/shared.rs:565 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:507 @@ -1744,7 +1746,7 @@ msgid "New Folder…" msgstr "Nouveau dossier…" #: crates/pdfs-gui/src/app/pages/browser.rs:953 -#: crates/pdfs-gui/src/app/pages/devices.rs:550 +#: crates/pdfs-gui/src/app/pages/devices.rs:554 #: crates/pdfs-gui/src/app/pages/shared.rs:582 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:522 #: crates/pdfs-gui/src/app/pages/trash.rs:242 @@ -1890,7 +1892,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1875 -#: crates/pdfs-gui/src/app/pages/devices.rs:973 +#: crates/pdfs-gui/src/app/pages/devices.rs:977 msgid "New name" msgstr "Nouveau nom" @@ -1920,6 +1922,7 @@ msgid "Choose the folder to move into." msgstr "Choisissez le dossier de destination." #: crates/pdfs-gui/src/app/pages/browser.rs:1969 +#: crates/pdfs-gui/src/app/pages/devices.rs:1120 msgid "Move Here" msgstr "Déplacer ici" @@ -2149,99 +2152,103 @@ msgstr "Parcourir les fichiers" msgid "Restore to This Computer…" msgstr "Restaurer sur cet ordinateur…" -#: crates/pdfs-gui/src/app/pages/devices.rs:451 +#: crates/pdfs-gui/src/app/pages/devices.rs:443 +msgid "Move to This Computer…" +msgstr "Déplacer vers cet ordinateur…" + +#: crates/pdfs-gui/src/app/pages/devices.rs:455 msgid "Continue This Backup Here…" msgstr "Continuer cette sauvegarde ici…" -#: crates/pdfs-gui/src/app/pages/devices.rs:456 +#: crates/pdfs-gui/src/app/pages/devices.rs:460 #: crates/pdfs-gui/src/app/pages/shared.rs:895 msgid "Remove…" msgstr "Retirer…" -#: crates/pdfs-gui/src/app/pages/devices.rs:567 +#: crates/pdfs-gui/src/app/pages/devices.rs:571 #, rust-format msgid "{platform} · last backup {time}" msgstr "{platform} · dernière sauvegarde {time}" #. Translators: {platform} is the device's platform, such as "Linux". -#: crates/pdfs-gui/src/app/pages/devices.rs:571 +#: crates/pdfs-gui/src/app/pages/devices.rs:575 #, rust-format msgid "{platform} · no backups yet" msgstr "{platform} · aucune sauvegarde pour l’instant" -#: crates/pdfs-gui/src/app/pages/devices.rs:587 +#: crates/pdfs-gui/src/app/pages/devices.rs:591 msgid "No folders backed up yet" msgstr "Aucun dossier sauvegardé pour l’instant" -#: crates/pdfs-gui/src/app/pages/devices.rs:590 +#: crates/pdfs-gui/src/app/pages/devices.rs:594 #, rust-format msgid "{n} folder backed up" msgid_plural "{n} folders backed up" msgstr[0] "{n} dossier sauvegardé" msgstr[1] "{n} dossiers sauvegardés" -#: crates/pdfs-gui/src/app/pages/devices.rs:603 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 #, rust-format msgid "{folders} · {n} needs attention" msgid_plural "{folders} · {n} need attention" msgstr[0] "{folders} · {n} requiert votre attention" msgstr[1] "{folders} · {n} requièrent votre attention" -#: crates/pdfs-gui/src/app/pages/devices.rs:614 +#: crates/pdfs-gui/src/app/pages/devices.rs:618 msgid "syncing…" msgstr "synchronisation…" -#: crates/pdfs-gui/src/app/pages/devices.rs:615 +#: crates/pdfs-gui/src/app/pages/devices.rs:619 msgid "sync error" msgstr "erreur de synchronisation" -#: crates/pdfs-gui/src/app/pages/devices.rs:616 +#: crates/pdfs-gui/src/app/pages/devices.rs:620 msgid "needs attention" msgstr "attention requise" -#: crates/pdfs-gui/src/app/pages/devices.rs:617 +#: crates/pdfs-gui/src/app/pages/devices.rs:621 msgid "up to date" msgstr "à jour" -#: crates/pdfs-gui/src/app/pages/devices.rs:629 +#: crates/pdfs-gui/src/app/pages/devices.rs:633 msgid "checking for changes…" msgstr "recherche de modifications…" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:636 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "recherche de modifications — {done} sur {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:644 +#: crates/pdfs-gui/src/app/pages/devices.rs:648 #, rust-format msgid "syncing {done} of {total}" msgstr "synchronisation de {done} sur {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:650 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "synchronisation de {file} — {done} sur {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:662 +#: crates/pdfs-gui/src/app/pages/devices.rs:666 msgid "Add Folder to Sync" msgstr "Ajouter un dossier à synchroniser" -#: crates/pdfs-gui/src/app/pages/devices.rs:686 +#: crates/pdfs-gui/src/app/pages/devices.rs:690 #: crates/pdfs-gui/src/app/pages/locations.rs:1593 msgid "Syncing folder…" msgstr "Synchronisation du dossier…" -#: crates/pdfs-gui/src/app/pages/devices.rs:689 #: crates/pdfs-gui/src/app/pages/devices.rs:693 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Couldn't add folder" msgstr "Impossible d’ajouter le dossier" -#: crates/pdfs-gui/src/app/pages/devices.rs:727 -#: crates/pdfs-gui/src/app/pages/devices.rs:733 +#: crates/pdfs-gui/src/app/pages/devices.rs:731 +#: crates/pdfs-gui/src/app/pages/devices.rs:737 msgid "Couldn't list folders to restore" msgstr "Impossible de lister les dossiers à restaurer" -#: crates/pdfs-gui/src/app/pages/devices.rs:759 +#: crates/pdfs-gui/src/app/pages/devices.rs:763 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2249,19 +2256,19 @@ msgstr "" "Cochez les dossiers à synchroniser sur cette machine et ajustez " "l’emplacement de chacun." -#: crates/pdfs-gui/src/app/pages/devices.rs:782 +#: crates/pdfs-gui/src/app/pages/devices.rs:786 msgid "Restore Folders" msgstr "Restaurer les dossiers" -#: crates/pdfs-gui/src/app/pages/devices.rs:789 +#: crates/pdfs-gui/src/app/pages/devices.rs:793 msgid "Restore" msgstr "Restaurer" -#: crates/pdfs-gui/src/app/pages/devices.rs:822 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 msgid "Restoring folders…" msgstr "Restauration des dossiers…" -#: crates/pdfs-gui/src/app/pages/devices.rs:834 +#: crates/pdfs-gui/src/app/pages/devices.rs:838 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2270,35 +2277,35 @@ msgstr "" "Ces dossiers sont sauvegardés depuis « {name} ». Les dossiers restaurés " "restent synchronisés avec la copie de cet ordinateur." -#: crates/pdfs-gui/src/app/pages/devices.rs:837 +#: crates/pdfs-gui/src/app/pages/devices.rs:841 msgid "These folders are backed up under this computer in Proton Drive." msgstr "Ces dossiers sont sauvegardés sous cet ordinateur dans Proton Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:846 +#: crates/pdfs-gui/src/app/pages/devices.rs:850 #, rust-format msgid "Nothing to restore — “{name}”'s folders are all synced here." msgstr "" "Rien à restaurer — les dossiers de « {name} » sont tous synchronisés ici." -#: crates/pdfs-gui/src/app/pages/devices.rs:849 +#: crates/pdfs-gui/src/app/pages/devices.rs:853 msgid "Nothing to restore — this computer's folders are all synced here." msgstr "" "Rien à restaurer — les dossiers de cet ordinateur sont tous synchronisés ici." -#: crates/pdfs-gui/src/app/pages/devices.rs:873 +#: crates/pdfs-gui/src/app/pages/devices.rs:877 msgid "Folder will switch to online only" msgstr "Le dossier passera en ligne uniquement" -#: crates/pdfs-gui/src/app/pages/devices.rs:875 +#: crates/pdfs-gui/src/app/pages/devices.rs:879 msgid "Folder will download and stay synced" msgstr "Le dossier va être téléchargé et rester synchronisé" -#: crates/pdfs-gui/src/app/pages/devices.rs:879 #: crates/pdfs-gui/src/app/pages/devices.rs:883 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 msgid "Couldn't change mode" msgstr "Impossible de changer de mode" -#: crates/pdfs-gui/src/app/pages/devices.rs:915 +#: crates/pdfs-gui/src/app/pages/devices.rs:919 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2315,7 +2322,7 @@ msgstr "" "arrêtée. Pour conserver une copie locale, annulez, passez le dossier en " "« Synchronisé » et attendez la fin du téléchargement avant de le retirer." -#: crates/pdfs-gui/src/app/pages/devices.rs:921 +#: crates/pdfs-gui/src/app/pages/devices.rs:925 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2329,68 +2336,68 @@ msgstr "" "synchronisés. Choisissez s’il faut aussi supprimer la copie dans Proton " "Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:926 +#: crates/pdfs-gui/src/app/pages/devices.rs:930 msgid "Stop Syncing Folder?" msgstr "Arrêter la synchronisation du dossier ?" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:935 msgid "Also delete from Proton Drive" msgstr "Supprimer aussi de Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:933 +#: crates/pdfs-gui/src/app/pages/devices.rs:937 msgid "Deletes the only copy of these files." msgstr "Supprime l’unique copie de ces fichiers." -#: crates/pdfs-gui/src/app/pages/devices.rs:935 +#: crates/pdfs-gui/src/app/pages/devices.rs:939 msgid "The local copy is unaffected." msgstr "La copie locale n’est pas affectée." -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:946 msgid "Stop Syncing" msgstr "Arrêter la synchronisation" -#: crates/pdfs-gui/src/app/pages/devices.rs:955 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 msgid "Stopped syncing folder" msgstr "Synchronisation du dossier arrêtée" -#: crates/pdfs-gui/src/app/pages/devices.rs:956 +#: crates/pdfs-gui/src/app/pages/devices.rs:960 msgid "Couldn't stop syncing the folder" msgstr "Impossible d’arrêter la synchronisation du dossier" -#: crates/pdfs-gui/src/app/pages/devices.rs:967 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Rename Computer" msgstr "Renommer l’ordinateur" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:969 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 #, rust-format msgid "Rename “{name}”." msgstr "Renommer « {name} »." -#: crates/pdfs-gui/src/app/pages/devices.rs:994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1006 +#: crates/pdfs-gui/src/app/pages/devices.rs:998 +#: crates/pdfs-gui/src/app/pages/devices.rs:1010 msgid "Couldn't rename the computer" msgstr "Impossible de renommer l’ordinateur" -#: crates/pdfs-gui/src/app/pages/devices.rs:995 +#: crates/pdfs-gui/src/app/pages/devices.rs:999 msgid "A name is required." msgstr "Un nom est requis." -#: crates/pdfs-gui/src/app/pages/devices.rs:1005 +#: crates/pdfs-gui/src/app/pages/devices.rs:1009 msgid "Computer renamed" msgstr "Ordinateur renommé" #. Translators: {name} is the computer's name, which the user must type. -#: crates/pdfs-gui/src/app/pages/devices.rs:1018 +#: crates/pdfs-gui/src/app/pages/devices.rs:1022 #, rust-format msgid "Type “{name}” to confirm" msgstr "Saisissez « {name} » pour confirmer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1023 +#: crates/pdfs-gui/src/app/pages/devices.rs:1027 msgid "Remove Computer?" msgstr "Retirer l’ordinateur ?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1026 +#: crates/pdfs-gui/src/app/pages/devices.rs:1030 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2405,25 +2412,25 @@ msgstr "" "fichiers présents sur cet ordinateur ne sont pas touchés — mais cette action " "ne peut pas être annulée d’ici." -#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1036 #: crates/pdfs-gui/src/app/pages/shared.rs:1028 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:529 msgid "Remove" msgstr "Retirer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1049 +#: crates/pdfs-gui/src/app/pages/devices.rs:1053 msgid "Computer removed" msgstr "Ordinateur retiré" -#: crates/pdfs-gui/src/app/pages/devices.rs:1050 +#: crates/pdfs-gui/src/app/pages/devices.rs:1054 msgid "Couldn't remove the computer" msgstr "Impossible de retirer l’ordinateur" -#: crates/pdfs-gui/src/app/pages/devices.rs:1068 +#: crates/pdfs-gui/src/app/pages/devices.rs:1072 msgid "Continue This Backup Here?" msgstr "Continuer cette sauvegarde ici ?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1071 +#: crates/pdfs-gui/src/app/pages/devices.rs:1075 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2442,20 +2449,50 @@ msgstr "" "ne sont pas déplacés. Utilisez ensuite « Restaurer sur cet ordinateur » pour " "récupérer ce que « {name} » synchronisait." -#: crates/pdfs-gui/src/app/pages/devices.rs:1076 +#: crates/pdfs-gui/src/app/pages/devices.rs:1080 msgid "Continue Here" msgstr "Continuer ici" #. Translators: {name} is the name of the computer whose backup this one took over. -#: crates/pdfs-gui/src/app/pages/devices.rs:1083 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 #, rust-format msgid "Now backing up as “{name}”" msgstr "Sauvegarde désormais en tant que « {name} »" -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1096 msgid "Couldn't continue that backup here" msgstr "Impossible de continuer cette sauvegarde ici" +#: crates/pdfs-gui/src/app/pages/devices.rs:1112 +msgid "Move Backup to This Computer?" +msgstr "Déplacer la sauvegarde vers cet ordinateur ?" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1115 +#, rust-format +msgid "" +"The folders backed up by “{name}” move into this computer's backup in Proton " +"Drive, and you choose where they go on this computer. “{name}” stays in your " +"account, without those folders.\n" +"\n" +"Stop Proton Drive on “{name}” first. Otherwise it sees its folders disappear." +msgstr "" +"Les dossiers sauvegardés par « {name} » passent dans la sauvegarde de cet " +"ordinateur dans Proton Drive, et vous choisissez où les placer sur cet " +"ordinateur. « {name} » reste dans votre compte, sans ces dossiers.\n" +"\n" +"Arrêtez d'abord Proton Drive sur « {name} ». Sinon, il verra ses dossiers " +"disparaître." + +#. Translators: {name} is the name of the computer whose folders moved to this one. +#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#, rust-format +msgid "Moved the backup of “{name}”" +msgstr "Sauvegarde de « {name} » déplacée" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1141 +msgid "Couldn't move that backup" +msgstr "Impossible de déplacer cette sauvegarde" + #: crates/pdfs-gui/src/app/pages/duplicates.rs:81 msgctxt "duplicates" msgid "Exact" diff --git a/po/it.po b/po/it.po index 23b5fb5..ff951b0 100644 --- a/po/it.po +++ b/po/it.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 2.1.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 10:15+0200\n" +"POT-Creation-Date: 2026-10-08 13:23+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: Italian\n" @@ -90,7 +90,7 @@ msgstr "Condivisi da me" #: crates/pdfs-gui/src/app/main.rs:776 #: crates/pdfs-gui/src/app/pages/devices.rs:140 -#: crates/pdfs-gui/src/app/pages/devices.rs:481 +#: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "Computer" @@ -262,11 +262,12 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1892 #: crates/pdfs-gui/src/app/pages/browser.rs:1968 #: crates/pdfs-gui/src/app/pages/browser.rs:2381 -#: crates/pdfs-gui/src/app/pages/devices.rs:788 -#: crates/pdfs-gui/src/app/pages/devices.rs:941 -#: crates/pdfs-gui/src/app/pages/devices.rs:979 -#: crates/pdfs-gui/src/app/pages/devices.rs:1031 -#: crates/pdfs-gui/src/app/pages/devices.rs:1075 +#: crates/pdfs-gui/src/app/pages/devices.rs:792 +#: crates/pdfs-gui/src/app/pages/devices.rs:945 +#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1035 +#: crates/pdfs-gui/src/app/pages/devices.rs:1079 +#: crates/pdfs-gui/src/app/pages/devices.rs:1119 #: crates/pdfs-gui/src/app/pages/locations.rs:809 #: crates/pdfs-gui/src/app/pages/locations.rs:1013 #: crates/pdfs-gui/src/app/pages/locations.rs:1488 @@ -312,7 +313,7 @@ msgstr "Cerca in Drive" #: crates/pdfs-gui/src/app/main.rs:1388 #: crates/pdfs-gui/src/app/pages/browser.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:554 +#: crates/pdfs-gui/src/app/pages/devices.rs:558 #: crates/pdfs-gui/src/app/pages/shared.rs:586 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:526 #: crates/pdfs-gui/src/app/pages/trash.rs:246 @@ -354,7 +355,7 @@ msgstr "Griglia / elenco" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1871 #: crates/pdfs-gui/src/app/pages/browser.rs:1893 -#: crates/pdfs-gui/src/app/pages/devices.rs:980 +#: crates/pdfs-gui/src/app/pages/devices.rs:984 #: crates/pdfs-gui/src/app/widgets/details.rs:159 msgid "Rename" msgstr "Rinomina" @@ -682,7 +683,7 @@ msgid "Couldn't add a sync folder" msgstr "Impossibile aggiungere una cartella sincronizzata" #: crates/pdfs-gui/src/app/pages/activity_text.rs:58 -#: crates/pdfs-gui/src/app/pages/devices.rs:823 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Couldn't restore folders" msgstr "Impossibile ripristinare le cartelle" @@ -1265,10 +1266,11 @@ msgstr "Impossibile caricare gli album" #: crates/pdfs-gui/src/app/pages/browser.rs:1848 #: crates/pdfs-gui/src/app/pages/browser.rs:2315 #: crates/pdfs-gui/src/app/pages/browser.rs:2491 -#: crates/pdfs-gui/src/app/pages/devices.rs:694 -#: crates/pdfs-gui/src/app/pages/devices.rs:734 -#: crates/pdfs-gui/src/app/pages/devices.rs:884 -#: crates/pdfs-gui/src/app/pages/devices.rs:1120 +#: crates/pdfs-gui/src/app/pages/devices.rs:698 +#: crates/pdfs-gui/src/app/pages/devices.rs:738 +#: crates/pdfs-gui/src/app/pages/devices.rs:888 +#: crates/pdfs-gui/src/app/pages/devices.rs:1156 +#: crates/pdfs-gui/src/app/pages/devices.rs:1185 #: crates/pdfs-gui/src/app/pages/duplicates.rs:220 #: crates/pdfs-gui/src/app/pages/locations.rs:536 #: crates/pdfs-gui/src/app/pages/locations.rs:783 @@ -1300,7 +1302,7 @@ msgstr "Il servizio di Proton Drive non ha risposto." #: crates/pdfs-gui/src/app/pages/albums.rs:206 #: crates/pdfs-gui/src/app/pages/browser.rs:840 -#: crates/pdfs-gui/src/app/pages/devices.rs:530 +#: crates/pdfs-gui/src/app/pages/devices.rs:534 #: crates/pdfs-gui/src/app/pages/photos.rs:3190 #: crates/pdfs-gui/src/app/pages/shared.rs:541 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474 @@ -1314,7 +1316,7 @@ msgstr "Apri" #: crates/pdfs-gui/src/app/pages/albums.rs:212 #: crates/pdfs-gui/src/app/pages/browser.rs:882 #: crates/pdfs-gui/src/app/pages/devices.rs:406 -#: crates/pdfs-gui/src/app/pages/devices.rs:444 +#: crates/pdfs-gui/src/app/pages/devices.rs:448 msgid "Rename…" msgstr "Rinomina…" @@ -1582,7 +1584,7 @@ msgstr "Dimensione icone" #. Translators: {n} is how many items are selected: files and folders in My Files, photos here. #: crates/pdfs-gui/src/app/pages/browser.rs:538 #: crates/pdfs-gui/src/app/pages/browser.rs:925 -#: crates/pdfs-gui/src/app/pages/devices.rs:539 +#: crates/pdfs-gui/src/app/pages/devices.rs:543 #: crates/pdfs-gui/src/app/pages/photos.rs:1988 #: crates/pdfs-gui/src/app/pages/shared.rs:565 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:507 @@ -1738,7 +1740,7 @@ msgid "New Folder…" msgstr "Nuova cartella…" #: crates/pdfs-gui/src/app/pages/browser.rs:953 -#: crates/pdfs-gui/src/app/pages/devices.rs:550 +#: crates/pdfs-gui/src/app/pages/devices.rs:554 #: crates/pdfs-gui/src/app/pages/shared.rs:582 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:522 #: crates/pdfs-gui/src/app/pages/trash.rs:242 @@ -1884,7 +1886,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1875 -#: crates/pdfs-gui/src/app/pages/devices.rs:973 +#: crates/pdfs-gui/src/app/pages/devices.rs:977 msgid "New name" msgstr "Nuovo nome" @@ -1914,6 +1916,7 @@ msgid "Choose the folder to move into." msgstr "Scegli la cartella di destinazione." #: crates/pdfs-gui/src/app/pages/browser.rs:1969 +#: crates/pdfs-gui/src/app/pages/devices.rs:1120 msgid "Move Here" msgstr "Sposta qui" @@ -2143,99 +2146,103 @@ msgstr "Sfoglia file" msgid "Restore to This Computer…" msgstr "Ripristina su questo computer…" -#: crates/pdfs-gui/src/app/pages/devices.rs:451 +#: crates/pdfs-gui/src/app/pages/devices.rs:443 +msgid "Move to This Computer…" +msgstr "Sposta su questo computer…" + +#: crates/pdfs-gui/src/app/pages/devices.rs:455 msgid "Continue This Backup Here…" msgstr "Continua questo backup qui…" -#: crates/pdfs-gui/src/app/pages/devices.rs:456 +#: crates/pdfs-gui/src/app/pages/devices.rs:460 #: crates/pdfs-gui/src/app/pages/shared.rs:895 msgid "Remove…" msgstr "Rimuovi…" -#: crates/pdfs-gui/src/app/pages/devices.rs:567 +#: crates/pdfs-gui/src/app/pages/devices.rs:571 #, rust-format msgid "{platform} · last backup {time}" msgstr "{platform} · ultimo backup {time}" #. Translators: {platform} is the device's platform, such as "Linux". -#: crates/pdfs-gui/src/app/pages/devices.rs:571 +#: crates/pdfs-gui/src/app/pages/devices.rs:575 #, rust-format msgid "{platform} · no backups yet" msgstr "{platform} · ancora nessun backup" -#: crates/pdfs-gui/src/app/pages/devices.rs:587 +#: crates/pdfs-gui/src/app/pages/devices.rs:591 msgid "No folders backed up yet" msgstr "Ancora nessuna cartella sottoposta a backup" -#: crates/pdfs-gui/src/app/pages/devices.rs:590 +#: crates/pdfs-gui/src/app/pages/devices.rs:594 #, rust-format msgid "{n} folder backed up" msgid_plural "{n} folders backed up" msgstr[0] "{n} cartella sottoposta a backup" msgstr[1] "{n} cartelle sottoposte a backup" -#: crates/pdfs-gui/src/app/pages/devices.rs:603 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 #, rust-format msgid "{folders} · {n} needs attention" msgid_plural "{folders} · {n} need attention" msgstr[0] "{folders} · {n} richiede attenzione" msgstr[1] "{folders} · {n} richiedono attenzione" -#: crates/pdfs-gui/src/app/pages/devices.rs:614 +#: crates/pdfs-gui/src/app/pages/devices.rs:618 msgid "syncing…" msgstr "sincronizzazione…" -#: crates/pdfs-gui/src/app/pages/devices.rs:615 +#: crates/pdfs-gui/src/app/pages/devices.rs:619 msgid "sync error" msgstr "errore di sincronizzazione" -#: crates/pdfs-gui/src/app/pages/devices.rs:616 +#: crates/pdfs-gui/src/app/pages/devices.rs:620 msgid "needs attention" msgstr "richiede attenzione" -#: crates/pdfs-gui/src/app/pages/devices.rs:617 +#: crates/pdfs-gui/src/app/pages/devices.rs:621 msgid "up to date" msgstr "aggiornato" -#: crates/pdfs-gui/src/app/pages/devices.rs:629 +#: crates/pdfs-gui/src/app/pages/devices.rs:633 msgid "checking for changes…" msgstr "controllo delle modifiche…" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:636 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "controllo delle modifiche: {done} di {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:644 +#: crates/pdfs-gui/src/app/pages/devices.rs:648 #, rust-format msgid "syncing {done} of {total}" msgstr "sincronizzazione {done} di {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:650 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "sincronizzazione di {file}: {done} di {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:662 +#: crates/pdfs-gui/src/app/pages/devices.rs:666 msgid "Add Folder to Sync" msgstr "Aggiungi cartella da sincronizzare" -#: crates/pdfs-gui/src/app/pages/devices.rs:686 +#: crates/pdfs-gui/src/app/pages/devices.rs:690 #: crates/pdfs-gui/src/app/pages/locations.rs:1593 msgid "Syncing folder…" msgstr "Sincronizzazione della cartella…" -#: crates/pdfs-gui/src/app/pages/devices.rs:689 #: crates/pdfs-gui/src/app/pages/devices.rs:693 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Couldn't add folder" msgstr "Impossibile aggiungere la cartella" -#: crates/pdfs-gui/src/app/pages/devices.rs:727 -#: crates/pdfs-gui/src/app/pages/devices.rs:733 +#: crates/pdfs-gui/src/app/pages/devices.rs:731 +#: crates/pdfs-gui/src/app/pages/devices.rs:737 msgid "Couldn't list folders to restore" msgstr "Impossibile elencare le cartelle da ripristinare" -#: crates/pdfs-gui/src/app/pages/devices.rs:759 +#: crates/pdfs-gui/src/app/pages/devices.rs:763 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2243,19 +2250,19 @@ msgstr "" "Seleziona le cartelle da sincronizzare su questa macchina e regola dove deve " "trovarsi ciascuna." -#: crates/pdfs-gui/src/app/pages/devices.rs:782 +#: crates/pdfs-gui/src/app/pages/devices.rs:786 msgid "Restore Folders" msgstr "Ripristina cartelle" -#: crates/pdfs-gui/src/app/pages/devices.rs:789 +#: crates/pdfs-gui/src/app/pages/devices.rs:793 msgid "Restore" msgstr "Ripristina" -#: crates/pdfs-gui/src/app/pages/devices.rs:822 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 msgid "Restoring folders…" msgstr "Ripristino delle cartelle…" -#: crates/pdfs-gui/src/app/pages/devices.rs:834 +#: crates/pdfs-gui/src/app/pages/devices.rs:838 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2264,39 +2271,39 @@ msgstr "" "Queste cartelle sono sottoposte a backup da “{name}”. Le cartelle " "ripristinate continuano a sincronizzarsi con la copia di quel computer." -#: crates/pdfs-gui/src/app/pages/devices.rs:837 +#: crates/pdfs-gui/src/app/pages/devices.rs:841 msgid "These folders are backed up under this computer in Proton Drive." msgstr "" "Queste cartelle sono sottoposte a backup sotto questo computer in Proton " "Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:846 +#: crates/pdfs-gui/src/app/pages/devices.rs:850 #, rust-format msgid "Nothing to restore — “{name}”'s folders are all synced here." msgstr "" "Niente da ripristinare: le cartelle di “{name}” sono già tutte sincronizzate " "qui." -#: crates/pdfs-gui/src/app/pages/devices.rs:849 +#: crates/pdfs-gui/src/app/pages/devices.rs:853 msgid "Nothing to restore — this computer's folders are all synced here." msgstr "" "Niente da ripristinare: le cartelle di questo computer sono già tutte " "sincronizzate qui." -#: crates/pdfs-gui/src/app/pages/devices.rs:873 +#: crates/pdfs-gui/src/app/pages/devices.rs:877 msgid "Folder will switch to online only" msgstr "La cartella passerà a solo online" -#: crates/pdfs-gui/src/app/pages/devices.rs:875 +#: crates/pdfs-gui/src/app/pages/devices.rs:879 msgid "Folder will download and stay synced" msgstr "La cartella verrà scaricata e resterà sincronizzata" -#: crates/pdfs-gui/src/app/pages/devices.rs:879 #: crates/pdfs-gui/src/app/pages/devices.rs:883 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 msgid "Couldn't change mode" msgstr "Impossibile cambiare modalità" -#: crates/pdfs-gui/src/app/pages/devices.rs:915 +#: crates/pdfs-gui/src/app/pages/devices.rs:919 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2314,7 +2321,7 @@ msgstr "" "cartella a Sincronizzata e attendi il completamento del download prima di " "rimuoverla." -#: crates/pdfs-gui/src/app/pages/devices.rs:921 +#: crates/pdfs-gui/src/app/pages/devices.rs:925 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2327,68 +2334,68 @@ msgstr "" "I file locali restano su questo disco e semplicemente smettono di essere " "sincronizzati. Scegli se eliminare anche la copia in Proton Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:926 +#: crates/pdfs-gui/src/app/pages/devices.rs:930 msgid "Stop Syncing Folder?" msgstr "Interrompere la sincronizzazione della cartella?" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:935 msgid "Also delete from Proton Drive" msgstr "Elimina anche da Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:933 +#: crates/pdfs-gui/src/app/pages/devices.rs:937 msgid "Deletes the only copy of these files." msgstr "Elimina l'unica copia di questi file." -#: crates/pdfs-gui/src/app/pages/devices.rs:935 +#: crates/pdfs-gui/src/app/pages/devices.rs:939 msgid "The local copy is unaffected." msgstr "La copia locale non viene modificata." -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:946 msgid "Stop Syncing" msgstr "Interrompi sincronizzazione" -#: crates/pdfs-gui/src/app/pages/devices.rs:955 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 msgid "Stopped syncing folder" msgstr "Sincronizzazione della cartella interrotta" -#: crates/pdfs-gui/src/app/pages/devices.rs:956 +#: crates/pdfs-gui/src/app/pages/devices.rs:960 msgid "Couldn't stop syncing the folder" msgstr "Impossibile interrompere la sincronizzazione della cartella" -#: crates/pdfs-gui/src/app/pages/devices.rs:967 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Rename Computer" msgstr "Rinomina computer" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:969 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 #, rust-format msgid "Rename “{name}”." msgstr "Rinomina “{name}”." -#: crates/pdfs-gui/src/app/pages/devices.rs:994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1006 +#: crates/pdfs-gui/src/app/pages/devices.rs:998 +#: crates/pdfs-gui/src/app/pages/devices.rs:1010 msgid "Couldn't rename the computer" msgstr "Impossibile rinominare il computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:995 +#: crates/pdfs-gui/src/app/pages/devices.rs:999 msgid "A name is required." msgstr "È richiesto un nome." -#: crates/pdfs-gui/src/app/pages/devices.rs:1005 +#: crates/pdfs-gui/src/app/pages/devices.rs:1009 msgid "Computer renamed" msgstr "Computer rinominato" #. Translators: {name} is the computer's name, which the user must type. -#: crates/pdfs-gui/src/app/pages/devices.rs:1018 +#: crates/pdfs-gui/src/app/pages/devices.rs:1022 #, rust-format msgid "Type “{name}” to confirm" msgstr "Digita “{name}” per confermare" -#: crates/pdfs-gui/src/app/pages/devices.rs:1023 +#: crates/pdfs-gui/src/app/pages/devices.rs:1027 msgid "Remove Computer?" msgstr "Rimuovere il computer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1026 +#: crates/pdfs-gui/src/app/pages/devices.rs:1030 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2403,25 +2410,25 @@ msgstr "" "insieme a esso. I file su quel computer non vengono toccati, ma questa " "operazione non può essere annullata da qui." -#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1036 #: crates/pdfs-gui/src/app/pages/shared.rs:1028 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:529 msgid "Remove" msgstr "Rimuovi" -#: crates/pdfs-gui/src/app/pages/devices.rs:1049 +#: crates/pdfs-gui/src/app/pages/devices.rs:1053 msgid "Computer removed" msgstr "Computer rimosso" -#: crates/pdfs-gui/src/app/pages/devices.rs:1050 +#: crates/pdfs-gui/src/app/pages/devices.rs:1054 msgid "Couldn't remove the computer" msgstr "Impossibile rimuovere il computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1068 +#: crates/pdfs-gui/src/app/pages/devices.rs:1072 msgid "Continue This Backup Here?" msgstr "Continuare questo backup qui?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1071 +#: crates/pdfs-gui/src/app/pages/devices.rs:1075 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2440,20 +2447,50 @@ msgstr "" "qui non vengono spostate. Usa poi “Ripristina su questo computer” per " "recuperare ciò che “{name}” sincronizzava." -#: crates/pdfs-gui/src/app/pages/devices.rs:1076 +#: crates/pdfs-gui/src/app/pages/devices.rs:1080 msgid "Continue Here" msgstr "Continua qui" #. Translators: {name} is the name of the computer whose backup this one took over. -#: crates/pdfs-gui/src/app/pages/devices.rs:1083 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 #, rust-format msgid "Now backing up as “{name}”" msgstr "Ora il backup avviene come “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1096 msgid "Couldn't continue that backup here" msgstr "Impossibile continuare quel backup qui" +#: crates/pdfs-gui/src/app/pages/devices.rs:1112 +msgid "Move Backup to This Computer?" +msgstr "Spostare il backup su questo computer?" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1115 +#, rust-format +msgid "" +"The folders backed up by “{name}” move into this computer's backup in Proton " +"Drive, and you choose where they go on this computer. “{name}” stays in your " +"account, without those folders.\n" +"\n" +"Stop Proton Drive on “{name}” first. Otherwise it sees its folders disappear." +msgstr "" +"Le cartelle di cui “{name}” esegue il backup passano nel backup di questo " +"computer su Proton Drive, e scegli tu dove metterle su questo computer. " +"“{name}” resta nel tuo account, senza quelle cartelle.\n" +"\n" +"Chiudi prima Proton Drive su “{name}”. Altrimenti vedrà sparire le sue " +"cartelle." + +#. Translators: {name} is the name of the computer whose folders moved to this one. +#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#, rust-format +msgid "Moved the backup of “{name}”" +msgstr "Backup di “{name}” spostato" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1141 +msgid "Couldn't move that backup" +msgstr "Impossibile spostare quel backup" + #: crates/pdfs-gui/src/app/pages/duplicates.rs:81 msgctxt "duplicates" msgid "Exact" diff --git a/po/nl.po b/po/nl.po index e07e726..f476373 100644 --- a/po/nl.po +++ b/po/nl.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 2.1.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 10:15+0200\n" +"POT-Creation-Date: 2026-10-08 13:23+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: Dutch\n" @@ -90,7 +90,7 @@ msgstr "Gedeeld door mij" #: crates/pdfs-gui/src/app/main.rs:776 #: crates/pdfs-gui/src/app/pages/devices.rs:140 -#: crates/pdfs-gui/src/app/pages/devices.rs:481 +#: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "Computers" @@ -263,11 +263,12 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1892 #: crates/pdfs-gui/src/app/pages/browser.rs:1968 #: crates/pdfs-gui/src/app/pages/browser.rs:2381 -#: crates/pdfs-gui/src/app/pages/devices.rs:788 -#: crates/pdfs-gui/src/app/pages/devices.rs:941 -#: crates/pdfs-gui/src/app/pages/devices.rs:979 -#: crates/pdfs-gui/src/app/pages/devices.rs:1031 -#: crates/pdfs-gui/src/app/pages/devices.rs:1075 +#: crates/pdfs-gui/src/app/pages/devices.rs:792 +#: crates/pdfs-gui/src/app/pages/devices.rs:945 +#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1035 +#: crates/pdfs-gui/src/app/pages/devices.rs:1079 +#: crates/pdfs-gui/src/app/pages/devices.rs:1119 #: crates/pdfs-gui/src/app/pages/locations.rs:809 #: crates/pdfs-gui/src/app/pages/locations.rs:1013 #: crates/pdfs-gui/src/app/pages/locations.rs:1488 @@ -313,7 +314,7 @@ msgstr "Drive doorzoeken" #: crates/pdfs-gui/src/app/main.rs:1388 #: crates/pdfs-gui/src/app/pages/browser.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:554 +#: crates/pdfs-gui/src/app/pages/devices.rs:558 #: crates/pdfs-gui/src/app/pages/shared.rs:586 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:526 #: crates/pdfs-gui/src/app/pages/trash.rs:246 @@ -355,7 +356,7 @@ msgstr "Raster / lijst" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1871 #: crates/pdfs-gui/src/app/pages/browser.rs:1893 -#: crates/pdfs-gui/src/app/pages/devices.rs:980 +#: crates/pdfs-gui/src/app/pages/devices.rs:984 #: crates/pdfs-gui/src/app/widgets/details.rs:159 msgid "Rename" msgstr "Hernoemen" @@ -683,7 +684,7 @@ msgid "Couldn't add a sync folder" msgstr "Kon geen synchronisatiemap toevoegen" #: crates/pdfs-gui/src/app/pages/activity_text.rs:58 -#: crates/pdfs-gui/src/app/pages/devices.rs:823 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Couldn't restore folders" msgstr "Kon mappen niet terugzetten" @@ -1261,10 +1262,11 @@ msgstr "Kon albums niet laden" #: crates/pdfs-gui/src/app/pages/browser.rs:1848 #: crates/pdfs-gui/src/app/pages/browser.rs:2315 #: crates/pdfs-gui/src/app/pages/browser.rs:2491 -#: crates/pdfs-gui/src/app/pages/devices.rs:694 -#: crates/pdfs-gui/src/app/pages/devices.rs:734 -#: crates/pdfs-gui/src/app/pages/devices.rs:884 -#: crates/pdfs-gui/src/app/pages/devices.rs:1120 +#: crates/pdfs-gui/src/app/pages/devices.rs:698 +#: crates/pdfs-gui/src/app/pages/devices.rs:738 +#: crates/pdfs-gui/src/app/pages/devices.rs:888 +#: crates/pdfs-gui/src/app/pages/devices.rs:1156 +#: crates/pdfs-gui/src/app/pages/devices.rs:1185 #: crates/pdfs-gui/src/app/pages/duplicates.rs:220 #: crates/pdfs-gui/src/app/pages/locations.rs:536 #: crates/pdfs-gui/src/app/pages/locations.rs:783 @@ -1296,7 +1298,7 @@ msgstr "De Proton Drive-service reageerde niet." #: crates/pdfs-gui/src/app/pages/albums.rs:206 #: crates/pdfs-gui/src/app/pages/browser.rs:840 -#: crates/pdfs-gui/src/app/pages/devices.rs:530 +#: crates/pdfs-gui/src/app/pages/devices.rs:534 #: crates/pdfs-gui/src/app/pages/photos.rs:3190 #: crates/pdfs-gui/src/app/pages/shared.rs:541 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474 @@ -1310,7 +1312,7 @@ msgstr "Openen" #: crates/pdfs-gui/src/app/pages/albums.rs:212 #: crates/pdfs-gui/src/app/pages/browser.rs:882 #: crates/pdfs-gui/src/app/pages/devices.rs:406 -#: crates/pdfs-gui/src/app/pages/devices.rs:444 +#: crates/pdfs-gui/src/app/pages/devices.rs:448 msgid "Rename…" msgstr "Hernoemen…" @@ -1578,7 +1580,7 @@ msgstr "Pictogramgrootte" #. Translators: {n} is how many items are selected: files and folders in My Files, photos here. #: crates/pdfs-gui/src/app/pages/browser.rs:538 #: crates/pdfs-gui/src/app/pages/browser.rs:925 -#: crates/pdfs-gui/src/app/pages/devices.rs:539 +#: crates/pdfs-gui/src/app/pages/devices.rs:543 #: crates/pdfs-gui/src/app/pages/photos.rs:1988 #: crates/pdfs-gui/src/app/pages/shared.rs:565 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:507 @@ -1734,7 +1736,7 @@ msgid "New Folder…" msgstr "Nieuwe map…" #: crates/pdfs-gui/src/app/pages/browser.rs:953 -#: crates/pdfs-gui/src/app/pages/devices.rs:550 +#: crates/pdfs-gui/src/app/pages/devices.rs:554 #: crates/pdfs-gui/src/app/pages/shared.rs:582 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:522 #: crates/pdfs-gui/src/app/pages/trash.rs:242 @@ -1878,7 +1880,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1875 -#: crates/pdfs-gui/src/app/pages/devices.rs:973 +#: crates/pdfs-gui/src/app/pages/devices.rs:977 msgid "New name" msgstr "Nieuwe naam" @@ -1908,6 +1910,7 @@ msgid "Choose the folder to move into." msgstr "Kies de map waarnaar je wilt verplaatsen." #: crates/pdfs-gui/src/app/pages/browser.rs:1969 +#: crates/pdfs-gui/src/app/pages/devices.rs:1120 msgid "Move Here" msgstr "Hierheen verplaatsen" @@ -2137,99 +2140,103 @@ msgstr "Bestanden bekijken" msgid "Restore to This Computer…" msgstr "Terugzetten op deze computer…" -#: crates/pdfs-gui/src/app/pages/devices.rs:451 +#: crates/pdfs-gui/src/app/pages/devices.rs:443 +msgid "Move to This Computer…" +msgstr "Naar deze computer verplaatsen…" + +#: crates/pdfs-gui/src/app/pages/devices.rs:455 msgid "Continue This Backup Here…" msgstr "Deze back-up hier voortzetten…" -#: crates/pdfs-gui/src/app/pages/devices.rs:456 +#: crates/pdfs-gui/src/app/pages/devices.rs:460 #: crates/pdfs-gui/src/app/pages/shared.rs:895 msgid "Remove…" msgstr "Verwijderen…" -#: crates/pdfs-gui/src/app/pages/devices.rs:567 +#: crates/pdfs-gui/src/app/pages/devices.rs:571 #, rust-format msgid "{platform} · last backup {time}" msgstr "{platform} · laatste back-up {time}" #. Translators: {platform} is the device's platform, such as "Linux". -#: crates/pdfs-gui/src/app/pages/devices.rs:571 +#: crates/pdfs-gui/src/app/pages/devices.rs:575 #, rust-format msgid "{platform} · no backups yet" msgstr "{platform} · nog geen back-ups" -#: crates/pdfs-gui/src/app/pages/devices.rs:587 +#: crates/pdfs-gui/src/app/pages/devices.rs:591 msgid "No folders backed up yet" msgstr "Nog geen mappen geback-upt" -#: crates/pdfs-gui/src/app/pages/devices.rs:590 +#: crates/pdfs-gui/src/app/pages/devices.rs:594 #, rust-format msgid "{n} folder backed up" msgid_plural "{n} folders backed up" msgstr[0] "{n} map in back-up" msgstr[1] "{n} mappen in back-up" -#: crates/pdfs-gui/src/app/pages/devices.rs:603 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 #, rust-format msgid "{folders} · {n} needs attention" msgid_plural "{folders} · {n} need attention" msgstr[0] "{folders} · {n} vereist aandacht" msgstr[1] "{folders} · {n} vereisen aandacht" -#: crates/pdfs-gui/src/app/pages/devices.rs:614 +#: crates/pdfs-gui/src/app/pages/devices.rs:618 msgid "syncing…" msgstr "synchroniseren…" -#: crates/pdfs-gui/src/app/pages/devices.rs:615 +#: crates/pdfs-gui/src/app/pages/devices.rs:619 msgid "sync error" msgstr "synchronisatiefout" -#: crates/pdfs-gui/src/app/pages/devices.rs:616 +#: crates/pdfs-gui/src/app/pages/devices.rs:620 msgid "needs attention" msgstr "vereist aandacht" -#: crates/pdfs-gui/src/app/pages/devices.rs:617 +#: crates/pdfs-gui/src/app/pages/devices.rs:621 msgid "up to date" msgstr "bijgewerkt" -#: crates/pdfs-gui/src/app/pages/devices.rs:629 +#: crates/pdfs-gui/src/app/pages/devices.rs:633 msgid "checking for changes…" msgstr "controleren op wijzigingen…" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:636 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "controleren op wijzigingen — {done} van {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:644 +#: crates/pdfs-gui/src/app/pages/devices.rs:648 #, rust-format msgid "syncing {done} of {total}" msgstr "synchroniseren: {done} van {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:650 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "{file} synchroniseren — {done} van {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:662 +#: crates/pdfs-gui/src/app/pages/devices.rs:666 msgid "Add Folder to Sync" msgstr "Map toevoegen aan synchronisatie" -#: crates/pdfs-gui/src/app/pages/devices.rs:686 +#: crates/pdfs-gui/src/app/pages/devices.rs:690 #: crates/pdfs-gui/src/app/pages/locations.rs:1593 msgid "Syncing folder…" msgstr "Map synchroniseren…" -#: crates/pdfs-gui/src/app/pages/devices.rs:689 #: crates/pdfs-gui/src/app/pages/devices.rs:693 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Couldn't add folder" msgstr "Kon map niet toevoegen" -#: crates/pdfs-gui/src/app/pages/devices.rs:727 -#: crates/pdfs-gui/src/app/pages/devices.rs:733 +#: crates/pdfs-gui/src/app/pages/devices.rs:731 +#: crates/pdfs-gui/src/app/pages/devices.rs:737 msgid "Couldn't list folders to restore" msgstr "Kon mappen om terug te zetten niet weergeven" -#: crates/pdfs-gui/src/app/pages/devices.rs:759 +#: crates/pdfs-gui/src/app/pages/devices.rs:763 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2237,19 +2244,19 @@ msgstr "" "Vink de mappen aan die je met dit apparaat wilt synchroniseren en pas aan " "waar elke map moet komen." -#: crates/pdfs-gui/src/app/pages/devices.rs:782 +#: crates/pdfs-gui/src/app/pages/devices.rs:786 msgid "Restore Folders" msgstr "Mappen herstellen" -#: crates/pdfs-gui/src/app/pages/devices.rs:789 +#: crates/pdfs-gui/src/app/pages/devices.rs:793 msgid "Restore" msgstr "Terugzetten" -#: crates/pdfs-gui/src/app/pages/devices.rs:822 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 msgid "Restoring folders…" msgstr "Mappen terugzetten…" -#: crates/pdfs-gui/src/app/pages/devices.rs:834 +#: crates/pdfs-gui/src/app/pages/devices.rs:838 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2258,38 +2265,38 @@ msgstr "" "Van deze mappen is een back-up gemaakt vanaf “{name}”. Teruggezette mappen " "blijven synchroniseren met de kopie van die computer." -#: crates/pdfs-gui/src/app/pages/devices.rs:837 +#: crates/pdfs-gui/src/app/pages/devices.rs:841 msgid "These folders are backed up under this computer in Proton Drive." msgstr "" "Van deze mappen is een back-up gemaakt onder deze computer in Proton Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:846 +#: crates/pdfs-gui/src/app/pages/devices.rs:850 #, rust-format msgid "Nothing to restore — “{name}”'s folders are all synced here." msgstr "" "Niets om terug te zetten — de mappen van “{name}” worden hier al allemaal " "gesynchroniseerd." -#: crates/pdfs-gui/src/app/pages/devices.rs:849 +#: crates/pdfs-gui/src/app/pages/devices.rs:853 msgid "Nothing to restore — this computer's folders are all synced here." msgstr "" "Niets om terug te zetten — de mappen van deze computer worden hier al " "allemaal gesynchroniseerd." -#: crates/pdfs-gui/src/app/pages/devices.rs:873 +#: crates/pdfs-gui/src/app/pages/devices.rs:877 msgid "Folder will switch to online only" msgstr "Map wordt alleen online" -#: crates/pdfs-gui/src/app/pages/devices.rs:875 +#: crates/pdfs-gui/src/app/pages/devices.rs:879 msgid "Folder will download and stay synced" msgstr "Map wordt gedownload en blijft gesynchroniseerd" -#: crates/pdfs-gui/src/app/pages/devices.rs:879 #: crates/pdfs-gui/src/app/pages/devices.rs:883 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 msgid "Couldn't change mode" msgstr "Kon modus niet wijzigen" -#: crates/pdfs-gui/src/app/pages/devices.rs:915 +#: crates/pdfs-gui/src/app/pages/devices.rs:919 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2306,7 +2313,7 @@ msgstr "" "kopie houden, annuleer dan, zet de map op Gesynchroniseerd en wacht tot de " "download klaar is voordat je hem verwijdert." -#: crates/pdfs-gui/src/app/pages/devices.rs:921 +#: crates/pdfs-gui/src/app/pages/devices.rs:925 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2319,68 +2326,68 @@ msgstr "" "De lokale bestanden blijven op deze schijf en worden gewoon niet meer " "gesynchroniseerd. Kies of je ook de kopie in Proton Drive wilt verwijderen." -#: crates/pdfs-gui/src/app/pages/devices.rs:926 +#: crates/pdfs-gui/src/app/pages/devices.rs:930 msgid "Stop Syncing Folder?" msgstr "Synchroniseren van map stoppen?" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:935 msgid "Also delete from Proton Drive" msgstr "Ook uit Proton Drive verwijderen" -#: crates/pdfs-gui/src/app/pages/devices.rs:933 +#: crates/pdfs-gui/src/app/pages/devices.rs:937 msgid "Deletes the only copy of these files." msgstr "Verwijdert de enige kopie van deze bestanden." -#: crates/pdfs-gui/src/app/pages/devices.rs:935 +#: crates/pdfs-gui/src/app/pages/devices.rs:939 msgid "The local copy is unaffected." msgstr "De lokale kopie blijft ongewijzigd." -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:946 msgid "Stop Syncing" msgstr "Synchroniseren stoppen" -#: crates/pdfs-gui/src/app/pages/devices.rs:955 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 msgid "Stopped syncing folder" msgstr "Synchroniseren van map gestopt" -#: crates/pdfs-gui/src/app/pages/devices.rs:956 +#: crates/pdfs-gui/src/app/pages/devices.rs:960 msgid "Couldn't stop syncing the folder" msgstr "Kon het synchroniseren van de map niet stoppen" -#: crates/pdfs-gui/src/app/pages/devices.rs:967 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Rename Computer" msgstr "Computer hernoemen" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:969 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 #, rust-format msgid "Rename “{name}”." msgstr "“{name}” hernoemen." -#: crates/pdfs-gui/src/app/pages/devices.rs:994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1006 +#: crates/pdfs-gui/src/app/pages/devices.rs:998 +#: crates/pdfs-gui/src/app/pages/devices.rs:1010 msgid "Couldn't rename the computer" msgstr "Kon de computer niet hernoemen" -#: crates/pdfs-gui/src/app/pages/devices.rs:995 +#: crates/pdfs-gui/src/app/pages/devices.rs:999 msgid "A name is required." msgstr "Een naam is vereist." -#: crates/pdfs-gui/src/app/pages/devices.rs:1005 +#: crates/pdfs-gui/src/app/pages/devices.rs:1009 msgid "Computer renamed" msgstr "Computer hernoemd" #. Translators: {name} is the computer's name, which the user must type. -#: crates/pdfs-gui/src/app/pages/devices.rs:1018 +#: crates/pdfs-gui/src/app/pages/devices.rs:1022 #, rust-format msgid "Type “{name}” to confirm" msgstr "Typ “{name}” om te bevestigen" -#: crates/pdfs-gui/src/app/pages/devices.rs:1023 +#: crates/pdfs-gui/src/app/pages/devices.rs:1027 msgid "Remove Computer?" msgstr "Computer verwijderen?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1026 +#: crates/pdfs-gui/src/app/pages/devices.rs:1030 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2395,25 +2402,25 @@ msgstr "" "mee verwijderd. De bestanden op die computer zelf blijven onaangeroerd — " "maar dit kan hier niet ongedaan worden gemaakt." -#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1036 #: crates/pdfs-gui/src/app/pages/shared.rs:1028 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:529 msgid "Remove" msgstr "Verwijderen" -#: crates/pdfs-gui/src/app/pages/devices.rs:1049 +#: crates/pdfs-gui/src/app/pages/devices.rs:1053 msgid "Computer removed" msgstr "Computer verwijderd" -#: crates/pdfs-gui/src/app/pages/devices.rs:1050 +#: crates/pdfs-gui/src/app/pages/devices.rs:1054 msgid "Couldn't remove the computer" msgstr "Kon de computer niet verwijderen" -#: crates/pdfs-gui/src/app/pages/devices.rs:1068 +#: crates/pdfs-gui/src/app/pages/devices.rs:1072 msgid "Continue This Backup Here?" msgstr "Deze back-up hier voortzetten?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1071 +#: crates/pdfs-gui/src/app/pages/devices.rs:1075 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2432,20 +2439,49 @@ msgstr "" "gesynchroniseerd, worden niet verplaatst. Gebruik daarna “Terugzetten op " "deze computer” om terug te halen wat “{name}” synchroniseerde." -#: crates/pdfs-gui/src/app/pages/devices.rs:1076 +#: crates/pdfs-gui/src/app/pages/devices.rs:1080 msgid "Continue Here" msgstr "Hier voortzetten" #. Translators: {name} is the name of the computer whose backup this one took over. -#: crates/pdfs-gui/src/app/pages/devices.rs:1083 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 #, rust-format msgid "Now backing up as “{name}”" msgstr "Back-up nu als “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1096 msgid "Couldn't continue that backup here" msgstr "Kon die back-up hier niet voortzetten" +#: crates/pdfs-gui/src/app/pages/devices.rs:1112 +msgid "Move Backup to This Computer?" +msgstr "Back-up naar deze computer verplaatsen?" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1115 +#, rust-format +msgid "" +"The folders backed up by “{name}” move into this computer's backup in Proton " +"Drive, and you choose where they go on this computer. “{name}” stays in your " +"account, without those folders.\n" +"\n" +"Stop Proton Drive on “{name}” first. Otherwise it sees its folders disappear." +msgstr "" +"De mappen waarvan “{name}” een back-up maakt, gaan naar de back-up van deze " +"computer in Proton Drive, en je kiest zelf waar ze op deze computer komen. " +"“{name}” blijft in je account, zonder die mappen.\n" +"\n" +"Stop Proton Drive eerst op “{name}”. Anders ziet het zijn mappen verdwijnen." + +#. Translators: {name} is the name of the computer whose folders moved to this one. +#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#, rust-format +msgid "Moved the backup of “{name}”" +msgstr "Back-up van “{name}” verplaatst" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1141 +msgid "Couldn't move that backup" +msgstr "Kon die back-up niet verplaatsen" + #: crates/pdfs-gui/src/app/pages/duplicates.rs:81 msgctxt "duplicates" msgid "Exact" diff --git a/po/pdfs.pot b/po/pdfs.pot index 19bb4c5..30067a0 100644 --- a/po/pdfs.pot +++ b/po/pdfs.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 3.1.2\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 10:25+0200\n" +"POT-Creation-Date: 2026-10-08 13:23+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -93,7 +93,7 @@ msgstr "" #: crates/pdfs-gui/src/app/main.rs:776 #: crates/pdfs-gui/src/app/pages/devices.rs:140 -#: crates/pdfs-gui/src/app/pages/devices.rs:481 +#: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "" @@ -239,11 +239,12 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1892 #: crates/pdfs-gui/src/app/pages/browser.rs:1968 #: crates/pdfs-gui/src/app/pages/browser.rs:2381 -#: crates/pdfs-gui/src/app/pages/devices.rs:788 -#: crates/pdfs-gui/src/app/pages/devices.rs:941 -#: crates/pdfs-gui/src/app/pages/devices.rs:979 -#: crates/pdfs-gui/src/app/pages/devices.rs:1031 -#: crates/pdfs-gui/src/app/pages/devices.rs:1075 +#: crates/pdfs-gui/src/app/pages/devices.rs:792 +#: crates/pdfs-gui/src/app/pages/devices.rs:945 +#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1035 +#: crates/pdfs-gui/src/app/pages/devices.rs:1079 +#: crates/pdfs-gui/src/app/pages/devices.rs:1119 #: crates/pdfs-gui/src/app/pages/locations.rs:809 #: crates/pdfs-gui/src/app/pages/locations.rs:1013 #: crates/pdfs-gui/src/app/pages/locations.rs:1488 @@ -289,7 +290,7 @@ msgstr "" #: crates/pdfs-gui/src/app/main.rs:1388 #: crates/pdfs-gui/src/app/pages/browser.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:554 +#: crates/pdfs-gui/src/app/pages/devices.rs:558 #: crates/pdfs-gui/src/app/pages/shared.rs:586 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:526 #: crates/pdfs-gui/src/app/pages/trash.rs:246 @@ -331,7 +332,7 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1871 #: crates/pdfs-gui/src/app/pages/browser.rs:1893 -#: crates/pdfs-gui/src/app/pages/devices.rs:980 +#: crates/pdfs-gui/src/app/pages/devices.rs:984 #: crates/pdfs-gui/src/app/widgets/details.rs:159 msgid "Rename" msgstr "" @@ -653,7 +654,7 @@ msgid "Couldn't add a sync folder" msgstr "" #: crates/pdfs-gui/src/app/pages/activity_text.rs:58 -#: crates/pdfs-gui/src/app/pages/devices.rs:823 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Couldn't restore folders" msgstr "" @@ -1219,10 +1220,11 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1848 #: crates/pdfs-gui/src/app/pages/browser.rs:2315 #: crates/pdfs-gui/src/app/pages/browser.rs:2491 -#: crates/pdfs-gui/src/app/pages/devices.rs:694 -#: crates/pdfs-gui/src/app/pages/devices.rs:734 -#: crates/pdfs-gui/src/app/pages/devices.rs:884 -#: crates/pdfs-gui/src/app/pages/devices.rs:1120 +#: crates/pdfs-gui/src/app/pages/devices.rs:698 +#: crates/pdfs-gui/src/app/pages/devices.rs:738 +#: crates/pdfs-gui/src/app/pages/devices.rs:888 +#: crates/pdfs-gui/src/app/pages/devices.rs:1156 +#: crates/pdfs-gui/src/app/pages/devices.rs:1185 #: crates/pdfs-gui/src/app/pages/duplicates.rs:220 #: crates/pdfs-gui/src/app/pages/locations.rs:536 #: crates/pdfs-gui/src/app/pages/locations.rs:783 @@ -1254,7 +1256,7 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/albums.rs:206 #: crates/pdfs-gui/src/app/pages/browser.rs:840 -#: crates/pdfs-gui/src/app/pages/devices.rs:530 +#: crates/pdfs-gui/src/app/pages/devices.rs:534 #: crates/pdfs-gui/src/app/pages/photos.rs:3190 #: crates/pdfs-gui/src/app/pages/shared.rs:541 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474 @@ -1268,7 +1270,7 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/albums.rs:212 #: crates/pdfs-gui/src/app/pages/browser.rs:882 #: crates/pdfs-gui/src/app/pages/devices.rs:406 -#: crates/pdfs-gui/src/app/pages/devices.rs:444 +#: crates/pdfs-gui/src/app/pages/devices.rs:448 msgid "Rename…" msgstr "" @@ -1536,7 +1538,7 @@ msgstr "" #. Translators: {n} is how many items are selected: files and folders in My Files, photos here. #: crates/pdfs-gui/src/app/pages/browser.rs:538 #: crates/pdfs-gui/src/app/pages/browser.rs:925 -#: crates/pdfs-gui/src/app/pages/devices.rs:539 +#: crates/pdfs-gui/src/app/pages/devices.rs:543 #: crates/pdfs-gui/src/app/pages/photos.rs:1988 #: crates/pdfs-gui/src/app/pages/shared.rs:565 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:507 @@ -1690,7 +1692,7 @@ msgid "New Folder…" msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:953 -#: crates/pdfs-gui/src/app/pages/devices.rs:550 +#: crates/pdfs-gui/src/app/pages/devices.rs:554 #: crates/pdfs-gui/src/app/pages/shared.rs:582 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:522 #: crates/pdfs-gui/src/app/pages/trash.rs:242 @@ -1832,7 +1834,7 @@ msgid "{status}. {message}" msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1875 -#: crates/pdfs-gui/src/app/pages/devices.rs:973 +#: crates/pdfs-gui/src/app/pages/devices.rs:977 msgid "New name" msgstr "" @@ -1862,6 +1864,7 @@ msgid "Choose the folder to move into." msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1969 +#: crates/pdfs-gui/src/app/pages/devices.rs:1120 msgid "Move Here" msgstr "" @@ -2089,150 +2092,154 @@ msgstr "" msgid "Restore to This Computer…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:451 +#: crates/pdfs-gui/src/app/pages/devices.rs:443 +msgid "Move to This Computer…" +msgstr "" + +#: crates/pdfs-gui/src/app/pages/devices.rs:455 msgid "Continue This Backup Here…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:456 +#: crates/pdfs-gui/src/app/pages/devices.rs:460 #: crates/pdfs-gui/src/app/pages/shared.rs:895 msgid "Remove…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:567 +#: crates/pdfs-gui/src/app/pages/devices.rs:571 #, rust-format msgid "{platform} · last backup {time}" msgstr "" #. Translators: {platform} is the device's platform, such as "Linux". -#: crates/pdfs-gui/src/app/pages/devices.rs:571 +#: crates/pdfs-gui/src/app/pages/devices.rs:575 #, rust-format msgid "{platform} · no backups yet" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:587 +#: crates/pdfs-gui/src/app/pages/devices.rs:591 msgid "No folders backed up yet" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:590 +#: crates/pdfs-gui/src/app/pages/devices.rs:594 #, rust-format msgid "{n} folder backed up" msgid_plural "{n} folders backed up" msgstr[0] "" msgstr[1] "" -#: crates/pdfs-gui/src/app/pages/devices.rs:603 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 #, rust-format msgid "{folders} · {n} needs attention" msgid_plural "{folders} · {n} need attention" msgstr[0] "" msgstr[1] "" -#: crates/pdfs-gui/src/app/pages/devices.rs:614 +#: crates/pdfs-gui/src/app/pages/devices.rs:618 msgid "syncing…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:615 +#: crates/pdfs-gui/src/app/pages/devices.rs:619 msgid "sync error" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:616 +#: crates/pdfs-gui/src/app/pages/devices.rs:620 msgid "needs attention" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:617 +#: crates/pdfs-gui/src/app/pages/devices.rs:621 msgid "up to date" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:629 +#: crates/pdfs-gui/src/app/pages/devices.rs:633 msgid "checking for changes…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:636 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:644 +#: crates/pdfs-gui/src/app/pages/devices.rs:648 #, rust-format msgid "syncing {done} of {total}" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:650 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:662 +#: crates/pdfs-gui/src/app/pages/devices.rs:666 msgid "Add Folder to Sync" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:686 +#: crates/pdfs-gui/src/app/pages/devices.rs:690 #: crates/pdfs-gui/src/app/pages/locations.rs:1593 msgid "Syncing folder…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:689 #: crates/pdfs-gui/src/app/pages/devices.rs:693 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Couldn't add folder" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:727 -#: crates/pdfs-gui/src/app/pages/devices.rs:733 +#: crates/pdfs-gui/src/app/pages/devices.rs:731 +#: crates/pdfs-gui/src/app/pages/devices.rs:737 msgid "Couldn't list folders to restore" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:759 +#: crates/pdfs-gui/src/app/pages/devices.rs:763 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:782 +#: crates/pdfs-gui/src/app/pages/devices.rs:786 msgid "Restore Folders" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:789 +#: crates/pdfs-gui/src/app/pages/devices.rs:793 msgid "Restore" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:822 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 msgid "Restoring folders…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:834 +#: crates/pdfs-gui/src/app/pages/devices.rs:838 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " "with that computer's copy." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:837 +#: crates/pdfs-gui/src/app/pages/devices.rs:841 msgid "These folders are backed up under this computer in Proton Drive." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:846 +#: crates/pdfs-gui/src/app/pages/devices.rs:850 #, rust-format msgid "Nothing to restore — “{name}”'s folders are all synced here." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:849 +#: crates/pdfs-gui/src/app/pages/devices.rs:853 msgid "Nothing to restore — this computer's folders are all synced here." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:873 +#: crates/pdfs-gui/src/app/pages/devices.rs:877 msgid "Folder will switch to online only" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:875 +#: crates/pdfs-gui/src/app/pages/devices.rs:879 msgid "Folder will download and stay synced" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:879 #: crates/pdfs-gui/src/app/pages/devices.rs:883 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 msgid "Couldn't change mode" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:915 +#: crates/pdfs-gui/src/app/pages/devices.rs:919 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2243,7 +2250,7 @@ msgid "" "finish before removing it." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:921 +#: crates/pdfs-gui/src/app/pages/devices.rs:925 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2252,68 +2259,68 @@ msgid "" "whether to also delete the copy in Proton Drive." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:926 +#: crates/pdfs-gui/src/app/pages/devices.rs:930 msgid "Stop Syncing Folder?" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:935 msgid "Also delete from Proton Drive" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:933 +#: crates/pdfs-gui/src/app/pages/devices.rs:937 msgid "Deletes the only copy of these files." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:935 +#: crates/pdfs-gui/src/app/pages/devices.rs:939 msgid "The local copy is unaffected." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:946 msgid "Stop Syncing" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:955 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 msgid "Stopped syncing folder" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:956 +#: crates/pdfs-gui/src/app/pages/devices.rs:960 msgid "Couldn't stop syncing the folder" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:967 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Rename Computer" msgstr "" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:969 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 #, rust-format msgid "Rename “{name}”." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1006 +#: crates/pdfs-gui/src/app/pages/devices.rs:998 +#: crates/pdfs-gui/src/app/pages/devices.rs:1010 msgid "Couldn't rename the computer" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:995 +#: crates/pdfs-gui/src/app/pages/devices.rs:999 msgid "A name is required." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1005 +#: crates/pdfs-gui/src/app/pages/devices.rs:1009 msgid "Computer renamed" msgstr "" #. Translators: {name} is the computer's name, which the user must type. -#: crates/pdfs-gui/src/app/pages/devices.rs:1018 +#: crates/pdfs-gui/src/app/pages/devices.rs:1022 #, rust-format msgid "Type “{name}” to confirm" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1023 +#: crates/pdfs-gui/src/app/pages/devices.rs:1027 msgid "Remove Computer?" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1026 +#: crates/pdfs-gui/src/app/pages/devices.rs:1030 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2323,25 +2330,25 @@ msgid "" "here." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1036 #: crates/pdfs-gui/src/app/pages/shared.rs:1028 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:529 msgid "Remove" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1049 +#: crates/pdfs-gui/src/app/pages/devices.rs:1053 msgid "Computer removed" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1050 +#: crates/pdfs-gui/src/app/pages/devices.rs:1054 msgid "Couldn't remove the computer" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1068 +#: crates/pdfs-gui/src/app/pages/devices.rs:1072 msgid "Continue This Backup Here?" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1071 +#: crates/pdfs-gui/src/app/pages/devices.rs:1075 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2353,20 +2360,44 @@ msgid "" "syncing." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1076 +#: crates/pdfs-gui/src/app/pages/devices.rs:1080 msgid "Continue Here" msgstr "" #. Translators: {name} is the name of the computer whose backup this one took over. -#: crates/pdfs-gui/src/app/pages/devices.rs:1083 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 #, rust-format msgid "Now backing up as “{name}”" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1096 msgid "Couldn't continue that backup here" msgstr "" +#: crates/pdfs-gui/src/app/pages/devices.rs:1112 +msgid "Move Backup to This Computer?" +msgstr "" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1115 +#, rust-format +msgid "" +"The folders backed up by “{name}” move into this computer's backup in Proton " +"Drive, and you choose where they go on this computer. “{name}” stays in your " +"account, without those folders.\n" +"\n" +"Stop Proton Drive on “{name}” first. Otherwise it sees its folders disappear." +msgstr "" + +#. Translators: {name} is the name of the computer whose folders moved to this one. +#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#, rust-format +msgid "Moved the backup of “{name}”" +msgstr "" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1141 +msgid "Couldn't move that backup" +msgstr "" + #: crates/pdfs-gui/src/app/pages/duplicates.rs:81 msgctxt "duplicates" msgid "Exact" diff --git a/po/pl.po b/po/pl.po index 426eeef..bb72796 100644 --- a/po/pl.po +++ b/po/pl.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 2.1.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 10:15+0200\n" +"POT-Creation-Date: 2026-10-08 13:23+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: Polish\n" @@ -92,7 +92,7 @@ msgstr "Udostępnione przeze mnie" #: crates/pdfs-gui/src/app/main.rs:776 #: crates/pdfs-gui/src/app/pages/devices.rs:140 -#: crates/pdfs-gui/src/app/pages/devices.rs:481 +#: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "Komputery" @@ -267,11 +267,12 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1892 #: crates/pdfs-gui/src/app/pages/browser.rs:1968 #: crates/pdfs-gui/src/app/pages/browser.rs:2381 -#: crates/pdfs-gui/src/app/pages/devices.rs:788 -#: crates/pdfs-gui/src/app/pages/devices.rs:941 -#: crates/pdfs-gui/src/app/pages/devices.rs:979 -#: crates/pdfs-gui/src/app/pages/devices.rs:1031 -#: crates/pdfs-gui/src/app/pages/devices.rs:1075 +#: crates/pdfs-gui/src/app/pages/devices.rs:792 +#: crates/pdfs-gui/src/app/pages/devices.rs:945 +#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1035 +#: crates/pdfs-gui/src/app/pages/devices.rs:1079 +#: crates/pdfs-gui/src/app/pages/devices.rs:1119 #: crates/pdfs-gui/src/app/pages/locations.rs:809 #: crates/pdfs-gui/src/app/pages/locations.rs:1013 #: crates/pdfs-gui/src/app/pages/locations.rs:1488 @@ -317,7 +318,7 @@ msgstr "Przeszukaj Drive" #: crates/pdfs-gui/src/app/main.rs:1388 #: crates/pdfs-gui/src/app/pages/browser.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:554 +#: crates/pdfs-gui/src/app/pages/devices.rs:558 #: crates/pdfs-gui/src/app/pages/shared.rs:586 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:526 #: crates/pdfs-gui/src/app/pages/trash.rs:246 @@ -359,7 +360,7 @@ msgstr "Siatka/lista" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1871 #: crates/pdfs-gui/src/app/pages/browser.rs:1893 -#: crates/pdfs-gui/src/app/pages/devices.rs:980 +#: crates/pdfs-gui/src/app/pages/devices.rs:984 #: crates/pdfs-gui/src/app/widgets/details.rs:159 msgid "Rename" msgstr "Zmień nazwę" @@ -700,7 +701,7 @@ msgid "Couldn't add a sync folder" msgstr "Nie można dodać katalogu synchronizacji" #: crates/pdfs-gui/src/app/pages/activity_text.rs:58 -#: crates/pdfs-gui/src/app/pages/devices.rs:823 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Couldn't restore folders" msgstr "Nie można przywrócić katalogów" @@ -1292,10 +1293,11 @@ msgstr "Nie można wczytać albumów" #: crates/pdfs-gui/src/app/pages/browser.rs:1848 #: crates/pdfs-gui/src/app/pages/browser.rs:2315 #: crates/pdfs-gui/src/app/pages/browser.rs:2491 -#: crates/pdfs-gui/src/app/pages/devices.rs:694 -#: crates/pdfs-gui/src/app/pages/devices.rs:734 -#: crates/pdfs-gui/src/app/pages/devices.rs:884 -#: crates/pdfs-gui/src/app/pages/devices.rs:1120 +#: crates/pdfs-gui/src/app/pages/devices.rs:698 +#: crates/pdfs-gui/src/app/pages/devices.rs:738 +#: crates/pdfs-gui/src/app/pages/devices.rs:888 +#: crates/pdfs-gui/src/app/pages/devices.rs:1156 +#: crates/pdfs-gui/src/app/pages/devices.rs:1185 #: crates/pdfs-gui/src/app/pages/duplicates.rs:220 #: crates/pdfs-gui/src/app/pages/locations.rs:536 #: crates/pdfs-gui/src/app/pages/locations.rs:783 @@ -1327,7 +1329,7 @@ msgstr "Usługa Proton Drive nie odpowiedziała." #: crates/pdfs-gui/src/app/pages/albums.rs:206 #: crates/pdfs-gui/src/app/pages/browser.rs:840 -#: crates/pdfs-gui/src/app/pages/devices.rs:530 +#: crates/pdfs-gui/src/app/pages/devices.rs:534 #: crates/pdfs-gui/src/app/pages/photos.rs:3190 #: crates/pdfs-gui/src/app/pages/shared.rs:541 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474 @@ -1341,7 +1343,7 @@ msgstr "Otwórz" #: crates/pdfs-gui/src/app/pages/albums.rs:212 #: crates/pdfs-gui/src/app/pages/browser.rs:882 #: crates/pdfs-gui/src/app/pages/devices.rs:406 -#: crates/pdfs-gui/src/app/pages/devices.rs:444 +#: crates/pdfs-gui/src/app/pages/devices.rs:448 msgid "Rename…" msgstr "Zmień nazwę…" @@ -1615,7 +1617,7 @@ msgstr "Rozmiar ikon" #. Translators: {n} is how many items are selected: files and folders in My Files, photos here. #: crates/pdfs-gui/src/app/pages/browser.rs:538 #: crates/pdfs-gui/src/app/pages/browser.rs:925 -#: crates/pdfs-gui/src/app/pages/devices.rs:539 +#: crates/pdfs-gui/src/app/pages/devices.rs:543 #: crates/pdfs-gui/src/app/pages/photos.rs:1988 #: crates/pdfs-gui/src/app/pages/shared.rs:565 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:507 @@ -1778,7 +1780,7 @@ msgid "New Folder…" msgstr "Nowy katalog…" #: crates/pdfs-gui/src/app/pages/browser.rs:953 -#: crates/pdfs-gui/src/app/pages/devices.rs:550 +#: crates/pdfs-gui/src/app/pages/devices.rs:554 #: crates/pdfs-gui/src/app/pages/shared.rs:582 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:522 #: crates/pdfs-gui/src/app/pages/trash.rs:242 @@ -1924,7 +1926,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1875 -#: crates/pdfs-gui/src/app/pages/devices.rs:973 +#: crates/pdfs-gui/src/app/pages/devices.rs:977 msgid "New name" msgstr "Nowa nazwa" @@ -1955,6 +1957,7 @@ msgid "Choose the folder to move into." msgstr "Proszę wybrać katalog docelowy." #: crates/pdfs-gui/src/app/pages/browser.rs:1969 +#: crates/pdfs-gui/src/app/pages/devices.rs:1120 msgid "Move Here" msgstr "Przenieś tutaj" @@ -2189,31 +2192,35 @@ msgstr "Przeglądaj pliki" msgid "Restore to This Computer…" msgstr "Przywróć na tym komputerze…" -#: crates/pdfs-gui/src/app/pages/devices.rs:451 +#: crates/pdfs-gui/src/app/pages/devices.rs:443 +msgid "Move to This Computer…" +msgstr "Przenieś na ten komputer…" + +#: crates/pdfs-gui/src/app/pages/devices.rs:455 msgid "Continue This Backup Here…" msgstr "Kontynuuj tę kopię zapasową tutaj…" -#: crates/pdfs-gui/src/app/pages/devices.rs:456 +#: crates/pdfs-gui/src/app/pages/devices.rs:460 #: crates/pdfs-gui/src/app/pages/shared.rs:895 msgid "Remove…" msgstr "Usuń…" -#: crates/pdfs-gui/src/app/pages/devices.rs:567 +#: crates/pdfs-gui/src/app/pages/devices.rs:571 #, rust-format msgid "{platform} · last backup {time}" msgstr "{platform} · ostatnia kopia zapasowa: {time}" #. Translators: {platform} is the device's platform, such as "Linux". -#: crates/pdfs-gui/src/app/pages/devices.rs:571 +#: crates/pdfs-gui/src/app/pages/devices.rs:575 #, rust-format msgid "{platform} · no backups yet" msgstr "{platform} · brak kopii zapasowych" -#: crates/pdfs-gui/src/app/pages/devices.rs:587 +#: crates/pdfs-gui/src/app/pages/devices.rs:591 msgid "No folders backed up yet" msgstr "Brak zarchiwizowanych katalogów" -#: crates/pdfs-gui/src/app/pages/devices.rs:590 +#: crates/pdfs-gui/src/app/pages/devices.rs:594 #, rust-format msgid "{n} folder backed up" msgid_plural "{n} folders backed up" @@ -2221,7 +2228,7 @@ msgstr[0] "{n} katalog z kopią zapasową" msgstr[1] "{n} katalogi z kopią zapasową" msgstr[2] "{n} katalogów z kopią zapasową" -#: crates/pdfs-gui/src/app/pages/devices.rs:603 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 #, rust-format msgid "{folders} · {n} needs attention" msgid_plural "{folders} · {n} need attention" @@ -2229,61 +2236,61 @@ msgstr[0] "{folders} · {n} wymaga uwagi" msgstr[1] "{folders} · {n} wymagają uwagi" msgstr[2] "{folders} · {n} wymaga uwagi" -#: crates/pdfs-gui/src/app/pages/devices.rs:614 +#: crates/pdfs-gui/src/app/pages/devices.rs:618 msgid "syncing…" msgstr "synchronizowanie…" -#: crates/pdfs-gui/src/app/pages/devices.rs:615 +#: crates/pdfs-gui/src/app/pages/devices.rs:619 msgid "sync error" msgstr "błąd synchronizacji" -#: crates/pdfs-gui/src/app/pages/devices.rs:616 +#: crates/pdfs-gui/src/app/pages/devices.rs:620 msgid "needs attention" msgstr "wymaga uwagi" -#: crates/pdfs-gui/src/app/pages/devices.rs:617 +#: crates/pdfs-gui/src/app/pages/devices.rs:621 msgid "up to date" msgstr "aktualne" -#: crates/pdfs-gui/src/app/pages/devices.rs:629 +#: crates/pdfs-gui/src/app/pages/devices.rs:633 msgid "checking for changes…" msgstr "sprawdzanie zmian…" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:636 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "sprawdzanie zmian — {done} z {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:644 +#: crates/pdfs-gui/src/app/pages/devices.rs:648 #, rust-format msgid "syncing {done} of {total}" msgstr "synchronizowanie {done} z {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:650 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "synchronizowanie {file} — {done} z {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:662 +#: crates/pdfs-gui/src/app/pages/devices.rs:666 msgid "Add Folder to Sync" msgstr "Dodaj katalog do synchronizacji" -#: crates/pdfs-gui/src/app/pages/devices.rs:686 +#: crates/pdfs-gui/src/app/pages/devices.rs:690 #: crates/pdfs-gui/src/app/pages/locations.rs:1593 msgid "Syncing folder…" msgstr "Synchronizowanie katalogu…" -#: crates/pdfs-gui/src/app/pages/devices.rs:689 #: crates/pdfs-gui/src/app/pages/devices.rs:693 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Couldn't add folder" msgstr "Nie można dodać katalogu" -#: crates/pdfs-gui/src/app/pages/devices.rs:727 -#: crates/pdfs-gui/src/app/pages/devices.rs:733 +#: crates/pdfs-gui/src/app/pages/devices.rs:731 +#: crates/pdfs-gui/src/app/pages/devices.rs:737 msgid "Couldn't list folders to restore" msgstr "Nie można wyświetlić listy katalogów do przywrócenia" -#: crates/pdfs-gui/src/app/pages/devices.rs:759 +#: crates/pdfs-gui/src/app/pages/devices.rs:763 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2291,19 +2298,19 @@ msgstr "" "Należy zaznaczyć katalogi do synchronizacji z tym komputerem i dostosować " "położenie każdego z nich." -#: crates/pdfs-gui/src/app/pages/devices.rs:782 +#: crates/pdfs-gui/src/app/pages/devices.rs:786 msgid "Restore Folders" msgstr "Przywróć katalogi" -#: crates/pdfs-gui/src/app/pages/devices.rs:789 +#: crates/pdfs-gui/src/app/pages/devices.rs:793 msgid "Restore" msgstr "Przywróć" -#: crates/pdfs-gui/src/app/pages/devices.rs:822 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 msgid "Restoring folders…" msgstr "Przywracanie katalogów…" -#: crates/pdfs-gui/src/app/pages/devices.rs:834 +#: crates/pdfs-gui/src/app/pages/devices.rs:838 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2312,37 +2319,37 @@ msgstr "" "Te katalogi mają kopię zapasową z komputera „{name}”. Przywrócone katalogi " "nadal będą synchronizowane z kopią tego komputera." -#: crates/pdfs-gui/src/app/pages/devices.rs:837 +#: crates/pdfs-gui/src/app/pages/devices.rs:841 msgid "These folders are backed up under this computer in Proton Drive." msgstr "Te katalogi mają kopię zapasową w Proton Drive pod tym komputerem." -#: crates/pdfs-gui/src/app/pages/devices.rs:846 +#: crates/pdfs-gui/src/app/pages/devices.rs:850 #, rust-format msgid "Nothing to restore — “{name}”'s folders are all synced here." msgstr "" "Nie ma nic do przywrócenia — wszystkie katalogi komputera „{name}” są tutaj " "synchronizowane." -#: crates/pdfs-gui/src/app/pages/devices.rs:849 +#: crates/pdfs-gui/src/app/pages/devices.rs:853 msgid "Nothing to restore — this computer's folders are all synced here." msgstr "" "Nie ma nic do przywrócenia — wszystkie katalogi tego komputera są tutaj " "synchronizowane." -#: crates/pdfs-gui/src/app/pages/devices.rs:873 +#: crates/pdfs-gui/src/app/pages/devices.rs:877 msgid "Folder will switch to online only" msgstr "Katalog przejdzie w tryb tylko online" -#: crates/pdfs-gui/src/app/pages/devices.rs:875 +#: crates/pdfs-gui/src/app/pages/devices.rs:879 msgid "Folder will download and stay synced" msgstr "Katalog zostanie pobrany i będzie synchronizowany" -#: crates/pdfs-gui/src/app/pages/devices.rs:879 #: crates/pdfs-gui/src/app/pages/devices.rs:883 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 msgid "Couldn't change mode" msgstr "Nie można zmienić trybu" -#: crates/pdfs-gui/src/app/pages/devices.rs:915 +#: crates/pdfs-gui/src/app/pages/devices.rs:919 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2359,7 +2366,7 @@ msgstr "" "pusty. Aby zachować lokalną kopię, należy anulować, przełączyć katalog na " "tryb Synchronizowany i poczekać na ukończenie pobierania przed usunięciem." -#: crates/pdfs-gui/src/app/pages/devices.rs:921 +#: crates/pdfs-gui/src/app/pages/devices.rs:925 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2372,68 +2379,68 @@ msgstr "" "Lokalne pliki pozostaną na tym dysku i po prostu przestaną być " "synchronizowane. Można też usunąć kopię w Proton Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:926 +#: crates/pdfs-gui/src/app/pages/devices.rs:930 msgid "Stop Syncing Folder?" msgstr "Zatrzymać synchronizację katalogu?" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:935 msgid "Also delete from Proton Drive" msgstr "Usuń także z Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:933 +#: crates/pdfs-gui/src/app/pages/devices.rs:937 msgid "Deletes the only copy of these files." msgstr "Usuwa jedyną kopię tych plików." -#: crates/pdfs-gui/src/app/pages/devices.rs:935 +#: crates/pdfs-gui/src/app/pages/devices.rs:939 msgid "The local copy is unaffected." msgstr "Lokalna kopia pozostanie bez zmian." -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:946 msgid "Stop Syncing" msgstr "Zatrzymaj synchronizację" -#: crates/pdfs-gui/src/app/pages/devices.rs:955 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 msgid "Stopped syncing folder" msgstr "Zatrzymano synchronizację katalogu" -#: crates/pdfs-gui/src/app/pages/devices.rs:956 +#: crates/pdfs-gui/src/app/pages/devices.rs:960 msgid "Couldn't stop syncing the folder" msgstr "Nie można zatrzymać synchronizacji katalogu" -#: crates/pdfs-gui/src/app/pages/devices.rs:967 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Rename Computer" msgstr "Zmiana nazwy komputera" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:969 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 #, rust-format msgid "Rename “{name}”." msgstr "Proszę podać nową nazwę komputera „{name}”." -#: crates/pdfs-gui/src/app/pages/devices.rs:994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1006 +#: crates/pdfs-gui/src/app/pages/devices.rs:998 +#: crates/pdfs-gui/src/app/pages/devices.rs:1010 msgid "Couldn't rename the computer" msgstr "Nie można zmienić nazwy komputera" -#: crates/pdfs-gui/src/app/pages/devices.rs:995 +#: crates/pdfs-gui/src/app/pages/devices.rs:999 msgid "A name is required." msgstr "Nazwa jest wymagana." -#: crates/pdfs-gui/src/app/pages/devices.rs:1005 +#: crates/pdfs-gui/src/app/pages/devices.rs:1009 msgid "Computer renamed" msgstr "Zmieniono nazwę komputera" #. Translators: {name} is the computer's name, which the user must type. -#: crates/pdfs-gui/src/app/pages/devices.rs:1018 +#: crates/pdfs-gui/src/app/pages/devices.rs:1022 #, rust-format msgid "Type “{name}” to confirm" msgstr "Proszę wpisać „{name}”, aby potwierdzić" -#: crates/pdfs-gui/src/app/pages/devices.rs:1023 +#: crates/pdfs-gui/src/app/pages/devices.rs:1027 msgid "Remove Computer?" msgstr "Usunąć komputer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1026 +#: crates/pdfs-gui/src/app/pages/devices.rs:1030 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2448,25 +2455,25 @@ msgstr "" "z nim. Pliki na samym komputerze pozostaną nietknięte — ale tej operacji nie " "można tutaj cofnąć." -#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1036 #: crates/pdfs-gui/src/app/pages/shared.rs:1028 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:529 msgid "Remove" msgstr "Usuń" -#: crates/pdfs-gui/src/app/pages/devices.rs:1049 +#: crates/pdfs-gui/src/app/pages/devices.rs:1053 msgid "Computer removed" msgstr "Usunięto komputer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1050 +#: crates/pdfs-gui/src/app/pages/devices.rs:1054 msgid "Couldn't remove the computer" msgstr "Nie można usunąć komputera" -#: crates/pdfs-gui/src/app/pages/devices.rs:1068 +#: crates/pdfs-gui/src/app/pages/devices.rs:1072 msgid "Continue This Backup Here?" msgstr "Kontynuować tę kopię zapasową tutaj?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1071 +#: crates/pdfs-gui/src/app/pages/devices.rs:1075 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2485,20 +2492,51 @@ msgstr "" "przeniesione. Aby odzyskać to, co synchronizował „{name}”, proszę potem użyć " "opcji „Przywróć na tym komputerze”." -#: crates/pdfs-gui/src/app/pages/devices.rs:1076 +#: crates/pdfs-gui/src/app/pages/devices.rs:1080 msgid "Continue Here" msgstr "Kontynuuj tutaj" #. Translators: {name} is the name of the computer whose backup this one took over. -#: crates/pdfs-gui/src/app/pages/devices.rs:1083 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 #, rust-format msgid "Now backing up as “{name}”" msgstr "Kopia zapasowa jest teraz tworzona jako „{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1096 msgid "Couldn't continue that backup here" msgstr "Nie można kontynuować tej kopii zapasowej tutaj" +#: crates/pdfs-gui/src/app/pages/devices.rs:1112 +msgid "Move Backup to This Computer?" +msgstr "Przenieść kopię zapasową na ten komputer?" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1115 +#, rust-format +msgid "" +"The folders backed up by “{name}” move into this computer's backup in Proton " +"Drive, and you choose where they go on this computer. “{name}” stays in your " +"account, without those folders.\n" +"\n" +"Stop Proton Drive on “{name}” first. Otherwise it sees its folders disappear." +msgstr "" +"Foldery, których kopię zapasową tworzy „{name}”, zostaną przeniesione do " +"kopii zapasowej tego komputera w Proton Drive, a Ty wybierzesz, gdzie mają " +"się znaleźć na tym komputerze. „{name}” pozostanie na Twoim koncie, bez tych " +"folderów.\n" +"\n" +"Najpierw zamknij Proton Drive na „{name}”. W przeciwnym razie zobaczy, jak " +"jego foldery znikają." + +#. Translators: {name} is the name of the computer whose folders moved to this one. +#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#, rust-format +msgid "Moved the backup of “{name}”" +msgstr "Przeniesiono kopię zapasową „{name}”" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1141 +msgid "Couldn't move that backup" +msgstr "Nie można przenieść tej kopii zapasowej" + #: crates/pdfs-gui/src/app/pages/duplicates.rs:81 msgctxt "duplicates" msgid "Exact" diff --git a/po/pt_BR.po b/po/pt_BR.po index 8730532..94502ad 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 2.1.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 10:15+0200\n" +"POT-Creation-Date: 2026-10-08 13:23+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: Portuguese (Brazil)\n" @@ -90,7 +90,7 @@ msgstr "Compartilhados por mim" #: crates/pdfs-gui/src/app/main.rs:776 #: crates/pdfs-gui/src/app/pages/devices.rs:140 -#: crates/pdfs-gui/src/app/pages/devices.rs:481 +#: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "Computadores" @@ -262,11 +262,12 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1892 #: crates/pdfs-gui/src/app/pages/browser.rs:1968 #: crates/pdfs-gui/src/app/pages/browser.rs:2381 -#: crates/pdfs-gui/src/app/pages/devices.rs:788 -#: crates/pdfs-gui/src/app/pages/devices.rs:941 -#: crates/pdfs-gui/src/app/pages/devices.rs:979 -#: crates/pdfs-gui/src/app/pages/devices.rs:1031 -#: crates/pdfs-gui/src/app/pages/devices.rs:1075 +#: crates/pdfs-gui/src/app/pages/devices.rs:792 +#: crates/pdfs-gui/src/app/pages/devices.rs:945 +#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1035 +#: crates/pdfs-gui/src/app/pages/devices.rs:1079 +#: crates/pdfs-gui/src/app/pages/devices.rs:1119 #: crates/pdfs-gui/src/app/pages/locations.rs:809 #: crates/pdfs-gui/src/app/pages/locations.rs:1013 #: crates/pdfs-gui/src/app/pages/locations.rs:1488 @@ -312,7 +313,7 @@ msgstr "Pesquisar no Drive" #: crates/pdfs-gui/src/app/main.rs:1388 #: crates/pdfs-gui/src/app/pages/browser.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:554 +#: crates/pdfs-gui/src/app/pages/devices.rs:558 #: crates/pdfs-gui/src/app/pages/shared.rs:586 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:526 #: crates/pdfs-gui/src/app/pages/trash.rs:246 @@ -354,7 +355,7 @@ msgstr "Grade / lista" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1871 #: crates/pdfs-gui/src/app/pages/browser.rs:1893 -#: crates/pdfs-gui/src/app/pages/devices.rs:980 +#: crates/pdfs-gui/src/app/pages/devices.rs:984 #: crates/pdfs-gui/src/app/widgets/details.rs:159 msgid "Rename" msgstr "Renomear" @@ -682,7 +683,7 @@ msgid "Couldn't add a sync folder" msgstr "Não foi possível adicionar uma pasta sincronizada" #: crates/pdfs-gui/src/app/pages/activity_text.rs:58 -#: crates/pdfs-gui/src/app/pages/devices.rs:823 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Couldn't restore folders" msgstr "Não foi possível restaurar as pastas" @@ -1262,10 +1263,11 @@ msgstr "Não foi possível carregar os álbuns" #: crates/pdfs-gui/src/app/pages/browser.rs:1848 #: crates/pdfs-gui/src/app/pages/browser.rs:2315 #: crates/pdfs-gui/src/app/pages/browser.rs:2491 -#: crates/pdfs-gui/src/app/pages/devices.rs:694 -#: crates/pdfs-gui/src/app/pages/devices.rs:734 -#: crates/pdfs-gui/src/app/pages/devices.rs:884 -#: crates/pdfs-gui/src/app/pages/devices.rs:1120 +#: crates/pdfs-gui/src/app/pages/devices.rs:698 +#: crates/pdfs-gui/src/app/pages/devices.rs:738 +#: crates/pdfs-gui/src/app/pages/devices.rs:888 +#: crates/pdfs-gui/src/app/pages/devices.rs:1156 +#: crates/pdfs-gui/src/app/pages/devices.rs:1185 #: crates/pdfs-gui/src/app/pages/duplicates.rs:220 #: crates/pdfs-gui/src/app/pages/locations.rs:536 #: crates/pdfs-gui/src/app/pages/locations.rs:783 @@ -1297,7 +1299,7 @@ msgstr "O serviço do Proton Drive não respondeu." #: crates/pdfs-gui/src/app/pages/albums.rs:206 #: crates/pdfs-gui/src/app/pages/browser.rs:840 -#: crates/pdfs-gui/src/app/pages/devices.rs:530 +#: crates/pdfs-gui/src/app/pages/devices.rs:534 #: crates/pdfs-gui/src/app/pages/photos.rs:3190 #: crates/pdfs-gui/src/app/pages/shared.rs:541 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474 @@ -1311,7 +1313,7 @@ msgstr "Abrir" #: crates/pdfs-gui/src/app/pages/albums.rs:212 #: crates/pdfs-gui/src/app/pages/browser.rs:882 #: crates/pdfs-gui/src/app/pages/devices.rs:406 -#: crates/pdfs-gui/src/app/pages/devices.rs:444 +#: crates/pdfs-gui/src/app/pages/devices.rs:448 msgid "Rename…" msgstr "Renomear…" @@ -1579,7 +1581,7 @@ msgstr "Tamanho dos ícones" #. Translators: {n} is how many items are selected: files and folders in My Files, photos here. #: crates/pdfs-gui/src/app/pages/browser.rs:538 #: crates/pdfs-gui/src/app/pages/browser.rs:925 -#: crates/pdfs-gui/src/app/pages/devices.rs:539 +#: crates/pdfs-gui/src/app/pages/devices.rs:543 #: crates/pdfs-gui/src/app/pages/photos.rs:1988 #: crates/pdfs-gui/src/app/pages/shared.rs:565 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:507 @@ -1733,7 +1735,7 @@ msgid "New Folder…" msgstr "Nova pasta…" #: crates/pdfs-gui/src/app/pages/browser.rs:953 -#: crates/pdfs-gui/src/app/pages/devices.rs:550 +#: crates/pdfs-gui/src/app/pages/devices.rs:554 #: crates/pdfs-gui/src/app/pages/shared.rs:582 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:522 #: crates/pdfs-gui/src/app/pages/trash.rs:242 @@ -1878,7 +1880,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1875 -#: crates/pdfs-gui/src/app/pages/devices.rs:973 +#: crates/pdfs-gui/src/app/pages/devices.rs:977 msgid "New name" msgstr "Novo nome" @@ -1908,6 +1910,7 @@ msgid "Choose the folder to move into." msgstr "Escolha a pasta de destino." #: crates/pdfs-gui/src/app/pages/browser.rs:1969 +#: crates/pdfs-gui/src/app/pages/devices.rs:1120 msgid "Move Here" msgstr "Mover para cá" @@ -2136,99 +2139,103 @@ msgstr "Navegar pelos arquivos" msgid "Restore to This Computer…" msgstr "Restaurar neste computador…" -#: crates/pdfs-gui/src/app/pages/devices.rs:451 +#: crates/pdfs-gui/src/app/pages/devices.rs:443 +msgid "Move to This Computer…" +msgstr "Mover para este computador…" + +#: crates/pdfs-gui/src/app/pages/devices.rs:455 msgid "Continue This Backup Here…" msgstr "Continuar este backup aqui…" -#: crates/pdfs-gui/src/app/pages/devices.rs:456 +#: crates/pdfs-gui/src/app/pages/devices.rs:460 #: crates/pdfs-gui/src/app/pages/shared.rs:895 msgid "Remove…" msgstr "Remover…" -#: crates/pdfs-gui/src/app/pages/devices.rs:567 +#: crates/pdfs-gui/src/app/pages/devices.rs:571 #, rust-format msgid "{platform} · last backup {time}" msgstr "{platform} · último backup {time}" #. Translators: {platform} is the device's platform, such as "Linux". -#: crates/pdfs-gui/src/app/pages/devices.rs:571 +#: crates/pdfs-gui/src/app/pages/devices.rs:575 #, rust-format msgid "{platform} · no backups yet" msgstr "{platform} · nenhum backup ainda" -#: crates/pdfs-gui/src/app/pages/devices.rs:587 +#: crates/pdfs-gui/src/app/pages/devices.rs:591 msgid "No folders backed up yet" msgstr "Nenhuma pasta com backup ainda" -#: crates/pdfs-gui/src/app/pages/devices.rs:590 +#: crates/pdfs-gui/src/app/pages/devices.rs:594 #, rust-format msgid "{n} folder backed up" msgid_plural "{n} folders backed up" msgstr[0] "{n} pasta com backup" msgstr[1] "{n} pastas com backup" -#: crates/pdfs-gui/src/app/pages/devices.rs:603 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 #, rust-format msgid "{folders} · {n} needs attention" msgid_plural "{folders} · {n} need attention" msgstr[0] "{folders} · {n} requer atenção" msgstr[1] "{folders} · {n} requerem atenção" -#: crates/pdfs-gui/src/app/pages/devices.rs:614 +#: crates/pdfs-gui/src/app/pages/devices.rs:618 msgid "syncing…" msgstr "sincronizando…" -#: crates/pdfs-gui/src/app/pages/devices.rs:615 +#: crates/pdfs-gui/src/app/pages/devices.rs:619 msgid "sync error" msgstr "erro de sincronização" -#: crates/pdfs-gui/src/app/pages/devices.rs:616 +#: crates/pdfs-gui/src/app/pages/devices.rs:620 msgid "needs attention" msgstr "requer atenção" -#: crates/pdfs-gui/src/app/pages/devices.rs:617 +#: crates/pdfs-gui/src/app/pages/devices.rs:621 msgid "up to date" msgstr "atualizado" -#: crates/pdfs-gui/src/app/pages/devices.rs:629 +#: crates/pdfs-gui/src/app/pages/devices.rs:633 msgid "checking for changes…" msgstr "verificando alterações…" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:636 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "verificando alterações — {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:644 +#: crates/pdfs-gui/src/app/pages/devices.rs:648 #, rust-format msgid "syncing {done} of {total}" msgstr "sincronizando {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:650 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "sincronizando {file} — {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:662 +#: crates/pdfs-gui/src/app/pages/devices.rs:666 msgid "Add Folder to Sync" msgstr "Adicionar pasta à sincronização" -#: crates/pdfs-gui/src/app/pages/devices.rs:686 +#: crates/pdfs-gui/src/app/pages/devices.rs:690 #: crates/pdfs-gui/src/app/pages/locations.rs:1593 msgid "Syncing folder…" msgstr "Sincronizando pasta…" -#: crates/pdfs-gui/src/app/pages/devices.rs:689 #: crates/pdfs-gui/src/app/pages/devices.rs:693 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Couldn't add folder" msgstr "Não foi possível adicionar a pasta" -#: crates/pdfs-gui/src/app/pages/devices.rs:727 -#: crates/pdfs-gui/src/app/pages/devices.rs:733 +#: crates/pdfs-gui/src/app/pages/devices.rs:731 +#: crates/pdfs-gui/src/app/pages/devices.rs:737 msgid "Couldn't list folders to restore" msgstr "Não foi possível listar as pastas para restaurar" -#: crates/pdfs-gui/src/app/pages/devices.rs:759 +#: crates/pdfs-gui/src/app/pages/devices.rs:763 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2236,19 +2243,19 @@ msgstr "" "Marque as pastas a sincronizar com esta máquina e ajuste onde cada uma deve " "ficar." -#: crates/pdfs-gui/src/app/pages/devices.rs:782 +#: crates/pdfs-gui/src/app/pages/devices.rs:786 msgid "Restore Folders" msgstr "Restaurar pastas" -#: crates/pdfs-gui/src/app/pages/devices.rs:789 +#: crates/pdfs-gui/src/app/pages/devices.rs:793 msgid "Restore" msgstr "Restaurar" -#: crates/pdfs-gui/src/app/pages/devices.rs:822 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 msgid "Restoring folders…" msgstr "Restaurando pastas…" -#: crates/pdfs-gui/src/app/pages/devices.rs:834 +#: crates/pdfs-gui/src/app/pages/devices.rs:838 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2257,36 +2264,36 @@ msgstr "" "Estas pastas têm backup a partir de “{name}”. As pastas restauradas " "continuam sincronizando com a cópia daquele computador." -#: crates/pdfs-gui/src/app/pages/devices.rs:837 +#: crates/pdfs-gui/src/app/pages/devices.rs:841 msgid "These folders are backed up under this computer in Proton Drive." msgstr "Estas pastas têm backup sob este computador no Proton Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:846 +#: crates/pdfs-gui/src/app/pages/devices.rs:850 #, rust-format msgid "Nothing to restore — “{name}”'s folders are all synced here." msgstr "" "Nada a restaurar — as pastas de “{name}” já estão todas sincronizadas aqui." -#: crates/pdfs-gui/src/app/pages/devices.rs:849 +#: crates/pdfs-gui/src/app/pages/devices.rs:853 msgid "Nothing to restore — this computer's folders are all synced here." msgstr "" "Nada a restaurar — as pastas deste computador já estão todas sincronizadas " "aqui." -#: crates/pdfs-gui/src/app/pages/devices.rs:873 +#: crates/pdfs-gui/src/app/pages/devices.rs:877 msgid "Folder will switch to online only" msgstr "A pasta passará a ser somente online" -#: crates/pdfs-gui/src/app/pages/devices.rs:875 +#: crates/pdfs-gui/src/app/pages/devices.rs:879 msgid "Folder will download and stay synced" msgstr "A pasta será baixada e permanecerá sincronizada" -#: crates/pdfs-gui/src/app/pages/devices.rs:879 #: crates/pdfs-gui/src/app/pages/devices.rs:883 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 msgid "Couldn't change mode" msgstr "Não foi possível alterar o modo" -#: crates/pdfs-gui/src/app/pages/devices.rs:915 +#: crates/pdfs-gui/src/app/pages/devices.rs:919 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2303,7 +2310,7 @@ msgstr "" "uma cópia local, cancele, mude a pasta para Sincronizada e aguarde o " "download terminar antes de removê-la." -#: crates/pdfs-gui/src/app/pages/devices.rs:921 +#: crates/pdfs-gui/src/app/pages/devices.rs:925 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2316,68 +2323,68 @@ msgstr "" "Os arquivos locais permanecem neste disco e simplesmente deixam de ser " "sincronizados. Escolha se também deseja excluir a cópia no Proton Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:926 +#: crates/pdfs-gui/src/app/pages/devices.rs:930 msgid "Stop Syncing Folder?" msgstr "Parar de sincronizar a pasta?" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:935 msgid "Also delete from Proton Drive" msgstr "Também excluir do Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:933 +#: crates/pdfs-gui/src/app/pages/devices.rs:937 msgid "Deletes the only copy of these files." msgstr "Exclui a única cópia destes arquivos." -#: crates/pdfs-gui/src/app/pages/devices.rs:935 +#: crates/pdfs-gui/src/app/pages/devices.rs:939 msgid "The local copy is unaffected." msgstr "A cópia local não é afetada." -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:946 msgid "Stop Syncing" msgstr "Parar de sincronizar" -#: crates/pdfs-gui/src/app/pages/devices.rs:955 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 msgid "Stopped syncing folder" msgstr "A pasta deixou de ser sincronizada" -#: crates/pdfs-gui/src/app/pages/devices.rs:956 +#: crates/pdfs-gui/src/app/pages/devices.rs:960 msgid "Couldn't stop syncing the folder" msgstr "Não foi possível parar de sincronizar a pasta" -#: crates/pdfs-gui/src/app/pages/devices.rs:967 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Rename Computer" msgstr "Renomear computador" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:969 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 #, rust-format msgid "Rename “{name}”." msgstr "Renomear “{name}”." -#: crates/pdfs-gui/src/app/pages/devices.rs:994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1006 +#: crates/pdfs-gui/src/app/pages/devices.rs:998 +#: crates/pdfs-gui/src/app/pages/devices.rs:1010 msgid "Couldn't rename the computer" msgstr "Não foi possível renomear o computador" -#: crates/pdfs-gui/src/app/pages/devices.rs:995 +#: crates/pdfs-gui/src/app/pages/devices.rs:999 msgid "A name is required." msgstr "Um nome é obrigatório." -#: crates/pdfs-gui/src/app/pages/devices.rs:1005 +#: crates/pdfs-gui/src/app/pages/devices.rs:1009 msgid "Computer renamed" msgstr "Computador renomeado" #. Translators: {name} is the computer's name, which the user must type. -#: crates/pdfs-gui/src/app/pages/devices.rs:1018 +#: crates/pdfs-gui/src/app/pages/devices.rs:1022 #, rust-format msgid "Type “{name}” to confirm" msgstr "Digite “{name}” para confirmar" -#: crates/pdfs-gui/src/app/pages/devices.rs:1023 +#: crates/pdfs-gui/src/app/pages/devices.rs:1027 msgid "Remove Computer?" msgstr "Remover computador?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1026 +#: crates/pdfs-gui/src/app/pages/devices.rs:1030 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2392,25 +2399,25 @@ msgstr "" "naquele próprio computador não são tocados — mas isso não pode ser desfeito " "daqui." -#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1036 #: crates/pdfs-gui/src/app/pages/shared.rs:1028 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:529 msgid "Remove" msgstr "Remover" -#: crates/pdfs-gui/src/app/pages/devices.rs:1049 +#: crates/pdfs-gui/src/app/pages/devices.rs:1053 msgid "Computer removed" msgstr "Computador removido" -#: crates/pdfs-gui/src/app/pages/devices.rs:1050 +#: crates/pdfs-gui/src/app/pages/devices.rs:1054 msgid "Couldn't remove the computer" msgstr "Não foi possível remover o computador" -#: crates/pdfs-gui/src/app/pages/devices.rs:1068 +#: crates/pdfs-gui/src/app/pages/devices.rs:1072 msgid "Continue This Backup Here?" msgstr "Continuar este backup aqui?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1071 +#: crates/pdfs-gui/src/app/pages/devices.rs:1075 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2429,20 +2436,50 @@ msgstr "" "movidas. Depois, use “Restaurar neste computador” para recuperar o que " "“{name}” sincronizava." -#: crates/pdfs-gui/src/app/pages/devices.rs:1076 +#: crates/pdfs-gui/src/app/pages/devices.rs:1080 msgid "Continue Here" msgstr "Continuar aqui" #. Translators: {name} is the name of the computer whose backup this one took over. -#: crates/pdfs-gui/src/app/pages/devices.rs:1083 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 #, rust-format msgid "Now backing up as “{name}”" msgstr "Agora fazendo backup como “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1096 msgid "Couldn't continue that backup here" msgstr "Não foi possível continuar esse backup aqui" +#: crates/pdfs-gui/src/app/pages/devices.rs:1112 +msgid "Move Backup to This Computer?" +msgstr "Mover o backup para este computador?" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1115 +#, rust-format +msgid "" +"The folders backed up by “{name}” move into this computer's backup in Proton " +"Drive, and you choose where they go on this computer. “{name}” stays in your " +"account, without those folders.\n" +"\n" +"Stop Proton Drive on “{name}” first. Otherwise it sees its folders disappear." +msgstr "" +"As pastas cujo backup é feito por “{name}” passam para o backup deste " +"computador no Proton Drive, e você escolhe onde ficam neste computador. " +"“{name}” continua na sua conta, sem essas pastas.\n" +"\n" +"Feche primeiro o Proton Drive em “{name}”. Caso contrário, ele verá as " +"pastas sumirem." + +#. Translators: {name} is the name of the computer whose folders moved to this one. +#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#, rust-format +msgid "Moved the backup of “{name}”" +msgstr "Backup de “{name}” movido" + +#: crates/pdfs-gui/src/app/pages/devices.rs:1141 +msgid "Couldn't move that backup" +msgstr "Não foi possível mover esse backup" + #: crates/pdfs-gui/src/app/pages/duplicates.rs:81 msgctxt "duplicates" msgid "Exact" From c0d211238bf7347ebad357a99be05e2f8006d956 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 8 Oct 2026 13:55:27 +0000 Subject: [PATCH 02/11] chore(aur): update packages to 3.1.2 --- .../aur/proton-drive-for-linux-bin/.SRCINFO | 20 +++++++++---------- .../aur/proton-drive-for-linux-bin/PKGBUILD | 4 ++-- .../aur/proton-drive-for-linux-git/.SRCINFO | 6 +++--- .../aur/proton-drive-for-linux-git/PKGBUILD | 2 +- packaging/aur/proton-drive-for-linux/.SRCINFO | 8 ++++---- packaging/aur/proton-drive-for-linux/PKGBUILD | 4 ++-- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packaging/aur/proton-drive-for-linux-bin/.SRCINFO b/packaging/aur/proton-drive-for-linux-bin/.SRCINFO index 2937c83..12d0708 100644 --- a/packaging/aur/proton-drive-for-linux-bin/.SRCINFO +++ b/packaging/aur/proton-drive-for-linux-bin/.SRCINFO @@ -1,6 +1,6 @@ pkgbase = proton-drive-for-linux-bin pkgdesc = Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries) - pkgver = 3.1.1 + pkgver = 3.1.2 pkgrel = 1 url = https://proton-drive.narl.io arch = x86_64 @@ -21,21 +21,21 @@ pkgbase = proton-drive-for-linux-bin optdepends = xdg-utils: "Open folder" action in the tray menu optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf) optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding - provides = proton-drive-for-linux=3.1.1 - provides = proton-drive-linux=3.1.1 + provides = proton-drive-for-linux=3.1.2 + provides = proton-drive-linux=3.1.2 provides = pdfs conflicts = proton-drive-linux conflicts = proton-drive-for-linux conflicts = proton-drive-for-linux-git options = !strip options = !debug - source = proton-drive-linux-3.1.1-x86_64.tar.gz::https://github.com/narrrl/proton-drive-linux/releases/download/v3.1.1/proton-drive-linux-3.1.1-x86_64.tar.gz - source = io.narl.proton-drive-linux-3.1.1.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.1.1/packaging/io.narl.proton-drive-linux.desktop - source = io.narl.proton-drive-linux-tray-3.1.1.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.1.1/packaging/io.narl.proton-drive-linux-tray.desktop - source = io.narl.proton-drive-linux-3.1.1.svg::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.1.1/packaging/io.narl.proton-drive-linux.svg - source = proton-drive-3.1.1.service::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.1.1/packaging/proton-drive.service - source = LICENSE-3.1.1::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.1.1/LICENSE - sha256sums = e8a2ca20dc5a4b050a85056196eb7f3f4a428db2a8d41aa09b813e007609c0b6 + source = proton-drive-linux-3.1.2-x86_64.tar.gz::https://github.com/narrrl/proton-drive-linux/releases/download/v3.1.2/proton-drive-linux-3.1.2-x86_64.tar.gz + source = io.narl.proton-drive-linux-3.1.2.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.1.2/packaging/io.narl.proton-drive-linux.desktop + source = io.narl.proton-drive-linux-tray-3.1.2.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.1.2/packaging/io.narl.proton-drive-linux-tray.desktop + source = io.narl.proton-drive-linux-3.1.2.svg::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.1.2/packaging/io.narl.proton-drive-linux.svg + source = proton-drive-3.1.2.service::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.1.2/packaging/proton-drive.service + source = LICENSE-3.1.2::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.1.2/LICENSE + sha256sums = 71fc50775cbd795717ed17c4a3994d8fdfb5ab30a223dcbc7f0e313f9ab615f7 sha256sums = d190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123 sha256sums = 1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0 sha256sums = e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828 diff --git a/packaging/aur/proton-drive-for-linux-bin/PKGBUILD b/packaging/aur/proton-drive-for-linux-bin/PKGBUILD index 8b7ac16..a00a6c0 100644 --- a/packaging/aur/proton-drive-for-linux-bin/PKGBUILD +++ b/packaging/aur/proton-drive-for-linux-bin/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Nils Pukropp pkgname=proton-drive-for-linux-bin -pkgver=3.1.1 +pkgver=3.1.2 pkgrel=1 pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries)" arch=('x86_64') @@ -34,7 +34,7 @@ source=("proton-drive-linux-$pkgver-x86_64.tar.gz::$_repo/releases/download/$_ta "io.narl.proton-drive-linux-$pkgver.svg::$_raw/packaging/io.narl.proton-drive-linux.svg" "proton-drive-$pkgver.service::$_raw/packaging/proton-drive.service" "LICENSE-$pkgver::$_raw/LICENSE") -sha256sums=('e8a2ca20dc5a4b050a85056196eb7f3f4a428db2a8d41aa09b813e007609c0b6' +sha256sums=('71fc50775cbd795717ed17c4a3994d8fdfb5ab30a223dcbc7f0e313f9ab615f7' 'd190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123' '1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0' 'e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828' diff --git a/packaging/aur/proton-drive-for-linux-git/.SRCINFO b/packaging/aur/proton-drive-for-linux-git/.SRCINFO index 37e070a..e759c4f 100644 --- a/packaging/aur/proton-drive-for-linux-git/.SRCINFO +++ b/packaging/aur/proton-drive-for-linux-git/.SRCINFO @@ -1,6 +1,6 @@ pkgbase = proton-drive-for-linux-git pkgdesc = Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (git) - pkgver = 3.1.1.r0.gedd62c5 + pkgver = 3.1.2.r0.g07c9b67 pkgrel = 1 url = https://proton-drive.narl.io arch = x86_64 @@ -24,8 +24,8 @@ pkgbase = proton-drive-for-linux-git optdepends = xdg-utils: "Open folder" action in the tray menu optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf) optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding - provides = proton-drive-for-linux=3.1.1.r0.gedd62c5 - provides = proton-drive-linux=3.1.1.r0.gedd62c5 + provides = proton-drive-for-linux=3.1.2.r0.g07c9b67 + provides = proton-drive-linux=3.1.2.r0.g07c9b67 provides = pdfs conflicts = proton-drive-linux conflicts = proton-drive-for-linux diff --git a/packaging/aur/proton-drive-for-linux-git/PKGBUILD b/packaging/aur/proton-drive-for-linux-git/PKGBUILD index 917d341..32b9cbd 100644 --- a/packaging/aur/proton-drive-for-linux-git/PKGBUILD +++ b/packaging/aur/proton-drive-for-linux-git/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Nils Pukropp pkgname=proton-drive-for-linux-git -pkgver=3.1.1.r0.gedd62c5 +pkgver=3.1.2.r0.g07c9b67 pkgrel=1 pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (git)" arch=('x86_64') diff --git a/packaging/aur/proton-drive-for-linux/.SRCINFO b/packaging/aur/proton-drive-for-linux/.SRCINFO index 2c454ba..9f0635a 100644 --- a/packaging/aur/proton-drive-for-linux/.SRCINFO +++ b/packaging/aur/proton-drive-for-linux/.SRCINFO @@ -1,6 +1,6 @@ pkgbase = proton-drive-for-linux pkgdesc = Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray - pkgver = 3.1.1 + pkgver = 3.1.2 pkgrel = 1 url = https://proton-drive.narl.io arch = x86_64 @@ -23,13 +23,13 @@ pkgbase = proton-drive-for-linux optdepends = xdg-utils: "Open folder" action in the tray menu optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf) optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding - provides = proton-drive-linux=3.1.1 + provides = proton-drive-linux=3.1.2 provides = pdfs conflicts = proton-drive-linux conflicts = proton-drive-for-linux-bin conflicts = proton-drive-for-linux-git options = !lto - source = proton-drive-for-linux-3.1.1.tar.gz::https://github.com/narrrl/proton-drive-linux/archive/refs/tags/v3.1.1.tar.gz - sha256sums = c16019c75959d5056c8ae486e0a4dc1f6346828ea93f346dfeae7da717629256 + source = proton-drive-for-linux-3.1.2.tar.gz::https://github.com/narrrl/proton-drive-linux/archive/refs/tags/v3.1.2.tar.gz + sha256sums = 260ed05c8b041cf38c5904f7543780de0d0cb06d9695a427a98250d2a9ec0eb1 pkgname = proton-drive-for-linux diff --git a/packaging/aur/proton-drive-for-linux/PKGBUILD b/packaging/aur/proton-drive-for-linux/PKGBUILD index 8994da5..5a06922 100644 --- a/packaging/aur/proton-drive-for-linux/PKGBUILD +++ b/packaging/aur/proton-drive-for-linux/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Nils Pukropp pkgname=proton-drive-for-linux -pkgver=3.1.1 +pkgver=3.1.2 pkgrel=1 pkgdesc="Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray" arch=('x86_64') @@ -24,7 +24,7 @@ conflicts=('proton-drive-linux' 'proton-drive-for-linux-bin' 'proton-drive-for-l # LTO has broken GTK/Rust links for this workspace; the release profile keeps it off. options=('!lto') source=("$pkgname-$pkgver.tar.gz::$_repo/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('c16019c75959d5056c8ae486e0a4dc1f6346828ea93f346dfeae7da717629256') +sha256sums=('260ed05c8b041cf38c5904f7543780de0d0cb06d9695a427a98250d2a9ec0eb1') _srcname="proton-drive-linux-$pkgver" From 7eb6aa06403d34f146afb9a53fb2f9e93d317209 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 8 Oct 2026 20:45:54 +0200 Subject: [PATCH 03/11] fix(fuse): skip unreadable folders in sync, never evict local-only files An unreadable subfolder, such as a container's root-owned cache, failed every pass of its synced folder, so nothing else in the tree reached Drive (#33, B196). The scan now leaves such a path out, together with an entry that vanished mid-scan and a name that is not UTF-8, and the pass drops everything under it from classification, the wipe guard and the push pass's deletions. It is never read as deleted and never downloaded into. An unreadable or absent root still fails the pass. The activity feed names unreadable paths once each time the set changes. Two related bugs on the way to online-only: - The push pass read deletions from the raw baseline, so a synced path that later became ignored was trashed on Drive (B197). - The switch evicted everything once the folder was idle, including ignored paths such as .git/, symlinks, unreadable folders and files changed after the pass, none of which are on Drive (B198). It now refuses and names the path. Closes #33 --- crates/pdfs-fuse/src/devices.rs | 17 + crates/pdfs-fuse/src/lib.rs | 3 + crates/pdfs-fuse/src/mount.rs | 1 + crates/pdfs-fuse/src/sync.rs | 641 ++++++++++++++++++++++----- crates/pdfs-fuse/src/sync/model.rs | 5 + crates/pdfs-fuse/src/sync/planner.rs | 90 ++++ docs/ARCHITECTURE.md | 6 +- docs/BUGS.md | 96 ++++ docs/CHANGELOG.md | 14 + docs/USER_GUIDE.md | 7 +- 10 files changed, 778 insertions(+), 102 deletions(-) diff --git a/crates/pdfs-fuse/src/devices.rs b/crates/pdfs-fuse/src/devices.rs index 79c131e..b0d10ad 100644 --- a/crates/pdfs-fuse/src/devices.rs +++ b/crates/pdfs-fuse/src/devices.rs @@ -842,6 +842,23 @@ impl Core { if folder.state != "idle" { return Err(SwitchBlocked::NotNow); } + // `idle` only says the last pass applied everything it tried. + // It never tries an ignored path, a symlink or a folder it + // cannot read, it defers a file open for writing, and a file can + // change after it ends. The eviction below deletes all of those + // for good, so it waits until nothing here is missing from Drive + // (B198). + let baseline = self + .db + .sync_entries(id) + .map_err(|e| SwitchBlocked::Failed(format!("db: {e:?}")))?; + if let Some(reason) = + sync::mirror_tree_unsynced(&local, &baseline, &self.ignore_rules(&local)) + { + return Err(SwitchBlocked::Failed(format!( + "going online-only would delete what is only on this computer: {reason}" + ))); + } let root_uid = parse_uid(&folder.remote_uid).ok_or_else(|| { SwitchBlocked::Failed(format!("bad remote uid: {}", folder.remote_uid)) })?; diff --git a/crates/pdfs-fuse/src/lib.rs b/crates/pdfs-fuse/src/lib.rs index 7e75e3b..d2a75b7 100644 --- a/crates/pdfs-fuse/src/lib.rs +++ b/crates/pdfs-fuse/src/lib.rs @@ -744,6 +744,9 @@ struct Core { /// uploading that same tree — the engine would upload files as they vanish /// and then walk the FUSE mount as if it were local. sync_locks: Arc>>>>, + /// Per-sync-folder paths the last pass could not read, so the activity feed + /// says so when the set changes rather than on every poll (B196). + sync_unscanned: Arc>>>, /// Nodes this daemon changed on the remote itself, and how many echoes of /// those changes the event feed still owes us. /// diff --git a/crates/pdfs-fuse/src/mount.rs b/crates/pdfs-fuse/src/mount.rs index fde3203..a5f163a 100644 --- a/crates/pdfs-fuse/src/mount.rs +++ b/crates/pdfs-fuse/src/mount.rs @@ -475,6 +475,7 @@ pub(crate) fn mount_with( sync_tx, mounts: Arc::new(Mutex::new(SecondaryMountRegistry::default())), sync_locks: Arc::new(Mutex::new(HashMap::new())), + sync_unscanned: Arc::new(Mutex::new(HashMap::new())), states: Arc::new(StateRegistry::default()), }; // Before anything can queue work against it: the drain thread below reaches diff --git a/crates/pdfs-fuse/src/sync.rs b/crates/pdfs-fuse/src/sync.rs index 22af1f1..ae8d21e 100644 --- a/crates/pdfs-fuse/src/sync.rs +++ b/crates/pdfs-fuse/src/sync.rs @@ -59,8 +59,9 @@ pub(crate) use planner::base_name; #[cfg(test)] use planner::conflict_path; use planner::{ - FilePlan, classification_order, conflict_path_with_suffix, filter_baseline, guard_local_wipe, - join_rel, parent_rel, plan_file, rel_to_path, remote_sig, unchanged_remote_size, + FilePlan, Unscanned, classification_order, conflict_path_with_suffix, filter_baseline, + guard_local_wipe, join_rel, local_deletions, parent_rel, plan_file, rel_to_path, remote_sig, + tracked_baseline, unchanged_remote_size, }; /// What a [`Pending`] op did, so the reconcile can update shared state on the /// engine thread (never inside a task). @@ -187,6 +188,7 @@ impl Core { "idle" }; let _ = self.db.sync_folder_set_state(folder.id, state, now_secs()); + self.report_unscanned(folder.id, name, &outcome.unscanned); // Summarise a pass that actually moved something. A poll over an // unchanged folder does nothing and says nothing — otherwise the // feed would fill with "synced, 0 changes" every two minutes. @@ -243,6 +245,37 @@ impl Core { } } + /// Tell the activity feed what a pass over this folder had to leave out + /// because it cannot be read, once each time that changes. Every other path + /// in the folder still synced, so this does not make the folder's state an + /// error; but a folder the user has no permission for is never going to + /// reach Drive, and they should hear that once rather than not at all + /// (B196). + fn report_unscanned(&self, folder_id: i64, name: &str, unscanned: &[String]) { + let previous = { + let mut last = self.sync_unscanned.lock(); + if unscanned.is_empty() { + last.remove(&folder_id) + } else { + last.insert(folder_id, unscanned.to_vec()) + } + }; + if unscanned.is_empty() || previous.as_deref() == Some(unscanned) { + return; + } + const SHOWN: usize = 3; + let mut detail = unscanned[..unscanned.len().min(SHOWN)].join(", "); + if unscanned.len() > SHOWN { + detail.push_str(&format!(" and {} more", unscanned.len() - SHOWN)); + } + self.log_activity( + ActivityKind::Sync, + format!("{} item(s) in {name} cannot be synced", unscanned.len()), + detail, + false, + ); + } + /// Compile the ignore rules for a synced folder: the global patterns from /// the config, plus any `.pdfsignore` at `local_root`. /// @@ -250,7 +283,7 @@ impl Core { /// than to "ignore nothing" — the defaults exclude build and VCS trees, and /// silently uploading those because a config read failed is the outcome this /// feature exists to prevent. - fn ignore_rules(&self, local_root: &Path) -> IgnoreRules { + pub(crate) fn ignore_rules(&self, local_root: &Path) -> IgnoreRules { let globals = match pdfs_core::config::AppDirs::new() { Ok(dirs) => dirs.load_config().resolved_ignore_patterns(), Err(e) => { @@ -313,15 +346,23 @@ impl Core { self.progress_scan_total(folder_id, baseline.len()); let mut local: HashMap = HashMap::new(); + let mut unscanned = Unscanned::default(); let writing = open_for_write_set(local_root); self.walk_local( - folder_id, local_root, local_root, rules, &writing, &mut local, + folder_id, + local_root, + rules, + &writing, + &mut local, + &mut unscanned, )?; // The guard compares against the paths this pass could still see: an - // ignored path is absent from `local` by rule, not by loss, and counting - // those as missing would trip the guard on every pass — wedging the - // folder's sync permanently — the moment a rule covers the whole tree. - guard_local_wipe(&filter_baseline(&baseline, rules), &local)?; + // ignored or unreadable path is absent from `local` by rule or by + // failure, not by loss, and counting those as missing would trip the + // guard on every pass — wedging the folder's sync permanently — the + // moment a rule covers the whole tree. + let tracked = tracked_baseline(&baseline, rules, &unscanned); + guard_local_wipe(&tracked, &local)?; // The remote folder uids come from the baseline instead of a walk: every // directory this device has synced recorded its uid there when it was @@ -391,6 +432,7 @@ impl Core { let mut outcome = Outcome { deferred: local.values().filter(|item| item.open_for_write).count(), + unscanned: unscanned.report(), ..Default::default() }; let mut order: Vec<&String> = local.keys().collect(); @@ -515,13 +557,8 @@ impl Core { // showing the folder's remote contents — otherwise everything they deleted // comes back the moment the switch lands. Shallowest first, skipping anything // under a folder already trashed (its children went with it). - let mut missing: Vec<&String> = baseline - .keys() - .filter(|rel| !local.contains_key(*rel)) - .collect(); - missing.sort_by_key(|p| p.matches('/').count()); let mut trashed: Vec = Vec::new(); - for rel in missing { + for rel in &local_deletions(&tracked, &local) { if trashed .iter() .any(|dir| rel.starts_with(&format!("{dir}/"))) @@ -586,12 +623,18 @@ impl Core { self.progress_scan_total(folder_id, baseline.len() * 2); let mut local: HashMap = HashMap::new(); + let mut unscanned = Unscanned::default(); let writing = open_for_write_set(local_root); self.walk_local( - folder_id, local_root, local_root, rules, &writing, &mut local, + folder_id, + local_root, + rules, + &writing, + &mut local, + &mut unscanned, )?; - // See `push_pass`: the guard must not count rule-excluded paths as lost. - guard_local_wipe(&filter_baseline(&baseline, rules), &local)?; + // See `push_pass`: the guard must not count excluded paths as lost. + guard_local_wipe(&tracked_baseline(&baseline, rules, &unscanned), &local)?; let mut remote: HashMap = HashMap::new(); let mut remote_dirs: HashMap = HashMap::new(); @@ -605,9 +648,15 @@ impl Core { &baseline, )?; - let order = classification_order(&local, &remote, &baseline, rules); + let mut order = classification_order(&local, &remote, &baseline, rules); + // A path the scan could not read is no more a local deletion than an + // ignored one, and a remote path under it has nowhere to be written. + order.retain(|rel| !unscanned.covers(rel)); - let mut outcome = Outcome::default(); + let mut outcome = Outcome { + unscanned: unscanned.report(), + ..Default::default() + }; // Folders to delete, collected here and removed deepest-first at the end // so a parent is never removed before its children. let mut delete_local_dirs: Vec = Vec::new(); @@ -873,19 +922,19 @@ impl Core { Ok(outcome) } - /// Recursively walk a local directory into `out`, keyed by `/`-joined relative - /// path. Symlinks and other special files are skipped. Reports each entry to the - /// pass's scan progress. + /// Recursively walk a local tree into `out`, keyed by `/`-joined relative + /// path. Symlinks and other special files are skipped, and paths that cannot + /// be read go to `unscanned`. Reports each entry to the pass's scan progress. fn walk_local( &self, folder_id: i64, root: &Path, - dir: &Path, rules: &IgnoreRules, writing: &HashSet, out: &mut HashMap, + unscanned: &mut Unscanned, ) -> Result<(), String> { - walk_local_tree(root, dir, rules, writing, out, &mut |rel| { + walk_local_tree(root, rules, writing, out, unscanned, &mut |rel| { self.progress_scanned(folder_id, base_name(rel)) }) } @@ -1582,51 +1631,120 @@ fn removed_locally(result: std::io::Result<()>) -> std::io::Result<()> { } /// The body of [`Core::walk_local`], without a `Core`: recursively collect the -/// local tree under `dir` into `out`, reporting each entry to `scanned`. +/// local tree under `root` into `out`, reporting each entry to `scanned`. /// -/// **Every failure here is fatal to the pass, deliberately.** A `read_dir` that -/// fails, an entry that cannot be stat-ed, a name that is not UTF-8 — each of -/// them would, if skipped, leave `out` describing a *smaller* tree than the one -/// on disk. Reconciliation reads a path present in the baseline and absent from -/// the scan as "the user deleted this", and propagates that to Drive. So an -/// unreadable subdirectory would trash its remote counterpart, and the deeper -/// the failure, the more it takes with it (`docs/BUGS.md` B55). Refusing the -/// whole pass keeps the previous baseline and leaves the remote alone; the next -/// pass retries once the transient condition clears. +/// **A path the scan cannot read is left out and recorded in `unscanned`, never +/// silently dropped.** Every pass drops what `unscanned` covers from +/// classification, so a baseline row under it is neither a local deletion nor a +/// download target (`docs/BUGS.md` B55). That covers a subfolder the pass has no +/// permission for, an entry that vanished between the listing and its `stat`, +/// and a name that is not UTF-8. Failing the whole pass on any of them, as this +/// did before, left one root-owned folder stopping everything else in the tree +/// from syncing, on every pass, for good (B196). +/// +/// The root is the exception, and so is any other I/O error: an absent or +/// unreadable root fails the pass, since leaving the root out would leave +/// nothing in, and an error this cannot name a cause for is not one to guess +/// about. /// /// Split out from the method so exactly that can be tested against a real tree /// with a real unreadable directory in it, which needs no `Core` at all. fn walk_local_tree( + root: &Path, + rules: &IgnoreRules, + writing: &HashSet, + out: &mut HashMap, + unscanned: &mut Unscanned, + scanned: &mut dyn FnMut(&str), +) -> Result<(), String> { + walk_local_dir(root, root, rules, writing, out, unscanned, scanned).map_err(|e| e.to_string()) +} + +/// Why [`walk_local_dir`] could not finish a folder. +enum ScanError { + /// An I/O error on `what`, which the caller may leave out of the pass. + Io { what: String, error: std::io::Error }, + /// Anything else, which fails the pass. + Failed(String), +} + +impl std::fmt::Display for ScanError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ScanError::Io { what, error } => write!(f, "{what}: {error}"), + ScanError::Failed(message) => f.write_str(message), + } + } +} + +/// Leave `rel` out of the pass for `failure`, or hand it back when it is one +/// that fails the pass. Permission denied is reported to the user; an entry +/// that vanished mid-scan is not, since the next pass sees it settled. +fn leave_out(rel: &str, failure: ScanError, unscanned: &mut Unscanned) -> Result<(), ScanError> { + let ScanError::Io { what, error } = &failure else { + return Err(failure); + }; + match error.kind() { + std::io::ErrorKind::PermissionDenied => { + warn!(%what, %error, "sync: cannot read; leaving it out of the pass"); + unscanned.unreadable(rel.to_string(), &error.to_string()); + Ok(()) + } + std::io::ErrorKind::NotFound => { + debug!(%what, "sync: gone mid-scan; leaving it out of the pass"); + unscanned.vanished(rel.to_string()); + Ok(()) + } + _ => Err(failure), + } +} + +fn walk_local_dir( root: &Path, dir: &Path, rules: &IgnoreRules, writing: &HashSet, out: &mut HashMap, + unscanned: &mut Unscanned, scanned: &mut dyn FnMut(&str), -) -> Result<(), String> { - let entries = std::fs::read_dir(dir).map_err(|e| format!("read {}: {e}", dir.display()))?; +) -> Result<(), ScanError> { + let io = |what: String| move |error| ScanError::Io { what, error }; + let entries = std::fs::read_dir(dir).map_err(io(format!("read {}", dir.display())))?; for entry in entries { - let entry = entry.map_err(|e| format!("read entry in {}: {e}", dir.display()))?; + let entry = entry.map_err(io(format!("read entry in {}", dir.display())))?; let path = entry.path(); - let meta = std::fs::symlink_metadata(&path) - .map_err(|e| format!("stat {}: {e}", path.display()))?; let stripped = path.strip_prefix(root).map_err(|e| { - format!( + ScanError::Failed(format!( "local path {} escaped sync root {}: {e}", path.display(), root.display() - ) - })?; - let rel = stripped.to_str().ok_or_else(|| { - format!( - "local path {} is not valid UTF-8; refusing a destructive sync pass", - path.display() - ) + )) })?; + let Some(rel) = stripped.to_str() else { + // Nothing can be keyed by this name: not the baseline, which only + // ever held paths this scan produced, and not Drive, whose names are + // UTF-8. Leaving it out cannot read as a deletion of anything. + warn!(path = %path.display(), "sync: name is not valid UTF-8; leaving it out of the pass"); + unscanned.unreadable( + stripped.to_string_lossy().into_owned(), + "name is not valid UTF-8", + ); + continue; + }; // Ignore our own in-flight download temp files. if rel.contains(".pdfs-tmp-") { continue; } + let meta = match std::fs::symlink_metadata(&path) { + Ok(meta) => meta, + // Gone since the listing, or in a folder that lists but does not + // allow `stat`. Either way this pass cannot say what it is. + Err(error) => { + let what = format!("stat {}", path.display()); + leave_out(rel, ScanError::Io { what, error }, unscanned)?; + continue; + } + }; // Ignored paths are dropped here as well as from the classification // union — not for correctness (the union filter is what makes this // safe) but so an ignored `node_modules/` is never descended into or @@ -1646,7 +1764,15 @@ fn walk_local_tree( }, ); scanned(rel); - walk_local_tree(root, &path, rules, writing, out, scanned)?; + if let Err(failure) = + walk_local_dir(root, &path, rules, writing, out, unscanned, scanned) + { + leave_out(rel, failure, unscanned)?; + // What the folder did list before failing goes with it: a + // half-read folder is as unknown as an unread one. + let prefix = format!("{rel}/"); + out.retain(|k, _| k != rel && !k.starts_with(&prefix)); + } } else if meta.is_file() { // Check whether any process holds this file open for writing. // The canonical path is used because /proc/*/fd links resolve @@ -1684,7 +1810,7 @@ pub(crate) fn mirror_subtree_unsynced( baseline: &HashMap, ) -> Option { let mut seen = HashSet::new(); - if let Some(reason) = unsynced_local(root, rel, baseline, &mut seen) { + if let Some(reason) = unsynced_local(root, rel, baseline, None, &mut seen) { return Some(reason); } let prefix = format!("{rel}/"); @@ -1695,45 +1821,93 @@ pub(crate) fn mirror_subtree_unsynced( .map(|r| format!("{r} was deleted here but not on Proton Drive yet")) } +/// [`mirror_subtree_unsynced`] for a whole mirror folder: why its local copy +/// holds something Proton Drive does not, or `None` when evicting it loses +/// nothing. The switch to on-demand asks this before it empties the folder. +/// +/// A push pass that ends `idle` is not enough on its own. It never uploads an +/// ignored path, a symlink or a folder it cannot read, it defers a file open +/// for writing, and a file can change after it finishes; the eviction deleted +/// all of those for good (B198). +/// +/// A tracked path deleted here but still in the baseline counts, since the +/// mount would show it again. An ignored one does not: ignoring never carries +/// a deletion to Drive, so its row stays in the baseline for good, and holding +/// the switch on it would hold it forever. +pub(crate) fn mirror_tree_unsynced( + root: &Path, + baseline: &HashMap, + rules: &IgnoreRules, +) -> Option { + let mut seen = HashSet::new(); + if let Some(reason) = unsynced_local(root, "", baseline, Some(rules), &mut seen) { + return Some(reason); + } + let mut missing: Vec = filter_baseline(baseline, rules) + .into_keys() + .filter(|r| !seen.contains(r.as_str())) + .collect(); + missing.sort_unstable(); + missing + .first() + .map(|r| format!("{r} was deleted here but not on Proton Drive yet")) +} + +/// The walk behind both checks. `rules`, when given, only names an ignored +/// path as such in the reason; it is still something only this disk has. fn unsynced_local( root: &Path, rel: &str, baseline: &HashMap, + rules: Option<&IgnoreRules>, seen: &mut HashSet, ) -> Option { - let path = root.join(rel_to_path(rel)); - let meta = match std::fs::symlink_metadata(&path) { - Ok(meta) => meta, - Err(e) => return Some(format!("cannot read {rel}: {e}")), + let path = if rel.is_empty() { + root.to_path_buf() + } else { + root.join(rel_to_path(rel)) }; - let Some(base) = baseline.get(rel) else { - return Some(format!("{rel} is not on Proton Drive yet")); - }; - seen.insert(rel.to_string()); - if meta.is_file() { - return (!LocalSig::from(&meta).same_content(&LocalSig::from(base))) - .then(|| format!("{rel} has changes that are not on Proton Drive yet")); - } - if !meta.is_dir() { - return Some(format!("{rel} is not a regular file or folder")); + // The folder itself has no baseline row; only what is in it does. + if !rel.is_empty() { + let meta = match std::fs::symlink_metadata(&path) { + Ok(meta) => meta, + Err(e) => return Some(format!("cannot read {rel}: {e}")), + }; + let Some(base) = baseline.get(rel) else { + if rules.is_some_and(|rules| rules.is_ignored(rel, meta.is_dir())) { + return Some(format!( + "{rel} is excluded from sync, so it is only on this computer" + )); + } + return Some(format!("{rel} is not on Proton Drive yet")); + }; + seen.insert(rel.to_string()); + if meta.is_file() { + return (!LocalSig::from(&meta).same_content(&LocalSig::from(base))) + .then(|| format!("{rel} has changes that are not on Proton Drive yet")); + } + if !meta.is_dir() { + return Some(format!("{rel} is not a regular file or folder")); + } } + let shown = if rel.is_empty() { "the folder" } else { rel }; let entries = match std::fs::read_dir(&path) { Ok(entries) => entries, - Err(e) => return Some(format!("cannot read {rel}: {e}")), + Err(e) => return Some(format!("cannot read {shown}: {e}")), }; for entry in entries { let name = match entry { Ok(entry) => entry.file_name(), - Err(e) => return Some(format!("cannot read {rel}: {e}")), + Err(e) => return Some(format!("cannot read {shown}: {e}")), }; let Some(name) = name.to_str() else { - return Some(format!("{rel} holds a name that is not valid UTF-8")); + return Some(format!("{shown} holds a name that is not valid UTF-8")); }; // A download the engine left half done; it is the engine's to clean up. if name.contains(".pdfs-tmp-") { continue; } - if let Some(reason) = unsynced_local(root, &format!("{rel}/{name}"), baseline, seen) { + if let Some(reason) = unsynced_local(root, &join_rel(rel, name), baseline, rules, seen) { return Some(reason); } } @@ -2154,7 +2328,15 @@ mod tests { let writing = HashSet::new(); let mut local: HashMap = HashMap::new(); let mut progress = no_progress(); - walk_local_tree(&root, &root, &rules, &writing, &mut local, &mut progress).unwrap(); + walk_local_tree( + &root, + &rules, + &writing, + &mut local, + &mut Unscanned::default(), + &mut progress, + ) + .unwrap(); assert!(local.is_empty(), "the replacement root is genuinely empty"); for size in 1..=3 { @@ -2170,7 +2352,15 @@ mod tests { // remounted folder resumes rather than staying wedged. std::fs::write(root.join("file0.txt"), b"back").unwrap(); let mut local: HashMap = HashMap::new(); - walk_local_tree(&root, &root, &rules, &writing, &mut local, &mut progress).unwrap(); + walk_local_tree( + &root, + &rules, + &writing, + &mut local, + &mut Unscanned::default(), + &mut progress, + ) + .unwrap(); let baseline: HashMap = [("file0.txt".to_string(), ())].into_iter().collect(); assert!(guard_local_wipe(&filter_baseline(&baseline, &rules), &local).is_ok()); @@ -2475,33 +2665,176 @@ mod tests { |_: &str| {} } - /// A directory the pass cannot read must fail the pass. Omitting it would - /// hand reconciliation a baseline-minus-subtree, which it reads as the user - /// having deleted that subtree and propagates to Drive (B55). + /// Scan `root` with no rules, returning what it found and left out. + fn scan(root: &Path) -> Result<(HashMap, Unscanned), String> { + let mut out = HashMap::new(); + let mut unscanned = Unscanned::default(); + let mut progress = no_progress(); + walk_local_tree( + root, + &rules_for(&[]), + &HashSet::new(), + &mut out, + &mut unscanned, + &mut progress, + )?; + Ok((out, unscanned)) + } + + fn sorted_keys(map: &HashMap) -> Vec<&str> { + let mut keys: Vec<&str> = map.keys().map(String::as_str).collect(); + keys.sort_unstable(); + keys + } + + /// B196, the report in #33: a folder the user may not read, such as a + /// container's root-owned cache, is left out of the pass and named, and + /// everything around it still scans. It used to fail the whole pass, so + /// nothing else in the tree ever synced again. #[test] - fn an_unreadable_subdirectory_fails_the_scan_instead_of_shrinking_it() { + fn an_unreadable_subdirectory_is_left_out_and_the_rest_scans() { use std::os::unix::fs::PermissionsExt as _; let root = sync_test_dir("scan-eacces"); std::fs::write(root.join("keep.txt"), b"a").unwrap(); - let locked = root.join("locked"); + std::fs::create_dir_all(root.join("docker/cache")).unwrap(); + std::fs::write(root.join("docker/compose.yml"), b"c").unwrap(); + let locked = root.join("docker/cache/locked"); std::fs::create_dir(&locked).unwrap(); std::fs::write(locked.join("inner.txt"), b"b").unwrap(); std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o000)).unwrap(); - let rules = rules_for(&[]); - let writing = HashSet::new(); - let mut out = HashMap::new(); - let mut progress = no_progress(); - let result = walk_local_tree(&root, &root, &rules, &writing, &mut out, &mut progress); + let result = scan(&root); std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o700)).unwrap(); std::fs::remove_dir_all(&root).unwrap(); - let error = result.expect_err("an unreadable directory must abort the pass"); - assert!( - error.contains("locked"), - "the error should name the subtree it could not read: {error}" + let (out, unscanned) = result.expect("one unreadable folder must not fail the pass"); + assert_eq!( + sorted_keys(&out), + vec!["docker", "docker/cache", "docker/compose.yml", "keep.txt"] + ); + assert!(unscanned.covers("docker/cache/locked")); + assert!(unscanned.covers("docker/cache/locked/inner.txt")); + assert!(!unscanned.covers("docker/cache")); + let report = unscanned.report(); + assert_eq!(report.len(), 1); + assert!(report[0].starts_with("docker/cache/locked ("), "{report:?}"); + } + + /// A folder with read but no search permission lists its names but cannot + /// `stat` them. Whatever it did list must go with it: a half-read folder + /// is as unknown as an unread one. + #[test] + fn a_listable_but_unsearchable_folder_is_left_out_whole() { + use std::os::unix::fs::PermissionsExt as _; + + let root = sync_test_dir("scan-no-search"); + std::fs::write(root.join("keep.txt"), b"a").unwrap(); + let half = root.join("half"); + std::fs::create_dir(&half).unwrap(); + std::fs::write(half.join("one.txt"), b"1").unwrap(); + std::fs::write(half.join("two.txt"), b"2").unwrap(); + std::fs::set_permissions(&half, std::fs::Permissions::from_mode(0o400)).unwrap(); + + let result = scan(&root); + + std::fs::set_permissions(&half, std::fs::Permissions::from_mode(0o700)).unwrap(); + std::fs::remove_dir_all(&root).unwrap(); + + let (out, unscanned) = result.unwrap(); + // Each entry fails its own `stat` and is left out by name. + assert_eq!(sorted_keys(&out), vec!["half", "keep.txt"]); + assert!(unscanned.covers("half/one.txt")); + assert!(unscanned.covers("half/two.txt")); + } + + /// The other half of B196: what was left out is not a deletion, and not a + /// download target either. Its baseline rows leave the wipe guard's set + /// and a push pass's deletions, and its paths leave classification. + #[test] + fn a_path_the_scan_left_out_is_never_a_local_deletion() { + let mut baseline = baseline_with("keep.txt", 1, 1); + baseline.extend(baseline_with("locked", 0, 0)); + baseline.extend(baseline_with("locked/inner.txt", 1, 1)); + baseline.extend(baseline_with("gone.txt", 1, 1)); + let local: HashMap = [("keep.txt".to_string(), local_item(false))] + .into_iter() + .collect(); + let mut unscanned = Unscanned::default(); + unscanned.unreadable("locked".to_string(), "Permission denied"); + let rules = rules_for(&[]); + + let tracked = tracked_baseline(&baseline, &rules, &unscanned); + assert_eq!(sorted_keys(&tracked), vec!["gone.txt", "keep.txt"]); + assert_eq!(local_deletions(&tracked, &local), vec!["gone.txt"]); + + // Every tracked path but one is gone, and the guard still lets the + // pass run: one survivor proves the tree is there. + assert!(guard_local_wipe(&tracked, &local).is_ok()); + // With everything readable left out, nothing is tracked, so nothing + // can be lost and the guard has nothing to object to. + let mut all = Unscanned::default(); + all.unreadable("keep.txt".to_string(), "Permission denied"); + all.unreadable("locked".to_string(), "Permission denied"); + all.vanished("gone.txt".to_string()); + assert!(tracked_baseline(&baseline, &rules, &all).is_empty()); + + let remote: HashMap = HashMap::new(); + let mut order = classification_order(&local, &remote, &baseline, &rules); + order.retain(|rel| !unscanned.covers(rel)); + order.sort_unstable(); + assert_eq!(order, vec!["gone.txt", "keep.txt"]); + } + + #[test] + fn unscanned_covers_a_path_and_what_is_under_it_but_not_its_siblings() { + let mut unscanned = Unscanned::default(); + unscanned.unreadable("a/locked".to_string(), "Permission denied"); + unscanned.vanished("b.txt".to_string()); + + assert!(unscanned.covers("a/locked")); + assert!(unscanned.covers("a/locked/x/y.txt")); + assert!(unscanned.covers("b.txt")); + assert!(!unscanned.covers("a")); + assert!(!unscanned.covers("a/locked2")); + assert!(!unscanned.covers("b.txt.bak")); + // A file that only vanished mid-scan is not worth telling anyone about. + assert_eq!(unscanned.report(), vec!["a/locked (Permission denied)"]); + } + + /// B197: a push pass read its deletions from the raw baseline, so a path + /// synced before it became ignored, which the walk skips, was trashed on + /// Drive, and the switch the push pass ran for then evicted the local copy. + #[test] + fn a_push_pass_never_reads_an_ignored_path_as_deleted() { + let mut baseline = baseline_with("notes.md", 1, 1); + baseline.extend(baseline_with("node_modules", 0, 0)); + baseline.extend(baseline_with("node_modules/x/index.js", 1, 1)); + baseline.extend(baseline_with("old.md", 1, 1)); + let local: HashMap = [("notes.md".to_string(), local_item(false))] + .into_iter() + .collect(); + let rules = rules_for(&["node_modules/"]); + + let tracked = tracked_baseline(&baseline, &rules, &Unscanned::default()); + + assert_eq!(local_deletions(&tracked, &local), vec!["old.md"]); + } + + /// Deletions come out shallowest first, so a trashed folder can take its + /// children with it. + #[test] + fn local_deletions_come_out_shallowest_first() { + let mut baseline = baseline_with("d/e/f.txt", 1, 1); + baseline.extend(baseline_with("d", 0, 0)); + baseline.extend(baseline_with("d/e", 0, 0)); + let local: HashMap = HashMap::new(); + let tracked = tracked_baseline(&baseline, &rules_for(&[]), &Unscanned::default()); + + assert_eq!( + local_deletions(&tracked, &local), + vec!["d", "d/e", "d/e/f.txt"] ); } @@ -2593,6 +2926,94 @@ mod tests { assert_eq!(reason, None); } + // ---- B198: going online-only never evicts what Drive does not have ----- + + #[test] + fn a_fully_synced_mirror_folder_may_go_online_only() { + let (root, baseline) = synced_subtree("evict-synced"); + let reason = mirror_tree_unsynced(&root, &baseline, &rules_for(&[])); + std::fs::remove_dir_all(&root).unwrap(); + assert_eq!(reason, None); + } + + /// The case that lost data: `.git/` is ignored by default, so no pass ever + /// uploads it, and the eviction deleted it with the rest. + #[test] + fn an_ignored_folder_only_this_disk_has_holds_the_switch_back() { + let (root, baseline) = synced_subtree("evict-ignored"); + std::fs::create_dir(root.join(".git")).unwrap(); + std::fs::write(root.join(".git/HEAD"), b"ref").unwrap(); + let reason = mirror_tree_unsynced(&root, &baseline, &rules_for(&[".git/"])); + std::fs::remove_dir_all(&root).unwrap(); + let reason = reason.expect("an ignored folder is only on this disk"); + assert!( + reason.contains(".git") && reason.contains("excluded"), + "{reason}" + ); + } + + /// An ignored row stays in the baseline for good once its file is gone, so + /// counting it as a deletion not carried up would hold the switch forever. + #[test] + fn a_deleted_ignored_path_does_not_hold_the_switch_back() { + let (root, mut baseline) = synced_subtree("evict-ignored-gone"); + baseline.insert( + "build/out.o".to_string(), + StoredSyncEntry { + rel_path: "build/out.o".to_string(), + ..baseline["dir/a.txt"].clone() + }, + ); + let rules = rules_for(&["build/"]); + let ignored = mirror_tree_unsynced(&root, &baseline, &rules); + let tracked = mirror_tree_unsynced(&root, &baseline, &rules_for(&[])); + std::fs::remove_dir_all(&root).unwrap(); + assert_eq!(ignored, None); + assert!(tracked.unwrap().contains("build/out.o")); + } + + #[test] + fn an_edit_after_the_last_pass_holds_the_switch_back() { + let (root, baseline) = synced_subtree("evict-edit"); + std::fs::write(root.join("dir/sub/b.txt"), b"edited since").unwrap(); + let reason = mirror_tree_unsynced(&root, &baseline, &rules_for(&[])); + std::fs::remove_dir_all(&root).unwrap(); + assert!(reason.unwrap().contains("dir/sub/b.txt")); + } + + #[test] + fn a_new_file_at_the_root_holds_the_switch_back() { + let (root, baseline) = synced_subtree("evict-new"); + std::fs::write(root.join("new.txt"), b"n").unwrap(); + let reason = mirror_tree_unsynced(&root, &baseline, &rules_for(&[])); + std::fs::remove_dir_all(&root).unwrap(); + assert!(reason.unwrap().contains("new.txt")); + } + + /// The folder B196 now leaves out of the pass instead of failing it is, + /// by the same token, something the eviction must not delete. + #[test] + fn an_unreadable_folder_holds_the_switch_back() { + use std::os::unix::fs::PermissionsExt as _; + + let (root, baseline) = synced_subtree("evict-eacces"); + let locked = root.join("dir/sub"); + std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o000)).unwrap(); + let reason = mirror_tree_unsynced(&root, &baseline, &rules_for(&[])); + std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o700)).unwrap(); + std::fs::remove_dir_all(&root).unwrap(); + assert!(reason.unwrap().contains("dir/sub")); + } + + #[test] + fn a_symlink_holds_the_switch_back() { + let (root, baseline) = synced_subtree("evict-symlink"); + std::os::unix::fs::symlink("dir/a.txt", root.join("link")).unwrap(); + let reason = mirror_tree_unsynced(&root, &baseline, &rules_for(&[])); + std::fs::remove_dir_all(&root).unwrap(); + assert!(reason.unwrap().contains("link")); + } + /// The same rule one level up: if the sync root itself cannot be read, the /// pass sees nothing, and "nothing" must never mean "delete everything". #[test] @@ -2607,7 +3028,14 @@ mod tests { let writing = HashSet::new(); let mut out = HashMap::new(); let mut progress = no_progress(); - let result = walk_local_tree(&root, &root, &rules, &writing, &mut out, &mut progress); + let result = walk_local_tree( + &root, + &rules, + &writing, + &mut out, + &mut Unscanned::default(), + &mut progress, + ); std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o700)).unwrap(); std::fs::remove_dir_all(&root).unwrap(); @@ -2628,30 +3056,40 @@ mod tests { let writing = HashSet::new(); let mut out = HashMap::new(); let mut progress = no_progress(); - assert!(walk_local_tree(&root, &root, &rules, &writing, &mut out, &mut progress).is_err()); + assert!( + walk_local_tree( + &root, + &rules, + &writing, + &mut out, + &mut Unscanned::default(), + &mut progress + ) + .is_err() + ); assert!(out.is_empty()); } - /// A name that is not valid UTF-8 cannot be keyed into the scan, so it - /// cannot be compared against the baseline either. Refusing the pass is the - /// only safe reading: skipping it would classify the file as deleted. + /// A name that is not valid UTF-8 cannot be keyed into the scan, but it + /// cannot be in the baseline or on Drive either, so leaving it out reads as + /// the deletion of nothing. It used to fail the pass (B196). #[test] - fn a_non_utf8_name_fails_the_scan() { + fn a_non_utf8_name_is_left_out_and_reported() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt as _; let root = sync_test_dir("scan-non-utf8"); std::fs::write(root.join(OsStr::from_bytes(b"bad\xffname")), b"x").unwrap(); + std::fs::write(root.join("good.txt"), b"y").unwrap(); - let rules = rules_for(&[]); - let writing = HashSet::new(); - let mut out = HashMap::new(); - let mut progress = no_progress(); - let result = walk_local_tree(&root, &root, &rules, &writing, &mut out, &mut progress); + let result = scan(&root); std::fs::remove_dir_all(&root).unwrap(); - let error = result.expect_err("a name the scan cannot key must abort the pass"); - assert!(error.contains("UTF-8"), "{error}"); + let (out, unscanned) = result.unwrap(); + assert_eq!(sorted_keys(&out), vec!["good.txt"]); + let report = unscanned.report(); + assert_eq!(report.len(), 1); + assert!(report[0].contains("UTF-8"), "{report:?}"); } /// The complement: a scan that *can* read everything reports everything, so @@ -2670,9 +3108,14 @@ mod tests { let writing = HashSet::new(); let mut out = HashMap::new(); let mut seen: Vec = Vec::new(); - let result = walk_local_tree(&root, &root, &rules, &writing, &mut out, &mut |rel| { - seen.push(rel.to_string()) - }); + let result = walk_local_tree( + &root, + &rules, + &writing, + &mut out, + &mut Unscanned::default(), + &mut |rel| seen.push(rel.to_string()), + ); std::fs::remove_dir_all(&root).unwrap(); result.unwrap(); diff --git a/crates/pdfs-fuse/src/sync/model.rs b/crates/pdfs-fuse/src/sync/model.rs index 81099ec..b1e3b42 100644 --- a/crates/pdfs-fuse/src/sync/model.rs +++ b/crates/pdfs-fuse/src/sync/model.rs @@ -98,6 +98,11 @@ pub(super) struct Outcome { pub(super) errors: usize, /// Files skipped because another process held them open for writing. pub(super) deferred: usize, + /// Paths the local scan could not read, left out of the pass. Not part of + /// [`is_empty`](Self::is_empty) or the summary: an unreadable folder stays + /// unreadable from one poll to the next, so the pass reports it once, when + /// the set changes, rather than on every poll. + pub(super) unscanned: Vec, } impl Outcome { diff --git a/crates/pdfs-fuse/src/sync/planner.rs b/crates/pdfs-fuse/src/sync/planner.rs index c918580..ce51994 100644 --- a/crates/pdfs-fuse/src/sync/planner.rs +++ b/crates/pdfs-fuse/src/sync/planner.rs @@ -90,6 +90,96 @@ pub(super) fn filter_baseline<'a, B>( .collect() } +/// The paths a local scan left out because it could not read them: a folder +/// it has no permission for, an entry that vanished between the listing and +/// its `stat`, a name that is not UTF-8. +/// +/// A left-out path is not a deleted one. A pass drops everything an entry +/// covers from classification, exactly as it drops ignored paths, so a +/// baseline row under an unreadable folder is neither read as a local deletion +/// nor answered with a download into a folder the pass cannot write. Failing +/// the whole pass instead is what B55 did, and it left one root-owned folder +/// stopping every other file in the tree from syncing (B196). +#[derive(Default)] +pub(super) struct Unscanned { + covered: HashSet, + /// The subset worth telling the user about, with why: an entry that only + /// vanished mid-scan is not, since the next pass sees it settled. + unreadable: Vec, +} + +impl Unscanned { + /// Leave out `rel`, which vanished while the scan looked at it. + pub(super) fn vanished(&mut self, rel: String) { + self.covered.insert(rel); + } + + /// Leave out `rel`, which cannot be read for `reason`. + pub(super) fn unreadable(&mut self, rel: String, reason: &str) { + self.unreadable.push(format!("{rel} ({reason})")); + self.covered.insert(rel); + } + + /// Whether `rel` is a left-out path or lies under one. + pub(super) fn covers(&self, rel: &str) -> bool { + if self.covered.is_empty() { + return false; + } + let mut at = rel; + loop { + if self.covered.contains(at) { + return true; + } + match at.rfind('/') { + Some(i) => at = &at[..i], + None => return false, + } + } + } + + /// The unreadable paths with their reasons, sorted, for the activity feed. + pub(super) fn report(&self) -> Vec { + let mut lines = self.unreadable.clone(); + lines.sort_unstable(); + lines + } +} + +/// The baseline paths a pass can see: [`filter_baseline`], minus everything +/// under a path the scan could not read. This is the set [`guard_local_wipe`] +/// checks and the set a push pass reads deletions from, since a path outside +/// it is absent from the local walk by rule or by failure, never by loss. +pub(super) fn tracked_baseline<'a, B>( + baseline: &'a HashMap, + rules: &IgnoreRules, + unscanned: &Unscanned, +) -> HashMap { + let mut tracked = filter_baseline(baseline, rules); + tracked.retain(|rel, _| !unscanned.covers(rel)); + tracked +} + +/// The baseline paths a push pass has to trash on Drive: tracked, and gone +/// from the local walk. Shallowest first, so a trashed folder takes its +/// children with it. +/// +/// Built from [`tracked_baseline`] rather than from the raw baseline. Reading +/// it raw trashed every synced path that later became ignored, because the walk +/// skips those, and the switch to on-demand that the push pass runs for then +/// deleted the local copy too (B197). +pub(super) fn local_deletions( + tracked: &HashMap, + local: &HashMap, +) -> Vec { + let mut missing: Vec = tracked + .keys() + .filter(|rel| !local.contains_key(*rel)) + .cloned() + .collect(); + missing.sort_by_key(|p| p.matches('/').count()); + missing +} + /// Refuse to run a pass whose local side has vanished in its entirety. /// /// When every baseline path is absent locally, the likely cause is an unavailable diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a0ea309..468e737 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -310,8 +310,10 @@ its changes go through the queue like My files. A filesystem watcher (`notify`) and a remote poll every 120 s feed a debounced pass (2 s, at most 30 s). Passes are serialized per daemon. A pass: -1. **Walks the local tree.** Any `readdir`, metadata or permission failure marks the scan - incomplete, and an incomplete scan cannot delete anything. +1. **Walks the local tree.** A path the user may not read, an entry that vanishes mid-scan and a + name that is not UTF-8 are left out, and the pass drops everything under them, as it drops + ignored paths, so a left-out path is never read as deleted. An unreadable root and any other + I/O error fail the pass. 2. **Walks the remote tree.** 3. **Loads the baseline**, the `sync_entry` rows: both sides as of the last successful pass. 4. **Classifies each path** by comparing local, remote and baseline, with `(mtime, size)` as the diff --git a/docs/BUGS.md b/docs/BUGS.md index 9cf6517..e3ced92 100644 --- a/docs/BUGS.md +++ b/docs/BUGS.md @@ -100,6 +100,9 @@ a live account since. | Entry | Title | |---|---| +| [B198](#b198--switching-to-online-only-deletes-local-files-that-never-reached-drive) | Switching to online-only deletes local files that never reached Drive | +| [B197](#b197--switching-to-online-only-trashes-synced-files-that-became-ignored) | Switching to online-only trashes synced files that became ignored | +| [B196](#b196--one-unreadable-folder-stops-the-whole-synced-folder-from-syncing) | One unreadable folder stops the whole synced folder from syncing | | [B195](#b195--the-app-crashes-when-proton-asks-for-a-captcha-on-secureblue) | The app crashes when Proton asks for a CAPTCHA on secureblue | | [B193](#b193--deleting-a-file-whose-create-failed-can-trash-a-file-of-the-users-on-drive) | Deleting a file whose create failed can trash a file of the user's on Drive | | [B191](#b191--a-file-deleted-after-its-create-failed-and-was-written-again-can-stay-on-drive) | A file deleted after its create failed and was written again can stay on Drive | @@ -298,6 +301,95 @@ a live account since. Newest first. +### B198 — Switching to online-only deletes local files that never reached Drive + +**Status:** Fixed (unverified). +**Found:** 2026-10-08, reading the switch while fixing B196. +**Where:** `crates/pdfs-fuse/src/devices.rs`, `Core::apply_sync_folder_mode`; +`crates/pdfs-fuse/src/sync.rs`, `mirror_tree_unsynced` + +**Repro:** Mirror a project folder with a `.git/` in it, then switch it to **Online only**. +`.git/` is gone from the disk and was never on Drive. + +**Cause.** The switch evicts the whole local tree once the folder is `idle`. `idle` only says the +last pass applied everything it tried, and a pass never tries an ignored path (`.git/` is ignored +by default), a symlink, or a folder it cannot read (B196). It also defers a file that is open for +writing without leaving `idle`, and a file can change after the pass ends. The eviction deleted +all of those. + +**Fix.** Before the mode is written and anything is evicted, the switch walks the folder against +the baseline, as a move out of a mirror folder already does, and refuses when something is only on +this disk. The message names the path; an ignored one is called excluded from sync. A tracked file +deleted here but not yet on Drive also refuses. An ignored one does not, since its baseline row +stays for good and would hold the switch forever. + +**Test:** unit tests `a_fully_synced_mirror_folder_may_go_online_only`, +`an_ignored_folder_only_this_disk_has_holds_the_switch_back`, +`a_deleted_ignored_path_does_not_hold_the_switch_back`, +`an_edit_after_the_last_pass_holds_the_switch_back`, `a_new_file_at_the_root_holds_the_switch_back`, +`an_unreadable_folder_holds_the_switch_back` and `a_symlink_holds_the_switch_back`. The switch +itself has not been driven against a live account. + +--- + +### B197 — Switching to online-only trashes synced files that became ignored + +**Status:** Fixed (unverified). +**Found:** 2026-10-08, reading the push pass while fixing B196. +**Where:** `crates/pdfs-fuse/src/sync.rs`, `Core::push_pass`; `crates/pdfs-fuse/src/sync/planner.rs`, +`local_deletions` + +**Repro:** Mirror a folder until `build/` is on Drive, add `build/` to `.pdfsignore`, then switch +the folder to **Online only**. `build/` goes to the Drive trash, and the switch then deletes the +local copy. + +**Cause.** The push pass that runs before the switch read its deletions as "in the baseline, not in +the local walk". The walk skips ignored paths, so every synced path that later became ignored read +as deleted. A full pass does not do this: it drops ignored paths from classification, which is +what the user guide promises ("Ignoring never deletes anything"). + +**Fix.** The push pass reads its deletions from the same filtered baseline the wipe guard checks: +without ignored paths, and without paths the scan could not read (B196). + +**Test:** unit tests `a_push_pass_never_reads_an_ignored_path_as_deleted` and +`local_deletions_come_out_shallowest_first`. + +--- + +### B196 — One unreadable folder stops the whole synced folder from syncing + +**Status:** Fixed (unverified). The reporter has not run the fix yet. +**Found:** 2026-10-08, [#33](https://github.com/narrrl/proton-drive-linux/issues/33). A synced +`~/Documents` held a restic cache a Docker container had created, which the user may not read. +Every pass failed with `Couldn't sync Documents: read …/restic/cache/…/snapshots: Permission +denied`, and nothing else in `Documents` synced. +**Where:** `crates/pdfs-fuse/src/sync.rs`, `walk_local_tree`; `crates/pdfs-fuse/src/sync/planner.rs`, +`Unscanned` + +**Repro:** In a mirror folder, `sudo mkdir locked && sudo chmod 700 locked`, then sync. + +**Cause.** B55 made every scan failure fatal to the pass, because a path left out of the scan read +as a local deletion and was trashed on Drive. That was safe, but a folder the user may not read is +not transient: the pass failed on every poll, for good. The same held for a file deleted between +the listing and its `stat`, which a busy folder hits now and then, and for a name that is not UTF-8. + +**Fix.** The scan leaves such a path out and records it, and the pass drops everything under it +from classification, the wipe guard and the push pass's deletions, as it already did for ignored +paths. A left-out path is then neither deleted on Drive nor downloaded into. A folder that lists +but does not allow `stat` is left out whole. An unreadable or absent root, and every other I/O +error, still fails the pass. The activity feed names the unreadable paths once each time the set +changes, and the folder stays `idle`. Adding the folder to `.pdfsignore` was already a workaround +and still silences the report. + +**Test:** unit tests `an_unreadable_subdirectory_is_left_out_and_the_rest_scans`, +`a_listable_but_unsearchable_folder_is_left_out_whole`, +`a_path_the_scan_left_out_is_never_a_local_deletion`, +`unscanned_covers_a_path_and_what_is_under_it_but_not_its_siblings` and +`a_non_utf8_name_is_left_out_and_reported`. `an_unreadable_root_fails_the_scan` and +`an_absent_root_fails_the_scan` still hold. + +--- + ### B195 — The app crashes when Proton asks for a CAPTCHA on secureblue **Status:** Fixed (unverified). Nobody has run the fix on secureblue yet. @@ -4822,6 +4914,10 @@ subdirectory holding a file, asserting the error names the subtree), strictness must not cost a working pass, so a healthy tree still reports every entry and still skips symlinks by design. +B196 later relaxed this: a path the user may not read, an entry that vanishes mid-scan and a name +that is not UTF-8 are left out of the pass instead of failing it, and the pass drops everything +under them from classification, so the B55 guarantee holds without the wedge. + Not covered: transient I/O errors, and an entry that disappears between `read_dir` and `stat`. Both are races that would need a filesystem shim to force; the code path they take is the same `symlink_metadata` failure the permission test drives. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cdf8744..52dd2fa 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -11,6 +11,20 @@ scratch (user data in `staging/` and `recovery/` is never touched by this). ## [Unreleased] +### Fixed +- **One unreadable folder stopped a synced folder from syncing at all.** A folder you may not + read, such as a cache a container created, failed every pass, so nothing else in the synced + folder reached Drive. Such a folder is now left out and named once in the activity feed, and + everything around it syncs. A file deleted during a scan, and a name that is not UTF-8, no + longer fail the pass either. + ([#33](https://github.com/narrrl/proton-drive-linux/issues/33)) +- **Switching a folder to online-only could delete files that were never on Drive.** Ignored + folders such as `.git/`, symlinks, unreadable folders and files changed after the last sync were + deleted with the local copy. The switch now refuses and names what is only on this computer. +- **Switching to online-only trashed synced files that had become ignored.** Adding a synced path + to `.pdfsignore` and then switching moved it to the Drive trash. Ignoring never deletes now, as + the user guide says. + ## [3.1.2] — 2026-10-08 Schema: **42**, unchanged. diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 7848828..6ee0406 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -96,7 +96,9 @@ app and in the web app. Each synced folder can also be switched to **Online only**. The local copy is replaced by a mount of the folder, so the files stay visible but take no disk space until opened. Switch with the **Synced / Online only** switch on the folder's card, or `pdfs sync mode ondemand` and back -with `pdfs sync mode mirror`. `pdfs locations` lists every local place the service occupies. +with `pdfs sync mode mirror`. The switch waits until everything in the folder is on Proton +Drive, and refuses while the folder holds something that never goes there: an ignored path such +as `.git/`, a symlink, or a folder you may not read. The message names it. `pdfs locations` lists every local place the service occupies. ### Moving between synced folders and My files @@ -146,6 +148,9 @@ places, and both apply: Rules are re-read on every pass. Ignoring never deletes anything: a file that becomes ignored is simply no longer tracked, and its copy on Proton Drive stays. +A folder you may not read, such as a cache a container created as root, is skipped the same way, +and the activity feed names it once. Add it to `.pdfsignore` to stop that message. + ## Changes waiting for Proton Drive A change you made in the Proton Drive folder exists only on this computer until it reaches From 3b80e9bc8f8852d8c2892df0677689541901ef9b Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 8 Oct 2026 21:26:56 +0200 Subject: [PATCH 04/11] test(core): make under-budget cache check deterministic The timing ratio between a near-empty and a 2500-entry index measured the CI runner's load and flaked on main. Plant an oversized index row behind the running total instead: any budget check that consults the index would evict and reseed, so the test fails without depending on wall-clock time. --- crates/pdfs-core/src/cache.rs | 40 +++++++++++------------------------ 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/crates/pdfs-core/src/cache.rs b/crates/pdfs-core/src/cache.rs index dde7458..225992c 100644 --- a/crates/pdfs-core/src/cache.rs +++ b/crates/pdfs-core/src/cache.rs @@ -2309,42 +2309,26 @@ mod tests { /// call also needs. #[test] fn budget_check_is_free_when_under_budget() { - use std::time::Instant; - // Cap far above what we store, so every store stays under budget. let (c, _d) = cache_capped(1 << 30); let payload = vec![0u8; 4096]; - - // Cost of a store against a near-empty index... - let t = Instant::now(); - for i in 0..500u64 { + for i in 0..8u64 { c.store_block(&uid("a"), 1, 1 << 30, blk(1 << 30, i), &payload) .unwrap(); } - let empty = t.elapsed() / 500; - // ...and against one with a few thousand entries in it. - for i in 500..2500u64 { - c.store_block(&uid("a"), 1, 1 << 30, blk(1 << 30, i), &payload) - .unwrap(); - } - let t = Instant::now(); - for i in 2500..3000u64 { - c.store_block(&uid("a"), 1, 1 << 30, blk(1 << 30, i), &payload) - .unwrap(); - } - let full = t.elapsed() / 500; + // Plant an index row far over the cap behind the running total's back, + // and older than every real block. Any budget check that consults the + // index would see the pool over budget, evict, and reseed the total. + c.db.cache_touch("planted", KIND_BLOCK, 1 << 40, 0).unwrap(); - println!("B4: store_block under budget — {empty:?} at ~0 entries, {full:?} at 2500"); - // A ratio, not a wall-clock bound: the claim is that the under-budget - // path does not grow with the index, and only a ratio tests that. An - // absolute threshold measures the machine's load instead, and this test - // runs alongside the rest of the suite. - assert!( - full < empty * 3, - "under-budget store should not scale with the index: \ - {empty:?} at ~0 entries vs {full:?} at 2500" - ); + c.store_block(&uid("a"), 1, 1 << 30, blk(1 << 30, 8), &payload) + .unwrap(); + + // Not a timing ratio: that measured the CI runner's load and flaked. + assert_eq!(c.block_bytes.load(Ordering::Relaxed), 9 * 4096); + let entries = c.db.cache_entries_by_kind(KIND_BLOCK).unwrap(); + assert_eq!(entries.len(), 10, "under-budget store evicted: {entries:?}"); } /// The running totals are a fast path, not a second source of truth: after From 922399b9a21a81a6e8dd73a0fc472616eb480f15 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 9 Oct 2026 02:18:47 +0200 Subject: [PATCH 05/11] fix(gui): even breadcrumb spacing, no invented hyphens in tile names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current folder in the breadcrumb trail was a bare label, so the "›" before it hugged it while the ones between buttons sat in their padding. One helper now builds the separator and the current segment for My Files, Shared with me and Computers, with a flat button's side padding. Grid tile names broke mid-word with a hyphen that is not in the name ("SteamSetup.e-xe"); turn Pango's inserted hyphens off. --- crates/pdfs-gui/resources/style.css | 3 +++ crates/pdfs-gui/src/app/main.rs | 1 + crates/pdfs-gui/src/app/pages/browser.rs | 11 ++-------- crates/pdfs-gui/src/app/pages/devices.rs | 11 ++-------- crates/pdfs-gui/src/app/pages/shared.rs | 11 ++-------- crates/pdfs-gui/src/app/widgets/crumb.rs | 21 ++++++++++++++++++++ crates/pdfs-gui/src/app/widgets/file_list.rs | 7 ++++++- crates/pdfs-gui/src/app/widgets/mod.rs | 1 + 8 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 crates/pdfs-gui/src/app/widgets/crumb.rs diff --git a/crates/pdfs-gui/resources/style.css b/crates/pdfs-gui/resources/style.css index 957d114..e46a333 100644 --- a/crates/pdfs-gui/resources/style.css +++ b/crates/pdfs-gui/resources/style.css @@ -48,6 +48,9 @@ .badge-cloud { color: color-mix(in srgb, currentColor 55%, transparent); } .badge-issue { color: var(--error-color); } .browser-statusbar { background-color: alpha(currentColor, 0.025); } +/* The folder on screen, last in the breadcrumb trail: a flat button's side + * padding, so the "›" before it is spaced like the ones between buttons. */ +.crumb-current { padding: 0 10px; } scale.browser-status-meter trough { min-width: 104px; min-height: 6px; } .bulk-bar { padding: 6px 8px; border-radius: 12px; background: color-mix(in srgb, var(--accent-bg-color) 12%, transparent); } .dropzone { border: 2px dashed alpha(currentColor, 0.25); border-radius: 16px; padding: 28px 18px; transition: background 160ms ease, border-color 160ms ease; } diff --git a/crates/pdfs-gui/src/app/main.rs b/crates/pdfs-gui/src/app/main.rs index 9bf08a5..8feb958 100644 --- a/crates/pdfs-gui/src/app/main.rs +++ b/crates/pdfs-gui/src/app/main.rs @@ -34,6 +34,7 @@ use pages::verify::*; use reload::{LoadTicket, Loader, replace_items}; use theme::{load_resources, proton_theme_active, set_proton_theme}; use widgets::compat::*; +use widgets::crumb::*; use widgets::details::*; use widgets::file_list::*; use widgets::menu::*; diff --git a/crates/pdfs-gui/src/app/pages/browser.rs b/crates/pdfs-gui/src/app/pages/browser.rs index d9c4e41..c7d2521 100644 --- a/crates/pdfs-gui/src/app/pages/browser.rs +++ b/crates/pdfs-gui/src/app/pages/browser.rs @@ -1220,9 +1220,7 @@ pub(crate) fn repaint_crumb(ui: &Rc, path: &str) { )); let mut acc = String::new(); for (i, seg) in segments.iter().enumerate() { - let sep = gtk4::Label::new(Some("›")); - sep.add_css_class("dim-label"); - ui.browser.crumb.append(&sep); + ui.browser.crumb.append(&crumb_separator()); acc = if acc.is_empty() { seg.to_string() } else { @@ -1237,12 +1235,7 @@ pub(crate) fn repaint_crumb(ui: &Rc, path: &str) { /// flat button that navigates to `target` (clearing any active search first). pub(crate) fn crumb_node(ui: &Rc, label: &str, target: &str, current: bool) -> gtk4::Widget { if current { - let l = gtk4::Label::builder() - .label(label) - .ellipsize(gtk4::pango::EllipsizeMode::Start) - .build(); - l.add_css_class("heading"); - return l.upcast(); + return crumb_current(label); } let button = gtk4::Button::builder().label(label).build(); button.add_css_class("flat"); diff --git a/crates/pdfs-gui/src/app/pages/devices.rs b/crates/pdfs-gui/src/app/pages/devices.rs index 14e26d7..cdd28c7 100644 --- a/crates/pdfs-gui/src/app/pages/devices.rs +++ b/crates/pdfs-gui/src/app/pages/devices.rs @@ -487,9 +487,7 @@ fn repaint_device_crumb(ui: &Rc, nav: &[(String, String)]) { nav.is_empty(), )); for (i, (_, name)) in nav.iter().enumerate() { - let sep = gtk4::Label::new(Some("›")); - sep.add_css_class("dim-label"); - crumb.append(&sep); + crumb.append(&crumb_separator()); crumb.append(&device_crumb_node(ui, name, i + 1, i + 1 == nav.len())); } } @@ -498,12 +496,7 @@ fn repaint_device_crumb(ui: &Rc, nav: &[(String, String)]) { /// that goes back up to `depth` levels below the device list. fn device_crumb_node(ui: &Rc, label: &str, depth: usize, current: bool) -> gtk4::Widget { if current { - let l = gtk4::Label::builder() - .label(label) - .ellipsize(gtk4::pango::EllipsizeMode::Start) - .build(); - l.add_css_class("heading"); - return l.upcast(); + return crumb_current(label); } let button = gtk4::Button::builder().label(label).build(); button.add_css_class("flat"); diff --git a/crates/pdfs-gui/src/app/pages/shared.rs b/crates/pdfs-gui/src/app/pages/shared.rs index 197d96b..efc1352 100644 --- a/crates/pdfs-gui/src/app/pages/shared.rs +++ b/crates/pdfs-gui/src/app/pages/shared.rs @@ -484,9 +484,7 @@ fn repaint_shared_crumb(ui: &Rc, nav: &[(String, String)]) { nav.is_empty(), )); for (i, (_, name)) in nav.iter().enumerate() { - let sep = gtk4::Label::new(Some("›")); - sep.add_css_class("dim-label"); - crumb.append(&sep); + crumb.append(&crumb_separator()); crumb.append(&shared_crumb_node(ui, name, i + 1, i + 1 == nav.len())); } } @@ -495,12 +493,7 @@ fn repaint_shared_crumb(ui: &Rc, nav: &[(String, String)]) { /// that goes back up to `depth` folders below the top level. fn shared_crumb_node(ui: &Rc, label: &str, depth: usize, current: bool) -> gtk4::Widget { if current { - let l = gtk4::Label::builder() - .label(label) - .ellipsize(gtk4::pango::EllipsizeMode::Start) - .build(); - l.add_css_class("heading"); - return l.upcast(); + return crumb_current(label); } let button = gtk4::Button::builder().label(label).build(); button.add_css_class("flat"); diff --git a/crates/pdfs-gui/src/app/widgets/crumb.rs b/crates/pdfs-gui/src/app/widgets/crumb.rs new file mode 100644 index 0000000..f99fbdf --- /dev/null +++ b/crates/pdfs-gui/src/app/widgets/crumb.rs @@ -0,0 +1,21 @@ +use crate::*; + +/// The "›" between two breadcrumb segments. +pub(crate) fn crumb_separator() -> gtk4::Label { + let sep = gtk4::Label::new(Some("›")); + sep.add_css_class("dim-label"); + sep +} + +/// The last breadcrumb segment, the folder on screen: a heading rather than a +/// button. It carries a flat button's side padding (`.crumb-current`), so the +/// "›" before it sits as far from it as from the button before. +pub(crate) fn crumb_current(label: &str) -> gtk4::Widget { + let l = gtk4::Label::builder() + .label(label) + .ellipsize(gtk4::pango::EllipsizeMode::Start) + .build(); + l.add_css_class("heading"); + l.add_css_class("crumb-current"); + l.upcast() +} diff --git a/crates/pdfs-gui/src/app/widgets/file_list.rs b/crates/pdfs-gui/src/app/widgets/file_list.rs index 3007ec9..dac98cf 100644 --- a/crates/pdfs-gui/src/app/widgets/file_list.rs +++ b/crates/pdfs-gui/src/app/widgets/file_list.rs @@ -132,8 +132,13 @@ impl FileList { // and the grid collapses to a single column. Allowing a mid-word // break is what keeps the two-line-then-ellipsis budget below // enforceable for *every* name rather than only the ones that - // happen to have spaces. + // happen to have spaces. Pango marks such a break with a hyphen + // that is not in the name ("SteamSetup.e-xe"); a file name is + // not prose, so break it bare, as Nautilus does. + let no_hyphens = gtk4::pango::AttrList::new(); + no_hyphens.insert(gtk4::pango::AttrInt::new_insert_hyphens(false)); let label = gtk4::Label::builder() + .attributes(&no_hyphens) .ellipsize(gtk4::pango::EllipsizeMode::End) .justify(gtk4::Justification::Center) .max_width_chars(13) diff --git a/crates/pdfs-gui/src/app/widgets/mod.rs b/crates/pdfs-gui/src/app/widgets/mod.rs index 3e4d1a4..e0f5620 100644 --- a/crates/pdfs-gui/src/app/widgets/mod.rs +++ b/crates/pdfs-gui/src/app/widgets/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod compat; +pub(crate) mod crumb; pub(crate) mod details; pub(crate) mod file_list; pub(crate) mod menu; From 7ebe8e5210f3f8d92f5f738daf53142453a81f62 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 9 Oct 2026 02:27:50 +0200 Subject: [PATCH 06/11] fix(fuse,core,gui): routine mirror scans no longer show as syncing Every mirror pass, the two-minute poll included, reported a "Checking {folder}" job, and the tray took any job for syncing, so its icon spun every two minutes over folders with nothing to do. JobItem gains a background flag (absent from older daemons, so false) for housekeeping that changes nothing in the user's files: a pass still scanning, the home index rebuild and the cache integrity check. The tray keeps them in its menu line but only turns busy for transfers or a job that is not background; a pass that queues work becomes a Syncing job and still spins the icon. The watcher also reconciles only the folder an event lies in, instead of walking every mirror folder over one edit. --- crates/pdfs-core/src/control.rs | 6 +++ crates/pdfs-fuse/src/background.rs | 2 +- crates/pdfs-fuse/src/devices.rs | 4 ++ crates/pdfs-fuse/src/lib.rs | 4 +- crates/pdfs-fuse/src/sync.rs | 28 ++++++++++- crates/pdfs-fuse/src/sync/engine.rs | 32 ++++++++++-- crates/pdfs-fuse/src/transfers.rs | 18 ++++++- crates/pdfs-gui/src/main.rs | 77 ++++++++++++++++++++++------- 8 files changed, 147 insertions(+), 24 deletions(-) diff --git a/crates/pdfs-core/src/control.rs b/crates/pdfs-core/src/control.rs index de904d0..ae0e043 100644 --- a/crates/pdfs-core/src/control.rs +++ b/crates/pdfs-core/src/control.rs @@ -1225,6 +1225,12 @@ pub struct JobItem { /// Steps known to need doing, or `0` when unknown (indeterminate progress). /// May *grow* mid-job as more work is discovered. pub total: u64, + /// Housekeeping that changes nothing in the user's files: a mirror folder + /// scan that finds nothing to do yet, an index rebuild, a cache check. Front + /// ends may show it, but should not report the drive as busy over it. Absent + /// from older daemons, which never set it. + #[serde(default)] + pub background: bool, } /// One in-flight transfer in a [`Response::Transfers`] snapshot. diff --git a/crates/pdfs-fuse/src/background.rs b/crates/pdfs-fuse/src/background.rs index 00772e6..7f14d5d 100644 --- a/crates/pdfs-fuse/src/background.rs +++ b/crates/pdfs-fuse/src/background.rs @@ -710,7 +710,7 @@ fn scan_local_once( // The walk has no idea how many files it will find, so the job counts what it // has seen and stays indeterminate. - let job = transfers.begin_job("Indexing this computer"); + let job = transfers.begin_background_job("Indexing this computer"); job.detail("Scanning your files"); let walked = localindex::scan(&[home], &excludes, |batch| { if let Err(e) = db.local_upsert_batch(generation, &batch) { diff --git a/crates/pdfs-fuse/src/devices.rs b/crates/pdfs-fuse/src/devices.rs index a432b34..476b4e0 100644 --- a/crates/pdfs-fuse/src/devices.rs +++ b/crates/pdfs-fuse/src/devices.rs @@ -657,12 +657,16 @@ impl Core { } else { p.total.max(p.done) as u64 }, + // Most passes find nothing; only one that queues work is + // the drive syncing. + background: true, }, SyncPhase::Applying => JobItem { title: format!("Syncing {folder}"), detail: p.current.clone(), done: p.done as u64, total: p.total.max(p.done) as u64, + background: false, }, }); } diff --git a/crates/pdfs-fuse/src/lib.rs b/crates/pdfs-fuse/src/lib.rs index d2a75b7..ff95fe1 100644 --- a/crates/pdfs-fuse/src/lib.rs +++ b/crates/pdfs-fuse/src/lib.rs @@ -1034,7 +1034,9 @@ impl Core { let core = self.clone(); std::thread::spawn(move || { - let job = core.transfers.begin_job("Checking the cache database"); + let job = core + .transfers + .begin_background_job("Checking the cache database"); job.detail("Verifying every page"); let problems = match core.db.integrity_check() { Ok(problems) => problems, diff --git a/crates/pdfs-fuse/src/sync.rs b/crates/pdfs-fuse/src/sync.rs index ae8d21e..abe74ba 100644 --- a/crates/pdfs-fuse/src/sync.rs +++ b/crates/pdfs-fuse/src/sync.rs @@ -50,7 +50,7 @@ mod planner; use engine::SYNC_CONCURRENCY; pub(crate) use engine::{SyncMsg, spawn}; #[cfg(test)] -use engine::{classify, settle_with}; +use engine::{classify, event_target, settle_with}; #[cfg(test)] use local_scan::is_write_mode; use local_scan::open_for_write_set; @@ -2097,6 +2097,32 @@ mod tests { (ids, started.elapsed()) } + #[test] + fn a_change_in_one_mirror_folder_reconciles_only_that_folder() { + let watched = vec![ + (PathBuf::from("/home/me/Docs"), 1), + (PathBuf::from("/home/me/Photos"), 2), + (PathBuf::from("/home/me/Photos/Raw"), 3), + ]; + let target = |paths: &[&str]| { + let paths: Vec = paths.iter().map(PathBuf::from).collect(); + match event_target(&watched, &paths) { + SyncMsg::Reconcile(id) => Some(id), + SyncMsg::ReconcileAll => None, + _ => panic!("unexpected message"), + } + }; + assert_eq!(target(&["/home/me/Docs/a.txt"]), Some(1)); + // A rename within one folder. + assert_eq!(target(&["/home/me/Docs/a", "/home/me/Docs/b"]), Some(1)); + // The deepest root wins. + assert_eq!(target(&["/home/me/Photos/Raw/x.cr2"]), Some(3)); + // Across folders, outside them all, or no path at all: walk everything. + assert_eq!(target(&["/home/me/Docs/a", "/home/me/Photos/a"]), None); + assert_eq!(target(&["/home/me/Docsx/a"]), None); + assert_eq!(target(&[]), None); + } + /// The bug this replaced a fixed sleep for: a save that keeps writing past /// the debounce used to be walked mid-write, uploading a torn snapshot as a /// real revision. The window has to restart on every event, so settling diff --git a/crates/pdfs-fuse/src/sync/engine.rs b/crates/pdfs-fuse/src/sync/engine.rs index 688aeb0..7755b5f 100644 --- a/crates/pdfs-fuse/src/sync/engine.rs +++ b/crates/pdfs-fuse/src/sync/engine.rs @@ -57,9 +57,10 @@ pub(crate) fn spawn(core: Core, rx: Receiver) -> Option) { // Paths the watcher currently covers, mapped to their folder id, so an event // path can be resolved back to the folder it belongs to. - let watched: Mutex> = Mutex::new(Vec::new()); + let watched: Arc>> = Arc::new(Mutex::new(Vec::new())); let tx_events = core.sync_tx.clone(); + let watched_events = watched.clone(); let mut watcher = match notify::recommended_watcher(move |res: notify::Result| { let Ok(event) = res else { return }; @@ -67,7 +68,8 @@ fn engine_loop(core: Core, rx: Receiver) { if matches!(event.kind, notify::EventKind::Access(_)) { return; } - let _ = tx_events.send(SyncMsg::ReconcileAll); + let msg = event_target(&watched_events.lock(), &event.paths); + let _ = tx_events.send(msg); }) { Ok(w) => w, Err(e) => { @@ -256,8 +258,31 @@ fn reconcile_all(core: &Core) { } } +/// The reconcile a watcher event asks for: the one mirror folder all its paths +/// lie in, so an edit in one folder does not walk every other one too. An event +/// that spans folders, or that no watched folder claims (a watch list a moment +/// out of date), falls back to every folder. +pub(super) fn event_target(watched: &[(PathBuf, i64)], paths: &[PathBuf]) -> SyncMsg { + let mut ids = paths.iter().map(|path| { + watched + .iter() + .filter(|(root, _)| path.starts_with(root)) + // The deepest root, should one mirror folder sit inside another. + .max_by_key(|(root, _)| root.components().count()) + .map(|(_, id)| *id) + }); + match ids.next() { + Some(Some(first)) if ids.all(|id| id == Some(first)) => SyncMsg::Reconcile(first), + _ => SyncMsg::ReconcileAll, + } +} + /// Bring the filesystem watches in line with the current mirror-folder set: /// watch newly-added folders, drop removed ones. +/// +/// The watch calls run on a copy, outside the lock: the watcher's event +/// callback takes the same lock, and a backend that hands a watch request to +/// its event thread and waits for the answer would deadlock against it. fn rewatch( core: &Core, watcher: &mut notify::RecommendedWatcher, @@ -274,7 +299,7 @@ fn rewatch( return; } }; - let mut have = watched.lock(); + let mut have = watched.lock().clone(); // Drop watches no longer wanted. have.retain(|(path, _)| { if want.iter().any(|(p, _)| p == path) { @@ -294,6 +319,7 @@ fn rewatch( Err(e) => warn!(path = %path.display(), error = %e, "sync: watch failed"), } } + *watched.lock() = have; } // ---- reconcile ------------------------------------------------------------ diff --git a/crates/pdfs-fuse/src/transfers.rs b/crates/pdfs-fuse/src/transfers.rs index 078f020..3e461b1 100644 --- a/crates/pdfs-fuse/src/transfers.rs +++ b/crates/pdfs-fuse/src/transfers.rs @@ -46,6 +46,8 @@ struct JobEntry { detail: Mutex, done: AtomicU64, total: AtomicU64, + /// Housekeeping that moves nothing; see [`JobItem::background`]. + background: bool, } /// The set of transfers and jobs currently in flight. Cloned `Arc`-style across @@ -160,12 +162,25 @@ impl TransferRegistry { /// that carries its progress and deregisters the job when dropped. The job /// starts indeterminate; give it a `total` once one is known. pub fn begin_job(self: &Arc, title: impl Into) -> JobGuard { + self.register_job(title.into(), false) + } + + /// [`TransferRegistry::begin_job`] for housekeeping the user did not ask + /// for and that changes nothing in their files, such as an index rebuild or + /// a cache check. It is reported, but front ends don't call the drive busy + /// over it. + pub fn begin_background_job(self: &Arc, title: impl Into) -> JobGuard { + self.register_job(title.into(), true) + } + + fn register_job(self: &Arc, title: String, background: bool) -> JobGuard { let id = self.next.fetch_add(1, Ordering::Relaxed); let entry = Arc::new(JobEntry { - title: title.into(), + title, detail: Mutex::new(String::new()), done: AtomicU64::new(0), total: AtomicU64::new(0), + background, }); self.jobs.lock().insert(id, entry.clone()); JobGuard { @@ -191,6 +206,7 @@ impl TransferRegistry { detail: e.detail.lock().clone(), done: e.done.load(Ordering::Relaxed), total: e.total.load(Ordering::Relaxed), + background: e.background, } }) .collect() diff --git a/crates/pdfs-gui/src/main.rs b/crates/pdfs-gui/src/main.rs index 27ae2d0..7e6cd8b 100644 --- a/crates/pdfs-gui/src/main.rs +++ b/crates/pdfs-gui/src/main.rs @@ -88,8 +88,9 @@ struct DriveState { /// Summarise a work snapshot into one menu line, or empty when idle. A single /// transfer names the file and its percentage; several collapse to counts so the /// menu stays one line regardless of queue depth. With nothing moving bytes, a -/// running job speaks for itself — a scan or an index rebuild is still "busy", -/// and the tray saying nothing there reads as "finished". +/// running job speaks for itself — a scan or an index rebuild is still work +/// going on, and the menu saying nothing there reads as "finished". The icon is +/// [`busy`]'s call, not this line's. fn sync_line(items: &[TransferItem], jobs: &[JobItem]) -> String { match items { [] => match jobs.first() { @@ -143,12 +144,12 @@ struct DriveTray { fn poll_state(socket: &Path, default_mountpoint: &Path) -> DriveState { match send(socket, &Request::Status) { Ok(status @ Response::Status { .. }) => { - // Same daemon is up, so a cheap follow-up request gives the sync line. - let sync = match send(socket, &Request::GetQueueStatus) { - Ok(Response::Transfers { items, jobs }) => sync_line(&items, &jobs), - _ => String::new(), + // Same daemon is up, so a cheap follow-up request says what moves. + let (items, jobs) = match send(socket, &Request::GetQueueStatus) { + Ok(Response::Transfers { items, jobs }) => (items, jobs), + _ => (Vec::new(), Vec::new()), }; - status_state(&status, sync, default_mountpoint) + status_state(&status, &items, &jobs, default_mountpoint) } // Socket answered but with something unexpected — treat as up but odd. Ok(_) => unexpected_reply(default_mountpoint), @@ -217,9 +218,14 @@ fn unexpected_reply(default_mountpoint: &Path) -> DriveState { } } -/// The state a running daemon's [`Response::Status`] describes, with `sync` -/// saying what it is moving right now. -fn status_state(status: &Response, sync: String, default_mountpoint: &Path) -> DriveState { +/// The state a running daemon's [`Response::Status`] describes, with `items` +/// and `jobs` saying what it is working on right now. +fn status_state( + status: &Response, + items: &[TransferItem], + jobs: &[JobItem], + default_mountpoint: &Path, +) -> DriveState { let Response::Status { mountpoint, pinned, @@ -239,7 +245,7 @@ fn status_state(status: &Response, sync: String, default_mountpoint: &Path) -> D paused, failing_ops, online, - queued.is_some() || !sync.is_empty(), + queued.is_some() || busy(items, jobs), ); DriveState { line: match (online, queued) { @@ -278,11 +284,19 @@ fn status_state(status: &Response, sync: String, default_mountpoint: &Path) -> D mounted: true, paused, mountpoint: PathBuf::from(mountpoint), - sync, + sync: sync_line(items, jobs), failing: failing_ops, } } +/// Whether the daemon is changing something: bytes moving, or a job the user +/// is waiting on. A mirror folder's routine scan and the like are background +/// jobs; they still get the menu line, but the icon spinning every two minutes +/// for a scan that finds nothing reads as constant syncing. +fn busy(items: &[TransferItem], jobs: &[JobItem]) -> bool { + !items.is_empty() || jobs.iter().any(|j| !j.background) +} + /// The phase a running mount is in. A pause is the user's own doing and says /// so first; failures outrank everything else the daemon reports. fn phase_of(paused: bool, failing: u64, online: bool, busy: bool) -> Phase { @@ -547,11 +561,7 @@ fn main() -> Result<()> { heard.take(feed); } let st = match &heard.status { - Some(status) => status_state( - status, - sync_line(&heard.items, &heard.jobs), - &default_mountpoint, - ), + Some(status) => status_state(status, &heard.items, &heard.jobs, &default_mountpoint), // Lost the daemon: ask once, which describes the sign-in instead. None => poll_state(&socket, &default_mountpoint), }; @@ -596,6 +606,39 @@ mod tests { assert_eq!(phase_of(false, 0, true, false), Phase::Synced); } + fn job(title: &str, background: bool) -> JobItem { + JobItem { + title: title.to_string(), + detail: String::new(), + done: 0, + total: 0, + background, + } + } + + #[test] + fn a_routine_scan_is_not_busy_but_a_sync_is() { + assert!(!busy(&[], &[])); + assert!(!busy(&[], &[job("Checking Documents", true)])); + assert!(busy( + &[], + &[ + job("Checking Documents", true), + job("Syncing Photos", false) + ] + )); + assert!(busy(&[], &[job("Uploading files", false)])); + } + + #[test] + fn a_job_from_an_older_daemon_counts_as_busy() { + let job: JobItem = serde_json::from_str( + r#"{"title":"Checking Documents","detail":"","done":0,"total":0}"#, + ) + .unwrap(); + assert!(!job.background); + } + #[test] fn the_issues_item_counts_in_the_singular_and_plural() { assert_eq!(issues_label(1), "1 issue — View"); From 9fb7e84d41533672c35c3122941dc6ff3e98cac5 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 9 Oct 2026 02:34:03 +0200 Subject: [PATCH 07/11] fix(fuse,gui): a stopped share leaves Shared by me at once (B199) Stop Sharing removes the link, invitations and members but cannot delete the share itself, and Drive keeps listing the empty share until its backend removes it, minutes later. The daemon now leaves a share with no member, invitation or link out of the listing (unless a lookup failed, so a live share is never hidden), and the app drops stopped items from the page as soon as the requests succeed instead of waiting for the slow reload. --- crates/pdfs-fuse/src/sharing.rs | 32 ++++++++++++------- crates/pdfs-gui/src/app/pages/shared_by_me.rs | 14 ++++++++ docs/BUGS.md | 27 ++++++++++++++++ 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/crates/pdfs-fuse/src/sharing.rs b/crates/pdfs-fuse/src/sharing.rs index c4875ca..4e429e1 100644 --- a/crates/pdfs-fuse/src/sharing.rs +++ b/crates/pdfs-fuse/src/sharing.rs @@ -660,6 +660,12 @@ impl Core { /// the shared uids; the per-node detail is then gathered best-effort — a single /// node racing with an unshare drops from the list rather than failing the whole /// request. + /// + /// A share with no member, no invitation and no link is left out: it is + /// shared with no one. Stop Sharing leaves exactly that behind, since the SDK + /// cannot delete a share, and Drive keeps listing it until its backend gets + /// round to removing it, minutes later. A share whose details could not all + /// be read stays listed, so a failed lookup never hides a live share. pub(crate) fn list_shared_by_me(&self) -> CoreResult> { let uids = self .rt @@ -678,24 +684,26 @@ impl Core { let members = self .rt .block_on(self.client.list_share_members(&uid)) - .map(|m| m.len()) - .unwrap_or(0); + .map(|m| m.len()); let proton_invites = self .rt .block_on(self.client.list_share_invitations(&uid)) - .map(|i| i.len()) - .unwrap_or(0); + .map(|i| i.len()); let external_invites = self .rt .block_on(self.client.list_external_invitations(&uid)) - .map(|i| i.len()) - .unwrap_or(0); - let link = self - .rt - .block_on(self.client.get_public_link(&uid)) - .ok() - .flatten() - .map(public_link_info); + .map(|i| i.len()); + let link = self.rt.block_on(self.client.get_public_link(&uid)); + if let (Ok(0), Ok(0), Ok(0), Ok(None)) = + (&members, &proton_invites, &external_invites, &link) + { + debug!(%uid, "shared by me: skipping an emptied share"); + continue; + } + let members = members.unwrap_or(0); + let proton_invites = proton_invites.unwrap_or(0); + let external_invites = external_invites.unwrap_or(0); + let link = link.ok().flatten().map(public_link_info); items.push(SharedItem { uid: uid.to_string(), is_dir: n.is_folder(), diff --git a/crates/pdfs-gui/src/app/pages/shared_by_me.rs b/crates/pdfs-gui/src/app/pages/shared_by_me.rs index 225d57c..c5e2eb6 100644 --- a/crates/pdfs-gui/src/app/pages/shared_by_me.rs +++ b/crates/pdfs-gui/src/app/pages/shared_by_me.rs @@ -565,6 +565,7 @@ pub(crate) fn prompt_stop_sharing(ui: &Rc, entries: &[DirEntry]) { let uids: Vec = entries.iter().map(|e| e.uid.clone()).collect(); dialog.connect_response(Some("stop"), move |_, _| { let done = done.clone(); + let stopped = uids.clone(); run_requests( &ui_c, uids.iter() @@ -572,6 +573,7 @@ pub(crate) fn prompt_stop_sharing(ui: &Rc, entries: &[DirEntry]) { .collect(), gettext_noop("Couldn't stop sharing"), move |ui| { + forget_shared_by_me(ui, &stopped); reload_listing(ui); toast(ui, &done); }, @@ -580,6 +582,18 @@ pub(crate) fn prompt_stop_sharing(ui: &Rc, entries: &[DirEntry]) { dialog.present(ui_window(ui).as_ref()); } +/// Take items whose sharing just stopped off the page now, rather than when +/// the reload that confirms it comes back: that reload asks after every share +/// in turn and takes a while with many of them. +fn forget_shared_by_me(ui: &Rc, uids: &[String]) { + let items = { + let mut items = ui.shared_by_me.items.borrow_mut(); + items.retain(|item| !uids.contains(&item.uid)); + items.clone() + }; + repaint_shared_by_me(ui, &items); +} + /// Open a shared item: a folder in My Files, a file the way My Files would. fn open_shared_by_me(ui: &Rc, entry: &DirEntry) { if entry.is_dir { diff --git a/docs/BUGS.md b/docs/BUGS.md index e3ced92..11c4899 100644 --- a/docs/BUGS.md +++ b/docs/BUGS.md @@ -81,6 +81,7 @@ original wording. | Entry | Title | What is left | |---|---|---| +| [B199](#b199--a-stopped-share-stays-on-the-shared-by-me-page-for-minutes) | A stopped share stays on the Shared by me page for minutes | The share itself is not deleted; the SDK has no call for it | | [B88](#b88--pdfs-pin-rename-move-delete-and-sharing-still-cannot-name-a-path-under-a-secondary-mount) | `pdfs pin`, `rename`, `move`, `delete` and sharing still cannot name a path under a secondary mount | `pin`, `unpin`, opening a file, thumbnails, uploads, sharing and versions | | [B72](#b72--pdfs-trash-never-returns) | `pdfs trash` never returns | The slowness of the first refresh is unmeasured | | [B71](#b71--conflict-sweep-is-not-production-ready-auto-trash-loop-crit-13) | Conflict sweep is not production-ready (auto-trash loop, CRIT-13) | Items 5 to 10 of the entry | @@ -301,6 +302,32 @@ a live account since. Newest first. +### B199 — A stopped share stays on the Shared by me page for minutes + +**Status:** Partly fixed. The share itself is still not deleted. +**Found:** 2026-10-09, reported from the app: after **Stop Sharing**, the row stayed, and an +older share showed with no people and no link. +**Where:** `crates/pdfs-fuse/src/sharing.rs`, `Core::stop_sharing_for_uid` and +`Core::list_shared_by_me`; `crates/pdfs-gui/src/app/pages/shared_by_me.rs`, `prompt_stop_sharing` + +**Repro:** Share a folder with a person, then choose **Stop Sharing…** on the Shared by me page. +The row stays, now with "—" for people and link, until a reload some minutes later. + +**Cause.** The Rust SDK has no call that deletes a share. Stop Sharing removes the public link, the +invitations and the members one by one, and leaves an empty share behind. Drive deletes an empty +share on its own, but only after a while, and until then `GET v2/volumes/{vid}/shares` still lists +it. The JS SDK ends `unshareNode` with `DELETE drive/shares/{shareId}?Force=1` for exactly this +reason. + +**Fix.** The daemon leaves a share with no member, no invitation and no link out of the Shared by +me listing, unless one of those lookups failed. The app also drops a stopped item from the page as +soon as the request succeeds, before the confirming reload. Left: a `delete_share` call in +`proton-drive-rs`, made at the end of `stop_sharing_for_uid`, so the share is really gone. + +**Test:** none automated; the listing needs a live account. + +--- + ### B198 — Switching to online-only deletes local files that never reached Drive **Status:** Fixed (unverified). From 085335ca14e633c80070e06968250779f448291a Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 9 Oct 2026 02:37:25 +0200 Subject: [PATCH 08/11] feat(gui): one main menu, on the sidebar's account row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sidebar header had a hamburger beside the brand, and the account row in the footer had its own ⋮ menu; both offered Preferences. The header button sat next to the page's own tools and read as one of them. The account row is now the main menu's button, as in Proton's own apps: Preferences, Keyboard Shortcuts and About, then Sign Out. The whole row opens it, upwards, and F10 still does as the primary menu. --- .../scalable/actions/pdfs-menu-symbolic.svg | 5 --- crates/pdfs-gui/resources/pdfs.gresource.xml | 1 - crates/pdfs-gui/resources/style.css | 4 +- crates/pdfs-gui/src/app/main.rs | 21 +---------- crates/pdfs-gui/src/app/pages/status.rs | 37 ++++++++++++------- 5 files changed, 28 insertions(+), 40 deletions(-) delete mode 100644 crates/pdfs-gui/resources/icons/scalable/actions/pdfs-menu-symbolic.svg diff --git a/crates/pdfs-gui/resources/icons/scalable/actions/pdfs-menu-symbolic.svg b/crates/pdfs-gui/resources/icons/scalable/actions/pdfs-menu-symbolic.svg deleted file mode 100644 index 5796a0f..0000000 --- a/crates/pdfs-gui/resources/icons/scalable/actions/pdfs-menu-symbolic.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/crates/pdfs-gui/resources/pdfs.gresource.xml b/crates/pdfs-gui/resources/pdfs.gresource.xml index 1026a5a..80b0b1b 100644 --- a/crates/pdfs-gui/resources/pdfs.gresource.xml +++ b/crates/pdfs-gui/resources/pdfs.gresource.xml @@ -14,7 +14,6 @@ scalable/actions/pdfs-details-pane-symbolic.svg scalable/actions/pdfs-link-symbolic.svg scalable/actions/pdfs-map-symbolic.svg - scalable/actions/pdfs-menu-symbolic.svg scalable/actions/pdfs-people-symbolic.svg scalable/actions/pdfs-share-symbolic.svg scalable/actions/pdfs-sync-symbolic.svg diff --git a/crates/pdfs-gui/resources/style.css b/crates/pdfs-gui/resources/style.css index e46a333..a21039a 100644 --- a/crates/pdfs-gui/resources/style.css +++ b/crates/pdfs-gui/resources/style.css @@ -18,7 +18,9 @@ .sidebar-status image.warning { color: var(--warning-color); } .sidebar-status image.error { color: var(--error-color); } .sidebar-quota progressbar trough, .sidebar-quota progressbar progress { min-height: 4px; } -.sidebar-account { padding: 4px 4px 4px 8px; } +/* The account row is the main menu's button: a row, not a pushbutton, so + * the name keeps the text weight. */ +button.sidebar-account { padding: 4px 8px; font-weight: normal; } /* A count of what waits on a page, at the end of its sidebar row */ .sidebar-badge { min-width: 10px; diff --git a/crates/pdfs-gui/src/app/main.rs b/crates/pdfs-gui/src/app/main.rs index 8feb958..7f8f67e 100644 --- a/crates/pdfs-gui/src/app/main.rs +++ b/crates/pdfs-gui/src/app/main.rs @@ -831,7 +831,6 @@ fn build_sidebar(footer: >k4::Box) -> (adw::NavigationPage, gtk4::ListBox) { let header = adw::HeaderBar::new(); header.set_title_widget(Some(&brand)); - header.pack_end(&build_primary_menu()); let scroll = gtk4::ScrolledWindow::builder() .hscrollbar_policy(gtk4::PolicyType::Never) @@ -1154,25 +1153,7 @@ fn toast_failure(ui: &Rc, what: &str, message: &str, kind: ErrorKind) { } } -/// The sidebar's primary (hamburger) menu: the app-level entries that don't -/// belong on any one page. -fn build_primary_menu() -> gtk4::MenuButton { - let menu = gio::Menu::new(); - menu.append(Some(&gettext("Preferences")), Some("win.preferences")); - menu.append(Some(&gettext("Keyboard Shortcuts")), Some("win.shortcuts")); - menu.append( - Some(&gettext("About Proton Drive for Linux")), - Some("win.about"), - ); - gtk4::MenuButton::builder() - .icon_name("pdfs-menu-symbolic") - .tooltip_text(gettext("Main menu")) - .primary(true) - .menu_model(&menu) - .build() -} - -/// Back the primary and account menus' entries with window actions. +/// Back the main menu's entries with window actions. fn install_window_actions(ui: &Rc, window: &adw::ApplicationWindow) { let preferences = gio::SimpleAction::new("preferences", None); let ui_prefs = ui.clone(); diff --git a/crates/pdfs-gui/src/app/pages/status.rs b/crates/pdfs-gui/src/app/pages/status.rs index adaece5..fab7142 100644 --- a/crates/pdfs-gui/src/app/pages/status.rs +++ b/crates/pdfs-gui/src/app/pages/status.rs @@ -381,23 +381,34 @@ pub(crate) fn build_main_page() -> MainWidgets { .hexpand(true) .ellipsize(gtk4::pango::EllipsizeMode::Middle) .build(); + // The app's one menu, on the account row the way Proton's own apps keep it: + // app-wide entries first, the account's last. The whole row opens it, and + // F10 too, as the primary menu. + let app_section = gio::Menu::new(); + app_section.append(Some(&gettext("Preferences")), Some("win.preferences")); + app_section.append(Some(&gettext("Keyboard Shortcuts")), Some("win.shortcuts")); + app_section.append( + Some(&gettext("About Proton Drive for Linux")), + Some("win.about"), + ); + let account_section = gio::Menu::new(); + account_section.append(Some(&gettext("Sign Out…")), Some("win.sign-out")); let account_menu = gio::Menu::new(); - account_menu.append(Some(&gettext("Preferences")), Some("win.preferences")); - let sign_out = gio::Menu::new(); - sign_out.append(Some(&gettext("Sign Out…")), Some("win.sign-out")); - account_menu.append_section(None, &sign_out); - let account_button = gtk4::MenuButton::builder() - .icon_name("view-more-symbolic") - .tooltip_text(gettext("Account")) + account_menu.append_section(None, &app_section); + account_menu.append_section(None, &account_section); + let row = gtk4::Box::new(gtk4::Orientation::Horizontal, 10); + row.append(&avatar); + row.append(&account_name); + row.append(>k4::Image::from_icon_name("view-more-symbolic")); + let account = gtk4::MenuButton::builder() + .child(&row) + .tooltip_text(gettext("Main menu")) .menu_model(&account_menu) - .valign(gtk4::Align::Center) + .direction(gtk4::ArrowType::Up) + .primary(true) .build(); - account_button.add_css_class("flat"); - let account = gtk4::Box::new(gtk4::Orientation::Horizontal, 10); + account.add_css_class("flat"); account.add_css_class("sidebar-account"); - account.append(&avatar); - account.append(&account_name); - account.append(&account_button); let footer = gtk4::Box::new(gtk4::Orientation::Vertical, 10); footer.add_css_class("sidebar-footer"); From 19854b2d76a4b1af2591c7029d3c08bd2593e4fe Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 9 Oct 2026 02:41:07 +0200 Subject: [PATCH 09/11] fix(gui): tidy the Share dialog Send Invitations and Create Public Link sat alone in boxed-list rows, which read as broken settings. They now sit under their section at its right edge. The role picker moves into the address row it applies to, people get avatars, a member needs no subtitle, and invitations say they are pending. The People and Public link sections show a loading row until the share is read, and one line instead of nothing when it can't be. The page is an AdwPreferencesPage, for the standard spacing. Translated in every catalog. --- .../pdfs-gui/src/app/widgets/share_dialog.rs | 122 ++- po/de.po | 956 +++++++++--------- po/en_GB.po | 956 +++++++++--------- po/es.po | 956 +++++++++--------- po/fr.po | 956 +++++++++--------- po/it.po | 956 +++++++++--------- po/nl.po | 956 +++++++++--------- po/pdfs.pot | 938 +++++++++-------- po/pl.po | 956 +++++++++--------- po/pt_BR.po | 956 +++++++++--------- 10 files changed, 4385 insertions(+), 4323 deletions(-) diff --git a/crates/pdfs-gui/src/app/widgets/share_dialog.rs b/crates/pdfs-gui/src/app/widgets/share_dialog.rs index d0e3c4e..8da0459 100644 --- a/crates/pdfs-gui/src/app/widgets/share_dialog.rs +++ b/crates/pdfs-gui/src/app/widgets/share_dialog.rs @@ -127,6 +127,26 @@ impl Drop for Busy { } } +/// A section's action, such as Send Invitations. It goes under the section's +/// rows, at their right edge, rather than in a row of its own: a row in a boxed +/// list reads as a setting, and a button alone in one looks misplaced. +fn group_button(label: &str) -> gtk4::Button { + let button = gtk4::Button::builder() + .label(label) + .halign(gtk4::Align::End) + .margin_top(12) + .build(); + button.add_css_class("suggested-action"); + button +} + +/// A section's only row while the share is being read. +fn loading_row() -> adw::ActionRow { + let row = adw::ActionRow::builder().title(gettext("Loading…")).build(); + row.add_prefix(&spinner()); + row +} + /// How an open Share dialog addresses its node. /// /// A node the browser is showing has a mountpoint-relative path. A node reached @@ -287,26 +307,17 @@ pub(crate) fn open_share_dialog(ui: &Rc, entry: &DirEntry) { .selected(0) .valign(gtk4::Align::Center) .build(); - let role_wrap = adw::ActionRow::builder().title(gettext("Role")).build(); - role_wrap.add_suffix(&role_drop); + // The role goes with the addresses it applies to, at the end of their row. + role_drop.set_tooltip_text(Some(&gettext("Role"))); + email_row.add_suffix(&role_drop); let message_row = adw::EntryRow::builder() .title(gettext("Message (optional)")) .build(); - let invite_btn = gtk4::Button::builder() - .label(gettext("Send Invitations")) - .halign(gtk4::Align::End) - .margin_top(6) - .build(); - invite_btn.add_css_class("suggested-action"); + let invite_btn = group_button(&gettext("Send Invitations")); invite_btn.set_sensitive(false); - let invite_wrap = adw::PreferencesRow::builder() - .activatable(false) - .child(&invite_btn) - .build(); invite_group.add(&email_row); - invite_group.add(&role_wrap); invite_group.add(&message_row); - invite_group.add(&invite_wrap); + invite_group.add(&invite_btn); let people = adw::PreferencesGroup::builder() .title(gettext("People with access")) @@ -315,21 +326,18 @@ pub(crate) fn open_share_dialog(ui: &Rc, entry: &DirEntry) { .title(gettext("Public link")) .build(); - let content = gtk4::Box::new(gtk4::Orientation::Vertical, 18); - content.set_margin_top(12); - content.set_margin_bottom(12); - content.set_margin_start(12); - content.set_margin_end(12); - content.append(&invite_group); - content.append(&people); - content.append(&link_group); - let clamp = adw::Clamp::builder().child(&content).build(); - let scroll = gtk4::ScrolledWindow::builder() - .vexpand(true) - .hscrollbar_policy(gtk4::PolicyType::Never) - .child(&clamp) - .build(); - toolbar.set_content(Some(&scroll)); + // Until the share is read, each section says so rather than standing + // empty under its title. + let people_loading = loading_row(); + people.add(&people_loading); + let link_loading = loading_row(); + link_group.add(&link_loading); + + let content = adw::PreferencesPage::new(); + content.add(&invite_group); + content.add(&people); + content.add(&link_group); + toolbar.set_content(Some(&content)); let dialog = adw::Dialog::builder() .title(pgettext("verb", "Share")) @@ -343,8 +351,8 @@ pub(crate) fn open_share_dialog(ui: &Rc, entry: &DirEntry) { target, people, link_group, - people_rows: RefCell::new(Vec::new()), - link_rows: RefCell::new(Vec::new()), + people_rows: RefCell::new(vec![people_loading.upcast()]), + link_rows: RefCell::new(vec![link_loading.upcast()]), }); // Check the addresses as they are typed: a malformed one marks the row as @@ -430,17 +438,35 @@ pub(crate) fn share_dialog_reload(state: &Rc) { repaint_share_link(&state, link.as_ref()); } Ok(Ok(Response::Error { message, .. })) => { + share_dialog_unread(&state); toast_error(&state.ui, &gettext("Couldn't load sharing"), &message) } - _ => toast_error( - &state.ui, - &gettext("Couldn't load sharing"), - &gettext("The Proton Drive service didn't respond."), - ), + _ => { + share_dialog_unread(&state); + toast_error( + &state.ui, + &gettext("Couldn't load sharing"), + &gettext("The Proton Drive service didn't respond."), + ) + } } }); } +/// The share couldn't be read: what the sections showed (rows still loading, +/// or ones that may now be stale) gives way to one line that says so. +fn share_dialog_unread(state: &Rc) { + for row in state.people_rows.borrow_mut().drain(..) { + state.people.remove(&row); + } + for row in state.link_rows.borrow_mut().drain(..) { + state.link_group.remove(&row); + } + let row = dim_row(&gettext("Couldn't load sharing")); + state.people.add(&row); + state.people_rows.borrow_mut().push(row.upcast()); +} + /// Rebuild the "People with access" rows from a fresh share listing. pub(crate) fn repaint_share_people(state: &Rc, entries: &[ShareEntry]) { for row in state.people_rows.borrow_mut().drain(..) { @@ -455,15 +481,18 @@ pub(crate) fn repaint_share_people(state: &Rc, entries: &[ShareEntr return; } for entry in entries { + // A member is the plain case and needs no word; an invitation says + // it is still waiting on the other side. let subtitle = match entry.kind { - ShareEntryKind::Member => gettext("Member"), - ShareEntryKind::ProtonInvite => gettext("Invited (pending)"), - ShareEntryKind::ExternalInvite => gettext("Invited (external, pending)"), + ShareEntryKind::Member => String::new(), + ShareEntryKind::ProtonInvite => gettext("Invitation pending"), + ShareEntryKind::ExternalInvite => gettext("Invitation pending, no Proton account yet"), }; let row = adw::ActionRow::builder() .title(&entry.email) .subtitle(&subtitle) .build(); + row.add_prefix(&adw::Avatar::new(32, Some(&entry.email), true)); // External invites can't have their role changed; show it read-only. // Members and Proton invites get a role dropdown. @@ -653,16 +682,7 @@ pub(crate) fn repaint_share_link(state: &Rc, link: Option<&PublicLi .build(); let expiry_row = adw::ActionRow::builder().title(gettext("Expires")).build(); expiry_row.add_suffix(&expiry_drop); - let create = gtk4::Button::builder() - .label(gettext("Create Public Link")) - .halign(gtk4::Align::End) - .margin_top(6) - .build(); - create.add_css_class("suggested-action"); - let create_wrap = adw::PreferencesRow::builder() - .activatable(false) - .child(&create) - .build(); + let create = group_button(&gettext("Create Public Link")); // Enter in the password field creates the link. let btn = create.clone(); @@ -689,11 +709,11 @@ pub(crate) fn repaint_share_link(state: &Rc, link: Option<&PublicLi state.link_group.add(&role_row); state.link_group.add(&pw_row); state.link_group.add(&expiry_row); - state.link_group.add(&create_wrap); + state.link_group.add(&create); rows.push(role_row.upcast()); rows.push(pw_row.upcast()); rows.push(expiry_row.upcast()); - rows.push(create_wrap.upcast()); + rows.push(create.upcast()); } } *state.link_rows.borrow_mut() = rows; diff --git a/po/de.po b/po/de.po index bdccd1d..fa14a00 100644 --- a/po/de.po +++ b/po/de.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 2.1.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-08 13:23+0200\n" +"POT-Creation-Date: 2026-10-09 02:38+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: German\n" @@ -57,50 +57,50 @@ msgstr "%-d. %b %Y" msgid "%A, %B %-d, %Y, %H:%M" msgstr "%A, %-d. %B %Y, %H:%M" -#: crates/pdfs-gui/src/app/main.rs:760 +#: crates/pdfs-gui/src/app/main.rs:761 #: crates/pdfs-gui/src/app/pages/activity_text.rs:539 #: crates/pdfs-gui/src/app/pages/browser.rs:454 #: crates/pdfs-gui/src/app/pages/browser.rs:819 #: crates/pdfs-gui/src/app/pages/browser.rs:1217 -#: crates/pdfs-gui/src/app/pages/browser.rs:2090 -#: crates/pdfs-gui/src/app/pages/browser.rs:2167 +#: crates/pdfs-gui/src/app/pages/browser.rs:2083 +#: crates/pdfs-gui/src/app/pages/browser.rs:2160 #: crates/pdfs-gui/src/app/pages/locations.rs:1624 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:252 #: crates/pdfs-gui/src/app/pages/trash.rs:191 crates/pdfs-gui/src/prompt.rs:171 msgid "My files" msgstr "Meine Dateien" -#: crates/pdfs-gui/src/app/main.rs:763 crates/pdfs-gui/src/app/main.rs:1407 +#: crates/pdfs-gui/src/app/main.rs:764 crates/pdfs-gui/src/app/main.rs:1389 #: crates/pdfs-gui/src/app/pages/albums.rs:638 #: crates/pdfs-gui/src/app/pages/photos.rs:842 #: crates/pdfs-gui/src/app/pages/places.rs:441 msgid "Photos" msgstr "Fotos" -#: crates/pdfs-gui/src/app/main.rs:768 +#: crates/pdfs-gui/src/app/main.rs:769 #: crates/pdfs-gui/src/app/pages/shared.rs:168 #: crates/pdfs-gui/src/app/pages/shared.rs:482 msgid "Shared with me" msgstr "Mit mir geteilt" -#: crates/pdfs-gui/src/app/main.rs:773 +#: crates/pdfs-gui/src/app/main.rs:774 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120 msgid "Shared by me" msgstr "Von mir geteilt" -#: crates/pdfs-gui/src/app/main.rs:776 +#: crates/pdfs-gui/src/app/main.rs:777 #: crates/pdfs-gui/src/app/pages/devices.rs:140 #: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "Computer" -#: crates/pdfs-gui/src/app/main.rs:777 +#: crates/pdfs-gui/src/app/main.rs:778 #: crates/pdfs-gui/src/app/pages/trash.rs:101 msgid "Trash" msgstr "Papierkorb" #. Translators: sidebar entry for the page listing this computer's synced folders. -#: crates/pdfs-gui/src/app/main.rs:779 +#: crates/pdfs-gui/src/app/main.rs:780 msgid "Sync" msgstr "Synchronisierung" @@ -132,29 +132,11 @@ msgstr "Etwas anderes hat dies zuerst geändert" msgid "Your Proton Drive is full" msgstr "Das Proton Drive ist voll" -#: crates/pdfs-gui/src/app/main.rs:1160 crates/pdfs-gui/src/app/main.rs:1389 -#: crates/pdfs-gui/src/app/pages/status.rs:385 -msgid "Preferences" -msgstr "Einstellungen" - -#: crates/pdfs-gui/src/app/main.rs:1161 -#: crates/pdfs-gui/src/app/widgets/compat.rs:262 -msgid "Keyboard Shortcuts" -msgstr "Tastenkürzel" - -#: crates/pdfs-gui/src/app/main.rs:1163 -msgid "About Proton Drive for Linux" -msgstr "Info zu Proton Drive for Linux" - -#: crates/pdfs-gui/src/app/main.rs:1168 -msgid "Main menu" -msgstr "Hauptmenü" - -#: crates/pdfs-gui/src/app/main.rs:1224 +#: crates/pdfs-gui/src/app/main.rs:1206 msgid "Proton Drive for Linux" msgstr "Proton Drive for Linux" -#: crates/pdfs-gui/src/app/main.rs:1232 +#: crates/pdfs-gui/src/app/main.rs:1214 msgid "" "Files-on-demand Proton Drive for the Linux desktop.\n" "\n" @@ -166,23 +148,23 @@ msgstr "" "Inoffizieller Client – nicht mit Proton AG verbunden, von Proton AG " "unterstützt oder befürwortet." -#: crates/pdfs-gui/src/app/main.rs:1237 +#: crates/pdfs-gui/src/app/main.rs:1219 msgid "Source Code" msgstr "Quellcode" -#: crates/pdfs-gui/src/app/main.rs:1241 +#: crates/pdfs-gui/src/app/main.rs:1223 msgid "Proton Terms of Service" msgstr "Nutzungsbedingungen von Proton" -#: crates/pdfs-gui/src/app/main.rs:1245 +#: crates/pdfs-gui/src/app/main.rs:1227 msgid "Proton Privacy Policy" msgstr "Datenschutzerklärung von Proton" -#: crates/pdfs-gui/src/app/main.rs:1250 +#: crates/pdfs-gui/src/app/main.rs:1232 msgid "Disclaimer" msgstr "Haftungsausschluss" -#: crates/pdfs-gui/src/app/main.rs:1254 +#: crates/pdfs-gui/src/app/main.rs:1236 msgid "" "This is an unofficial, community-built client. It is not affiliated with, " "endorsed by, or supported by Proton AG.\n" @@ -207,7 +189,7 @@ msgstr "" "bereitgestellt. Daten, deren Verlust nicht hinnehmbar ist, sollten " "zusätzlich gesichert werden." -#: crates/pdfs-gui/src/app/main.rs:1262 +#: crates/pdfs-gui/src/app/main.rs:1244 msgid "" "Places are named with data from GeoNames, licensed under GeoNames, lizenziert unter CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -227,7 +209,7 @@ msgstr "" "href=\"https://www.naturalearthdata.com/\">Natural Earth gezeichnet, die " "gemeinfrei sind." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, licensed under CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -224,7 +206,7 @@ msgstr "" "\">Natural Earth land outlines and country borders, which are in the " "public domain." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, con licencia CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -225,7 +207,7 @@ msgstr "" "fronteras de países de Natural " "Earth, que son de dominio público." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, sous licence CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -226,7 +208,7 @@ msgstr "" "frontières des pays de Natural " "Earth, qui sont dans le domaine public." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, con licenza CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -224,7 +206,7 @@ msgstr "" "confini nazionali di Natural " "Earth, che sono di pubblico dominio." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, gelicentieerd onder CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -225,7 +207,7 @@ msgstr "" "href=\"https://www.naturalearthdata.com/\">Natural Earth, die in het " "publieke domein vallen." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the \n" "Language-Team: LANGUAGE \n" @@ -60,50 +60,50 @@ msgstr "" msgid "%A, %B %-d, %Y, %H:%M" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:760 +#: crates/pdfs-gui/src/app/main.rs:761 #: crates/pdfs-gui/src/app/pages/activity_text.rs:539 #: crates/pdfs-gui/src/app/pages/browser.rs:454 #: crates/pdfs-gui/src/app/pages/browser.rs:819 #: crates/pdfs-gui/src/app/pages/browser.rs:1217 -#: crates/pdfs-gui/src/app/pages/browser.rs:2090 -#: crates/pdfs-gui/src/app/pages/browser.rs:2167 +#: crates/pdfs-gui/src/app/pages/browser.rs:2083 +#: crates/pdfs-gui/src/app/pages/browser.rs:2160 #: crates/pdfs-gui/src/app/pages/locations.rs:1624 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:252 #: crates/pdfs-gui/src/app/pages/trash.rs:191 crates/pdfs-gui/src/prompt.rs:171 msgid "My files" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:763 crates/pdfs-gui/src/app/main.rs:1407 +#: crates/pdfs-gui/src/app/main.rs:764 crates/pdfs-gui/src/app/main.rs:1389 #: crates/pdfs-gui/src/app/pages/albums.rs:638 #: crates/pdfs-gui/src/app/pages/photos.rs:842 #: crates/pdfs-gui/src/app/pages/places.rs:441 msgid "Photos" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:768 +#: crates/pdfs-gui/src/app/main.rs:769 #: crates/pdfs-gui/src/app/pages/shared.rs:168 #: crates/pdfs-gui/src/app/pages/shared.rs:482 msgid "Shared with me" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:773 +#: crates/pdfs-gui/src/app/main.rs:774 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120 msgid "Shared by me" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:776 +#: crates/pdfs-gui/src/app/main.rs:777 #: crates/pdfs-gui/src/app/pages/devices.rs:140 #: crates/pdfs-gui/src/app/pages/devices.rs:485 msgid "Computers" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:777 +#: crates/pdfs-gui/src/app/main.rs:778 #: crates/pdfs-gui/src/app/pages/trash.rs:101 msgid "Trash" msgstr "" #. Translators: sidebar entry for the page listing this computer's synced folders. -#: crates/pdfs-gui/src/app/main.rs:779 +#: crates/pdfs-gui/src/app/main.rs:780 msgid "Sync" msgstr "" @@ -135,29 +135,11 @@ msgstr "" msgid "Your Proton Drive is full" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1160 crates/pdfs-gui/src/app/main.rs:1389 -#: crates/pdfs-gui/src/app/pages/status.rs:385 -msgid "Preferences" -msgstr "" - -#: crates/pdfs-gui/src/app/main.rs:1161 -#: crates/pdfs-gui/src/app/widgets/compat.rs:262 -msgid "Keyboard Shortcuts" -msgstr "" - -#: crates/pdfs-gui/src/app/main.rs:1163 -msgid "About Proton Drive for Linux" -msgstr "" - -#: crates/pdfs-gui/src/app/main.rs:1168 -msgid "Main menu" -msgstr "" - -#: crates/pdfs-gui/src/app/main.rs:1224 +#: crates/pdfs-gui/src/app/main.rs:1206 msgid "Proton Drive for Linux" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1232 +#: crates/pdfs-gui/src/app/main.rs:1214 msgid "" "Files-on-demand Proton Drive for the Linux desktop.\n" "\n" @@ -165,23 +147,23 @@ msgid "" "AG." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1237 +#: crates/pdfs-gui/src/app/main.rs:1219 msgid "Source Code" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1241 +#: crates/pdfs-gui/src/app/main.rs:1223 msgid "Proton Terms of Service" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1245 +#: crates/pdfs-gui/src/app/main.rs:1227 msgid "Proton Privacy Policy" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1250 +#: crates/pdfs-gui/src/app/main.rs:1232 msgid "Disclaimer" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1254 +#: crates/pdfs-gui/src/app/main.rs:1236 msgid "" "This is an unofficial, community-built client. It is not affiliated with, " "endorsed by, or supported by Proton AG.\n" @@ -194,21 +176,21 @@ msgid "" "of any data you cannot afford to lose." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1262 +#: crates/pdfs-gui/src/app/main.rs:1244 msgid "" "Places are named with data from GeoNames, licensed under CC BY 4.0." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " "public domain." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames udostępnionych na licencji CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -228,7 +210,7 @@ msgstr "" "www.naturalearthdata.com/\">Natural Earth, które należą do domeny " "publicznej." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, licenciados sob CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1270 +#: crates/pdfs-gui/src/app/main.rs:1252 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -224,7 +206,7 @@ msgstr "" "fronteiras de países do Natural Earth, que são de domínio público." -#: crates/pdfs-gui/src/app/main.rs:1278 +#: crates/pdfs-gui/src/app/main.rs:1260 msgid "" "The street map, when turned on, comes from OpenFreeMap in the Date: Fri, 9 Oct 2026 02:44:00 +0200 Subject: [PATCH 10/11] fix(gui): context menus size to fit instead of scrolling A popover is sized and placed again when its parent's layout manager allocates it. Context menus were parented to the outermost list view, and the keyboard menu to the ScrolledWindow; neither has a layout manager, so a menu kept the size it was first given and often opened too short, with a scrollbar. The host is now the first widget from the list view up that has a layout manager, which still keeps menus out of list items and the theme rules that style their insides. --- crates/pdfs-gui/src/app/widgets/menu.rs | 32 ++++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/crates/pdfs-gui/src/app/widgets/menu.rs b/crates/pdfs-gui/src/app/widgets/menu.rs index 74dc19f..0e55a15 100644 --- a/crates/pdfs-gui/src/app/widgets/menu.rs +++ b/crates/pdfs-gui/src/app/widgets/menu.rs @@ -128,20 +128,34 @@ impl ActionMenu { } } -/// Where a menu opened over `anchor` should live in the widget tree: the -/// outermost grid or list view around it, or `anchor` itself. CSS descendant -/// selectors reach through a popover to its parent, and themes style the -/// insides of list items (`gridview > child box { margin: 12px }` in -/// Catppuccin); a menu parented to a tile picks those rules up and puts a wide -/// margin around every section. Under the view itself it is no item's child. +/// Where a menu opened over `anchor` should live in the widget tree. +/// +/// Not inside a list item: CSS descendant selectors reach through a popover to +/// its parent, and themes style the insides of list items (`gridview > child +/// box { margin: 12px }` in Catppuccin); a menu parented to a tile picks those +/// rules up and puts a wide margin around every section. So the search starts +/// at the outermost grid or list view around `anchor`. +/// +/// Not on a widget without a layout manager either: a popover is sized and +/// placed again when its parent's layout manager allocates it. The list views +/// and `ScrolledWindow` have none, so a menu parented there kept the size it was +/// first given, often too small, and opened scrolling. The host is the first +/// widget from there up that has one. fn popover_host(anchor: >k4::Widget) -> gtk4::Widget { - let mut host = anchor.clone(); + let mut start = anchor.clone(); let mut widget = anchor.parent(); while let Some(w) = widget { widget = w.parent(); if w.is::() || w.is::() || w.is::() { - host = w; + start = w; } } - host + let mut host = Some(start.clone()); + while let Some(w) = host { + if w.layout_manager().is_some() { + return w; + } + host = w.parent(); + } + start } From c4847c4d52d471d907620e6db565a26b12a8e5c1 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 9 Oct 2026 02:56:26 +0200 Subject: [PATCH 11/11] chore(release): 3.2.0 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- docs/CHANGELOG.md | 24 +++++++++++++++++++++++- packaging/PKGBUILD | 2 +- po/pdfs.pot | 2 +- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed5509b..1eb4a04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3474,7 +3474,7 @@ checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" [[package]] name = "pdfs-cli" -version = "3.1.2" +version = "3.2.0" dependencies = [ "anyhow", "clap", @@ -3490,7 +3490,7 @@ dependencies = [ [[package]] name = "pdfs-core" -version = "3.1.2" +version = "3.2.0" dependencies = [ "async-trait", "base64 0.23.1", @@ -3516,7 +3516,7 @@ dependencies = [ [[package]] name = "pdfs-fuse" -version = "3.1.2" +version = "3.2.0" dependencies = [ "async-trait", "base64 0.23.1", @@ -3538,7 +3538,7 @@ dependencies = [ [[package]] name = "pdfs-gui" -version = "3.1.2" +version = "3.2.0" dependencies = [ "anyhow", "async-channel", diff --git a/Cargo.toml b/Cargo.toml index c4ec5bd..7092855 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["crates/pdfs-core", "crates/pdfs-fuse", "crates/pdfs-cli", "crates/pdfs-gui"] [workspace.package] -version = "3.1.2" +version = "3.2.0" edition = "2024" license = "MIT" authors = ["Nils Pukropp "] diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index bc4f752..6126c49 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -11,6 +11,10 @@ scratch (user data in `staging/` and `recovery/` is never touched by this). ## [Unreleased] +## [3.2.0] — 2026-10-08 + +Schema: **42**, unchanged. + ### Fixed - **One unreadable folder stopped a synced folder from syncing at all.** A folder you may not read, such as a cache a container created, failed every pass, so nothing else in the synced @@ -24,7 +28,23 @@ scratch (user data in `staging/` and `recovery/` is never touched by this). - **Switching to online-only trashed synced files that had become ignored.** Adding a synced path to `.pdfsignore` and then switching moved it to the Drive trash. Ignoring never deletes now, as the user guide says. - +- **A stopped share stayed on the Shared by me page for minutes.** Stop Sharing now takes it off + the page at once, and an emptied share no longer shows with no people and no link. +- **The tray icon showed syncing every two minutes.** The routine check of a synced folder no + longer counts as syncing. Only work that changes files spins the icon. An edit in one synced + folder also no longer rescans all the others. +- **Right-click menus sometimes opened too short and had to be scrolled.** +- **The Share dialog looked unfinished.** Its buttons now sit under their sections instead of in a + row of their own. The role is chosen beside the addresses, people show with avatars, and both + sections show that they are loading. +- **Breadcrumbs spaced the "›" unevenly, and grid file names were broken with hyphens they do not + have.** + +### Changed +- **One main menu.** The menu button beside "Proton Drive" is gone. Preferences, Keyboard + Shortcuts, About and Sign Out are all in the account row at the bottom of the sidebar. F10 opens + it. + ### Added - **Move another computer's backup to this one.** **Move to This Computer…** on the Computers page, or `pdfs devices migrate UID`, moves every folder another computer backs up, for example an old @@ -1686,6 +1706,8 @@ First stable release: FUSE files-on-demand mount, sync daemon under `proton-driv - The outstanding FUSE defects tracked in `docs/BUGS.md`, plus a truncate defect, validated by a new POSIX compliance suite for the filesystem. +[3.2.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v3.2.0 + [3.1.2]: https://github.com/narrrl/proton-drive-linux/releases/tag/v3.1.2 [3.1.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v3.1.1 diff --git a/packaging/PKGBUILD b/packaging/PKGBUILD index fbb2a0f..372b02f 100644 --- a/packaging/PKGBUILD +++ b/packaging/PKGBUILD @@ -9,7 +9,7 @@ # Build + install: cd packaging && makepkg -fi pkgname=proton-drive-linux -pkgver=3.1.2 +pkgver=3.2.0 pkgrel=1 pkgdesc="Unofficial Proton Drive client for Linux: files-on-demand FUSE mount, CLI, GTK4 app + tray" arch=('x86_64') diff --git a/po/pdfs.pot b/po/pdfs.pot index c6f5510..251be91 100644 --- a/po/pdfs.pot +++ b/po/pdfs.pot @@ -6,7 +6,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: proton-drive-linux 3.1.2\n" +"Project-Id-Version: proton-drive-linux 3.2.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" "POT-Creation-Date: 2026-10-09 02:38+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"