diff --git a/Cargo.lock b/Cargo.lock index 903bfe2..6ba869b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3779,9 +3779,9 @@ dependencies = [ [[package]] name = "proton-drive-rs" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f70318394b2815b009c52f804d75747086db93a338ceb6eae62541f2b25d5bf" +checksum = "a8fa2a88130883010b32035e208aaa36fb275f6d7d78ed44a1069b1d33d24481" dependencies = [ "async-trait", "base64 0.23.1", @@ -3806,9 +3806,9 @@ dependencies = [ [[package]] name = "proton-sdk" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4196392032f56b3bbaac0bb1f24eea62fa7e2a0af72004de8d08e518d14720fb" +checksum = "af0bf8d50fa829d1a65b09c6855b606578d02734d4089874c971d1063c23c353" dependencies = [ "aes-gcm 0.11.1", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 0911a02..92c34d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,8 @@ keywords = ["proton", "drive", "fuse", "sync", "linux"] categories = ["filesystem", "command-line-utilities"] [workspace.dependencies] -proton-sdk = { version = "0.10.0" } -proton-drive-rs = { version = "0.10.0" } +proton-sdk = { version = "0.10.1" } +proton-drive-rs = { version = "0.10.1" } # Internal crates. pdfs-core = { path = "crates/pdfs-core" } diff --git a/crates/pdfs-cli/src/journal.rs b/crates/pdfs-cli/src/journal.rs new file mode 100644 index 0000000..a689a09 --- /dev/null +++ b/crates/pdfs-cli/src/journal.rs @@ -0,0 +1,122 @@ +//! Log priorities for journald. +//! +//! journald files each line a service writes at the unit's default priority, +//! info, unless the line opens with a syslog `` prefix. Without one every +//! WARN and ERROR the daemon logs is stored as info, and the +//! `journalctl -p warning` that the troubleshooting guide asks for shows +//! nothing (`docs/BUGS.md` B258). + +use std::fmt; +use std::os::unix::fs::MetadataExt; + +use tracing::{Event, Level, Subscriber}; +use tracing_subscriber::fmt::format::Writer; +use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields}; +use tracing_subscriber::registry::LookupSpan; + +/// Whether standard output is the journal stream systemd connected this +/// process to. +/// +/// `JOURNAL_STREAM` alone does not say so: a terminal started from a user unit +/// inherits it, and every `pdfs` run in that terminal would print the +/// prefixes. systemd sets it to the stream's `device:inode`, so it is compared +/// against what fd 1 actually is. +pub(crate) fn stdout_is_journal() -> bool { + let Some(stream) = std::env::var_os("JOURNAL_STREAM") else { + return false; + }; + let Ok(stdout) = std::fs::metadata("/proc/self/fd/1") else { + return false; + }; + stream + .to_str() + .is_some_and(|value| names_stream(value, stdout.dev(), stdout.ino())) +} + +/// Whether a `JOURNAL_STREAM` value names the file `dev`:`ino`. +fn names_stream(value: &str, dev: u64, ino: u64) -> bool { + value + .split_once(':') + .is_some_and(|(d, i)| d.parse() == Ok(dev) && i.parse() == Ok(ino)) +} + +/// The syslog priority journald should file a `level` line at. +fn priority(level: &Level) -> u8 { + match *level { + Level::ERROR => 3, + Level::WARN => 4, + Level::INFO => 6, + Level::DEBUG | Level::TRACE => 7, + } +} + +/// An event format that opens each line with its `` priority, then writes +/// the line as `F` would. +pub(crate) struct Prioritized(pub(crate) F); + +impl FormatEvent for Prioritized +where + S: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, + F: FormatEvent, +{ + fn format_event( + &self, + ctx: &FmtContext<'_, S, N>, + mut writer: Writer<'_>, + event: &Event<'_>, + ) -> fmt::Result { + write!(writer, "<{}>", priority(event.metadata().level()))?; + self.0.format_event(ctx, writer, event) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io; + use std::sync::{Arc, Mutex}; + + #[derive(Clone)] + struct Buf(Arc>>); + + impl io::Write for Buf { + fn write(&mut self, bytes: &[u8]) -> io::Result { + self.0.lock().unwrap().extend_from_slice(bytes); + Ok(bytes.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + #[test] + fn each_line_opens_with_the_priority_of_its_level() { + let buf = Buf(Arc::new(Mutex::new(Vec::new()))); + let writer = buf.clone(); + let subscriber = tracing_subscriber::fmt() + .with_max_level(Level::DEBUG) + .with_ansi(false) + .with_writer(move || writer.clone()) + .event_format(Prioritized(tracing_subscriber::fmt::format())) + .finish(); + tracing::subscriber::with_default(subscriber, || { + tracing::error!("e"); + tracing::warn!("w"); + tracing::info!("i"); + tracing::debug!("d"); + }); + let out = String::from_utf8(buf.0.lock().unwrap().clone()).unwrap(); + let prefixes: Vec<&str> = out.lines().map(|line| &line[..3]).collect(); + assert_eq!(prefixes, ["<3>", "<4>", "<6>", "<7>"]); + } + + #[test] + fn journal_stream_must_name_the_same_file() { + assert!(names_stream("8:12345", 8, 12345)); + assert!(!names_stream("8:12345", 8, 54321)); + assert!(!names_stream("9:12345", 8, 12345)); + assert!(!names_stream("8", 8, 12345)); + assert!(!names_stream("", 8, 12345)); + } +} diff --git a/crates/pdfs-cli/src/main.rs b/crates/pdfs-cli/src/main.rs index 75f6e3b..f9493fa 100644 --- a/crates/pdfs-cli/src/main.rs +++ b/crates/pdfs-cli/src/main.rs @@ -20,6 +20,8 @@ use pdfs_core::control::{ use pdfs_core::db::Db; use pdfs_core::service; +mod journal; + /// `println!` for this binary, shadowing std's, which panics when stdout is /// closed: `pdfs ls | head` ended in a "Broken pipe" panic once `head` had its /// lines. This one hands the failure to [`stdout_written`] instead. @@ -879,15 +881,22 @@ fn cli_error(kind: ErrorKind, message: &str) -> String { } fn main() -> Result<()> { - tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::try_from_default_env() - // `pgp` logs a benign WARN when it re-serializes a Proton secret-key - // packet with a longer (but equivalent) length encoding than the - // original; drop it to error so normal runs stay quiet. - .unwrap_or_else(|_| "info,pgp=error".into()), - ) - .init(); + let filter = tracing_subscriber::EnvFilter::try_from_default_env() + // `pgp` logs a benign WARN when it re-serializes a Proton secret-key + // packet with a longer (but equivalent) length encoding than the + // original; drop it to error so normal runs stay quiet. + .unwrap_or_else(|_| "info,pgp=error".into()); + if journal::stdout_is_journal() { + // journalctl colours lines by the priority the prefix gives them, so + // the escape codes would only be noise in the stored text. + tracing_subscriber::fmt() + .with_env_filter(filter) + .with_ansi(false) + .event_format(journal::Prioritized(tracing_subscriber::fmt::format())) + .init(); + } else { + tracing_subscriber::fmt().with_env_filter(filter).init(); + } let cli = Cli::parse(); JSON_OUTPUT.store(cli.json, std::sync::atomic::Ordering::Relaxed); @@ -1458,7 +1467,11 @@ fn cmd_sync_restore(yes: bool, device: Option) -> Result<()> { items: chosen, }, None => CtlRequest::RestoreSyncFolders { items: chosen }, - })?) + })?)?; + // The daemon acks before it works, so the outcome, and any folder it had + // to skip, only shows up later. + println!("Run `pdfs activity` to see which folders were restored."); + Ok(()) } /// Expand a leading `~` in a hand-typed path. The daemon requires an absolute @@ -2375,12 +2388,21 @@ where /// an external unmount triggers a remount; errors back off and retry. This is /// the entry point for the systemd user service. fn cmd_daemon(mountpoint: Option) -> Result<()> { + let state_dir = AppDirs::new()?.state_dir(); + // A failure an earlier daemon recorded is not this one's. + pdfs_core::mountfail::clear(&state_dir); + let mut failure: Option = None; loop { // Wait until a session is stored. The GUI enables this service on login, // but the service may also start at boot before the user has logged in. wait_for_session(auth::ready, pdfs_fuse::idle_sleep)?; - match mount_once(mountpoint.clone()) { + let outcome = mount_once(mountpoint.clone()); + if outcome.is_ok() { + // The mount came up, so the next failure starts a new run of them. + failure = None; + } + match outcome { Ok(pdfs_fuse::MountOutcome::Shutdown) => { tracing::info!("daemon stopping"); return Ok(()); @@ -2395,13 +2417,51 @@ fn cmd_daemon(mountpoint: Option) -> Result<()> { Err(e) => { // `{:#}` so the whole anyhow chain lands in the log — the top // context alone ("mount failed") never says *why*. - tracing::error!(error = format!("{e:#}"), "mount failed; retrying in 5s"); - pdfs_fuse::idle_sleep(std::time::Duration::from_secs(5)); + let error = format!("{e:#}"); + let now = unix_now(); + let attempts = failure.as_ref().map_or(1, |f| f.attempts.saturating_add(1)); + let delay = mount_retry_delay(attempts); + tracing::error!( + error, + attempts, + retry_secs = delay.as_secs(), + "mount failed; retrying" + ); + let record = pdfs_core::mountfail::MountFailure { + error: error.clone(), + since: failure.as_ref().map_or(now, |f| f.since), + attempts, + next_attempt: now + delay.as_secs() as i64, + }; + if let Err(e) = pdfs_core::mountfail::record(&state_dir, &record) { + tracing::warn!(error = %e, "could not record the mount failure"); + } + pdfs_fuse::notify_status(&format!("Mount failing: {error}")); + failure = Some(record); + pdfs_fuse::idle_sleep(delay); } } } } +/// How long to wait before mount attempt `attempts + 1`, after `attempts` +/// failures in a row. +/// +/// Doubles from 5 s to a 2-minute ceiling. A flat 5 s retry sent about four +/// requests to Proton per attempt, tens of thousands a day from an account +/// whose mount fails on a key, which no retry fixes (`docs/BUGS.md` B263). The +/// ceiling stays at minutes because a cold start without a network fails the +/// same way, and must mount soon after the network returns. +fn mount_retry_delay(attempts: u32) -> std::time::Duration { + const BASE_SECS: u64 = 5; + const MAX_SECS: u64 = 120; + + let factor = 1u64 + .checked_shl(attempts.saturating_sub(1)) + .unwrap_or(u64::MAX); + std::time::Duration::from_secs(BASE_SECS.saturating_mul(factor).min(MAX_SECS)) +} + /// One mount run: resume the session, mount, and block until the mount ends /// (clean shutdown or external unmount). Returns the [`MountOutcome`] so the /// daemon loop can decide whether to exit or remount. @@ -3096,6 +3156,24 @@ fn human_secs(secs: u64) -> String { } } +/// The sync engine's line in `pdfs diagnostics`: whether it runs, and which +/// folder's pass it is on. +fn sync_engine_line(sync: &pdfs_core::control::SyncEngineState) -> String { + let mut line = match (sync.running, sync.pass_folder) { + (false, _) => "STOPPED; synced folders do not sync until the daemon restarts".to_string(), + (true, None) => "idle".to_string(), + (true, Some(folder)) => format!( + "pass on folder {folder} for {}, last progress {} ago", + human_secs(sync.pass_age_secs), + human_secs(sync.pass_idle_secs), + ), + }; + if sync.panics > 0 { + line.push_str(&format!("; {} pass(es) panicked", sync.panics)); + } + line +} + fn cmd_diagnostics() -> Result<()> { let response = control_request(CtlRequest::Diagnostics)?; if emit_json(&response)? { @@ -3131,6 +3209,9 @@ fn cmd_diagnostics() -> Result<()> { human_secs(worker.age_secs) ); } + if let Some(sync) = &d.sync { + println!("sync engine: {}", sync_engine_line(sync)); + } if d.inflight.is_empty() { println!("in-flight requests: none"); } else { @@ -3369,6 +3450,79 @@ fn cmd_sync_limit(up: Option, down: Option) -> Result<()> { })?) } +/// How long the oldest write may wait to upload before `diagnose` calls it +/// out. Under a day it is a slow link or a big file; past it, something is +/// holding the queue. +const STAGED_STALE_SECS: u64 = 86_400; + +/// `diagnose` findings for the queue and the pause, from the `Status` reply. +/// `paused_for` is how long a timed pause still runs, in seconds. +fn sync_health( + failing_ops: u64, + failing_error: Option<&str>, + staged_bytes: u64, + staged_oldest_secs: u64, + paused: bool, + paused_for: Option, +) -> Vec<(DiagnoseLevel, &'static str, String)> { + let mut findings = Vec::new(); + if failing_ops > 0 { + findings.push(( + DiagnoseLevel::Warn, + " failing writes", + match failing_error { + Some(error) => format!("{failing_ops} keep failing; the last error: {error}"), + None => format!("{failing_ops} keep failing"), + }, + )); + } + if staged_bytes > 0 && staged_oldest_secs >= STAGED_STALE_SECS { + findings.push(( + DiagnoseLevel::Warn, + " oldest unsent write", + format!( + "{} old, {} only on this computer", + human_secs(staged_oldest_secs), + human_bytes(staged_bytes) + ), + )); + } + if paused { + findings.push(( + DiagnoseLevel::Warn, + " sync", + match paused_for { + Some(left) if left > 0 => { + format!("paused for another {}", human_secs(left as u64)) + } + _ => "paused until resumed (`pdfs sync resume`)".to_string(), + }, + )); + } + findings +} + +/// `diagnose` findings for the synced folders that need someone to look. +fn folder_health( + folders: &[pdfs_core::control::SyncFolderInfo], +) -> Vec<(DiagnoseLevel, String, String)> { + folders + .iter() + .filter_map(|folder| { + let what = match folder.state.as_str() { + "error" => "sync error; `pdfs sync list` and the activity feed say more", + "conflict" => "conflict copies to look at", + _ => return None, + }; + Some(( + DiagnoseLevel::Warn, + format!(" synced folder [{}]", folder.id), + format!("{}: {what}", folder.local_path), + )) + }) + .collect() +} + fn unix_now() -> i64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -3705,21 +3859,26 @@ fn cmd_diagnose() -> Result<()> { let socket_exists = socket.exists(); let socket_kind_ok = std::fs::symlink_metadata(&socket).is_ok_and(|m| m.file_type().is_socket()); + // A stale socket file with no listener is the common failure, so the reply + // is what decides this rather than the file's existence. + let status = control_request(CtlRequest::Status); report.finding( if socket_exists && !socket_kind_ok { DiagnoseLevel::Fail - } else if socket_exists { + } else if socket_exists && status.is_ok() { DiagnoseLevel::Ok } else { DiagnoseLevel::Warn }, " control socket", - socket.display().to_string(), + if socket_exists && socket_kind_ok && status.is_err() { + format!("{} (left over; nothing listens on it)", socket.display()) + } else { + socket.display().to_string() + }, ); - // A stale socket file with no listener is the common failure, so the reply - // is what decides this rather than the file's existence. let mut daemon_responding = false; - match control_request(CtlRequest::Status) { + match status { Ok(CtlResponse::Status { mountpoint, online, @@ -3727,6 +3886,12 @@ fn cmd_diagnose() -> Result<()> { tokens_unsaved, pending_uploads, pending_changes, + failing_ops, + failing_error, + staged_bytes, + staged_oldest_secs, + paused, + paused_until, version, .. }) => { @@ -3801,6 +3966,27 @@ fn cmd_diagnose() -> Result<()> { format!("{pending} waiting to upload") }, ); + // The rest of the daemon's state that the report used to drop: + // writes that keep failing, a pause, a folder in error. Each of + // them stops changes from reaching Drive while everything above + // reads as fine (`docs/BUGS.md` B268). + for (level, check, detail) in sync_health( + failing_ops, + failing_error.as_deref(), + staged_bytes, + staged_oldest_secs, + paused, + paused_until.map(|until| until - unix_now()), + ) { + report.finding(level, check, detail); + } + if let Ok(CtlResponse::SyncFolders { items }) = + control_request(CtlRequest::ListSyncFolders) + { + for (level, check, detail) in folder_health(&items) { + report.finding(level, &check, detail); + } + } match await_integrity_check() { Ok(CtlResponse::CacheReport { integrity_problems, @@ -3857,7 +4043,23 @@ fn cmd_diagnose() -> Result<()> { " daemon responding", format!("stopped or unreachable: {e:#}"), ); - println!(" (start it with `systemctl --user start proton-drive.service`)"); + match pdfs_core::mountfail::read(&dirs.state_dir()) { + // A daemon whose mount keeps failing never opens the socket, + // so this is the only place its reason shows (B263). + Some(failure) => report.finding( + DiagnoseLevel::Fail, + " mount", + format!( + "failing for {}, {} attempt(s): {}", + human_secs(unix_now().saturating_sub(failure.since).max(0) as u64), + failure.attempts, + failure.error + ), + ), + None => println!( + " (start it with `systemctl --user start proton-drive.service`)" + ), + } } } @@ -3982,6 +4184,50 @@ mod pause_tests { mod diagnose_tests { use super::*; + /// A healthy queue says nothing; writes that keep failing, an old unsent + /// write and a pause each say so (`docs/BUGS.md` B268). + #[test] + fn a_stuck_queue_or_a_pause_is_reported() { + assert!(sync_health(0, None, 0, 0, false, None).is_empty()); + assert!(sync_health(0, None, 4096, STAGED_STALE_SECS - 1, false, None).is_empty()); + let checks: Vec<&str> = sync_health( + 2, + Some("upload a.txt: quota"), + 4096, + STAGED_STALE_SECS, + true, + Some(600), + ) + .into_iter() + .map(|(level, check, _)| { + assert!(matches!(level, DiagnoseLevel::Warn)); + check.trim() + }) + .collect(); + assert_eq!(checks, ["failing writes", "oldest unsent write", "sync"]); + } + + /// Issue #53: a folder that would not sync, and `diagnose` found nothing. + #[test] + fn a_synced_folder_in_error_is_reported() { + let folder = |id: i64, state: &str| pdfs_core::control::SyncFolderInfo { + id, + local_path: format!("/home/u/f{id}"), + remote_uid: "vol~link".into(), + mode: "mirror".into(), + state: state.into(), + last_sync: 0, + pending_mode: None, + progress: None, + paused: false, + }; + let findings = + folder_health(&[folder(1, "idle"), folder(2, "error"), folder(3, "syncing")]); + assert_eq!(findings.len(), 1); + assert_eq!(findings[0].1.trim(), "synced folder [2]"); + assert!(findings[0].2.starts_with("/home/u/f2: sync error")); + } + #[test] fn finding_severity_is_counted_centrally() { let mut report = DiagnoseReport::default(); @@ -4017,6 +4263,15 @@ mod diagnose_tests { #[cfg(test)] mod login_wait_tests { use super::*; + + /// The mount retry backs off from 5 s to a ceiling of minutes, rather than + /// asking Proton every 5 s forever (`docs/BUGS.md` B263). + #[test] + fn a_failing_mount_backs_off_to_a_ceiling() { + let secs: Vec = (1..=8).map(|n| mount_retry_delay(n).as_secs()).collect(); + assert_eq!(secs, [5, 10, 20, 40, 80, 120, 120, 120]); + assert_eq!(mount_retry_delay(u32::MAX).as_secs(), 120); + } use std::time::Duration; #[test] diff --git a/crates/pdfs-core/src/control.rs b/crates/pdfs-core/src/control.rs index c111146..3b7c590 100644 --- a/crates/pdfs-core/src/control.rs +++ b/crates/pdfs-core/src/control.rs @@ -1980,6 +1980,29 @@ pub struct Diagnostics { pub control_handler_limit: usize, /// Queued mutations not yet drained. pub pending_ops: u64, + /// The folder-sync engine. `None` from a daemon that predates the field. + #[serde(default)] + pub sync: Option, +} + +/// What the folder-sync engine is doing, for [`Diagnostics`]. +/// +/// The engine is one thread that runs every synced folder's passes in turn. +/// Dead or stuck, it looked exactly like an idle one: workers idle, nothing +/// queued, nothing in the journal (`docs/BUGS.md` B259). +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct SyncEngineState { + /// Whether the engine thread is running. Once it has ended, nothing syncs + /// until the daemon restarts. + pub running: bool, + /// The synced folder whose pass is running, if one is. + pub pass_folder: Option, + /// How long that pass has been running. + pub pass_age_secs: u64, + /// How long since that pass last reported progress. + pub pass_idle_secs: u64, + /// Passes that ended in a panic since the daemon started. + pub panics: u64, } /// The daemon's reply to a [`Request`]. diff --git a/crates/pdfs-core/src/error.rs b/crates/pdfs-core/src/error.rs index 8bdd102..f1e580a 100644 --- a/crates/pdfs-core/src/error.rs +++ b/crates/pdfs-core/src/error.rs @@ -160,13 +160,14 @@ impl CoreError { /// telling the user their connection is down beats telling them the /// server refused. The SDK's other failures say what went wrong instead /// (see [`proton_kind`]). + /// + /// Callers also hand it this crate's [`Error`] from the cache and the + /// database, which is not a transport error at all. Taking those for + /// "offline" told the user their connection was down when a pin failed on + /// the database (`docs/BUGS.md` B266), so the source chain is searched for + /// either type before falling back. pub fn from_api(e: &(dyn std::error::Error + 'static), what: &str) -> Self { - let kind = match e.downcast_ref::() { - Some(proton) => proton_kind(proton), - // Not the SDK's at all: a timeout around the call, or the socket. - None => ErrorKind::Offline, - }; - Self::new(kind, format!("{what}: {e}")) + Self::new(api_kind_of(e), format!("{what}: {e}")) } /// Prefix the message while keeping the classification. @@ -208,19 +209,40 @@ impl From for CoreError { impl From for CoreError { fn from(e: Error) -> Self { - let kind = match &e { - Error::NotLoggedIn - | Error::ReloginRequired - | Error::Locked - | Error::WrongMailboxPassword - | Error::HumanVerificationRequired(_) => ErrorKind::Denied, - Error::Proton(proton) => proton_kind(proton), - _ => ErrorKind::Internal, - }; - Self::new(kind, e.to_string()) + Self::new(core_kind(&e), e.to_string()) } } +/// What this crate's [`Error`] means to a front-end. +fn core_kind(e: &Error) -> ErrorKind { + match e { + Error::NotLoggedIn + | Error::ReloginRequired + | Error::Locked + | Error::WrongMailboxPassword + | Error::HumanVerificationRequired(_) => ErrorKind::Denied, + Error::Proton(proton) => proton_kind(proton), + _ => ErrorKind::Internal, + } +} + +/// The kind for [`CoreError::from_api`]: the first SDK or crate error in `e`'s +/// source chain decides it. +fn api_kind_of(e: &(dyn std::error::Error + 'static)) -> ErrorKind { + let mut cause = Some(e); + while let Some(err) = cause { + if let Some(proton) = err.downcast_ref::() { + return proton_kind(proton); + } + if let Some(core) = err.downcast_ref::() { + return core_kind(core); + } + cause = err.source(); + } + // Not the SDK's at all: a timeout around the call, or the socket. + ErrorKind::Offline +} + /// What a failed SDK call means to a front-end. /// /// Only a transport failure is the connection. The rest happened on this @@ -414,6 +436,20 @@ mod tests { assert_ne!(e.kind, ErrorKind::Offline); } + /// A pin or a search that fails on the database is not the connection, so + /// it must not say "You're offline" (`docs/BUGS.md` B266). An SDK error + /// wrapped in this crate's type classifies as the SDK error does. + #[test] + fn a_database_error_handed_to_from_api_is_not_offline() { + let db = Error::Other("database is locked".into()); + assert_eq!(CoreError::from_api(&db, "pin").kind, ErrorKind::Internal); + let wrapped = Error::Proton(api(C::DoesNotExist)); + assert_eq!( + CoreError::from_api(&wrapped, "load node").kind, + ErrorKind::NotFound + ); + } + /// `Error` is the plumbing type; converting it must classify as sharply as /// the direct path, or the two disagree about the same failure. #[test] diff --git a/crates/pdfs-core/src/lib.rs b/crates/pdfs-core/src/lib.rs index ed19c4f..0f79ec4 100644 --- a/crates/pdfs-core/src/lib.rs +++ b/crates/pdfs-core/src/lib.rs @@ -15,6 +15,7 @@ pub mod kernelkey; pub mod keycheck; pub mod localindex; pub mod menu; +pub mod mountfail; pub mod mounts; pub mod opener; pub mod places; diff --git a/crates/pdfs-core/src/mountfail.rs b/crates/pdfs-core/src/mountfail.rs new file mode 100644 index 0000000..130a155 --- /dev/null +++ b/crates/pdfs-core/src/mountfail.rs @@ -0,0 +1,75 @@ +//! Why the daemon's mount keeps failing, for the front ends to read. +//! +//! The control socket opens only once a mount has succeeded, so a daemon whose +//! mount fails on every try had no way to say why. The app and the tray showed +//! "Connecting…" for days while the reason sat in the journal (`docs/BUGS.md` +//! B263). The daemon now writes its last failure to a small file in the state +//! directory, next to the control socket, and removes it once a mount is up. + +use std::path::{Path, PathBuf}; + +use serde::{Deserialize, Serialize}; + +/// The file's name inside the state directory. +const FILE_NAME: &str = "mount-failure.json"; + +/// The last failed mount attempt. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct MountFailure { + /// The error, in English, as the journal has it. + pub error: String, + /// Unix seconds of the first failure in this run of failures. + pub since: i64, + /// Failed attempts in a row. + pub attempts: u32, + /// Unix seconds of the next attempt. + pub next_attempt: i64, +} + +/// Where the failure is kept for the state directory `state_dir`. +pub fn path(state_dir: &Path) -> PathBuf { + state_dir.join(FILE_NAME) +} + +/// Record `failure`, replacing the one before it. Written to a temporary file +/// and renamed, so a reader never sees half of it. +pub fn record(state_dir: &Path, failure: &MountFailure) -> std::io::Result<()> { + let json = serde_json::to_vec(failure).map_err(std::io::Error::other)?; + let tmp = state_dir.join(format!("{FILE_NAME}.tmp")); + std::fs::write(&tmp, json)?; + std::fs::rename(&tmp, path(state_dir)) +} + +/// Forget the failure: a mount is up, or a new daemon has started. +pub fn clear(state_dir: &Path) { + let _ = std::fs::remove_file(path(state_dir)); +} + +/// The recorded failure, if there is one and it can be read. +pub fn read(state_dir: &Path) -> Option { + let bytes = std::fs::read(path(state_dir)).ok()?; + serde_json::from_slice(&bytes).ok() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_recorded_failure_reads_back_until_cleared() { + let dir = std::env::temp_dir().join(format!("pdfs-mountfail-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + assert_eq!(read(&dir), None); + let failure = MountFailure { + error: "fetch My Files root: missing key".into(), + since: 1_700_000_000, + attempts: 3, + next_attempt: 1_700_000_040, + }; + record(&dir, &failure).unwrap(); + assert_eq!(read(&dir), Some(failure)); + clear(&dir); + assert_eq!(read(&dir), None); + let _ = std::fs::remove_dir_all(&dir); + } +} diff --git a/crates/pdfs-fuse/src/control.rs b/crates/pdfs-fuse/src/control.rs index ea23938..55327c2 100644 --- a/crates/pdfs-fuse/src/control.rs +++ b/crates/pdfs-fuse/src/control.rs @@ -1526,9 +1526,17 @@ fn handle_control_conn(core: &Core, username: &str, mountpoint: &Path, stream: U let core = core.clone(); std::thread::spawn(move || { match core.restore_sync_folders(&items, device.as_deref()) { - Ok(message) => { + Ok(outcome) => { core.touch_profile(); - core.log_activity(ActivityKind::Download, &message, "", true); + if !outcome.complete() { + warn!(skipped = ?outcome.skipped, "restore skipped folders"); + } + core.log_activity( + ActivityKind::Download, + outcome.summary(), + outcome.skipped.join("; "), + outcome.complete(), + ); } Err(e) => { warn!(error = %e, "restore failed"); diff --git a/crates/pdfs-fuse/src/diagnostics.rs b/crates/pdfs-fuse/src/diagnostics.rs index 5cde5df..f775043 100644 --- a/crates/pdfs-fuse/src/diagnostics.rs +++ b/crates/pdfs-fuse/src/diagnostics.rs @@ -163,6 +163,7 @@ impl Core { control_handlers: super::control::active_handlers(), control_handler_limit: super::control::handler_limit(), pending_ops: self.pending_ops_quickly(), + sync: Some(self.sync_engine.report()), } } diff --git a/crates/pdfs-fuse/src/filesystem.rs b/crates/pdfs-fuse/src/filesystem.rs index 52d8751..8100c3c 100644 --- a/crates/pdfs-fuse/src/filesystem.rs +++ b/crates/pdfs-fuse/src/filesystem.rs @@ -1434,6 +1434,14 @@ impl ProtonFs { // (`ContentCache::clone_or_copy`). if let Err(e) = ContentCache::clone_or_copy(&pending.path, &path) { let _ = std::fs::remove_file(&path); + // The drain lets go of the pending entry before it + // discards the blob, so a blob that went away under the + // copy is a revision that landed or was replaced: the + // same race the re-check below catches, only earlier. + if self.core.pending_blob(&uid).as_ref() != Some(&pending.path) { + warn!(%uid, attempt, "queued revision went away while opening for write; retrying"); + continue; + } error!(%uid, source = %pending.path.display(), error = %e, "copy queued revision into write scratch failed"); break 'install Err(Errno::EIO); diff --git a/crates/pdfs-fuse/src/lib.rs b/crates/pdfs-fuse/src/lib.rs index cc5c887..29a256e 100644 --- a/crates/pdfs-fuse/src/lib.rs +++ b/crates/pdfs-fuse/src/lib.rs @@ -124,7 +124,7 @@ use mount::{ }; use reads::{BlockFlight, BlockRing, PREFETCH_BUDGET, Prefetch, ReaderSlot, STREAM_BYPASS_MIN}; use state::{Entry, Intervals, PendingRevision, State, StateGuard, WriteHandle}; -pub use systemd::idle_sleep; +pub use systemd::{idle_sleep, status as notify_status}; use tracing::{debug, error, info, warn}; use transfers::{CountingWriter, JobGuard, OwnedCountingReader, TransferRegistry}; use r#virtual::{ @@ -728,6 +728,8 @@ struct Core { /// can say what a pass is doing rather than just "syncing". An entry exists /// only while that folder's reconcile pass is running. sync_progress: Arc>>, + /// Whether the folder-sync engine is alive, and which pass it is on. + sync_engine: Arc, /// Channel to the folder-sync engine: nudges it to /// reconcile a folder, reconcile everything, or re-scan its watch set. sync_tx: std::sync::mpsc::Sender, @@ -5997,6 +5999,7 @@ impl Core { if let Some(p) = self.sync_progress.lock().get_mut(&folder_id) { f(p); } + self.sync_engine.moved(); } /// Set the number of items the scan expects to check, from the size of the diff --git a/crates/pdfs-fuse/src/mount.rs b/crates/pdfs-fuse/src/mount.rs index 1aeccdc..ff94d36 100644 --- a/crates/pdfs-fuse/src/mount.rs +++ b/crates/pdfs-fuse/src/mount.rs @@ -507,6 +507,7 @@ pub(crate) fn mount_with( events: Arc::new(Default::default()), indexing: Arc::new(AtomicBool::new(false)), sync_progress: Arc::new(Mutex::new(HashMap::new())), + sync_engine: Arc::default(), sync_tx, mounts: Arc::new(Mutex::new(SecondaryMountRegistry::default())), sync_locks: Arc::new(Mutex::new(HashMap::new())), @@ -540,12 +541,11 @@ pub(crate) fn mount_with( // and sweeps all operating on a mount that no longer exists (`docs/BUGS.md` B44). let mut workers: Vec> = Vec::new(); for worker in 0..DRAIN_WORKERS { - let core = core.clone(); - workers.push( - std::thread::Builder::new() - .name(format!("pdfs-drain-{worker}")) - .spawn(move || core.run_pending_drain(worker == 0))?, - ); + let drain_core = core.clone(); + let spawned = std::thread::Builder::new() + .name(format!("pdfs-drain-{worker}")) + .spawn(move || drain_core.run_pending_drain(worker == 0)); + keep_or_abandon(&core, control_socket, &mut workers, spawned)?; } // Start the folder-sync engine. It watches every mirror folder, polls the @@ -565,24 +565,22 @@ pub(crate) fn mount_with( info!("conflict sweep disabled by configuration"); } else { info!(mode = ?sweep_mode, "conflict sweep enabled"); - let core = core.clone(); - workers.push( - std::thread::Builder::new() - .name("pdfs-conflict-sweep".into()) - .spawn(move || core.run_conflict_sweep_loop())?, - ); + let sweep_core = core.clone(); + let spawned = std::thread::Builder::new() + .name("pdfs-conflict-sweep".into()) + .spawn(move || sweep_core.run_conflict_sweep_loop()); + keep_or_abandon(&core, control_socket, &mut workers, spawned)?; } // Watch the link for the life of the mount: a mount from the cache waits for // the network to come back, and an online one can lose it at any moment and // must notice it coming back just the same. { - let core = core.clone(); - workers.push( - std::thread::Builder::new() - .name("pdfs-online-probe".into()) - .spawn(move || core.run_online_probe())?, - ); + let probe_core = core.clone(); + let spawned = std::thread::Builder::new() + .name("pdfs-online-probe".into()) + .spawn(move || probe_core.run_online_probe()); + keep_or_abandon(&core, control_socket, &mut workers, spawned)?; } // Keep the launcher prompt's "This computer" index fresh. Its own thread: @@ -593,11 +591,10 @@ pub(crate) fn mount_with( let transfers = core.transfers.clone(); let mountpoint = mountpoint.to_path_buf(); let shutdown = core.shutdown.clone(); - workers.push( - std::thread::Builder::new() - .name("pdfs-localindex".into()) - .spawn(move || run_local_index(db, indexing, transfers, mountpoint, shutdown))?, - ); + let spawned = std::thread::Builder::new() + .name("pdfs-localindex".into()) + .spawn(move || run_local_index(db, indexing, transfers, mountpoint, shutdown)); + keep_or_abandon(&core, control_socket, &mut workers, spawned)?; } // Bind the control socket before the FUSE session takes over the thread. A @@ -606,12 +603,18 @@ pub(crate) fn mount_with( let old_umask = unsafe { libc::umask(0o77) }; let listener_res = UnixListener::bind(control_socket); unsafe { libc::umask(old_umask) }; - let listener = listener_res?; + let listener = match listener_res { + Ok(listener) => listener, + Err(error) => { + abandon_workers(&core, control_socket, workers); + return Err(error); + } + }; // Owner-only before anything can connect: a peer on this socket commands the // daemon's authenticated session without a credential of its own (B6). if let Err(e) = pdfs_core::config::restrict_socket(control_socket) { error!(error = %e, "cannot restrict control socket permissions; refusing to serve"); - let _ = std::fs::remove_file(control_socket); + abandon_workers(&core, control_socket, workers); return Err(std::io::Error::other(format!( "control socket permissions: {e}" ))); @@ -622,7 +625,7 @@ pub(crate) fn mount_with( let bg = match spawn_session(&core, mountpoint, root) { Ok(session) => session, Err(error) => { - let _ = std::fs::remove_file(control_socket); + abandon_workers(&core, control_socket, workers); return Err(error); } }; @@ -642,7 +645,7 @@ pub(crate) fn mount_with( Err(error) => { // A failure here abandons the mount, so the background threads // started above have to be told to stop with it. - stop_workers(&core, control_socket, workers); + abandon_workers(&core, control_socket, workers); let secondaries = core.mounts.lock().close_and_drain(); for (id, mount) in secondaries { if let Err(unmount_error) = mount.teardown() { @@ -692,8 +695,12 @@ pub(crate) fn mount_with( } } // The mount is live and the control socket answers: this is the moment the - // unit is actually usable, which is what `Type=notify` reports. + // unit is actually usable, which is what `Type=notify` reports. Whatever + // failure an earlier attempt recorded for the front ends is over. systemd::ready(); + if let Some(state_dir) = control_socket.parent() { + pdfs_core::mountfail::clear(state_dir); + } // Re-establish on-demand mounts only after the primary session is live. // Keep the worker so shutdown can close publication, wait for any in-flight @@ -859,6 +866,43 @@ fn stop_workers(core: &Core, control_socket: &Path, workers: Vec>) { + halt_workers(core, control_socket, workers); +} + +/// Keep a worker the mount just spawned, or, if the spawn failed, stop the +/// ones already running and hand the error back. +fn keep_or_abandon( + core: &Core, + control_socket: &Path, + workers: &mut Vec>, + spawned: std::io::Result>, +) -> std::io::Result<()> { + match spawned { + Ok(handle) => { + workers.push(handle); + Ok(()) + } + Err(error) => { + abandon_workers(core, control_socket, std::mem::take(workers)); + Err(error) + } + } +} + +/// Signal every worker to stop, then join them up to [`WORKER_JOIN_DEADLINE`]. +fn halt_workers(core: &Core, control_socket: &Path, workers: Vec>) { core.shutdown.stop(); core.wake_drain(); let _ = core.sync_tx.send(sync::SyncMsg::Stop); diff --git a/crates/pdfs-fuse/src/profile.rs b/crates/pdfs-fuse/src/profile.rs index 77f1f28..cd24a8d 100644 --- a/crates/pdfs-fuse/src/profile.rs +++ b/crates/pdfs-fuse/src/profile.rs @@ -388,7 +388,7 @@ impl Core { &self, items: &[pdfs_core::control::RestoreItem], device: Option<&str>, - ) -> CoreResult { + ) -> CoreResult { if items.is_empty() { return Err(CoreError::invalid("nothing to restore")); } @@ -434,10 +434,19 @@ impl Core { continue; } }; - let id = self - .db - .sync_folder_add(&local.to_string_lossy(), &item.remote_uid, &device.share_id) - .map_err(|e| CoreError::internal(format!("db: {e:?}")))?; + let id = match self.db.sync_folder_add( + &local.to_string_lossy(), + &item.remote_uid, + &device.share_id, + ) { + Ok(id) => id, + Err(e) => { + // The folders restored before this one still need their + // watches. + let _ = self.sync_tx.send(sync::SyncMsg::Rewatch); + return Err(CoreError::internal(format!("db: {e:?}"))); + } + }; // `ondemand` is applied through the normal request path, which // mounts the FUSE session and handles the folder being busy — the // restore does not get its own copy of that logic. @@ -454,12 +463,35 @@ impl Core { for reason in &skipped { info!(reason, "restore skipped a folder"); } - let mut message = format!("restored {restored} folder(s)"); - if !skipped.is_empty() { - message.push_str(&format!("; skipped: {}", skipped.join(", "))); - } info!(restored, skipped = skipped.len(), "restore complete"); - Ok(message) + Ok(RestoreOutcome { restored, skipped }) + } +} + +/// What a restore did: how many folders it attached, and why it left out the +/// others. +pub(crate) struct RestoreOutcome { + pub(crate) restored: usize, + pub(crate) skipped: Vec, +} + +impl RestoreOutcome { + /// The activity entry's headline. + pub(crate) fn summary(&self) -> String { + match (self.restored, self.skipped.len()) { + (n, 0) => format!("restored {n} folder(s)"), + (0, k) => format!("restored no folders; skipped {k}"), + (n, k) => format!("restored {n} folder(s); skipped {k}"), + } + } + + /// Whether the restore did all it was asked. A skipped folder is a + /// failure the user has to see: the reply went out as "restoring folders" + /// before any work, and a restore that skipped every folder used to be + /// logged as a success, so nothing anywhere said that nothing would + /// download (`docs/BUGS.md` B269). + pub(crate) fn complete(&self) -> bool { + self.skipped.is_empty() } } diff --git a/crates/pdfs-fuse/src/sim/daemon.rs b/crates/pdfs-fuse/src/sim/daemon.rs index 3f352dc..81b41f6 100644 --- a/crates/pdfs-fuse/src/sim/daemon.rs +++ b/crates/pdfs-fuse/src/sim/daemon.rs @@ -608,14 +608,22 @@ mod tests { daemon.pending(), drive.tree() ); - // The bytes kept for the handle went once the trash landed with it closed. - let cached: Vec<_> = std::fs::read_dir(dir.join("cache")) - .unwrap() - .filter_map(|e| e.ok()) - .filter(|e| e.file_type().is_ok_and(|t| t.is_file())) - .map(|e| e.file_name()) - .collect(); - assert!(cached.is_empty(), "{cached:?}"); + // The bytes kept for the handle went once the trash landed with it + // closed. The queue empties as the trash's row goes, a moment before + // the eviction, so this waits too. + let cached = || -> Vec<_> { + std::fs::read_dir(dir.join("cache")) + .unwrap() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_ok_and(|t| t.is_file())) + .map(|e| e.file_name()) + .collect() + }; + assert!( + wait_until(Duration::from_secs(30), || cached().is_empty()), + "{:?}", + cached() + ); assert!(matches!(daemon.stop(), Ok(MountOutcome::Shutdown))); let _ = std::fs::remove_dir_all(&dir); @@ -2063,6 +2071,21 @@ mod tests { daemon.write_new(&path, b"first").unwrap(); assert!(wait_until(Duration::from_secs(30), || held.reached())); std::fs::write(&path, b"second").unwrap(); + // The kernel sends the release after `close` returns. Attaching the + // bytes resets the create's attempts, so a release that came after the + // lost reply had it retried at once, and the retry landed before + // anything here saw it fail. The bytes it replaced go only once they + // are attached. + let staged = dir.join("cache").join("staging"); + assert!(wait_until(Duration::from_secs(30), || { + let blobs: Vec> = std::fs::read_dir(&staged) + .into_iter() + .flatten() + .filter_map(|e| e.ok()) + .filter_map(|e| std::fs::read(e.path()).ok()) + .collect(); + blobs.iter().any(|b| b == b"second") && !blobs.iter().any(|b| b == b"first") + })); drop(held); assert!(wait_until(Duration::from_secs(30), || { daemon diff --git a/crates/pdfs-fuse/src/supervisor.rs b/crates/pdfs-fuse/src/supervisor.rs index c7cfdf3..6e54f87 100644 --- a/crates/pdfs-fuse/src/supervisor.rs +++ b/crates/pdfs-fuse/src/supervisor.rs @@ -36,6 +36,11 @@ const TICK: Duration = Duration::from_secs(15); /// would train everyone to ignore the real one. const STALL_AFTER: Duration = Duration::from_secs(120); +/// How long a sync pass may go without reporting progress before it is called +/// out. Much longer than [`STALL_AFTER`]: one large file in a pass reports +/// nothing until its whole transfer is done. +const SYNC_STALL_AFTER: Duration = Duration::from_secs(900); + /// How often resident size goes into the log. const RSS_EVERY: Duration = Duration::from_secs(300); @@ -137,6 +142,26 @@ fn report_stalls(report: &pdfs_core::control::Diagnostics, last_completed: (u64, "the transfer lane has not completed a job since the last check" ); } + // The engine is one thread for every synced folder, so a dead or stuck one + // stops all of them while the worker pool looks idle (B259). + if let Some(sync) = &report.sync { + if !sync.running { + stalled = true; + warn!( + "the sync engine is not running; synced folders do not sync until the daemon restarts" + ); + } else if let Some(folder) = sync.pass_folder + && sync.pass_idle_secs >= SYNC_STALL_AFTER.as_secs() + { + stalled = true; + warn!( + folder, + age_secs = sync.pass_age_secs, + idle_secs = sync.pass_idle_secs, + "a sync pass has not reported progress for a long time" + ); + } + } stalled } @@ -169,7 +194,7 @@ fn probe(control_socket: &Path) -> bool { #[cfg(test)] mod tests { use super::*; - use pdfs_core::control::{Diagnostics, InflightRequest, WorkerState}; + use pdfs_core::control::{Diagnostics, InflightRequest, SyncEngineState, WorkerState}; fn busy_worker(age_secs: u64) -> WorkerState { WorkerState { @@ -226,4 +251,45 @@ mod tests { assert!(!report_stalls(&report, (0, 30))); assert!(report_stalls(&report, (0, 40))); } + + fn with_sync(sync: SyncEngineState) -> Diagnostics { + Diagnostics { + sync: Some(sync), + ..Default::default() + } + } + + /// A sync engine that has ended stops every synced folder while the + /// workers look idle, which is what issue #53 looked like (B259). + #[test] + fn a_stopped_sync_engine_is_a_stall() { + assert!(report_stalls( + &with_sync(SyncEngineState::default()), + (0, 0) + )); + } + + /// A pass that keeps reporting progress is working, however long it runs; + /// one that has gone quiet past the threshold is not. + #[test] + fn only_a_quiet_sync_pass_is_a_stall() { + let busy = SyncEngineState { + running: true, + pass_folder: Some(3), + pass_age_secs: SYNC_STALL_AFTER.as_secs() * 4, + pass_idle_secs: 5, + panics: 0, + }; + assert!(!report_stalls(&with_sync(busy.clone()), (0, 0))); + let quiet = SyncEngineState { + pass_idle_secs: SYNC_STALL_AFTER.as_secs(), + ..busy + }; + assert!(report_stalls(&with_sync(quiet), (0, 0))); + let idle = SyncEngineState { + running: true, + ..Default::default() + }; + assert!(!report_stalls(&with_sync(idle), (0, 0))); + } } diff --git a/crates/pdfs-fuse/src/sync.rs b/crates/pdfs-fuse/src/sync.rs index 864e522..67a6458 100644 --- a/crates/pdfs-fuse/src/sync.rs +++ b/crates/pdfs-fuse/src/sync.rs @@ -49,7 +49,9 @@ mod local_scan; mod model; mod planner; pub(crate) use engine::SYNC_CONCURRENCY; -pub(crate) use engine::{SyncMsg, spawn}; +pub(crate) use engine::{EngineHealth, SyncMsg, spawn}; +#[cfg(test)] +use engine::{Running, burst_order, keep_wanted}; #[cfg(test)] use engine::{classify, event_target, settle_with}; #[cfg(test)] @@ -67,6 +69,11 @@ use planner::{ plan_file, rel_to_path, remote_sig, remote_subtree_changed, revision_moved, tracked_baseline, unchanged_remote_size, }; + +/// A pass that took at least this long is logged at info even when it changed +/// nothing, so the journal says where a slow engine spends its time. +const SLOW_PASS: Duration = Duration::from_secs(30); + /// What a [`Pending`] op did, so the reconcile can update shared state on the /// engine thread (never inside a task). enum Applied { @@ -154,6 +161,14 @@ impl Core { // The cost is one small file read against a pass that is about to walk the // whole tree. let rules = self.ignore_rules(&local_root); + // A first pass is the one a user waits on after adding or restoring a + // folder, so it is said at info; the poll's passes stay at debug. + if folder.last_sync == 0 { + info!(id = folder.id, path = %folder.local_path, "sync: first pass starting"); + } else { + debug!(id = folder.id, "sync: pass starting"); + } + let started = Instant::now(); self.progress_begin(folder.id); let result = if push_only { self.push_pass(folder.id, &local_root, &remote_root, &rules) @@ -195,6 +210,18 @@ impl Core { "idle" }; let _ = self.db.sync_folder_set_state(folder.id, state, now_secs()); + let elapsed = started.elapsed(); + if folder.last_sync == 0 || !outcome.is_empty() || elapsed >= SLOW_PASS { + info!( + id = folder.id, + state, + elapsed_ms = elapsed.as_millis() as u64, + summary = %outcome.summary(), + "sync: pass finished" + ); + } else { + debug!(id = folder.id, state, "sync: pass finished"); + } 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 @@ -1135,14 +1162,22 @@ impl Core { let uids = self .rt .block_on(self.drive.enumerate_folder_children_node_uids(folder)) - .map_err(|e| format!("enumerate {folder}: {e}"))?; + .map_err(|e| format!("enumerate {folder}: {e}")); + let uids = match unlisted_gap(walk, prefix, uids)? { + Some(uids) => uids, + None => return Ok(()), + }; if uids.is_empty() { return Ok(()); } let nodes = self .rt .block_on(self.drive.enumerate_nodes_light(&uids)) - .map_err(|e| format!("resolve nodes: {e}"))?; + .map_err(|e| format!("resolve nodes: {e}")); + let nodes = match unlisted_gap(walk, prefix, nodes)? { + Some(nodes) => nodes, + None => return Ok(()), + }; // The SDK leaves out a node it cannot decrypt, and says so only in its // log. Its name went with it, so all this can record is the folder. if nodes.len() < uids.len() { @@ -1186,10 +1221,14 @@ impl Core { let sized: HashMap)> = if stale.is_empty() { HashMap::new() } else { - self.rt + let full = self + .rt .block_on(self.drive.enumerate_nodes(&stale)) - .map_err(|e| format!("resolve nodes: {e}"))? - .iter() + .map_err(|e| format!("resolve nodes: {e}")); + let Some(full) = unlisted_gap(walk, prefix, full)? else { + return Ok(()); + }; + full.iter() .map(|n| { ( n.uid.to_string(), @@ -2007,6 +2046,34 @@ struct RemoteWalk<'a> { gaps: &'a mut RemoteGaps, } +/// A listing call's result in [`Core::walk_remote`]. A failure below the root +/// records `prefix` as unlisted and returns `None`, so the walk goes on +/// without that folder. A failure at the root still fails the pass, since +/// nothing would be left to compare. +/// +/// One listing error, after the SDK's own retries, used to fail the whole +/// walk. Nothing is applied before the walk ends, so on a link that drops a +/// request now and then a large first sync started over from nothing on +/// every pass and never got past it (`docs/BUGS.md` B271). +fn unlisted_gap( + walk: &mut RemoteWalk<'_>, + prefix: &str, + result: Result, +) -> Result, PassAbort> { + match result { + Ok(value) => Ok(Some(value)), + Err(error) if prefix.is_empty() => Err(error.into()), + Err(error) => { + warn!( + folder = prefix, + error, "sync: a folder on Drive could not be listed; leaving it out of this pass" + ); + walk.gaps.unlisted(prefix); + Ok(None) + } + } +} + /// Whether the file at `path` is no longer what the scan saw as `planned`. /// A file that is gone has nothing left to lose, so it has not changed. fn changed_since_scan(path: &Path, planned: &LocalItem) -> bool { @@ -2438,6 +2505,72 @@ fn system_mtime_ns(meta: &std::fs::Metadata) -> Option { mod tests { use super::*; + /// A panic in a pass ends that pass and is counted; the engine is not + /// left believing the pass still runs (`docs/BUGS.md` B259). + #[test] + fn a_panicking_pass_is_contained_and_counted() { + let health = EngineHealth::default(); + assert!(!health.contain(7, || panic!("a pass that panics"))); + let report = health.report(); + assert_eq!(report.panics, 1); + assert_eq!(report.pass_folder, None); + assert!(health.contain(7, || {})); + assert_eq!(health.report().panics, 1); + } + + /// Folders named by id run before the rest of a `ReconcileAll` in the + /// same burst, so a folder just added does not wait behind a pass over + /// every other folder (`docs/BUGS.md` B260). + #[test] + fn named_folders_run_before_the_rest_of_a_burst() { + let ids: HashSet = [9, 4].into(); + assert_eq!(burst_order(&ids, &[1, 4, 6, 9]), [4, 9, 1, 6]); + assert_eq!(burst_order(&HashSet::new(), &[1, 4]), [1, 4]); + // A folder the list no longer has still gets the pass it asked for. + assert_eq!(burst_order(&[12].into(), &[1]), [12, 1]); + } + + /// A folder removed and added again keeps its watch under the new id; + /// a folder that is gone loses its watch (`docs/BUGS.md` B272). + #[test] + fn a_re_added_folder_keeps_its_watch_under_the_new_id() { + let mut have = vec![(PathBuf::from("/a"), 1), (PathBuf::from("/b"), 2)]; + let dropped = keep_wanted(&mut have, &[(PathBuf::from("/a"), 7)]); + assert_eq!(have, [(PathBuf::from("/a"), 7)]); + assert_eq!(dropped, [PathBuf::from("/b")]); + } + + /// While a pass runs, the report names its folder, which is what tells a + /// long pass apart from an idle engine. + #[test] + fn the_running_pass_is_reported() { + let health = EngineHealth::default(); + let mut seen = None; + health.contain(7, || seen = health.report().pass_folder); + assert_eq!(seen, Some(7)); + } + + /// The engine reads as running only while its thread lives, including + /// when the thread ends in a panic. + #[test] + fn the_engine_reads_as_stopped_once_its_thread_ends() { + let health = Arc::new(EngineHealth::default()); + let thread_health = health.clone(); + let (started_tx, started_rx) = std::sync::mpsc::channel(); + let (end_tx, end_rx) = std::sync::mpsc::channel::<()>(); + let thread = std::thread::spawn(move || { + let _running = Running::start(&thread_health); + started_tx.send(()).unwrap(); + let _ = end_rx.recv(); + panic!("the engine thread panics"); + }); + started_rx.recv().unwrap(); + assert!(health.report().running); + end_tx.send(()).unwrap(); + assert!(thread.join().is_err()); + assert!(!health.report().running); + } + /// A removal of something already gone has produced the state the sync /// engine wanted, so its baseline is dropped rather than retried forever. /// Anything else stays an error and keeps the baseline (`docs/BUGS.md` B42). diff --git a/crates/pdfs-fuse/src/sync/engine.rs b/crates/pdfs-fuse/src/sync/engine.rs index 5664392..8d6fb00 100644 --- a/crates/pdfs-fuse/src/sync/engine.rs +++ b/crates/pdfs-fuse/src/sync/engine.rs @@ -1,5 +1,11 @@ //! Filesystem watcher, polling, and debounce control plane. +use std::panic::AssertUnwindSafe; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; + +use pdfs_core::control::SyncEngineState; +use tracing::error; + use super::*; /// How long the watcher must go quiet before a burst counts as finished. @@ -23,6 +29,9 @@ const POLL_INTERVAL: Duration = Duration::from_secs(120); /// unusable done one-at-a-time; this bounds concurrency like the bulk-upload /// engine does. pub(crate) const SYNC_CONCURRENCY: usize = 8; +/// How long after a failed watch the engine tries the watches again. Each try +/// walks the folder's whole tree, so not on every burst. +const REWATCH_RETRY: Duration = Duration::from_secs(600); /// A message to the sync engine's control thread. pub(crate) enum SyncMsg { @@ -53,32 +62,159 @@ pub(crate) fn spawn(core: Core, rx: Receiver) -> Option>, + panics: AtomicU64, +} + +#[derive(Clone, Copy)] +struct PassClock { + folder: i64, + started: Instant, + moved: Instant, +} + +impl EngineHealth { + /// Note that the running pass reported progress. + pub(crate) fn moved(&self) { + if let Some(pass) = self.pass.lock().as_mut() { + pass.moved = Instant::now(); + } + } + + /// The engine's state now. `try_lock`, like the rest of the diagnostics: + /// a report must not wait on the thing it reports about. + pub(crate) fn report(&self) -> SyncEngineState { + let pass = self.pass.try_lock().and_then(|pass| *pass); + SyncEngineState { + running: self.running.load(Ordering::Relaxed), + pass_folder: pass.map(|p| p.folder), + pass_age_secs: pass.map_or(0, |p| p.started.elapsed().as_secs()), + pass_idle_secs: pass.map_or(0, |p| p.moved.elapsed().as_secs()), + panics: self.panics.load(Ordering::Relaxed), + } + } + + /// Run `pass` over `folder` as the current pass. Returns `false` when it + /// panicked: the panic ends that pass, not the engine. + pub(super) fn contain(&self, folder: i64, pass: impl FnOnce()) -> bool { + let now = Instant::now(); + *self.pass.lock() = Some(PassClock { + folder, + started: now, + moved: now, + }); + let finished = std::panic::catch_unwind(AssertUnwindSafe(pass)).is_ok(); + *self.pass.lock() = None; + if !finished { + self.panics.fetch_add(1, Ordering::Relaxed); + } + finished + } +} + +/// Marks the engine running for as long as it lives. +pub(super) struct Running<'a>(&'a EngineHealth); + +impl<'a> Running<'a> { + pub(super) fn start(health: &'a EngineHealth) -> Self { + health.running.store(true, Ordering::Relaxed); + Self(health) + } +} + +impl Drop for Running<'_> { + fn drop(&mut self) { + self.0.running.store(false, Ordering::Relaxed); + if std::thread::panicking() { + error!("sync engine stopped by a panic; nothing syncs until the daemon restarts"); + } + } +} + +/// One folder's pass, contained: a panic in it sets the folder to `error` and +/// leaves the engine running for the next one. +fn run_pass(core: &Core, folder: &StoredSyncFolder) { + if core + .sync_engine + .contain(folder.id, || core.reconcile_folder(folder)) + { + return; + } + core.progress_end(folder.id); + let _ = core + .db + .sync_folder_set_state(folder.id, "error", folder.last_sync); + error!( + id = folder.id, + path = %folder.local_path, + "sync: pass panicked; the folder is set to error and tried again on the next poll" + ); + core.log_activity( + ActivityKind::Sync, + format!("sync failed for {}", base_name(&folder.local_path)), + "an internal error stopped the pass; it is tried again on the next poll", + false, + ); +} + +/// The pass a `Reconcile(id)` asked for. A folder removed since, or one the +/// database cannot read, is said so rather than dropped in silence. +fn run_pass_by_id(core: &Core, id: i64) { + match core.db.sync_folder_get(id) { + Ok(Some(folder)) => run_pass(core, &folder), + Ok(None) => debug!(id, "sync: folder removed before its pass; skipping"), + Err(e) => warn!(id, error = ?e, "sync: cannot read folder; skipping its pass"), + } +} + /// The engine control loop. Single-threaded so reconcile passes never overlap. fn engine_loop(core: Core, rx: Receiver) { + let health = core.sync_engine.clone(); + let _running = Running::start(&health); // 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: 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 }; - // Ignore pure metadata/access noise; content changes are what matter. - if matches!(event.kind, notify::EventKind::Access(_)) { - return; - } - let msg = event_target(&watched_events.lock(), &event.paths); - let _ = tx_events.send(msg); - }) { - Ok(w) => w, + let handler = move |res: notify::Result| { + let event = match res { + Ok(event) => event, + // Running out of inotify watches for a new subfolder, say: that + // subfolder's changes are then found by the poll only (B262). Err(e) => { - warn!(error = %e, "filesystem watcher unavailable; sync is poll-only"); - // Still run the loop so polling and explicit SyncNow work. - poll_only_loop(&core, rx); + warn!(error = %e, "sync: filesystem watcher error"); return; } }; + // Ignore pure metadata/access noise; content changes are what matter. + if matches!(event.kind, notify::EventKind::Access(_)) { + return; + } + let msg = event_target(&watched_events.lock(), &event.paths); + let _ = tx_events.send(msg); + }; + // The local walk skips symlinks, so following them here would only watch + // trees outside the folder, and run passes over it for changes made there. + let config = notify::Config::default().with_follow_symlinks(false); + let mut watcher = match notify::RecommendedWatcher::new(handler, config) { + Ok(w) => w, + Err(e) => { + warn!(error = %e, "filesystem watcher unavailable; sync is poll-only"); + // Still run the loop so polling and explicit SyncNow work. + poll_only_loop(&core, rx); + return; + } + }; // Periodic remote poll. { @@ -96,8 +232,9 @@ fn engine_loop(core: Core, rx: Receiver) { .ok(); } - rewatch(&core, &mut watcher, &watched); - reconcile_all(&core); + let mut unwatched = rewatch(&core, &mut watcher, &watched); + let mut last_rewatch = Instant::now(); + run_burst(&core, &HashSet::new(), true); while let Ok(msg) = rx.recv() { let mut ids: HashSet = HashSet::new(); @@ -110,21 +247,16 @@ fn engine_loop(core: Core, rx: Receiver) { return; } + // A folder whose watch failed is tried again now and then, not only + // when the folder set changes. + if unwatched && last_rewatch.elapsed() >= REWATCH_RETRY { + do_rewatch = true; + } if do_rewatch { - rewatch(&core, &mut watcher, &watched); - } - if all { - reconcile_all(&core); - } else { - for id in ids { - if core.sync_paused() { - break; - } - if let Ok(Some(folder)) = core.db.sync_folder_get(id) { - core.reconcile_folder(&folder); - } - } + unwatched = rewatch(&core, &mut watcher, &watched); + last_rewatch = Instant::now(); } + run_burst(&core, &ids, all); } } @@ -141,7 +273,7 @@ fn poll_only_loop(core: &Core, rx: Receiver) { } }); } - reconcile_all(core); + run_burst(core, &HashSet::new(), true); while let Ok(msg) = rx.recv() { let mut ids: HashSet = HashSet::new(); let mut all = false; @@ -152,21 +284,59 @@ fn poll_only_loop(core: &Core, rx: Receiver) { info!("sync engine stopping"); return; } - if all { - reconcile_all(core); - } else { - for id in ids { - if core.sync_paused() { - break; - } - if let Ok(Some(folder)) = core.db.sync_folder_get(id) { - core.reconcile_folder(&folder); - } + run_burst(core, &ids, all); + } +} + +/// Run the passes one burst asked for: the folders named by id, then, when the +/// burst also holds a `ReconcileAll`, every other folder. +/// +/// The named folders go first. A poll's `ReconcileAll` in the same burst used +/// to take their place, and a folder just added, which has the highest id, +/// waited behind a full pass over every other folder (`docs/BUGS.md` B260). +/// +/// Skipped while sync is paused, and cut short when a pause lands between +/// passes: the resume sends a fresh [`SyncMsg::ReconcileAll`], which walks +/// everything the watcher reported meanwhile. +fn run_burst(core: &Core, ids: &HashSet, all: bool) { + if core.sync_paused() { + debug!("sync: paused, skipping reconcile"); + return; + } + let folders = if all { + match core.db.sync_folder_list() { + Ok(folders) => folders, + Err(e) => { + warn!(error = ?e, "sync: cannot list folders"); + Vec::new() } } + } else { + Vec::new() + }; + let listed: Vec = folders.iter().map(|f| f.id).collect(); + for id in burst_order(ids, &listed) { + if core.sync_paused() { + break; + } + // A folder named by id is read again: it may have been added or + // changed after the list was read. + match folders.iter().find(|f| f.id == id) { + Some(folder) if !ids.contains(&id) => run_pass(core, folder), + _ => run_pass_by_id(core, id), + } } } +/// The order of a burst's passes: the folders named by id, lowest first, then +/// the rest of `listed` in its own order. +pub(super) fn burst_order(ids: &HashSet, listed: &[i64]) -> Vec { + let mut order: Vec = ids.iter().copied().collect(); + order.sort_unstable(); + order.extend(listed.iter().copied().filter(|id| !ids.contains(id))); + order +} + /// Absorb the rest of an event burst, returning once the watcher has been quiet /// for `quiet` or `cap` has elapsed since the first event — whichever comes /// first. The caller has already classified the event that opened the burst. @@ -238,26 +408,6 @@ pub(super) fn classify( true } -/// Reconcile every mirror folder in turn. Skipped while sync is paused: the -/// resume sends a fresh [`SyncMsg::ReconcileAll`], which walks everything the -/// watcher reported meanwhile. -fn reconcile_all(core: &Core) { - if core.sync_paused() { - debug!("sync: paused, skipping reconcile"); - return; - } - let folders = match core.db.sync_folder_list() { - Ok(f) => f, - Err(e) => { - warn!(error = ?e, "sync: cannot list folders"); - return; - } - }; - for folder in folders { - core.reconcile_folder(&folder); - } -} - /// 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 @@ -278,7 +428,8 @@ pub(super) fn event_target(watched: &[(PathBuf, i64)], paths: &[PathBuf]) -> Syn } /// Bring the filesystem watches in line with the current mirror-folder set: -/// watch newly-added folders, drop removed ones. +/// watch newly-added folders, drop removed ones. Returns whether a folder is +/// left unwatched because its watch failed. /// /// 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 @@ -287,7 +438,7 @@ fn rewatch( core: &Core, watcher: &mut notify::RecommendedWatcher, watched: &Mutex>, -) { +) -> bool { let want: Vec<(PathBuf, i64)> = match core.db.sync_folder_list() { Ok(f) => f .into_iter() @@ -296,30 +447,60 @@ fn rewatch( .collect(), Err(e) => { warn!(error = ?e, "sync: cannot list folders to watch"); - return; + return false; } }; let mut have = watched.lock().clone(); // Drop watches no longer wanted. - have.retain(|(path, _)| { - if want.iter().any(|(p, _)| p == path) { - true - } else { - let _ = watcher.unwatch(path); - false - } - }); + for path in keep_wanted(&mut have, &want) { + let _ = watcher.unwatch(&path); + } // Add newly wanted watches. + let mut failed = false; for (path, id) in &want { if have.iter().any(|(p, _)| p == path) { continue; } match watcher.watch(path, RecursiveMode::Recursive) { Ok(()) => have.push((path.clone(), *id)), - Err(e) => warn!(path = %path.display(), error = %e, "sync: watch failed"), + Err(e) => { + // A recursive watch that fails partway keeps the watches it had + // added. Their events would match no folder and run a pass over + // every one, and they hold watches the next folder needs (B262). + let _ = watcher.unwatch(path); + failed = true; + warn!( + path = %path.display(), + error = %e, + "sync: watch failed; the poll finds this folder's changes until a retry \ + works. A low fs.inotify.max_user_watches is the usual cause" + ); + } } } *watched.lock() = have; + failed +} + +/// Keep the watches in `have` that `want` still asks for, and return the paths +/// of the others, to unwatch. +/// +/// A path is matched alone, but takes the id `want` gives it: a folder removed +/// and added again in one burst keeps its watch, and its events go to the new +/// row rather than to the removed one (`docs/BUGS.md` B272). +pub(super) fn keep_wanted(have: &mut Vec<(PathBuf, i64)>, want: &[(PathBuf, i64)]) -> Vec { + let mut dropped = Vec::new(); + have.retain_mut(|(path, id)| match want.iter().find(|(p, _)| p == path) { + Some((_, wanted)) => { + *id = *wanted; + true + } + None => { + dropped.push(path.clone()); + false + } + }); + dropped } // ---- reconcile ------------------------------------------------------------ diff --git a/crates/pdfs-fuse/src/sync/planner.rs b/crates/pdfs-fuse/src/sync/planner.rs index 988bdf6..42264ff 100644 --- a/crates/pdfs-fuse/src/sync/planner.rs +++ b/crates/pdfs-fuse/src/sync/planner.rs @@ -236,6 +236,9 @@ pub(super) struct RemoteGaps { short: HashSet, /// Paths seen, but whose entry could not be read. unread: HashSet, + /// Folders whose listing failed outright, after the SDK's retries. Nothing + /// under one is known, so all of it is left out of the pass (B271). + unlisted: HashSet, } impl RemoteGaps { @@ -249,8 +252,13 @@ impl RemoteGaps { self.unread.insert(rel); } + /// The listing of `folder` failed, so nothing under it was walked. + pub(super) fn unlisted(&mut self, folder: &str) { + self.unlisted.insert(folder.to_string()); + } + pub(super) fn is_empty(&self) -> bool { - self.short.is_empty() && self.unread.is_empty() + self.short.is_empty() && self.unread.is_empty() && self.unlisted.is_empty() } /// Whether `rel` may be missing from `remote` only because the walk could @@ -272,7 +280,7 @@ impl RemoteGaps { let mut at = parent_rel(rel); loop { if remote_dirs.contains_key(at) { - return self.short.contains(at); + return self.short.contains(at) || self.unlisted.contains(at); } if at.is_empty() { return false; @@ -295,6 +303,9 @@ impl RemoteGaps { .iter() .map(|rel| format!("{rel} (cannot be read on Proton Drive)")), ) + .chain(self.unlisted.iter().map(|rel| { + format!("{rel} (could not be listed on Proton Drive; tried again next pass)") + })) .collect(); lines.sort_unstable(); lines @@ -844,6 +855,32 @@ mod file_plan_tests { assert!(gaps.hides("top.txt", &remote, &dirs)); } + /// A folder whose listing failed was walked no further: nothing under it + /// reads as deleted on Drive, and a sibling that listed still does + /// (`docs/BUGS.md` B271). + #[test] + fn a_folder_that_failed_to_list_hides_everything_under_it() { + let remote = HashMap::from([ + ("docs".to_string(), folder()), + ("pics".to_string(), folder()), + ]); + let dirs = HashMap::from([ + (String::new(), uid("root")), + ("docs".to_string(), uid("docs")), + ("pics".to_string(), uid("pics")), + ]); + let mut gaps = RemoteGaps::default(); + gaps.unlisted("docs"); + assert!(gaps.hides("docs/a.txt", &remote, &dirs)); + assert!(gaps.hides("docs/sub/deep.txt", &remote, &dirs)); + assert!(!gaps.hides("docs", &remote, &dirs)); + assert!(!gaps.hides("pics/gone.jpg", &remote, &dirs)); + assert_eq!( + gaps.report(), + ["docs (could not be listed on Proton Drive; tried again next pass)"] + ); + } + #[test] fn a_file_whose_size_could_not_be_read_is_hidden() { let remote: HashMap = HashMap::new(); diff --git a/crates/pdfs-fuse/src/systemd.rs b/crates/pdfs-fuse/src/systemd.rs index 22683bc..f54091e 100644 --- a/crates/pdfs-fuse/src/systemd.rs +++ b/crates/pdfs-fuse/src/systemd.rs @@ -64,11 +64,19 @@ fn send_abstract(socket: &UnixDatagram, name: &str, message: &str) -> bool { /// so `systemctl --user start` returns when the mount actually works rather than /// when the process exists. pub(crate) fn ready() { - if notify("READY=1\n") { + if notify("READY=1\nSTATUS=Mounted\n") { debug!("notified systemd: ready"); } } +/// Set the one-line status `systemctl --user status` shows for the unit, such +/// as why the mount keeps failing (`docs/BUGS.md` B263). +pub fn status(text: &str) { + // A newline would end the `STATUS=` line and start another variable. + let line = text.replace('\n', " "); + notify(&format!("STATUS={line}\n")); +} + /// Tell the manager the daemon is still healthy. pub(crate) fn watchdog_ping() { let _ = notify("WATCHDOG=1\n"); diff --git a/crates/pdfs-gui/src/app/main.rs b/crates/pdfs-gui/src/app/main.rs index 5db14d0..a30fa50 100644 --- a/crates/pdfs-gui/src/app/main.rs +++ b/crates/pdfs-gui/src/app/main.rs @@ -1797,13 +1797,35 @@ pub(crate) fn service_unreachable( ); return; } - status( - &ui, - "network-idle-symbolic", - &gettext("Connecting…"), - &gettext("Waiting for the Proton Drive service to start."), - false, - ); + // Running but not answering may be a mount that fails on every try, + // which never opens the socket. Its reason is in a file the daemon + // keeps for this (B263); "Connecting…" alone looked like a slow start + // for as long as it lasted. + let state_dir = ui.dirs.state_dir(); + let failure = gio::spawn_blocking(move || pdfs_core::mountfail::read(&state_dir)) + .await + .ok() + .flatten(); + match failure { + Some(failure) => status( + &ui, + "dialog-warning-symbolic", + &gettext("Can't open your Drive"), + // Translators: {error} is an error message, in English. + &gettext_f( + "The Proton Drive service keeps failing to start and tries again on its own. The last error was: {error}", + &[("error", &failure.error)], + ), + true, + ), + None => status( + &ui, + "network-idle-symbolic", + &gettext("Connecting…"), + &gettext("Waiting for the Proton Drive service to start."), + false, + ), + } glib::timeout_add_local_once(CONNECT_RETRY_INTERVAL, move || { if ui.stack.visible_child_name().as_deref() == Some(page) { reload(&ui); diff --git a/crates/pdfs-gui/src/app/pages/devices.rs b/crates/pdfs-gui/src/app/pages/devices.rs index 16635ad..b11517a 100644 --- a/crates/pdfs-gui/src/app/pages/devices.rs +++ b/crates/pdfs-gui/src/app/pages/devices.rs @@ -597,6 +597,19 @@ pub(crate) fn this_computer_subtitle(folders: &[SyncFolderInfo]) -> String { folders.len() as u64, &[], ); + // A folder whose first pass has not run is not backed up yet, whatever its + // state column says (`docs/BUGS.md` B261). + let waiting = folders.iter().filter(|f| never_synced(f)).count(); + let count = match waiting { + 0 => count, + // Translators: {folders} is "N folders backed up"; {n} counts the folders whose first sync has not run yet. + n => ngettext_f( + "{folders} · {n} waiting for its first sync", + "{folders} · {n} waiting for their first sync", + n as u64, + &[("folders", &count)], + ), + }; let attention = folders .iter() .filter(|f| f.state == "error" || f.state == "conflict") @@ -613,12 +626,21 @@ pub(crate) fn this_computer_subtitle(folders: &[SyncFolderInfo]) -> String { } } -/// Human label for a synced folder's `state` column. -pub(crate) fn sync_state_label(state: &str) -> String { +/// Whether a synced folder is a mirror whose first pass has not finished. +/// Its `state` starts out `idle`, which would read as "up to date". +fn never_synced(folder: &SyncFolderInfo) -> bool { + folder.mode == "mirror" && folder.last_sync == 0 && folder.state == "idle" +} + +/// Human label for a synced folder's `state` column. `never_synced` is a +/// mirror folder whose first pass has not finished: its `state` starts out +/// `idle`, and "up to date" would say the opposite of the truth (B261). +pub(crate) fn sync_state_label(state: &str, never_synced: bool) -> String { match state { "syncing" => gettext("syncing…"), "error" => gettext("sync error"), "conflict" => gettext("needs attention"), + _ if never_synced => gettext("waiting for first sync"), _ => gettext("up to date"), } } @@ -1274,7 +1296,7 @@ mod tests { remote_uid: "vol~link".into(), mode: "mirror".into(), state: state.into(), - last_sync: 0, + last_sync: 1_700_000_000, pending_mode: None, progress: None, paused: false, @@ -1286,6 +1308,33 @@ mod tests { assert_eq!(this_computer_subtitle(&[]), "No folders backed up yet"); } + /// A folder added a moment ago is not backed up yet, and the row must not + /// say it is (`docs/BUGS.md` B261). An on-demand folder has no first sync. + #[test] + fn a_folder_before_its_first_sync_is_not_counted_as_backed_up() { + let new = SyncFolderInfo { + last_sync: 0, + ..folder("idle") + }; + assert_eq!( + this_computer_subtitle(&[folder("idle"), new.clone()]), + "2 folders backed up · 1 waiting for its first sync" + ); + let failed = SyncFolderInfo { + state: "error".into(), + ..new.clone() + }; + assert_eq!( + this_computer_subtitle(&[new.clone(), failed]), + "2 folders backed up · 1 waiting for its first sync · 1 needs attention" + ); + let on_demand = SyncFolderInfo { + mode: "ondemand".into(), + ..new + }; + assert_eq!(this_computer_subtitle(&[on_demand]), "1 folder backed up"); + } + #[test] fn folders_needing_attention_are_counted_not_hidden() { // The per-folder rows live on the Sync page, so this row is the only diff --git a/crates/pdfs-gui/src/app/pages/locations.rs b/crates/pdfs-gui/src/app/pages/locations.rs index 65963d2..a538248 100644 --- a/crates/pdfs-gui/src/app/pages/locations.rs +++ b/crates/pdfs-gui/src/app/pages/locations.rs @@ -1729,7 +1729,10 @@ pub(crate) fn location_subtitle(spec: &MountSpec) -> String { parts.push(match &spec.progress { Some(p) => sync_progress_label(p), None if spec.paused => pgettext("state", "paused"), - None => sync_state_label(&spec.state).to_string(), + None => sync_state_label( + &spec.state, + spec.mode == MountMode::Mirror && spec.last_sync == 0, + ), }); } // Only the locations that are supposed to have a session report its absence: @@ -1775,7 +1778,25 @@ mod tests { MountMode::Mirror, false, ); + assert_eq!(location_subtitle(&spec), "Synced · waiting for first sync"); + } + + /// A folder whose first pass has not run is not up to date, though its + /// state column starts out `idle` (`docs/BUGS.md` B261). + #[test] + fn a_folder_reads_up_to_date_only_after_its_first_sync() { + let mut spec = spec( + MountKind::Device { sync_folder_id: 7 }, + MountMode::Mirror, + false, + ); + assert_eq!(location_subtitle(&spec), "Synced · waiting for first sync"); + spec.last_sync = 1_700_000_000; assert_eq!(location_subtitle(&spec), "Synced · up to date"); + // A folder that failed before it ever synced says so. + spec.last_sync = 0; + spec.state = "error".into(); + assert_eq!(location_subtitle(&spec), "Synced · sync error"); } #[test] diff --git a/crates/pdfs-gui/src/main.rs b/crates/pdfs-gui/src/main.rs index 083466e..8471f79 100644 --- a/crates/pdfs-gui/src/main.rs +++ b/crates/pdfs-gui/src/main.rs @@ -194,6 +194,24 @@ fn poll_state(socket: &Path, default_mountpoint: &Path) -> DriveState { ), Phase::Attention, ), + // A daemon whose mount fails on every try never opens the + // socket; the app says why (B263). + Ok(s) + if socket + .parent() + .and_then(pdfs_core::mountfail::read) + .is_some() => + { + ( + // Translators: {username} is the Proton account name. The + // service keeps failing to start; the app shows the error. + gettext_f( + "Signed in as {username} — can't open your Drive", + &[("username", &s.username)], + ), + Phase::Attention, + ) + } Ok(s) => ( // Translators: {username} is the Proton account name. gettext_f( diff --git a/docs/BUGS.md b/docs/BUGS.md index 82a9423..23fc1ca 100644 --- a/docs/BUGS.md +++ b/docs/BUGS.md @@ -68,6 +68,10 @@ original wording. | Entry | Title | What is left | |---|---|---| +| [B275](#b275--on-a-slow-link-a-files-last-write-can-land-only-in-a-conflict-copy) | On a slow link, a file's last write can land only in a conflict copy | Cause not found; seen once in CI | +| [B273](#b273--a-restore-proposes-another-computers-paths-when-the-two-share-a-hostname) | A restore proposes another computer's paths when the two share a hostname | Not fixed | +| [B270](#b270--one-file-left-in-a-mirror-folder-clears-the-wipe-guard-for-all-the-others) | One file left in a mirror folder clears the wipe guard for all the others | Not fixed | +| [B267](#b267--a-key-failure-at-a-start-from-a-warm-cache-shows-as-offline-for-good) | A key failure at a start from a warm cache shows as offline for good | Not fixed | | [B134](#b134--a-file-deleted-online-while-the-drain-checks-its-write-is-kept-as-a-conflict-copy) | A file deleted online while the drain checks its write is kept as a conflict copy | Only with `local_first` off | | [B91](#b91--the-daemon-hung-overnight-and-nothing-could-say-what-it-was-waiting-on) | The daemon hung overnight, and nothing could say what it was waiting on | The cause is not diagnosed; the daemon now reports where it is stuck and is restarted | | [B67](#b67--fresh-state-restore-can-silently-select-a-new-device-high-22) | Fresh-state restore can silently select a new device (HIGH-22) | An ambiguous device after a loss of local state is not asked about | @@ -80,6 +84,11 @@ original wording. | Entry | Title | What is left | |---|---|---| +| [B269](#b269--a-restore-that-skipped-every-folder-reports-success) | A restore that skipped every folder reports success | The app shows the result only in the activity feed; no offer to replace or reset the folder in the way | +| [B268](#b268--pdfs-diagnose-says-no-problems-found-while-sync-is-stuck) | `pdfs diagnose` says "No problems found" while sync is stuck | The sync engine's state is not checked | +| [B263](#b263--a-mount-that-can-never-succeed-retries-every-5-s-and-tells-only-the-journal) | A mount that can never succeed retries every 5 s and tells only the journal | Errors no retry fixes back off like network errors | +| [B262](#b262--watching-a-large-synced-folder-holds-up-the-sync-engine-and-a-failed-watch-is-never-retried) | Watching a large synced folder holds up the sync engine, and a failed watch is never retried | The first pass still waits for the watch; a failed watch is not shown in the app | +| [B259](#b259--the-sync-engine-can-die-or-hang-and-nothing-shows-it-until-a-restart) | The sync engine can die or hang, and nothing shows it until a restart | No pass deadline; an ended engine is reported, not restarted; the trigger in #53 is not known | | [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 | @@ -100,6 +109,15 @@ a live account since. | Entry | Title | |---|---| +| [B265](#b265--address-keys-whose-token-is-for-an-inactive-account-key-never-open) | Address keys whose token is for an inactive account key never open | +| [B274](#b274--opening-a-file-for-writing-fails-with-eio-while-its-queued-revision-lands) | Opening a file for writing fails with EIO while its queued revision lands | +| [B271](#b271--one-failed-listing-makes-a-large-first-sync-start-over-from-nothing) | One failed listing makes a large first sync start over from nothing | +| [B266](#b266--pin-unpin-search-and-node-loads-report-a-database-error-as-offline) | Pin, unpin, search and node loads report a database error as "offline" | +| [B264](#b264--a-mount-that-fails-after-starting-its-workers-leaves-them-running) | A mount that fails after starting its workers leaves them running | +| [B261](#b261--a-folder-that-has-never-synced-reads-up-to-date) | A folder that has never synced reads "up to date" | +| [B272](#b272--after-a-folder-is-removed-and-added-again-its-file-events-go-to-the-old-id) | After a folder is removed and added again, its file events go to the old id | +| [B260](#b260--the-poll-pushes-a-new-folders-first-sync-behind-every-other-folder) | The poll pushes a new folder's first sync behind every other folder | +| [B258](#b258--journalctl--p-warning-shows-nothing-whatever-the-daemon-logged) | `journalctl -p warning` shows nothing, whatever the daemon logged | | [B257](#b257--deleting-a-new-file-while-it-is-open-can-drop-the-bytes-its-handle-reads) | Deleting a new file while it is open can drop the bytes its handle reads | | [B254](#b254--an-edit-in-the-same-second-as-a-new-upload-never-arrives) | An edit in the same second as a new upload never arrives | | [B253](#b253--the-app-shows-the-sign-in-page-when-the-keyring-is-locked) | The app shows the sign-in page when the keyring is locked | @@ -360,6 +378,505 @@ a live account since. Newest first. +### B275 — On a slow link, a file's last write can land only in a conflict copy + +**Status:** Open +**Found:** 2026-10-31, CI on #60: `one_client_on_a_slow_link`, profile `one-client-wifi` seed 2, +failed with `Drive does not hold what the clients wrote, 120s after the links came up`. The +same seed passed 4 times locally, and #60 changes no daemon logic, so the failure depends on +timing. +**Where:** not located; the drain's conflict check in `crates/pdfs-fuse/src/drain.rs` is the +first suspect + +**Repro:** Only the CI run above. The last steps created `x/z/y/a.txt` (37 bytes), wrote 42 +bytes into it at offset 20, and brought every link up. Drive then held the file at its first +37 bytes, and the full content was in `x/z/y/a (sync-conflict …).txt`. No other client wrote +to it. + +**Cause.** Not known. Nothing was lost, since the conflict copy holds the bytes, but the file +conflicted with its own create. B183 and B184 found the same symptom after a lost create +reply. + +**Fix.** Not done. Needs a reproduction: run the seed in a loop under load and keep the state +directory of a failing run. + +--- + +### B274 — Opening a file for writing fails with EIO while its queued revision lands + +**Status:** Fixed, not verified live +**Found:** 2026-10-31, CI: the simulation +`a_file_whose_upload_was_not_read_back_takes_two_writes_at_once` failed once with +`Input/output error` on a write, after `copy queued revision into write scratch failed … No +such file or directory`. It did not fail in 54 local runs. +**Where:** the write open in `crates/pdfs-fuse/src/filesystem.rs` + +**Repro:** Write a file, and open it for writing again at the moment its queued revision +finishes uploading. The open fails with EIO, and the program sees its save fail. + +**Cause.** The open samples the queued revision, then copies its blob into the new handle's +scratch file. The drain lets go of the pending entry and then deletes the blob when the upload +lands. A blob deleted between the sample and the copy failed the open, although the re-check +just after the copy already retries a revision that changed during it. + +**Fix.** A failed copy checks whether the pending entry still names that blob. When it does +not, the revision landed or was replaced, and the open goes around again with the base the +file has now, as the re-check does. Since the drain drops the entry before the blob, a blob +that is gone and still named is a real failure and still returns EIO. + +**Test:** none deterministic; the simulation above covers the window. + +--- + +### B273 — A restore proposes another computer's paths when the two share a hostname + +**Status:** Open +**Found:** 2026-10-31, code audit after issue #46. +**Where:** `saved_folder` in `crates/pdfs-core/src/profile.rs` + +**Repro:** Two computers with the same hostname, for example both left at a distribution's +default `archlinux`, sync the same device. Restore a folder on one of them. The proposed local +path and mode come from whichever of the two profiles is found first, not from this computer's. + +**Cause.** `saved_folder` prefers the profile whose `hostname` matches. The machine id that +B252 added tells computers apart, but this lookup does not use it. + +**Fix.** Not done. Match on the machine id first, then fall back to the hostname for profiles +written before it existed. + +--- + +### B272 — After a folder is removed and added again, its file events go to the old id + +**Status:** Fixed, not verified live +**Found:** 2026-10-31, code audit after issue #53. +**Where:** `rewatch` in `crates/pdfs-fuse/src/sync/engine.rs` + +**Repro:** Remove a synced folder and add the same path again before the engine handles the +first `Rewatch`, so both land in one burst. Edits in the folder are not synced until the +two-minute poll, and the same goes for every later edit until the daemon restarts. + +**Cause.** `rewatch` matches the watches it has against the ones it wants by path alone. The +path is still wanted, so the old entry stays with the removed folder's id. Its events become +`Reconcile(old id)`, `sync_folder_get` finds no row, and the request is dropped. + +**Fix.** `keep_wanted` keeps a watch whose path is still wanted and gives it the id the +folder list has now. + +**Test:** `a_re_added_folder_keeps_its_watch_under_the_new_id` in `pdfs-fuse`. + +--- + +### B271 — One failed listing makes a large first sync start over from nothing + +**Status:** Fixed, not verified live +**Found:** 2026-10-31, code audit after issue #46: a folder of about 19,900 items restored on a +second computer sat at `scanning…` with nothing downloaded, and the journal showed +`RequestTimeout (http 408)`. +**Where:** `walk_remote` in `crates/pdfs-fuse/src/sync.rs` + +**Repro:** Restore or add a mirror folder with tens of thousands of items on Drive, on a link +that drops a request now and then. The pass walks the whole remote tree before it downloads +anything. When one folder's listing fails after the SDK's retries, the pass fails, and the +next one walks the whole tree again. On a slow enough link it never gets past the walk. + +**Cause.** Each `enumerate_folder_children_node_uids`, `enumerate_nodes_light` and +`enumerate_nodes` call in the walk ends the whole walk through `?`. Nothing is applied until +the walk is done. Files that did download keep their baseline rows, so only the walk is lost, +but on a first sync that is all of it. Progress is also counted only after a folder's full +listing, so a large folder shows `scanning…` with 0 checked for a long time. + +**Fix.** A listing that fails below the sync root records that folder as a gap in +`RemoteGaps`, next to the folders whose listing came back short. The planner deletes nothing +under a gap, the rest of the pass still applies, and the pass report names the folder as +tried again next pass. A failed listing of the root itself still fails the pass, since there is +nothing left to apply. The progress count inside one large folder is unchanged. + +**Test:** `a_folder_that_failed_to_list_hides_everything_under_it` in `sync::planner`. + +--- + +### B270 — One file left in a mirror folder clears the wipe guard for all the others + +**Status:** Open +**Found:** 2026-10-31, code audit after issue #46, whose reporter emptied a synced folder +several times. +**Where:** `guard_local_wipe` and `guard_remote_wipe` in `crates/pdfs-fuse/src/sync/planner.rs` + +**Repro:** Sync a mirror folder with many files. Make all of them but one unavailable here: a +half-mounted disk, a restored backup that brought back one file, or a script that emptied the +folder and wrote one file back. The next pass trashes every other file on Drive. On a device +that two computers sync, the other computer then deletes its copies too. + +**Cause.** The guard refuses a pass only when every baseline path is missing. One surviving +path is enough to clear it, so it guards against an empty folder and nothing short of that. +The remote guard has the same shape. + +**Fix.** Not done. Also refuse when most of a large baseline is missing at once, and leave the +choice to the user: delete on the other side, or sync from scratch. + +--- + +### B269 — A restore that skipped every folder reports success + +**Status:** Partly fixed +**Found:** 2026-10-31, code audit after issue #46: the journal showed `restore complete +restored=0 skipped=1` several times, and the user saw nothing. +**Where:** the `RestoreSyncFolders` and `RestoreDeviceFolders` handler in +`crates/pdfs-fuse/src/control.rs`; `restore_sync_folders` in `crates/pdfs-fuse/src/profile.rs` + +**Repro:** Restore a folder from another computer to a path that is already a sync root here, +for example a folder left over from an earlier attempt. `pdfs devices join` prints `restoring +folders`, and the app shows the same. Nothing downloads, and no error appears anywhere. + +**Cause.** The daemon acks before it works, because a restore outlasts the socket timeout. The +result, `restored 0 folder(s); skipped: …`, is then logged as a successful activity, so the +reason is only in the activity text and the journal. When the database insert fails partway, +the `?` also skips the `Rewatch` for the folders already restored. + +**Fix.** `restore_sync_folders` returns what it restored and what it skipped, with the reason +for each skip. The daemon logs a restore that skipped any folder as a failed activity, with the +reasons as its detail, and warns in the journal. A database error partway now sends the +`Rewatch` for the folders already restored before it returns. `pdfs devices join` points to +`pdfs activity` for the result. + +Left: the app shows the result only in the activity feed, and nothing offers to replace or reset +the folder in the way. + +--- + +### B268 — `pdfs diagnose` says "No problems found" while sync is stuck + +**Status:** Partly fixed. The sync engine's own state is not checked yet. +**Found:** 2026-10-31, issue #53: a folder that never synced, and `diagnose` found nothing. +**Where:** `diagnose` in `crates/pdfs-cli/src/main.rs` + +**Repro:** Leave a synced folder in `error`, or have queued writes that keep failing, or pause +sync. `pdfs diagnose` reports every check `[ok]` and exits 0. + +**Cause.** The `Status` reply is matched with `..`, which drops `failing_ops`, `failing_error`, +`parked_uploads`, `staged_oldest_secs` and `paused`. `diagnose` never asks for the synced +folders or for `Diagnostics`, so a folder in error and a stalled worker are not seen. A +control socket file with no daemon behind it is also reported `[ok]`; only the next line says +the daemon does not answer. + +**Fix.** `diagnose` warns about writes that keep failing (with the last error), a write that +has waited a day or more to upload, a pause, and each synced folder in `error` or `conflict` +(`sync_health` and `folder_health` in `crates/pdfs-cli/src/main.rs`). A control socket file +that nothing listens on is a warning, not `[ok]`. Parked uploads are left out: a file still +wearing a download's temporary name is normal. Not done: the sync engine's state, which +`Diagnostics` reports once B259's change is in. + +**Test:** `a_stuck_queue_or_a_pause_is_reported` and `a_synced_folder_in_error_is_reported` in +`pdfs-cli`. On 2026-10-31 the new `diagnose` against a healthy 3.6.0 daemon still reported +"No problems found". + +--- + +### B267 — A key failure at a start from a warm cache shows as offline for good + +**Status:** Open +**Found:** 2026-10-31, code audit after issue #40. B202 fixed the same path for a refused +session only. +**Where:** `fetch_or_recall_root` in `crates/pdfs-fuse/src/mount.rs`; `run_online_probe` in +`crates/pdfs-fuse/src/link.rs` + +**Repro:** Sign in once so the cache holds the My Files root. Then start the daemon in a state +where the root cannot be decrypted, for example the address-key failure of B265. The mount +comes up "offline", the app says you are offline, and it stays so while the network is fine. + +**Cause.** Any error from `get_my_files_folder` other than a revoked session becomes +`Start::Offline` when a cached root exists. The online probe then retries the same call and +logs its failures at debug level, so the journal at the default level says nothing. + +**Fix.** Not done. Use `Start::Offline` only for an error `is_network_error` accepts, and +report the others as their own state through `Status`. + +--- + +### B266 — Pin, unpin, search and node loads report a database error as "offline" + +**Status:** Fixed, not verified live +**Found:** 2026-10-31, code audit after issue #38. B205 fixed the photo upload path only. +**Where:** `CoreError::from_api` in `crates/pdfs-core/src/error.rs`; its callers in +`crates/pdfs-fuse/src/lib.rs` (`pin`, `unpin`, the searches, `load node`) + +**Repro:** Pin a folder while the cache database is locked or full. The app says "You're +offline" and drops the reason. + +**Cause.** `from_api` classifies any error that is not a `ProtonError` as `Offline`, because +it was written for SDK calls, where that means a transport error. These callers pass it +`pdfs_core::Error` values from the cache and the database. A `ProtonError` wrapped in +`pdfs_core::Error::Proton` also misses the downcast, since the source chain is not walked. + +**Fix.** `from_api` walks the source chain. The first `ProtonError` or `pdfs_core::Error` in it +decides the kind, the same way `From` does, and only an error with neither, a transport +error, reads as offline. That covers every caller at once, the database ones included. + +**Test:** `a_database_error_handed_to_from_api_is_not_offline` in `pdfs-core`. + +--- + +### B265 — Address keys whose token is for an inactive account key never open + +**Status:** Fixed, not verified live +**Found:** 2026-10-31, code audit after issue #40: a Duo account whose mount failed on every +start with `fetch My Files root: cryptography error: failed to decrypt: missing key`, after three +`failed to decrypt address key token` warnings. The web app worked. B210's fix does not apply, +because the account has an active volume. +**Where:** `user_keys` and `decrypt_address` in `proton-sdk` (`crates/proton-sdk/src/account.rs` +in `proton-sdk-rs`) + +**Repro:** Not reproduced. An account whose address-key tokens are encrypted to an account key +that is no longer active cannot open those address keys, and so cannot open the My Files share. + +**Cause.** `user_keys` skips every inactive account key, even one the password opens, and the +address-key tokens are decrypted only with what it returns. `keycheck.rs` in this repository +already reports "token is for inactive account key", so the case is known. Separately, +`addresses()` fails as a whole through `?` when one address has no usable primary key, which +takes the working addresses down with it. + +**Fix.** In `proton-sdk` 0.10.1 (narrrl/proton-sdk-rs#3). Tokens are tried with every account +key that opens, active ones first, and signing uses active keys only. An address that cannot be +opened is left out with a warning, and the others work; `addresses()` fails only when none +opens. When the left-out address is the default, `default_address` fails with the reason +rather than signing with another address. `key_report` walks the same chain, so `pdfs diagnose` +reports such a key as opening. Not verified against an account with an inactive key, since +none is at hand; issue #40's reporter can confirm. + +**Test:** `an_address_key_whose_token_is_for_an_inactive_account_key_opens`, +`an_address_whose_keys_do_not_open_leaves_the_others_working` and +`an_account_whose_addresses_all_fail_to_open_is_an_error` in `proton-sdk`. + +--- + +### B264 — A mount that fails after starting its workers leaves them running + +**Status:** Fixed, not verified live +**Found:** 2026-10-31, code audit after issue #40. +**Where:** `run_mount` in `crates/pdfs-fuse/src/mount.rs` + +**Repro:** Make the mount fail after the workers start: a busy mountpoint, or a control socket +whose permissions cannot be set. The daemon retries every 5 s, and the drain, sync, sweep, +probe and index threads of the failed attempt keep running beside the next one. + +**Cause.** The early returns after the workers are spawned (the `?` on the socket bind, the +permission check, a `spawn_session` error, and a `?` on a later thread spawn) skip +`stop_workers`. The threads hold a `Core`, and the `Core` holds the database. If that keeps +the `flock` held, every later attempt fails with "another daemon is using cache.db" instead +of the real error. That part is inferred from the code and not observed. + +**Fix.** Every early return in `mount` after the first worker starts stops the workers +(`abandon_workers`, `keep_or_abandon` in `crates/pdfs-fuse/src/mount.rs`). Unlike a normal stop +it does not send `STOPPING=1`, because the daemon is about to retry and systemd would +otherwise wait for it to exit and then kill it. The control-thread failure path, which already +stopped its workers, no longer sends it either. + +**Test:** none of its own; the simulations (`sim::daemon`, 55 passed) cover the normal start +and stop. + +--- + +### B263 — A mount that can never succeed retries every 5 s and tells only the journal + +**Status:** Partly fixed. Errors that no retry fixes are not told apart from network errors yet. +**Found:** 2026-10-31, issue #40: the app showed "Connecting… waiting for the Proton Drive +service" for days, `pdfs diagnose` only reported the control socket missing, and the reporter +uninstalled. +**Where:** the mount loop in `crates/pdfs-cli/src/main.rs`; `run_mount` in +`crates/pdfs-fuse/src/mount.rs` + +**Repro:** Start the daemon with a session whose keys cannot open My Files (B265). The journal +shows `mount failed; retrying in 5s` forever. The app and the tray wait for the service, and +nothing says why. + +**Cause.** Every mount error gets the same 5 s retry, whether it is a network error or one that +no retry fixes, such as a key or a 4xx answer. Each attempt builds a new client and sends about +four requests, so a broken account sends tens of thousands a day, which may also feed +Proton's sign-in blocks (#39). The control socket is bound only after the mount succeeds, and +the error is written nowhere a front end can read it. + +**Fix.** +- The retry doubles from 5 s to a 2-minute ceiling (`mount_retry_delay` in + `crates/pdfs-cli/src/main.rs`), about 720 attempts a day instead of 17,000. The ceiling stays + at minutes because a cold start without a network fails the same way and has to mount soon + after the network returns. +- Each failure is written to `mount-failure.json` in the state directory + (`crates/pdfs-core/src/mountfail.rs`), with the error, since when it fails and the attempt + count. A mount that comes up removes it, and so does a new daemon. +- The app shows "Can't open your Drive" with the error and a Retry button instead of + "Connecting…". The tray says "Signed in as … — can't open your Drive". `pdfs diagnose` reports + `[FAIL] mount: failing for …`. `systemctl --user status` shows the error as the unit's status. +- Not done: key, `KeysLocked` and 4xx errors still retry like network errors, only less often. + +**Test:** `a_failing_mount_backs_off_to_a_ceiling` in `pdfs-cli`; +`a_recorded_failure_reads_back_until_cleared` in `pdfs-core`. On 2026-10-31 a second +`pdfs daemon` beside the running one failed on the database lock three times in 18 s (at 0, 5 +and 15 s) and recorded `attempts: 3` with the lock error. + +--- + +### B262 — Watching a large synced folder holds up the sync engine, and a failed watch is never retried + +**Status:** Partly fixed. A watch is still set up before the first pass, and a failed watch +is not shown in the folder's state. +**Found:** 2026-10-31, code audit after issue #53. +**Where:** `rewatch` and `engine_loop` in `crates/pdfs-fuse/src/sync/engine.rs` + +**Repro:** Add a synced folder with many subfolders, or one that holds a symlink to a large +tree, on a system near `fs.inotify.max_user_watches`. The new folder's first pass waits until +the recursive watch is set up. When the watch fails, the journal has one `sync: watch failed` +line, the folder is never watched, and later changes are found only by the two-minute poll. + +**Cause.** +- `rewatch` runs on the engine thread before the reconcile, and notify's recursive watch walks + the whole tree, at startup for every folder. +- notify follows symlinks by default, and the local walk does not. +- When `watch` fails partway, the watches added before the failure stay registered, but the + folder is not recorded as watched. Their events match no folder and become `ReconcileAll`, + and they use up watches the next folder needs. +- Errors the watcher reports later are dropped in its callback. + +**Fix.** +- A failed watch is unwatched, which removes the watches it had added under the folder, and + is tried again every 10 minutes. The warning names `fs.inotify.max_user_watches`. +- The watcher no longer follows symlinks. +- Errors the watcher reports are logged. +- Not done: the watch still runs before the folder's first pass. Watching first is what keeps + a change made during the pass from being missed, so the order stays; the cost is one walk + of a new folder's tree. A failed watch is not shown in the folder's state yet. + +**Test:** none for the watch failure itself; it needs the inotify limit reached. + +--- + +### B261 — A folder that has never synced reads "up to date" + +**Status:** Fixed, not verified live +**Found:** 2026-10-31, issue #53: "In Computers I add the folder. It appears as synced." +**Where:** `sync_folder` in `crates/pdfs-core/src/db/migrations.rs`; `sync_state_label` in +`crates/pdfs-gui/src/app/pages/devices.rs`; the location rows in +`crates/pdfs-gui/src/app/pages/locations.rs` + +**Repro:** Add a synced folder. Until its first pass starts, the app shows it as up to date. +The same happens after a switch from on-demand to mirror, after **Sync From Scratch…**, and for +every folder while sync is paused for the whole account. + +**Cause.** The `state` column defaults to `idle`, and every state the label does not know reads +"up to date". `last_sync` is 0 for a folder that has never synced, but the rows do not look at +it, and a test pins the label for that case. The rows only show a folder's own pause, not the +account-wide one. + +**Fix.** A mirror folder whose `last_sync` is 0 and whose state is `idle` reads "waiting for +first sync" on its row. The This computer row on the Computers page counts such folders, as in +"2 folders backed up · 1 waiting for its first sync". An on-demand folder has no first sync, +so it is left out. The account-wide pause stays where it already is, the pause card at the top +of the page; repeating it on every row would only add noise. + +**Test:** `a_folder_reads_up_to_date_only_after_its_first_sync` and +`a_folder_before_its_first_sync_is_not_counted_as_backed_up` in `pdfs-gui`; the pinned test now +expects the new label. + +--- + +### B260 — The poll pushes a new folder's first sync behind every other folder + +**Status:** Fixed, not verified live +**Found:** 2026-10-31, code audit after issue #53. +**Where:** `engine_loop` in `crates/pdfs-fuse/src/sync/engine.rs` + +**Repro:** Sync a few large mirror folders whose passes take longer than two minutes, then add +another folder. Its first pass waits until every other folder has been walked again, each +time. + +**Cause.** The poll sends `ReconcileAll` every two minutes whether or not a pass is still +running. When a burst holds both a `ReconcileAll` and a folder's `Reconcile(id)`, the loop runs +only `reconcile_all`, which goes in id order. A new folder has the highest id, so it always +goes last. + +**Fix.** `run_burst` runs the folders a burst names by id first, lowest id first, and then +every other folder when the burst also holds a `ReconcileAll`. A named folder is read again +before its pass. A pause that lands between passes now stops the rest of the burst. + +**Test:** `named_folders_run_before_the_rest_of_a_burst` in `pdfs-fuse`. + +--- + +### B259 — The sync engine can die or hang, and nothing shows it until a restart + +**Status:** Partly fixed. A hung pass is reported but not ended, and a panic outside a pass +still stops the engine; it is reported, not restarted. +**Found:** 2026-10-31, issue #53: a newly added folder did not sync until the reporter restarted +the app. All FUSE workers were idle, nothing was queued, and the warning-level journal was +empty (B258). +**Where:** `spawn` and `engine_loop` in `crates/pdfs-fuse/src/sync/engine.rs`; +`reconcile_pass` in `crates/pdfs-fuse/src/sync.rs`; `crates/pdfs-fuse/src/diagnostics.rs` + +**Repro:** Not reproduced. Make one pass panic, or block in a call that does not return. Every +later add, `pdfs sync now`, file event and poll is dropped without a word. The folder that was +syncing stays `syncing` with its last progress. A restart runs a pass over every folder, which +is why it helps. + +**Cause.** +- The engine is one thread, and nothing catches a panic in a pass. When it unwinds, the + receiver is gone and every `let _ = sync_tx.send(..)` fails silently. The poll thread stops + at its first failed send. +- The thread's `JoinHandle` is checked only at shutdown. +- A pass has no overall deadline. Each request is bounded, but a pass is not; B165 dealt with + the stop only. +- Nothing logs a pass starting, or ending without changes. `Diagnostics` covers the FUSE + workers and the request queue, not this thread, so "workers idle, nothing queued" says + nothing about sync. +- A database error when the engine reads a folder drops that folder's request without a log. + +**Fix.** +- Each pass runs under `catch_unwind` (`EngineHealth::contain` in + `crates/pdfs-fuse/src/sync/engine.rs`). A panic ends that pass only: its progress is cleared, + the folder is set to `error`, the activity feed says so, and the next poll tries again. +- `Diagnostics` carries the engine's state: running or not, the folder of the running pass, how + long it has run and how long since it last reported progress, and how many passes panicked. + `pdfs diagnostics` prints it as `sync engine: …`. +- The supervisor warns when the engine thread has ended, and when a pass has reported no + progress for 15 minutes. +- A first pass logs its start and end at info, as does any pass that changed something or + took 30 s or more. A request for a folder the database cannot read is logged. +- Not done: a deadline for a pass, and restarting an engine thread that has ended. The + channel's receiver ends with the thread, so a restart needs a new channel in every `Core` + clone. + +**Test:** `a_panicking_pass_is_contained_and_counted`, `the_running_pass_is_reported` and +`the_engine_reads_as_stopped_once_its_thread_ends` in `pdfs-fuse`; +`a_stopped_sync_engine_is_a_stall` and `only_a_quiet_sync_pass_is_a_stall` in the supervisor. + +--- + +### B258 — `journalctl -p warning` shows nothing, whatever the daemon logged + +**Status:** Fixed, not verified live +**Found:** 2026-10-31, issue #53: the reporter ran `journalctl --user -u proton-drive.service -b +-p warning`, as `docs/TROUBLESHOOTING.md` tells them to, and got `-- No entries --`. +**Where:** `main` in `crates/pdfs-cli/src/main.rs`; `packaging/proton-drive.service` + +**Repro:** Make the daemon log a warning, then run the command above. It prints no entries. +Without `-p` the warning is there. + +**Cause.** The daemon logs with plain `tracing_subscriber::fmt()` to standard output. journald +stores every line of a service's output at the unit's default priority, info, unless the line +starts with a `` prefix. So every `WARN` and `ERROR` line is stored as info, and the +troubleshooting guide's filter hides all of them. + +**Fix.** When standard output is the journal stream (`JOURNAL_STREAM` names the same +device and inode as fd 1), each line starts with the `` prefix for its level, and ANSI colours +are off. A terminal that merely inherited `JOURNAL_STREAM` keeps the plain format +(`crates/pdfs-cli/src/journal.rs`). + +**Test:** `each_line_opens_with_the_priority_of_its_level` and +`journal_stream_must_name_the_same_file` in `pdfs-cli`. On 2026-10-31, `pdfs diagnose` run with +`RUST_LOG=debug` under `systemd-run --user -p StandardOutput=journal` stored its debug lines at +`PRIORITY=7` and its report at 6. + +--- + ### B257 — Deleting a new file while it is open can drop the bytes its handle reads **Status:** Fixed, not verified live @@ -1562,6 +2079,8 @@ what it holds. What was synced in it is still deleted. ### B210 — After a password reset, the mount retries "missing key" forever **Status:** Fixed, not verified live +**See also:** B265. The account in issue #40 has an active volume, so this fix does not run for +it; its "missing key" comes from address keys that do not open. **Found:** 2026-10-15, issue #40: on a fresh 3.4.0 install the sign-in worked, but the daemon logged `mount failed: fetch My Files root: cryptography error: failed to decrypt: missing key` every 5 seconds. The app stayed on "Connecting…" because the daemon never got far enough to @@ -1707,6 +2226,7 @@ front-end is the stale one, and restarting the service would not help. ### B205 — A photo upload the SDK refuses says "You're offline" **Status:** Fixed, not verified live +**See also:** B266, the same misclassification on the pin, unpin, search and node-load paths. **Found:** 2026-10-11, issue #38: uploading an image from the Photos page showed "You're offline" while other uploads went through. Each `upload_photo` failed about 5 µs after it started, with no request sent, and the journal gave no reason. @@ -1791,6 +2311,7 @@ taken as this session's, so the daemon keeps writing its later rotations. ### B202 — A refused session shows as offline forever, and nothing says to sign in again **Status:** Fixed, not verified live +**See also:** B267. Other errors at a start from a warm cache still mount as offline. **Found:** 2026-10-09, issue #35: the tray and `pdfs diagnose` said "offline, 3 queued", and diagnose also said "keyring session: ok", while every call in the log failed with `InvalidRefreshToken (http 400)`. `pdfs sync now` and restarting the service changed nothing. @@ -2732,6 +3253,8 @@ fix). ### B165 — A stop waits on a stuck thread until systemd kills the daemon **Status:** Fixed (unverified). +**See also:** B259. A pass still has no deadline while the daemon runs, and a stuck or dead +sync engine is not reported. **Found:** 2026-10-04, by the account run on 2.10.0 (cleanup after on-demand pairing 2). The daemon was stopped at 15:53:04 and logged "control socket closing", but never "sync engine stopping" or "daemon stopping". systemd killed it 15 s later (`TimeoutStopSec`). The run's diff --git a/packaging/aur/proton-drive-for-linux-bin/.SRCINFO b/packaging/aur/proton-drive-for-linux-bin/.SRCINFO index ec3c521..c30ba1e 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.5.0 + pkgver = 3.6.0 pkgrel = 1 url = https://proton-drive.narl.io install = proton-drive.install @@ -22,21 +22,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.5.0 - provides = proton-drive-linux=3.5.0 + provides = proton-drive-for-linux=3.6.0 + provides = proton-drive-linux=3.6.0 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.5.0-x86_64.tar.gz::https://github.com/narrrl/proton-drive-linux/releases/download/v3.5.0/proton-drive-linux-3.5.0-x86_64.tar.gz - source = io.narl.proton-drive-linux-3.5.0.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.5.0/packaging/io.narl.proton-drive-linux.desktop - source = io.narl.proton-drive-linux-tray-3.5.0.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.5.0/packaging/io.narl.proton-drive-linux-tray.desktop - source = io.narl.proton-drive-linux-3.5.0.svg::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.5.0/packaging/io.narl.proton-drive-linux.svg - source = proton-drive-3.5.0.service::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.5.0/packaging/proton-drive.service - source = LICENSE-3.5.0::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.5.0/LICENSE - sha256sums = 902f60de5f471cb15c84d0ecb29cb4644a0ac52ce36d2be54c16b4befeba8de0 + source = proton-drive-linux-3.6.0-x86_64.tar.gz::https://github.com/narrrl/proton-drive-linux/releases/download/v3.6.0/proton-drive-linux-3.6.0-x86_64.tar.gz + source = io.narl.proton-drive-linux-3.6.0.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.6.0/packaging/io.narl.proton-drive-linux.desktop + source = io.narl.proton-drive-linux-tray-3.6.0.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.6.0/packaging/io.narl.proton-drive-linux-tray.desktop + source = io.narl.proton-drive-linux-3.6.0.svg::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.6.0/packaging/io.narl.proton-drive-linux.svg + source = proton-drive-3.6.0.service::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.6.0/packaging/proton-drive.service + source = LICENSE-3.6.0::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v3.6.0/LICENSE + sha256sums = 8e9b76b0df238af1c8978c33b4c27c0c548e1faa81b368d0d271ed564d0a76c7 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 b2c4568..85c120f 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.5.0 +pkgver=3.6.0 pkgrel=1 pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries)" arch=('x86_64') @@ -36,7 +36,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=('902f60de5f471cb15c84d0ecb29cb4644a0ac52ce36d2be54c16b4befeba8de0' +sha256sums=('8e9b76b0df238af1c8978c33b4c27c0c548e1faa81b368d0d271ed564d0a76c7' 'd190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123' '1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0' 'e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828' diff --git a/packaging/aur/proton-drive-for-linux-git/.SRCINFO b/packaging/aur/proton-drive-for-linux-git/.SRCINFO index cf926de..f411943 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.5.0.r0.geb25c16 + pkgver = 3.6.0.r0.g2720191 pkgrel = 1 url = https://proton-drive.narl.io install = proton-drive.install @@ -25,8 +25,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.5.0.r0.geb25c16 - provides = proton-drive-linux=3.5.0.r0.geb25c16 + provides = proton-drive-for-linux=3.6.0.r0.g2720191 + provides = proton-drive-linux=3.6.0.r0.g2720191 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 689c87f..abf9474 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.5.0.r0.geb25c16 +pkgver=3.6.0.r0.g2720191 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 0ce7f91..6528282 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.5.0 + pkgver = 3.6.0 pkgrel = 1 url = https://proton-drive.narl.io install = proton-drive.install @@ -24,13 +24,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.5.0 + provides = proton-drive-linux=3.6.0 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.5.0.tar.gz::https://github.com/narrrl/proton-drive-linux/archive/refs/tags/v3.5.0.tar.gz - sha256sums = 5eb0e001ec2301ffb8b2b8429e907fab32810d6eba541bf978a0ea97867d8bc9 + source = proton-drive-for-linux-3.6.0.tar.gz::https://github.com/narrrl/proton-drive-linux/archive/refs/tags/v3.6.0.tar.gz + sha256sums = d9b16b1027816a379d6a11c6396d779f751a6f94becaa16b17439420f561d13d pkgname = proton-drive-for-linux diff --git a/packaging/aur/proton-drive-for-linux/PKGBUILD b/packaging/aur/proton-drive-for-linux/PKGBUILD index e203c31..493bc03 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.5.0 +pkgver=3.6.0 pkgrel=1 pkgdesc="Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray" arch=('x86_64') @@ -26,7 +26,7 @@ options=('!lto') # Stops the running daemon before removal, so the mount stops mirroring deletes. install=proton-drive.install source=("$pkgname-$pkgver.tar.gz::$_repo/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('5eb0e001ec2301ffb8b2b8429e907fab32810d6eba541bf978a0ea97867d8bc9') +sha256sums=('d9b16b1027816a379d6a11c6396d779f751a6f94becaa16b17439420f561d13d') _srcname="proton-drive-linux-$pkgver" diff --git a/po/de.po b/po/de.po index 7ac1044..aa6d5d3 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-26 22:26+0100\n" +"POT-Creation-Date: 2026-10-31 19:26+0100\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: German\n" @@ -249,13 +249,13 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1917 #: crates/pdfs-gui/src/app/pages/browser.rs:2039 #: crates/pdfs-gui/src/app/pages/browser.rs:2540 -#: crates/pdfs-gui/src/app/pages/devices.rs:804 -#: crates/pdfs-gui/src/app/pages/devices.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:995 -#: crates/pdfs-gui/src/app/pages/devices.rs:1047 -#: crates/pdfs-gui/src/app/pages/devices.rs:1091 -#: crates/pdfs-gui/src/app/pages/devices.rs:1131 -#: crates/pdfs-gui/src/app/pages/devices.rs:1194 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 +#: crates/pdfs-gui/src/app/pages/devices.rs:979 +#: crates/pdfs-gui/src/app/pages/devices.rs:1017 +#: crates/pdfs-gui/src/app/pages/devices.rs:1069 +#: crates/pdfs-gui/src/app/pages/devices.rs:1113 +#: crates/pdfs-gui/src/app/pages/devices.rs:1153 +#: crates/pdfs-gui/src/app/pages/devices.rs:1216 #: crates/pdfs-gui/src/app/pages/locations.rs:850 #: crates/pdfs-gui/src/app/pages/locations.rs:1054 #: crates/pdfs-gui/src/app/pages/locations.rs:1537 @@ -350,7 +350,7 @@ msgstr "Raster / Liste" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1896 #: crates/pdfs-gui/src/app/pages/browser.rs:1918 -#: crates/pdfs-gui/src/app/pages/devices.rs:996 +#: crates/pdfs-gui/src/app/pages/devices.rs:1018 #: crates/pdfs-gui/src/app/widgets/details.rs:161 msgid "Rename" msgstr "Umbenennen" @@ -424,14 +424,27 @@ msgstr "Nicht verbunden" msgid "The Proton Drive service isn't running." msgstr "Der Proton-Drive-Dienst läuft nicht." -#: crates/pdfs-gui/src/app/main.rs:1803 +#: crates/pdfs-gui/src/app/main.rs:1813 +msgid "Can't open your Drive" +msgstr "Proton Drive kann nicht geöffnet werden" + +#: crates/pdfs-gui/src/app/main.rs:1816 +#, rust-format +msgid "" +"The Proton Drive service keeps failing to start and tries again on its own. " +"The last error was: {error}" +msgstr "" +"Der Proton-Drive-Dienst kann wiederholt nicht starten und versucht es " +"selbstständig erneut. Letzter Fehler: {error}" + +#: crates/pdfs-gui/src/app/main.rs:1824 #: crates/pdfs-gui/src/app/pages/status.rs:355 #: crates/pdfs-gui/src/hud/mod.rs:269 crates/pdfs-gui/src/hud/mod.rs:579 -#: crates/pdfs-gui/src/main.rs:475 crates/pdfs-gui/src/main.rs:517 +#: crates/pdfs-gui/src/main.rs:493 crates/pdfs-gui/src/main.rs:535 msgid "Connecting…" msgstr "Verbindung wird hergestellt …" -#: crates/pdfs-gui/src/app/main.rs:1804 +#: crates/pdfs-gui/src/app/main.rs:1825 msgid "Waiting for the Proton Drive service to start." msgstr "Warten auf den Start des Proton-Drive-Dienstes." @@ -682,7 +695,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:839 +#: crates/pdfs-gui/src/app/pages/devices.rs:861 msgid "Couldn't restore folders" msgstr "Ordner konnten nicht wiederhergestellt werden" @@ -1274,12 +1287,12 @@ msgstr "Alben konnten nicht geladen werden" #: crates/pdfs-gui/src/app/pages/browser.rs:1873 #: crates/pdfs-gui/src/app/pages/browser.rs:2500 #: crates/pdfs-gui/src/app/pages/browser.rs:2650 -#: crates/pdfs-gui/src/app/pages/devices.rs:707 -#: crates/pdfs-gui/src/app/pages/devices.rs:747 -#: crates/pdfs-gui/src/app/pages/devices.rs:900 -#: crates/pdfs-gui/src/app/pages/devices.rs:1170 -#: crates/pdfs-gui/src/app/pages/devices.rs:1231 -#: crates/pdfs-gui/src/app/pages/devices.rs:1260 +#: crates/pdfs-gui/src/app/pages/devices.rs:729 +#: crates/pdfs-gui/src/app/pages/devices.rs:769 +#: crates/pdfs-gui/src/app/pages/devices.rs:922 +#: crates/pdfs-gui/src/app/pages/devices.rs:1192 +#: crates/pdfs-gui/src/app/pages/devices.rs:1253 +#: crates/pdfs-gui/src/app/pages/devices.rs:1282 #: crates/pdfs-gui/src/app/pages/duplicates.rs:221 #: crates/pdfs-gui/src/app/pages/locations.rs:577 #: crates/pdfs-gui/src/app/pages/locations.rs:824 @@ -1929,7 +1942,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1900 -#: crates/pdfs-gui/src/app/pages/devices.rs:989 +#: crates/pdfs-gui/src/app/pages/devices.rs:1011 msgid "New name" msgstr "Neuer Name" @@ -1954,7 +1967,7 @@ msgstr "Den Zielordner auswählen." #: crates/pdfs-gui/src/app/pages/browser.rs:1989 #: crates/pdfs-gui/src/app/pages/browser.rs:1994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1195 +#: crates/pdfs-gui/src/app/pages/devices.rs:1217 msgid "Move Here" msgstr "Hierher verschieben" @@ -2279,76 +2292,87 @@ msgid_plural "{n} folders backed up" msgstr[0] "{n} Ordner gesichert" msgstr[1] "{n} Ordner gesichert" -#: crates/pdfs-gui/src/app/pages/devices.rs:608 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 +#, rust-format +msgid "{folders} · {n} waiting for its first sync" +msgid_plural "{folders} · {n} waiting for their first sync" +msgstr[0] "{folders} · {n} wartet auf die erste Synchronisierung" +msgstr[1] "{folders} · {n} warten auf die erste Synchronisierung" + +#: crates/pdfs-gui/src/app/pages/devices.rs:621 #, 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:619 +#: crates/pdfs-gui/src/app/pages/devices.rs:640 msgid "syncing…" msgstr "wird synchronisiert …" -#: crates/pdfs-gui/src/app/pages/devices.rs:620 +#: crates/pdfs-gui/src/app/pages/devices.rs:641 msgid "sync error" msgstr "Synchronisierungsfehler" -#: crates/pdfs-gui/src/app/pages/devices.rs:621 +#: crates/pdfs-gui/src/app/pages/devices.rs:642 msgid "needs attention" msgstr "erfordert Aufmerksamkeit" -#: crates/pdfs-gui/src/app/pages/devices.rs:622 +#: crates/pdfs-gui/src/app/pages/devices.rs:643 +msgid "waiting for first sync" +msgstr "wartet auf erste Synchronisierung" + +#: crates/pdfs-gui/src/app/pages/devices.rs:644 msgid "up to date" msgstr "aktuell" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 msgid "checking for changes…" msgstr "wird auf Änderungen geprüft …" -#: crates/pdfs-gui/src/app/pages/devices.rs:638 +#: crates/pdfs-gui/src/app/pages/devices.rs:660 #, rust-format msgid "checking for changes — {n} item checked" msgid_plural "checking for changes — {n} items checked" msgstr[0] "wird auf Änderungen geprüft – {n} Element geprüft" msgstr[1] "wird auf Änderungen geprüft – {n} Elemente geprüft" -#: crates/pdfs-gui/src/app/pages/devices.rs:645 +#: crates/pdfs-gui/src/app/pages/devices.rs:667 #, 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:657 +#: crates/pdfs-gui/src/app/pages/devices.rs:679 #, rust-format msgid "syncing {done} of {total}" msgstr "{done} von {total} wird synchronisiert" -#: crates/pdfs-gui/src/app/pages/devices.rs:663 +#: crates/pdfs-gui/src/app/pages/devices.rs:685 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "{file} wird synchronisiert – {done} von {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:675 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Add Folder to Sync" msgstr "Ordner zur Synchronisierung hinzufügen" -#: crates/pdfs-gui/src/app/pages/devices.rs:699 +#: crates/pdfs-gui/src/app/pages/devices.rs:721 #: crates/pdfs-gui/src/app/pages/locations.rs:1559 #: crates/pdfs-gui/src/app/pages/locations.rs:1686 msgid "Syncing folder…" msgstr "Ordner wird synchronisiert …" -#: crates/pdfs-gui/src/app/pages/devices.rs:702 -#: crates/pdfs-gui/src/app/pages/devices.rs:706 +#: crates/pdfs-gui/src/app/pages/devices.rs:724 +#: crates/pdfs-gui/src/app/pages/devices.rs:728 msgid "Couldn't add folder" msgstr "Ordner konnte nicht hinzugefügt werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:740 -#: crates/pdfs-gui/src/app/pages/devices.rs:746 +#: crates/pdfs-gui/src/app/pages/devices.rs:762 +#: crates/pdfs-gui/src/app/pages/devices.rs:768 msgid "Couldn't list folders to restore" msgstr "Wiederherstellbare Ordner konnten nicht aufgelistet werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:772 +#: crates/pdfs-gui/src/app/pages/devices.rs:794 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2356,19 +2380,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:798 +#: crates/pdfs-gui/src/app/pages/devices.rs:820 msgid "Restore Folders" msgstr "Ordner wiederherstellen" -#: crates/pdfs-gui/src/app/pages/devices.rs:805 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Restore" msgstr "Wiederherstellen" -#: crates/pdfs-gui/src/app/pages/devices.rs:838 +#: crates/pdfs-gui/src/app/pages/devices.rs:860 msgid "Restoring folders…" msgstr "Ordner werden wiederhergestellt …" -#: crates/pdfs-gui/src/app/pages/devices.rs:850 +#: crates/pdfs-gui/src/app/pages/devices.rs:872 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2377,37 +2401,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:853 +#: crates/pdfs-gui/src/app/pages/devices.rs:875 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:862 +#: crates/pdfs-gui/src/app/pages/devices.rs:884 #, 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:865 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 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:889 +#: crates/pdfs-gui/src/app/pages/devices.rs:911 msgid "Folder will switch to online only" msgstr "Ordner wird auf „Nur online“ umgestellt" -#: crates/pdfs-gui/src/app/pages/devices.rs:891 +#: crates/pdfs-gui/src/app/pages/devices.rs:913 msgid "Folder will download and stay synced" msgstr "Ordner wird heruntergeladen und bleibt synchronisiert" -#: crates/pdfs-gui/src/app/pages/devices.rs:895 -#: crates/pdfs-gui/src/app/pages/devices.rs:899 +#: crates/pdfs-gui/src/app/pages/devices.rs:917 +#: crates/pdfs-gui/src/app/pages/devices.rs:921 msgid "Couldn't change mode" msgstr "Modus konnte nicht geändert werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:953 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2425,7 +2449,7 @@ msgstr "" "Ordner auf „Synchronisiert“ umstellen und vor dem Entfernen warten, bis der " "Download abgeschlossen ist." -#: crates/pdfs-gui/src/app/pages/devices.rs:937 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2439,68 +2463,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:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:964 msgid "Stop Syncing Folder?" msgstr "Synchronisierung des Ordners beenden?" -#: crates/pdfs-gui/src/app/pages/devices.rs:947 +#: crates/pdfs-gui/src/app/pages/devices.rs:969 msgid "Also delete from Proton Drive" msgstr "Auch aus Proton Drive löschen" -#: crates/pdfs-gui/src/app/pages/devices.rs:949 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Deletes the only copy of these files." msgstr "Löscht die einzige Kopie dieser Dateien." -#: crates/pdfs-gui/src/app/pages/devices.rs:951 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 msgid "The local copy is unaffected." msgstr "Die lokale Kopie bleibt unverändert." -#: crates/pdfs-gui/src/app/pages/devices.rs:958 +#: crates/pdfs-gui/src/app/pages/devices.rs:980 msgid "Stop Syncing" msgstr "Synchronisierung beenden" -#: crates/pdfs-gui/src/app/pages/devices.rs:971 +#: crates/pdfs-gui/src/app/pages/devices.rs:993 msgid "Stopped syncing folder" msgstr "Synchronisierung des Ordners beendet" -#: crates/pdfs-gui/src/app/pages/devices.rs:972 +#: crates/pdfs-gui/src/app/pages/devices.rs:994 msgid "Couldn't stop syncing the folder" msgstr "Synchronisierung des Ordners konnte nicht beendet werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1005 msgid "Rename Computer" msgstr "Computer umbenennen" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:985 +#: crates/pdfs-gui/src/app/pages/devices.rs:1007 #, rust-format msgid "Rename “{name}”." msgstr "„{name}“ umbenennen." -#: crates/pdfs-gui/src/app/pages/devices.rs:1010 -#: crates/pdfs-gui/src/app/pages/devices.rs:1022 +#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1044 msgid "Couldn't rename the computer" msgstr "Der Computer konnte nicht umbenannt werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:1011 +#: crates/pdfs-gui/src/app/pages/devices.rs:1033 msgid "A name is required." msgstr "Ein Name ist erforderlich." -#: crates/pdfs-gui/src/app/pages/devices.rs:1021 +#: crates/pdfs-gui/src/app/pages/devices.rs:1043 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:1034 +#: crates/pdfs-gui/src/app/pages/devices.rs:1056 #, rust-format msgid "Type “{name}” to confirm" msgstr "Zur Bestätigung „{name}“ eingeben" -#: crates/pdfs-gui/src/app/pages/devices.rs:1039 +#: crates/pdfs-gui/src/app/pages/devices.rs:1061 msgid "Remove Computer?" msgstr "Computer entfernen?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1042 +#: crates/pdfs-gui/src/app/pages/devices.rs:1064 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2515,26 +2539,26 @@ 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:1048 +#: crates/pdfs-gui/src/app/pages/devices.rs:1070 #: crates/pdfs-gui/src/app/pages/shared.rs:1025 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:634 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:876 msgid "Remove" msgstr "Entfernen" -#: crates/pdfs-gui/src/app/pages/devices.rs:1065 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 msgid "Computer removed" msgstr "Computer entfernt" -#: crates/pdfs-gui/src/app/pages/devices.rs:1066 +#: crates/pdfs-gui/src/app/pages/devices.rs:1088 msgid "Couldn't remove the computer" msgstr "Der Computer konnte nicht entfernt werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:1084 +#: crates/pdfs-gui/src/app/pages/devices.rs:1106 msgid "Continue This Backup Here?" msgstr "Diese Sicherung hier fortsetzen?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1087 +#: crates/pdfs-gui/src/app/pages/devices.rs:1109 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2553,25 +2577,25 @@ 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:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1114 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:1099 +#: crates/pdfs-gui/src/app/pages/devices.rs:1121 #, rust-format msgid "Now backing up as “{name}”" msgstr "Sicherung jetzt als „{name}“" -#: crates/pdfs-gui/src/app/pages/devices.rs:1108 +#: crates/pdfs-gui/src/app/pages/devices.rs:1130 msgid "Couldn't continue that backup here" msgstr "Diese Sicherung konnte hier nicht fortgesetzt werden" -#: crates/pdfs-gui/src/app/pages/devices.rs:1124 +#: crates/pdfs-gui/src/app/pages/devices.rs:1146 msgid "Sync With This Computer?" msgstr "Mit diesem Computer synchronisieren?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#: crates/pdfs-gui/src/app/pages/devices.rs:1149 #, rust-format msgid "" "This computer and “{name}” keep the same folders in step. A change or a " @@ -2588,25 +2612,25 @@ msgstr "" "„{name}“. Bereits hier synchronisierte Ordner bleiben, wo sie sind. Als " "Nächstes wählen Sie, welche seiner Ordner hier synchronisiert werden." -#: crates/pdfs-gui/src/app/pages/devices.rs:1132 +#: crates/pdfs-gui/src/app/pages/devices.rs:1154 msgid "Sync Here" msgstr "Hier synchronisieren" #. Translators: {name} is the name of the other computer this one now syncs with. -#: crates/pdfs-gui/src/app/pages/devices.rs:1139 +#: crates/pdfs-gui/src/app/pages/devices.rs:1161 #, rust-format msgid "Now syncing with “{name}”" msgstr "Jetzt synchron mit „{name}“" -#: crates/pdfs-gui/src/app/pages/devices.rs:1155 +#: crates/pdfs-gui/src/app/pages/devices.rs:1177 msgid "Couldn't sync with that computer" msgstr "Synchronisierung mit diesem Computer nicht möglich" -#: crates/pdfs-gui/src/app/pages/devices.rs:1187 +#: crates/pdfs-gui/src/app/pages/devices.rs:1209 msgid "Move Backup to This Computer?" msgstr "Sicherung auf diesen Computer verschieben?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1190 +#: crates/pdfs-gui/src/app/pages/devices.rs:1212 #, rust-format msgid "" "The folders backed up by “{name}” move into this computer's backup in Proton " @@ -2623,12 +2647,12 @@ msgstr "" "verschwinden." #. Translators: {name} is the name of the computer whose folders moved to this one. -#: crates/pdfs-gui/src/app/pages/devices.rs:1202 +#: crates/pdfs-gui/src/app/pages/devices.rs:1224 #, rust-format msgid "Moved the backup of “{name}”" msgstr "Sicherung von „{name}“ verschoben" -#: crates/pdfs-gui/src/app/pages/devices.rs:1216 +#: crates/pdfs-gui/src/app/pages/devices.rs:1238 msgid "Couldn't move that backup" msgstr "Diese Sicherung konnte nicht verschoben werden" @@ -2908,7 +2932,7 @@ msgid "Sync paused until you resume" msgstr "Synchronisierung bis zur Fortsetzung pausiert" #: crates/pdfs-gui/src/app/pages/locations.rs:570 -#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:290 +#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:308 msgid "Sync paused" msgstr "Synchronisierung pausiert" @@ -3374,7 +3398,7 @@ msgid "paused" msgstr "pausiert" #. Translators: a folder's files can't be opened right now. -#: crates/pdfs-gui/src/app/pages/locations.rs:1742 +#: crates/pdfs-gui/src/app/pages/locations.rs:1745 msgid "not available" msgstr "nicht verfügbar" @@ -5136,7 +5160,7 @@ msgstr "Wird {time} fortgesetzt" msgid "Until you resume" msgstr "Bis zur manuellen Fortsetzung" -#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:292 +#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:310 #, rust-format msgid "{n} change needs attention" msgid_plural "{n} changes need attention" @@ -6371,52 +6395,57 @@ msgstr "Proton Drive reagiert nicht" msgid "Signed in as {username} — locked" msgstr "Angemeldet als {username} – gesperrt" -#: crates/pdfs-gui/src/main.rs:200 +#: crates/pdfs-gui/src/main.rs:209 +#, rust-format +msgid "Signed in as {username} — can't open your Drive" +msgstr "Angemeldet als {username} – Proton Drive kann nicht geöffnet werden" + +#: crates/pdfs-gui/src/main.rs:218 #, rust-format msgid "Signed in as {username} — not connected" msgstr "Angemeldet als {username} – nicht verbunden" -#: crates/pdfs-gui/src/main.rs:205 +#: crates/pdfs-gui/src/main.rs:223 msgid "Not signed in" msgstr "Nicht angemeldet" #. Translators: {error} is an error message, in English. -#: crates/pdfs-gui/src/main.rs:208 +#: crates/pdfs-gui/src/main.rs:226 #, rust-format msgid "Error: {error}" msgstr "Fehler: {error}" -#: crates/pdfs-gui/src/main.rs:230 +#: crates/pdfs-gui/src/main.rs:248 msgid "Proton Drive sent an unexpected reply" msgstr "Proton Drive hat unerwartet geantwortet" -#: crates/pdfs-gui/src/main.rs:284 +#: crates/pdfs-gui/src/main.rs:302 msgid "Session expired — sign in again" msgstr "Sitzung abgelaufen – erneut anmelden" -#: crates/pdfs-gui/src/main.rs:286 +#: crates/pdfs-gui/src/main.rs:304 msgid "Sign-in not saved — unlock your keyring" msgstr "Anmeldung nicht gespeichert – Schlüsselbund entsperren" -#: crates/pdfs-gui/src/main.rs:289 +#: crates/pdfs-gui/src/main.rs:307 msgid "Update not finished — restart Proton Drive" msgstr "Update nicht abgeschlossen – Proton Drive neu starten" -#: crates/pdfs-gui/src/main.rs:299 +#: crates/pdfs-gui/src/main.rs:317 #, rust-format msgid "Up to date in {mountpoint} ({n} pinned)" msgid_plural "Up to date in {mountpoint} ({n} pinned)" msgstr[0] "Aktuell in {mountpoint} ({n} angeheftet)" msgstr[1] "Aktuell in {mountpoint} ({n} angeheftet)" -#: crates/pdfs-gui/src/main.rs:306 +#: crates/pdfs-gui/src/main.rs:324 #, rust-format msgid "Syncing — {queued} ({n} pinned)" msgid_plural "Syncing — {queued} ({n} pinned)" msgstr[0] "Wird synchronisiert – {queued} ({n} angeheftet)" msgstr[1] "Wird synchronisiert – {queued} ({n} angeheftet)" -#: crates/pdfs-gui/src/main.rs:313 +#: crates/pdfs-gui/src/main.rs:331 #, rust-format msgid "Offline — cached files only ({n} pinned)" msgid_plural "Offline — cached files only ({n} pinned)" @@ -6424,60 +6453,60 @@ msgstr[0] "Offline – nur zwischengespeicherte Dateien ({n} angeheftet)" msgstr[1] "Offline – nur zwischengespeicherte Dateien ({n} angeheftet)" #. Translators: {queued} is a queue summary such as "3 uploads queued". -#: crates/pdfs-gui/src/main.rs:319 +#: crates/pdfs-gui/src/main.rs:337 #, rust-format msgid "Offline — {queued}" msgstr "Offline – {queued}" #. Translators: a menu item that opens the list of failing changes. -#: crates/pdfs-gui/src/main.rs:358 +#: crates/pdfs-gui/src/main.rs:376 #, rust-format msgid "{n} issue — View" msgid_plural "{n} issues — View" msgstr[0] "{n} Problem – Anzeigen" msgstr[1] "{n} Probleme – Anzeigen" -#: crates/pdfs-gui/src/main.rs:465 +#: crates/pdfs-gui/src/main.rs:483 msgid "Sign In…" msgstr "Anmelden …" -#: crates/pdfs-gui/src/main.rs:466 +#: crates/pdfs-gui/src/main.rs:484 msgid "Sign In Again…" msgstr "Erneut anmelden …" -#: crates/pdfs-gui/src/main.rs:469 +#: crates/pdfs-gui/src/main.rs:487 msgid "Open Proton Drive" msgstr "Proton Drive öffnen" -#: crates/pdfs-gui/src/main.rs:473 +#: crates/pdfs-gui/src/main.rs:491 msgid "Restart Proton Drive" msgstr "Proton Drive neu starten" -#: crates/pdfs-gui/src/main.rs:482 +#: crates/pdfs-gui/src/main.rs:500 msgid "Open Folder" msgstr "Ordner öffnen" -#: crates/pdfs-gui/src/main.rs:494 +#: crates/pdfs-gui/src/main.rs:512 msgid "Resume Sync" msgstr "Synchronisierung fortsetzen" -#: crates/pdfs-gui/src/main.rs:496 +#: crates/pdfs-gui/src/main.rs:514 msgid "Pause Sync" msgstr "Synchronisierung pausieren" -#: crates/pdfs-gui/src/main.rs:515 +#: crates/pdfs-gui/src/main.rs:533 msgid "Connect" msgstr "Verbinden" -#: crates/pdfs-gui/src/main.rs:522 +#: crates/pdfs-gui/src/main.rs:540 msgid "Hide Tray Icon" msgstr "Symbol im Benachrichtigungsfeld ausblenden" -#: crates/pdfs-gui/src/main.rs:526 +#: crates/pdfs-gui/src/main.rs:544 msgid "Stop Proton Drive…" msgstr "Proton Drive beenden …" -#: crates/pdfs-gui/src/main.rs:530 +#: crates/pdfs-gui/src/main.rs:548 msgid "Quit" msgstr "Beenden" diff --git a/po/en_GB.po b/po/en_GB.po index 5a2e3c1..d35055a 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-26 22:26+0100\n" +"POT-Creation-Date: 2026-10-31 19:26+0100\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" @@ -245,13 +245,13 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1917 #: crates/pdfs-gui/src/app/pages/browser.rs:2039 #: crates/pdfs-gui/src/app/pages/browser.rs:2540 -#: crates/pdfs-gui/src/app/pages/devices.rs:804 -#: crates/pdfs-gui/src/app/pages/devices.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:995 -#: crates/pdfs-gui/src/app/pages/devices.rs:1047 -#: crates/pdfs-gui/src/app/pages/devices.rs:1091 -#: crates/pdfs-gui/src/app/pages/devices.rs:1131 -#: crates/pdfs-gui/src/app/pages/devices.rs:1194 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 +#: crates/pdfs-gui/src/app/pages/devices.rs:979 +#: crates/pdfs-gui/src/app/pages/devices.rs:1017 +#: crates/pdfs-gui/src/app/pages/devices.rs:1069 +#: crates/pdfs-gui/src/app/pages/devices.rs:1113 +#: crates/pdfs-gui/src/app/pages/devices.rs:1153 +#: crates/pdfs-gui/src/app/pages/devices.rs:1216 #: crates/pdfs-gui/src/app/pages/locations.rs:850 #: crates/pdfs-gui/src/app/pages/locations.rs:1054 #: crates/pdfs-gui/src/app/pages/locations.rs:1537 @@ -346,7 +346,7 @@ msgstr "Grid / list" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1896 #: crates/pdfs-gui/src/app/pages/browser.rs:1918 -#: crates/pdfs-gui/src/app/pages/devices.rs:996 +#: crates/pdfs-gui/src/app/pages/devices.rs:1018 #: crates/pdfs-gui/src/app/widgets/details.rs:161 msgid "Rename" msgstr "Rename" @@ -420,14 +420,27 @@ msgstr "Not connected" msgid "The Proton Drive service isn't running." msgstr "The Proton Drive service isn't running." -#: crates/pdfs-gui/src/app/main.rs:1803 +#: crates/pdfs-gui/src/app/main.rs:1813 +msgid "Can't open your Drive" +msgstr "Can't open your Drive" + +#: crates/pdfs-gui/src/app/main.rs:1816 +#, rust-format +msgid "" +"The Proton Drive service keeps failing to start and tries again on its own. " +"The last error was: {error}" +msgstr "" +"The Proton Drive service keeps failing to start and tries again on its own. " +"The last error was: {error}" + +#: crates/pdfs-gui/src/app/main.rs:1824 #: crates/pdfs-gui/src/app/pages/status.rs:355 #: crates/pdfs-gui/src/hud/mod.rs:269 crates/pdfs-gui/src/hud/mod.rs:579 -#: crates/pdfs-gui/src/main.rs:475 crates/pdfs-gui/src/main.rs:517 +#: crates/pdfs-gui/src/main.rs:493 crates/pdfs-gui/src/main.rs:535 msgid "Connecting…" msgstr "Connecting…" -#: crates/pdfs-gui/src/app/main.rs:1804 +#: crates/pdfs-gui/src/app/main.rs:1825 msgid "Waiting for the Proton Drive service to start." msgstr "Waiting for the Proton Drive service to start." @@ -674,7 +687,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:839 +#: crates/pdfs-gui/src/app/pages/devices.rs:861 msgid "Couldn't restore folders" msgstr "Couldn't restore folders" @@ -1262,12 +1275,12 @@ msgstr "Couldn't load albums" #: crates/pdfs-gui/src/app/pages/browser.rs:1873 #: crates/pdfs-gui/src/app/pages/browser.rs:2500 #: crates/pdfs-gui/src/app/pages/browser.rs:2650 -#: crates/pdfs-gui/src/app/pages/devices.rs:707 -#: crates/pdfs-gui/src/app/pages/devices.rs:747 -#: crates/pdfs-gui/src/app/pages/devices.rs:900 -#: crates/pdfs-gui/src/app/pages/devices.rs:1170 -#: crates/pdfs-gui/src/app/pages/devices.rs:1231 -#: crates/pdfs-gui/src/app/pages/devices.rs:1260 +#: crates/pdfs-gui/src/app/pages/devices.rs:729 +#: crates/pdfs-gui/src/app/pages/devices.rs:769 +#: crates/pdfs-gui/src/app/pages/devices.rs:922 +#: crates/pdfs-gui/src/app/pages/devices.rs:1192 +#: crates/pdfs-gui/src/app/pages/devices.rs:1253 +#: crates/pdfs-gui/src/app/pages/devices.rs:1282 #: crates/pdfs-gui/src/app/pages/duplicates.rs:221 #: crates/pdfs-gui/src/app/pages/locations.rs:577 #: crates/pdfs-gui/src/app/pages/locations.rs:824 @@ -1910,7 +1923,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1900 -#: crates/pdfs-gui/src/app/pages/devices.rs:989 +#: crates/pdfs-gui/src/app/pages/devices.rs:1011 msgid "New name" msgstr "New name" @@ -1935,7 +1948,7 @@ msgstr "Choose the folder to move into." #: crates/pdfs-gui/src/app/pages/browser.rs:1989 #: crates/pdfs-gui/src/app/pages/browser.rs:1994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1195 +#: crates/pdfs-gui/src/app/pages/devices.rs:1217 msgid "Move Here" msgstr "Move Here" @@ -2256,76 +2269,87 @@ 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:608 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 +#, rust-format +msgid "{folders} · {n} waiting for its first sync" +msgid_plural "{folders} · {n} waiting for their first sync" +msgstr[0] "{folders} · {n} waiting for its first sync" +msgstr[1] "{folders} · {n} waiting for their first sync" + +#: crates/pdfs-gui/src/app/pages/devices.rs:621 #, 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:619 +#: crates/pdfs-gui/src/app/pages/devices.rs:640 msgid "syncing…" msgstr "syncing…" -#: crates/pdfs-gui/src/app/pages/devices.rs:620 +#: crates/pdfs-gui/src/app/pages/devices.rs:641 msgid "sync error" msgstr "sync error" -#: crates/pdfs-gui/src/app/pages/devices.rs:621 +#: crates/pdfs-gui/src/app/pages/devices.rs:642 msgid "needs attention" msgstr "needs attention" -#: crates/pdfs-gui/src/app/pages/devices.rs:622 +#: crates/pdfs-gui/src/app/pages/devices.rs:643 +msgid "waiting for first sync" +msgstr "waiting for first sync" + +#: crates/pdfs-gui/src/app/pages/devices.rs:644 msgid "up to date" msgstr "up to date" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 msgid "checking for changes…" msgstr "checking for changes…" -#: crates/pdfs-gui/src/app/pages/devices.rs:638 +#: crates/pdfs-gui/src/app/pages/devices.rs:660 #, rust-format msgid "checking for changes — {n} item checked" msgid_plural "checking for changes — {n} items checked" msgstr[0] "checking for changes — {n} item checked" msgstr[1] "checking for changes — {n} items checked" -#: crates/pdfs-gui/src/app/pages/devices.rs:645 +#: crates/pdfs-gui/src/app/pages/devices.rs:667 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "checking for changes — {done} of {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:657 +#: crates/pdfs-gui/src/app/pages/devices.rs:679 #, rust-format msgid "syncing {done} of {total}" msgstr "syncing {done} of {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:663 +#: crates/pdfs-gui/src/app/pages/devices.rs:685 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "syncing {file} — {done} of {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:675 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Add Folder to Sync" msgstr "Add Folder to Sync" -#: crates/pdfs-gui/src/app/pages/devices.rs:699 +#: crates/pdfs-gui/src/app/pages/devices.rs:721 #: crates/pdfs-gui/src/app/pages/locations.rs:1559 #: crates/pdfs-gui/src/app/pages/locations.rs:1686 msgid "Syncing folder…" msgstr "Syncing folder…" -#: crates/pdfs-gui/src/app/pages/devices.rs:702 -#: crates/pdfs-gui/src/app/pages/devices.rs:706 +#: crates/pdfs-gui/src/app/pages/devices.rs:724 +#: crates/pdfs-gui/src/app/pages/devices.rs:728 msgid "Couldn't add folder" msgstr "Couldn't add folder" -#: crates/pdfs-gui/src/app/pages/devices.rs:740 -#: crates/pdfs-gui/src/app/pages/devices.rs:746 +#: crates/pdfs-gui/src/app/pages/devices.rs:762 +#: crates/pdfs-gui/src/app/pages/devices.rs:768 msgid "Couldn't list folders to restore" msgstr "Couldn't list folders to restore" -#: crates/pdfs-gui/src/app/pages/devices.rs:772 +#: crates/pdfs-gui/src/app/pages/devices.rs:794 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2333,19 +2357,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:798 +#: crates/pdfs-gui/src/app/pages/devices.rs:820 msgid "Restore Folders" msgstr "Restore Folders" -#: crates/pdfs-gui/src/app/pages/devices.rs:805 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Restore" msgstr "Restore" -#: crates/pdfs-gui/src/app/pages/devices.rs:838 +#: crates/pdfs-gui/src/app/pages/devices.rs:860 msgid "Restoring folders…" msgstr "Restoring folders…" -#: crates/pdfs-gui/src/app/pages/devices.rs:850 +#: crates/pdfs-gui/src/app/pages/devices.rs:872 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2354,33 +2378,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:853 +#: crates/pdfs-gui/src/app/pages/devices.rs:875 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:862 +#: crates/pdfs-gui/src/app/pages/devices.rs:884 #, 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:865 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 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:889 +#: crates/pdfs-gui/src/app/pages/devices.rs:911 msgid "Folder will switch to online only" msgstr "Folder will switch to online only" -#: crates/pdfs-gui/src/app/pages/devices.rs:891 +#: crates/pdfs-gui/src/app/pages/devices.rs:913 msgid "Folder will download and stay synced" msgstr "Folder will download and stay synced" -#: crates/pdfs-gui/src/app/pages/devices.rs:895 -#: crates/pdfs-gui/src/app/pages/devices.rs:899 +#: crates/pdfs-gui/src/app/pages/devices.rs:917 +#: crates/pdfs-gui/src/app/pages/devices.rs:921 msgid "Couldn't change mode" msgstr "Couldn't change mode" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:953 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2397,7 +2421,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:937 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2410,68 +2434,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:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:964 msgid "Stop Syncing Folder?" msgstr "Stop Syncing Folder?" -#: crates/pdfs-gui/src/app/pages/devices.rs:947 +#: crates/pdfs-gui/src/app/pages/devices.rs:969 msgid "Also delete from Proton Drive" msgstr "Also delete from Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:949 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Deletes the only copy of these files." msgstr "Deletes the only copy of these files." -#: crates/pdfs-gui/src/app/pages/devices.rs:951 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 msgid "The local copy is unaffected." msgstr "The local copy is unaffected." -#: crates/pdfs-gui/src/app/pages/devices.rs:958 +#: crates/pdfs-gui/src/app/pages/devices.rs:980 msgid "Stop Syncing" msgstr "Stop Syncing" -#: crates/pdfs-gui/src/app/pages/devices.rs:971 +#: crates/pdfs-gui/src/app/pages/devices.rs:993 msgid "Stopped syncing folder" msgstr "Stopped syncing folder" -#: crates/pdfs-gui/src/app/pages/devices.rs:972 +#: crates/pdfs-gui/src/app/pages/devices.rs:994 msgid "Couldn't stop syncing the folder" msgstr "Couldn't stop syncing the folder" -#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1005 msgid "Rename Computer" msgstr "Rename Computer" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:985 +#: crates/pdfs-gui/src/app/pages/devices.rs:1007 #, rust-format msgid "Rename “{name}”." msgstr "Rename “{name}”." -#: crates/pdfs-gui/src/app/pages/devices.rs:1010 -#: crates/pdfs-gui/src/app/pages/devices.rs:1022 +#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1044 msgid "Couldn't rename the computer" msgstr "Couldn't rename the computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1011 +#: crates/pdfs-gui/src/app/pages/devices.rs:1033 msgid "A name is required." msgstr "A name is required." -#: crates/pdfs-gui/src/app/pages/devices.rs:1021 +#: crates/pdfs-gui/src/app/pages/devices.rs:1043 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:1034 +#: crates/pdfs-gui/src/app/pages/devices.rs:1056 #, rust-format msgid "Type “{name}” to confirm" msgstr "Type “{name}” to confirm" -#: crates/pdfs-gui/src/app/pages/devices.rs:1039 +#: crates/pdfs-gui/src/app/pages/devices.rs:1061 msgid "Remove Computer?" msgstr "Remove Computer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1042 +#: crates/pdfs-gui/src/app/pages/devices.rs:1064 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2486,26 +2510,26 @@ msgstr "" "on that computer itself are not touched — but this cannot be undone from " "here." -#: crates/pdfs-gui/src/app/pages/devices.rs:1048 +#: crates/pdfs-gui/src/app/pages/devices.rs:1070 #: crates/pdfs-gui/src/app/pages/shared.rs:1025 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:634 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:876 msgid "Remove" msgstr "Remove" -#: crates/pdfs-gui/src/app/pages/devices.rs:1065 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 msgid "Computer removed" msgstr "Computer removed" -#: crates/pdfs-gui/src/app/pages/devices.rs:1066 +#: crates/pdfs-gui/src/app/pages/devices.rs:1088 msgid "Couldn't remove the computer" msgstr "Couldn't remove the computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1084 +#: crates/pdfs-gui/src/app/pages/devices.rs:1106 msgid "Continue This Backup Here?" msgstr "Continue This Backup Here?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1087 +#: crates/pdfs-gui/src/app/pages/devices.rs:1109 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2524,25 +2548,25 @@ msgstr "" "“Restore to This Computer” afterwards to bring back what “{name}” was " "syncing." -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1114 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:1099 +#: crates/pdfs-gui/src/app/pages/devices.rs:1121 #, rust-format msgid "Now backing up as “{name}”" msgstr "Now backing up as “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1108 +#: crates/pdfs-gui/src/app/pages/devices.rs:1130 msgid "Couldn't continue that backup here" msgstr "Couldn't continue that backup here" -#: crates/pdfs-gui/src/app/pages/devices.rs:1124 +#: crates/pdfs-gui/src/app/pages/devices.rs:1146 msgid "Sync With This Computer?" msgstr "Sync With This Computer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#: crates/pdfs-gui/src/app/pages/devices.rs:1149 #, rust-format msgid "" "This computer and “{name}” keep the same folders in step. A change or a " @@ -2559,25 +2583,25 @@ msgstr "" "Folders already synced here stay where they are. Next you choose which of " "its folders to sync here." -#: crates/pdfs-gui/src/app/pages/devices.rs:1132 +#: crates/pdfs-gui/src/app/pages/devices.rs:1154 msgid "Sync Here" msgstr "Sync Here" #. Translators: {name} is the name of the other computer this one now syncs with. -#: crates/pdfs-gui/src/app/pages/devices.rs:1139 +#: crates/pdfs-gui/src/app/pages/devices.rs:1161 #, rust-format msgid "Now syncing with “{name}”" msgstr "Now syncing with “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1155 +#: crates/pdfs-gui/src/app/pages/devices.rs:1177 msgid "Couldn't sync with that computer" msgstr "Couldn't sync with that computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1187 +#: crates/pdfs-gui/src/app/pages/devices.rs:1209 msgid "Move Backup to This Computer?" msgstr "Move Backup to This Computer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1190 +#: crates/pdfs-gui/src/app/pages/devices.rs:1212 #, rust-format msgid "" "The folders backed up by “{name}” move into this computer's backup in Proton " @@ -2593,12 +2617,12 @@ msgstr "" "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:1202 +#: crates/pdfs-gui/src/app/pages/devices.rs:1224 #, rust-format msgid "Moved the backup of “{name}”" msgstr "Moved the backup of “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1216 +#: crates/pdfs-gui/src/app/pages/devices.rs:1238 msgid "Couldn't move that backup" msgstr "Couldn't move that backup" @@ -2869,7 +2893,7 @@ msgid "Sync paused until you resume" msgstr "Sync paused until you resume" #: crates/pdfs-gui/src/app/pages/locations.rs:570 -#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:290 +#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:308 msgid "Sync paused" msgstr "Sync paused" @@ -3321,7 +3345,7 @@ msgid "paused" msgstr "paused" #. Translators: a folder's files can't be opened right now. -#: crates/pdfs-gui/src/app/pages/locations.rs:1742 +#: crates/pdfs-gui/src/app/pages/locations.rs:1745 msgid "not available" msgstr "not available" @@ -5035,7 +5059,7 @@ msgstr "Resumes {time}" msgid "Until you resume" msgstr "Until you resume" -#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:292 +#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:310 #, rust-format msgid "{n} change needs attention" msgid_plural "{n} changes need attention" @@ -6246,52 +6270,57 @@ msgstr "Proton Drive isn't responding" msgid "Signed in as {username} — locked" msgstr "Signed in as {username} — locked" -#: crates/pdfs-gui/src/main.rs:200 +#: crates/pdfs-gui/src/main.rs:209 +#, rust-format +msgid "Signed in as {username} — can't open your Drive" +msgstr "Signed in as {username} — can't open your Drive" + +#: crates/pdfs-gui/src/main.rs:218 #, rust-format msgid "Signed in as {username} — not connected" msgstr "Signed in as {username} — not connected" -#: crates/pdfs-gui/src/main.rs:205 +#: crates/pdfs-gui/src/main.rs:223 msgid "Not signed in" msgstr "Not signed in" #. Translators: {error} is an error message, in English. -#: crates/pdfs-gui/src/main.rs:208 +#: crates/pdfs-gui/src/main.rs:226 #, rust-format msgid "Error: {error}" msgstr "Error: {error}" -#: crates/pdfs-gui/src/main.rs:230 +#: crates/pdfs-gui/src/main.rs:248 msgid "Proton Drive sent an unexpected reply" msgstr "Proton Drive sent an unexpected reply" -#: crates/pdfs-gui/src/main.rs:284 +#: crates/pdfs-gui/src/main.rs:302 msgid "Session expired — sign in again" msgstr "Session expired — sign in again" -#: crates/pdfs-gui/src/main.rs:286 +#: crates/pdfs-gui/src/main.rs:304 msgid "Sign-in not saved — unlock your keyring" msgstr "Sign-in not saved — unlock your keyring" -#: crates/pdfs-gui/src/main.rs:289 +#: crates/pdfs-gui/src/main.rs:307 msgid "Update not finished — restart Proton Drive" msgstr "Update not finished — restart Proton Drive" -#: crates/pdfs-gui/src/main.rs:299 +#: crates/pdfs-gui/src/main.rs:317 #, rust-format msgid "Up to date in {mountpoint} ({n} pinned)" msgid_plural "Up to date in {mountpoint} ({n} pinned)" msgstr[0] "Up to date in {mountpoint} ({n} pinned)" msgstr[1] "Up to date in {mountpoint} ({n} pinned)" -#: crates/pdfs-gui/src/main.rs:306 +#: crates/pdfs-gui/src/main.rs:324 #, rust-format msgid "Syncing — {queued} ({n} pinned)" msgid_plural "Syncing — {queued} ({n} pinned)" msgstr[0] "Syncing — {queued} ({n} pinned)" msgstr[1] "Syncing — {queued} ({n} pinned)" -#: crates/pdfs-gui/src/main.rs:313 +#: crates/pdfs-gui/src/main.rs:331 #, rust-format msgid "Offline — cached files only ({n} pinned)" msgid_plural "Offline — cached files only ({n} pinned)" @@ -6299,60 +6328,60 @@ msgstr[0] "Offline — cached files only ({n} pinned)" msgstr[1] "Offline — cached files only ({n} pinned)" #. Translators: {queued} is a queue summary such as "3 uploads queued". -#: crates/pdfs-gui/src/main.rs:319 +#: crates/pdfs-gui/src/main.rs:337 #, rust-format msgid "Offline — {queued}" msgstr "Offline — {queued}" #. Translators: a menu item that opens the list of failing changes. -#: crates/pdfs-gui/src/main.rs:358 +#: crates/pdfs-gui/src/main.rs:376 #, rust-format msgid "{n} issue — View" msgid_plural "{n} issues — View" msgstr[0] "{n} issue — View" msgstr[1] "{n} issues — View" -#: crates/pdfs-gui/src/main.rs:465 +#: crates/pdfs-gui/src/main.rs:483 msgid "Sign In…" msgstr "Sign In…" -#: crates/pdfs-gui/src/main.rs:466 +#: crates/pdfs-gui/src/main.rs:484 msgid "Sign In Again…" msgstr "Sign In Again…" -#: crates/pdfs-gui/src/main.rs:469 +#: crates/pdfs-gui/src/main.rs:487 msgid "Open Proton Drive" msgstr "Open Proton Drive" -#: crates/pdfs-gui/src/main.rs:473 +#: crates/pdfs-gui/src/main.rs:491 msgid "Restart Proton Drive" msgstr "Restart Proton Drive" -#: crates/pdfs-gui/src/main.rs:482 +#: crates/pdfs-gui/src/main.rs:500 msgid "Open Folder" msgstr "Open Folder" -#: crates/pdfs-gui/src/main.rs:494 +#: crates/pdfs-gui/src/main.rs:512 msgid "Resume Sync" msgstr "Resume Sync" -#: crates/pdfs-gui/src/main.rs:496 +#: crates/pdfs-gui/src/main.rs:514 msgid "Pause Sync" msgstr "Pause Sync" -#: crates/pdfs-gui/src/main.rs:515 +#: crates/pdfs-gui/src/main.rs:533 msgid "Connect" msgstr "Connect" -#: crates/pdfs-gui/src/main.rs:522 +#: crates/pdfs-gui/src/main.rs:540 msgid "Hide Tray Icon" msgstr "Hide Tray Icon" -#: crates/pdfs-gui/src/main.rs:526 +#: crates/pdfs-gui/src/main.rs:544 msgid "Stop Proton Drive…" msgstr "Stop Proton Drive…" -#: crates/pdfs-gui/src/main.rs:530 +#: crates/pdfs-gui/src/main.rs:548 msgid "Quit" msgstr "Quit" diff --git a/po/es.po b/po/es.po index 9ecbbaf..a68e8ea 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-26 22:26+0100\n" +"POT-Creation-Date: 2026-10-31 19:26+0100\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: Spanish\n" @@ -247,13 +247,13 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1917 #: crates/pdfs-gui/src/app/pages/browser.rs:2039 #: crates/pdfs-gui/src/app/pages/browser.rs:2540 -#: crates/pdfs-gui/src/app/pages/devices.rs:804 -#: crates/pdfs-gui/src/app/pages/devices.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:995 -#: crates/pdfs-gui/src/app/pages/devices.rs:1047 -#: crates/pdfs-gui/src/app/pages/devices.rs:1091 -#: crates/pdfs-gui/src/app/pages/devices.rs:1131 -#: crates/pdfs-gui/src/app/pages/devices.rs:1194 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 +#: crates/pdfs-gui/src/app/pages/devices.rs:979 +#: crates/pdfs-gui/src/app/pages/devices.rs:1017 +#: crates/pdfs-gui/src/app/pages/devices.rs:1069 +#: crates/pdfs-gui/src/app/pages/devices.rs:1113 +#: crates/pdfs-gui/src/app/pages/devices.rs:1153 +#: crates/pdfs-gui/src/app/pages/devices.rs:1216 #: crates/pdfs-gui/src/app/pages/locations.rs:850 #: crates/pdfs-gui/src/app/pages/locations.rs:1054 #: crates/pdfs-gui/src/app/pages/locations.rs:1537 @@ -348,7 +348,7 @@ msgstr "Cuadrícula / lista" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1896 #: crates/pdfs-gui/src/app/pages/browser.rs:1918 -#: crates/pdfs-gui/src/app/pages/devices.rs:996 +#: crates/pdfs-gui/src/app/pages/devices.rs:1018 #: crates/pdfs-gui/src/app/widgets/details.rs:161 msgid "Rename" msgstr "Renombrar" @@ -422,14 +422,27 @@ msgstr "Sin conexión" msgid "The Proton Drive service isn't running." msgstr "El servicio de Proton Drive no está en ejecución." -#: crates/pdfs-gui/src/app/main.rs:1803 +#: crates/pdfs-gui/src/app/main.rs:1813 +msgid "Can't open your Drive" +msgstr "No se puede abrir Proton Drive" + +#: crates/pdfs-gui/src/app/main.rs:1816 +#, rust-format +msgid "" +"The Proton Drive service keeps failing to start and tries again on its own. " +"The last error was: {error}" +msgstr "" +"El servicio de Proton Drive no consigue iniciarse y vuelve a intentarlo por " +"sí solo. Último error: {error}" + +#: crates/pdfs-gui/src/app/main.rs:1824 #: crates/pdfs-gui/src/app/pages/status.rs:355 #: crates/pdfs-gui/src/hud/mod.rs:269 crates/pdfs-gui/src/hud/mod.rs:579 -#: crates/pdfs-gui/src/main.rs:475 crates/pdfs-gui/src/main.rs:517 +#: crates/pdfs-gui/src/main.rs:493 crates/pdfs-gui/src/main.rs:535 msgid "Connecting…" msgstr "Conectando…" -#: crates/pdfs-gui/src/app/main.rs:1804 +#: crates/pdfs-gui/src/app/main.rs:1825 msgid "Waiting for the Proton Drive service to start." msgstr "Esperando a que se inicie el servicio de Proton Drive." @@ -679,7 +692,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:839 +#: crates/pdfs-gui/src/app/pages/devices.rs:861 msgid "Couldn't restore folders" msgstr "No se pudieron restaurar las carpetas" @@ -1268,12 +1281,12 @@ msgstr "No se pudieron cargar los álbumes" #: crates/pdfs-gui/src/app/pages/browser.rs:1873 #: crates/pdfs-gui/src/app/pages/browser.rs:2500 #: crates/pdfs-gui/src/app/pages/browser.rs:2650 -#: crates/pdfs-gui/src/app/pages/devices.rs:707 -#: crates/pdfs-gui/src/app/pages/devices.rs:747 -#: crates/pdfs-gui/src/app/pages/devices.rs:900 -#: crates/pdfs-gui/src/app/pages/devices.rs:1170 -#: crates/pdfs-gui/src/app/pages/devices.rs:1231 -#: crates/pdfs-gui/src/app/pages/devices.rs:1260 +#: crates/pdfs-gui/src/app/pages/devices.rs:729 +#: crates/pdfs-gui/src/app/pages/devices.rs:769 +#: crates/pdfs-gui/src/app/pages/devices.rs:922 +#: crates/pdfs-gui/src/app/pages/devices.rs:1192 +#: crates/pdfs-gui/src/app/pages/devices.rs:1253 +#: crates/pdfs-gui/src/app/pages/devices.rs:1282 #: crates/pdfs-gui/src/app/pages/duplicates.rs:221 #: crates/pdfs-gui/src/app/pages/locations.rs:577 #: crates/pdfs-gui/src/app/pages/locations.rs:824 @@ -1921,7 +1934,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1900 -#: crates/pdfs-gui/src/app/pages/devices.rs:989 +#: crates/pdfs-gui/src/app/pages/devices.rs:1011 msgid "New name" msgstr "Nombre nuevo" @@ -1946,7 +1959,7 @@ msgstr "Elija la carpeta de destino." #: crates/pdfs-gui/src/app/pages/browser.rs:1989 #: crates/pdfs-gui/src/app/pages/browser.rs:1994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1195 +#: crates/pdfs-gui/src/app/pages/devices.rs:1217 msgid "Move Here" msgstr "Mover aquí" @@ -2268,76 +2281,87 @@ 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:608 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 +#, rust-format +msgid "{folders} · {n} waiting for its first sync" +msgid_plural "{folders} · {n} waiting for their first sync" +msgstr[0] "{folders} · {n} espera su primera sincronización" +msgstr[1] "{folders} · {n} esperan su primera sincronización" + +#: crates/pdfs-gui/src/app/pages/devices.rs:621 #, 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:619 +#: crates/pdfs-gui/src/app/pages/devices.rs:640 msgid "syncing…" msgstr "sincronizando…" -#: crates/pdfs-gui/src/app/pages/devices.rs:620 +#: crates/pdfs-gui/src/app/pages/devices.rs:641 msgid "sync error" msgstr "error de sincronización" -#: crates/pdfs-gui/src/app/pages/devices.rs:621 +#: crates/pdfs-gui/src/app/pages/devices.rs:642 msgid "needs attention" msgstr "requiere atención" -#: crates/pdfs-gui/src/app/pages/devices.rs:622 +#: crates/pdfs-gui/src/app/pages/devices.rs:643 +msgid "waiting for first sync" +msgstr "esperando la primera sincronización" + +#: crates/pdfs-gui/src/app/pages/devices.rs:644 msgid "up to date" msgstr "actualizado" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 msgid "checking for changes…" msgstr "buscando cambios…" -#: crates/pdfs-gui/src/app/pages/devices.rs:638 +#: crates/pdfs-gui/src/app/pages/devices.rs:660 #, rust-format msgid "checking for changes — {n} item checked" msgid_plural "checking for changes — {n} items checked" msgstr[0] "buscando cambios: {n} elemento revisado" msgstr[1] "buscando cambios: {n} elementos revisados" -#: crates/pdfs-gui/src/app/pages/devices.rs:645 +#: crates/pdfs-gui/src/app/pages/devices.rs:667 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "buscando cambios: {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:657 +#: crates/pdfs-gui/src/app/pages/devices.rs:679 #, rust-format msgid "syncing {done} of {total}" msgstr "sincronizando {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:663 +#: crates/pdfs-gui/src/app/pages/devices.rs:685 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "sincronizando {file}: {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:675 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Add Folder to Sync" msgstr "Añadir carpeta para sincronizar" -#: crates/pdfs-gui/src/app/pages/devices.rs:699 +#: crates/pdfs-gui/src/app/pages/devices.rs:721 #: crates/pdfs-gui/src/app/pages/locations.rs:1559 #: crates/pdfs-gui/src/app/pages/locations.rs:1686 msgid "Syncing folder…" msgstr "Sincronizando la carpeta…" -#: crates/pdfs-gui/src/app/pages/devices.rs:702 -#: crates/pdfs-gui/src/app/pages/devices.rs:706 +#: crates/pdfs-gui/src/app/pages/devices.rs:724 +#: crates/pdfs-gui/src/app/pages/devices.rs:728 msgid "Couldn't add folder" msgstr "No se pudo añadir la carpeta" -#: crates/pdfs-gui/src/app/pages/devices.rs:740 -#: crates/pdfs-gui/src/app/pages/devices.rs:746 +#: crates/pdfs-gui/src/app/pages/devices.rs:762 +#: crates/pdfs-gui/src/app/pages/devices.rs:768 msgid "Couldn't list folders to restore" msgstr "No se pudieron listar las carpetas para restaurar" -#: crates/pdfs-gui/src/app/pages/devices.rs:772 +#: crates/pdfs-gui/src/app/pages/devices.rs:794 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2345,19 +2369,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:798 +#: crates/pdfs-gui/src/app/pages/devices.rs:820 msgid "Restore Folders" msgstr "Restaurar carpetas" -#: crates/pdfs-gui/src/app/pages/devices.rs:805 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Restore" msgstr "Restaurar" -#: crates/pdfs-gui/src/app/pages/devices.rs:838 +#: crates/pdfs-gui/src/app/pages/devices.rs:860 msgid "Restoring folders…" msgstr "Restaurando carpetas…" -#: crates/pdfs-gui/src/app/pages/devices.rs:850 +#: crates/pdfs-gui/src/app/pages/devices.rs:872 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2366,36 +2390,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:853 +#: crates/pdfs-gui/src/app/pages/devices.rs:875 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:862 +#: crates/pdfs-gui/src/app/pages/devices.rs:884 #, 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:865 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 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:889 +#: crates/pdfs-gui/src/app/pages/devices.rs:911 msgid "Folder will switch to online only" msgstr "La carpeta pasará a solo en línea" -#: crates/pdfs-gui/src/app/pages/devices.rs:891 +#: crates/pdfs-gui/src/app/pages/devices.rs:913 msgid "Folder will download and stay synced" msgstr "La carpeta se descargará y permanecerá sincronizada" -#: crates/pdfs-gui/src/app/pages/devices.rs:895 -#: crates/pdfs-gui/src/app/pages/devices.rs:899 +#: crates/pdfs-gui/src/app/pages/devices.rs:917 +#: crates/pdfs-gui/src/app/pages/devices.rs:921 msgid "Couldn't change mode" msgstr "No se pudo cambiar el modo" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:953 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2412,7 +2436,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:937 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2425,68 +2449,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:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:964 msgid "Stop Syncing Folder?" msgstr "¿Dejar de sincronizar la carpeta?" -#: crates/pdfs-gui/src/app/pages/devices.rs:947 +#: crates/pdfs-gui/src/app/pages/devices.rs:969 msgid "Also delete from Proton Drive" msgstr "Eliminar también de Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:949 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Deletes the only copy of these files." msgstr "Elimina la única copia de estos archivos." -#: crates/pdfs-gui/src/app/pages/devices.rs:951 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 msgid "The local copy is unaffected." msgstr "La copia local no se ve afectada." -#: crates/pdfs-gui/src/app/pages/devices.rs:958 +#: crates/pdfs-gui/src/app/pages/devices.rs:980 msgid "Stop Syncing" msgstr "Dejar de sincronizar" -#: crates/pdfs-gui/src/app/pages/devices.rs:971 +#: crates/pdfs-gui/src/app/pages/devices.rs:993 msgid "Stopped syncing folder" msgstr "Se dejó de sincronizar la carpeta" -#: crates/pdfs-gui/src/app/pages/devices.rs:972 +#: crates/pdfs-gui/src/app/pages/devices.rs:994 msgid "Couldn't stop syncing the folder" msgstr "No se pudo dejar de sincronizar la carpeta" -#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1005 msgid "Rename Computer" msgstr "Renombrar equipo" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:985 +#: crates/pdfs-gui/src/app/pages/devices.rs:1007 #, rust-format msgid "Rename “{name}”." msgstr "Renombrar “{name}”." -#: crates/pdfs-gui/src/app/pages/devices.rs:1010 -#: crates/pdfs-gui/src/app/pages/devices.rs:1022 +#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1044 msgid "Couldn't rename the computer" msgstr "No se pudo renombrar el equipo" -#: crates/pdfs-gui/src/app/pages/devices.rs:1011 +#: crates/pdfs-gui/src/app/pages/devices.rs:1033 msgid "A name is required." msgstr "Se necesita un nombre." -#: crates/pdfs-gui/src/app/pages/devices.rs:1021 +#: crates/pdfs-gui/src/app/pages/devices.rs:1043 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:1034 +#: crates/pdfs-gui/src/app/pages/devices.rs:1056 #, rust-format msgid "Type “{name}” to confirm" msgstr "Escriba “{name}” para confirmar" -#: crates/pdfs-gui/src/app/pages/devices.rs:1039 +#: crates/pdfs-gui/src/app/pages/devices.rs:1061 msgid "Remove Computer?" msgstr "¿Quitar el equipo?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1042 +#: crates/pdfs-gui/src/app/pages/devices.rs:1064 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2500,26 +2524,26 @@ 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:1048 +#: crates/pdfs-gui/src/app/pages/devices.rs:1070 #: crates/pdfs-gui/src/app/pages/shared.rs:1025 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:634 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:876 msgid "Remove" msgstr "Quitar" -#: crates/pdfs-gui/src/app/pages/devices.rs:1065 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 msgid "Computer removed" msgstr "Equipo quitado" -#: crates/pdfs-gui/src/app/pages/devices.rs:1066 +#: crates/pdfs-gui/src/app/pages/devices.rs:1088 msgid "Couldn't remove the computer" msgstr "No se pudo quitar el equipo" -#: crates/pdfs-gui/src/app/pages/devices.rs:1084 +#: crates/pdfs-gui/src/app/pages/devices.rs:1106 msgid "Continue This Backup Here?" msgstr "¿Continuar esta copia de seguridad aquí?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1087 +#: crates/pdfs-gui/src/app/pages/devices.rs:1109 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2538,25 +2562,25 @@ 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:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1114 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:1099 +#: crates/pdfs-gui/src/app/pages/devices.rs:1121 #, 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:1108 +#: crates/pdfs-gui/src/app/pages/devices.rs:1130 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:1124 +#: crates/pdfs-gui/src/app/pages/devices.rs:1146 msgid "Sync With This Computer?" msgstr "¿Sincronizar con este equipo?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#: crates/pdfs-gui/src/app/pages/devices.rs:1149 #, rust-format msgid "" "This computer and “{name}” keep the same folders in step. A change or a " @@ -2573,25 +2597,25 @@ msgstr "" "Proton Drive. Las carpetas ya sincronizadas aquí se quedan donde están. A " "continuación, elija qué carpetas suyas sincronizar aquí." -#: crates/pdfs-gui/src/app/pages/devices.rs:1132 +#: crates/pdfs-gui/src/app/pages/devices.rs:1154 msgid "Sync Here" msgstr "Sincronizar aquí" #. Translators: {name} is the name of the other computer this one now syncs with. -#: crates/pdfs-gui/src/app/pages/devices.rs:1139 +#: crates/pdfs-gui/src/app/pages/devices.rs:1161 #, rust-format msgid "Now syncing with “{name}”" msgstr "Ahora se sincroniza con “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1155 +#: crates/pdfs-gui/src/app/pages/devices.rs:1177 msgid "Couldn't sync with that computer" msgstr "No se pudo sincronizar con ese equipo" -#: crates/pdfs-gui/src/app/pages/devices.rs:1187 +#: crates/pdfs-gui/src/app/pages/devices.rs:1209 msgid "Move Backup to This Computer?" msgstr "¿Mover la copia de seguridad a este equipo?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1190 +#: crates/pdfs-gui/src/app/pages/devices.rs:1212 #, rust-format msgid "" "The folders backed up by “{name}” move into this computer's backup in Proton " @@ -2608,12 +2632,12 @@ msgstr "" "carpetas." #. Translators: {name} is the name of the computer whose folders moved to this one. -#: crates/pdfs-gui/src/app/pages/devices.rs:1202 +#: crates/pdfs-gui/src/app/pages/devices.rs:1224 #, 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:1216 +#: crates/pdfs-gui/src/app/pages/devices.rs:1238 msgid "Couldn't move that backup" msgstr "No se pudo mover esa copia de seguridad" @@ -2891,7 +2915,7 @@ msgid "Sync paused until you resume" msgstr "Sincronización en pausa hasta que la reanude" #: crates/pdfs-gui/src/app/pages/locations.rs:570 -#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:290 +#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:308 msgid "Sync paused" msgstr "Sincronización en pausa" @@ -3348,7 +3372,7 @@ msgid "paused" msgstr "en pausa" #. Translators: a folder's files can't be opened right now. -#: crates/pdfs-gui/src/app/pages/locations.rs:1742 +#: crates/pdfs-gui/src/app/pages/locations.rs:1745 msgid "not available" msgstr "no disponible" @@ -5095,7 +5119,7 @@ msgstr "Se reanuda {time}" msgid "Until you resume" msgstr "Hasta que la reanude" -#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:292 +#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:310 #, rust-format msgid "{n} change needs attention" msgid_plural "{n} changes need attention" @@ -6327,52 +6351,57 @@ msgstr "Proton Drive no responde" msgid "Signed in as {username} — locked" msgstr "Sesión iniciada como {username} — bloqueado" -#: crates/pdfs-gui/src/main.rs:200 +#: crates/pdfs-gui/src/main.rs:209 +#, rust-format +msgid "Signed in as {username} — can't open your Drive" +msgstr "Sesión iniciada como {username} — no se puede abrir Proton Drive" + +#: crates/pdfs-gui/src/main.rs:218 #, rust-format msgid "Signed in as {username} — not connected" msgstr "Sesión iniciada como {username} — sin conexión" -#: crates/pdfs-gui/src/main.rs:205 +#: crates/pdfs-gui/src/main.rs:223 msgid "Not signed in" msgstr "Sesión no iniciada" #. Translators: {error} is an error message, in English. -#: crates/pdfs-gui/src/main.rs:208 +#: crates/pdfs-gui/src/main.rs:226 #, rust-format msgid "Error: {error}" msgstr "Error: {error}" -#: crates/pdfs-gui/src/main.rs:230 +#: crates/pdfs-gui/src/main.rs:248 msgid "Proton Drive sent an unexpected reply" msgstr "Proton Drive envió una respuesta inesperada" -#: crates/pdfs-gui/src/main.rs:284 +#: crates/pdfs-gui/src/main.rs:302 msgid "Session expired — sign in again" msgstr "Sesión caducada: vuelva a iniciar sesión" -#: crates/pdfs-gui/src/main.rs:286 +#: crates/pdfs-gui/src/main.rs:304 msgid "Sign-in not saved — unlock your keyring" msgstr "Inicio de sesión no guardado: desbloquee el depósito de claves" -#: crates/pdfs-gui/src/main.rs:289 +#: crates/pdfs-gui/src/main.rs:307 msgid "Update not finished — restart Proton Drive" msgstr "Actualización sin terminar: reinicie Proton Drive" -#: crates/pdfs-gui/src/main.rs:299 +#: crates/pdfs-gui/src/main.rs:317 #, rust-format msgid "Up to date in {mountpoint} ({n} pinned)" msgid_plural "Up to date in {mountpoint} ({n} pinned)" msgstr[0] "Actualizado en {mountpoint} ({n} fijado)" msgstr[1] "Actualizado en {mountpoint} ({n} fijados)" -#: crates/pdfs-gui/src/main.rs:306 +#: crates/pdfs-gui/src/main.rs:324 #, rust-format msgid "Syncing — {queued} ({n} pinned)" msgid_plural "Syncing — {queued} ({n} pinned)" msgstr[0] "Sincronizando: {queued} ({n} anclado)" msgstr[1] "Sincronizando: {queued} ({n} anclados)" -#: crates/pdfs-gui/src/main.rs:313 +#: crates/pdfs-gui/src/main.rs:331 #, rust-format msgid "Offline — cached files only ({n} pinned)" msgid_plural "Offline — cached files only ({n} pinned)" @@ -6380,60 +6409,60 @@ msgstr[0] "Sin conexión: solo archivos en caché ({n} anclado)" msgstr[1] "Sin conexión: solo archivos en caché ({n} anclados)" #. Translators: {queued} is a queue summary such as "3 uploads queued". -#: crates/pdfs-gui/src/main.rs:319 +#: crates/pdfs-gui/src/main.rs:337 #, rust-format msgid "Offline — {queued}" msgstr "Sin conexión: {queued}" #. Translators: a menu item that opens the list of failing changes. -#: crates/pdfs-gui/src/main.rs:358 +#: crates/pdfs-gui/src/main.rs:376 #, rust-format msgid "{n} issue — View" msgid_plural "{n} issues — View" msgstr[0] "{n} problema: ver" msgstr[1] "{n} problemas: ver" -#: crates/pdfs-gui/src/main.rs:465 +#: crates/pdfs-gui/src/main.rs:483 msgid "Sign In…" msgstr "Iniciar sesión…" -#: crates/pdfs-gui/src/main.rs:466 +#: crates/pdfs-gui/src/main.rs:484 msgid "Sign In Again…" msgstr "Volver a iniciar sesión…" -#: crates/pdfs-gui/src/main.rs:469 +#: crates/pdfs-gui/src/main.rs:487 msgid "Open Proton Drive" msgstr "Abrir Proton Drive" -#: crates/pdfs-gui/src/main.rs:473 +#: crates/pdfs-gui/src/main.rs:491 msgid "Restart Proton Drive" msgstr "Reiniciar Proton Drive" -#: crates/pdfs-gui/src/main.rs:482 +#: crates/pdfs-gui/src/main.rs:500 msgid "Open Folder" msgstr "Abrir carpeta" -#: crates/pdfs-gui/src/main.rs:494 +#: crates/pdfs-gui/src/main.rs:512 msgid "Resume Sync" msgstr "Reanudar la sincronización" -#: crates/pdfs-gui/src/main.rs:496 +#: crates/pdfs-gui/src/main.rs:514 msgid "Pause Sync" msgstr "Pausar la sincronización" -#: crates/pdfs-gui/src/main.rs:515 +#: crates/pdfs-gui/src/main.rs:533 msgid "Connect" msgstr "Conectar" -#: crates/pdfs-gui/src/main.rs:522 +#: crates/pdfs-gui/src/main.rs:540 msgid "Hide Tray Icon" msgstr "Ocultar el icono de la bandeja" -#: crates/pdfs-gui/src/main.rs:526 +#: crates/pdfs-gui/src/main.rs:544 msgid "Stop Proton Drive…" msgstr "Detener Proton Drive…" -#: crates/pdfs-gui/src/main.rs:530 +#: crates/pdfs-gui/src/main.rs:548 msgid "Quit" msgstr "Salir" diff --git a/po/fr.po b/po/fr.po index 8a20308..6a7093f 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-26 22:26+0100\n" +"POT-Creation-Date: 2026-10-31 19:26+0100\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: French\n" @@ -249,13 +249,13 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1917 #: crates/pdfs-gui/src/app/pages/browser.rs:2039 #: crates/pdfs-gui/src/app/pages/browser.rs:2540 -#: crates/pdfs-gui/src/app/pages/devices.rs:804 -#: crates/pdfs-gui/src/app/pages/devices.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:995 -#: crates/pdfs-gui/src/app/pages/devices.rs:1047 -#: crates/pdfs-gui/src/app/pages/devices.rs:1091 -#: crates/pdfs-gui/src/app/pages/devices.rs:1131 -#: crates/pdfs-gui/src/app/pages/devices.rs:1194 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 +#: crates/pdfs-gui/src/app/pages/devices.rs:979 +#: crates/pdfs-gui/src/app/pages/devices.rs:1017 +#: crates/pdfs-gui/src/app/pages/devices.rs:1069 +#: crates/pdfs-gui/src/app/pages/devices.rs:1113 +#: crates/pdfs-gui/src/app/pages/devices.rs:1153 +#: crates/pdfs-gui/src/app/pages/devices.rs:1216 #: crates/pdfs-gui/src/app/pages/locations.rs:850 #: crates/pdfs-gui/src/app/pages/locations.rs:1054 #: crates/pdfs-gui/src/app/pages/locations.rs:1537 @@ -350,7 +350,7 @@ msgstr "Grille / liste" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1896 #: crates/pdfs-gui/src/app/pages/browser.rs:1918 -#: crates/pdfs-gui/src/app/pages/devices.rs:996 +#: crates/pdfs-gui/src/app/pages/devices.rs:1018 #: crates/pdfs-gui/src/app/widgets/details.rs:161 msgid "Rename" msgstr "Renommer" @@ -424,14 +424,27 @@ msgstr "Non connecté" msgid "The Proton Drive service isn't running." msgstr "Le service Proton Drive n’est pas en cours d’exécution." -#: crates/pdfs-gui/src/app/main.rs:1803 +#: crates/pdfs-gui/src/app/main.rs:1813 +msgid "Can't open your Drive" +msgstr "Impossible d’ouvrir Proton Drive" + +#: crates/pdfs-gui/src/app/main.rs:1816 +#, rust-format +msgid "" +"The Proton Drive service keeps failing to start and tries again on its own. " +"The last error was: {error}" +msgstr "" +"Le service Proton Drive n’arrive pas à démarrer et réessaie de lui-même. " +"Dernière erreur : {error}" + +#: crates/pdfs-gui/src/app/main.rs:1824 #: crates/pdfs-gui/src/app/pages/status.rs:355 #: crates/pdfs-gui/src/hud/mod.rs:269 crates/pdfs-gui/src/hud/mod.rs:579 -#: crates/pdfs-gui/src/main.rs:475 crates/pdfs-gui/src/main.rs:517 +#: crates/pdfs-gui/src/main.rs:493 crates/pdfs-gui/src/main.rs:535 msgid "Connecting…" msgstr "Connexion…" -#: crates/pdfs-gui/src/app/main.rs:1804 +#: crates/pdfs-gui/src/app/main.rs:1825 msgid "Waiting for the Proton Drive service to start." msgstr "En attente du démarrage du service Proton Drive." @@ -682,7 +695,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:839 +#: crates/pdfs-gui/src/app/pages/devices.rs:861 msgid "Couldn't restore folders" msgstr "Impossible de restaurer les dossiers" @@ -1279,12 +1292,12 @@ msgstr "Impossible de charger les albums" #: crates/pdfs-gui/src/app/pages/browser.rs:1873 #: crates/pdfs-gui/src/app/pages/browser.rs:2500 #: crates/pdfs-gui/src/app/pages/browser.rs:2650 -#: crates/pdfs-gui/src/app/pages/devices.rs:707 -#: crates/pdfs-gui/src/app/pages/devices.rs:747 -#: crates/pdfs-gui/src/app/pages/devices.rs:900 -#: crates/pdfs-gui/src/app/pages/devices.rs:1170 -#: crates/pdfs-gui/src/app/pages/devices.rs:1231 -#: crates/pdfs-gui/src/app/pages/devices.rs:1260 +#: crates/pdfs-gui/src/app/pages/devices.rs:729 +#: crates/pdfs-gui/src/app/pages/devices.rs:769 +#: crates/pdfs-gui/src/app/pages/devices.rs:922 +#: crates/pdfs-gui/src/app/pages/devices.rs:1192 +#: crates/pdfs-gui/src/app/pages/devices.rs:1253 +#: crates/pdfs-gui/src/app/pages/devices.rs:1282 #: crates/pdfs-gui/src/app/pages/duplicates.rs:221 #: crates/pdfs-gui/src/app/pages/locations.rs:577 #: crates/pdfs-gui/src/app/pages/locations.rs:824 @@ -1933,7 +1946,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1900 -#: crates/pdfs-gui/src/app/pages/devices.rs:989 +#: crates/pdfs-gui/src/app/pages/devices.rs:1011 msgid "New name" msgstr "Nouveau nom" @@ -1958,7 +1971,7 @@ msgstr "Choisissez le dossier de destination." #: crates/pdfs-gui/src/app/pages/browser.rs:1989 #: crates/pdfs-gui/src/app/pages/browser.rs:1994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1195 +#: crates/pdfs-gui/src/app/pages/devices.rs:1217 msgid "Move Here" msgstr "Déplacer ici" @@ -2281,76 +2294,87 @@ 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:608 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 +#, rust-format +msgid "{folders} · {n} waiting for its first sync" +msgid_plural "{folders} · {n} waiting for their first sync" +msgstr[0] "{folders} · {n} attend sa première synchronisation" +msgstr[1] "{folders} · {n} attendent leur première synchronisation" + +#: crates/pdfs-gui/src/app/pages/devices.rs:621 #, 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:619 +#: crates/pdfs-gui/src/app/pages/devices.rs:640 msgid "syncing…" msgstr "synchronisation…" -#: crates/pdfs-gui/src/app/pages/devices.rs:620 +#: crates/pdfs-gui/src/app/pages/devices.rs:641 msgid "sync error" msgstr "erreur de synchronisation" -#: crates/pdfs-gui/src/app/pages/devices.rs:621 +#: crates/pdfs-gui/src/app/pages/devices.rs:642 msgid "needs attention" msgstr "attention requise" -#: crates/pdfs-gui/src/app/pages/devices.rs:622 +#: crates/pdfs-gui/src/app/pages/devices.rs:643 +msgid "waiting for first sync" +msgstr "en attente de la première synchronisation" + +#: crates/pdfs-gui/src/app/pages/devices.rs:644 msgid "up to date" msgstr "à jour" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 msgid "checking for changes…" msgstr "recherche de modifications…" -#: crates/pdfs-gui/src/app/pages/devices.rs:638 +#: crates/pdfs-gui/src/app/pages/devices.rs:660 #, rust-format msgid "checking for changes — {n} item checked" msgid_plural "checking for changes — {n} items checked" msgstr[0] "recherche de modifications — {n} élément vérifié" msgstr[1] "recherche de modifications — {n} éléments vérifiés" -#: crates/pdfs-gui/src/app/pages/devices.rs:645 +#: crates/pdfs-gui/src/app/pages/devices.rs:667 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "recherche de modifications — {done} sur {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:657 +#: crates/pdfs-gui/src/app/pages/devices.rs:679 #, rust-format msgid "syncing {done} of {total}" msgstr "synchronisation de {done} sur {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:663 +#: crates/pdfs-gui/src/app/pages/devices.rs:685 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "synchronisation de {file} — {done} sur {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:675 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Add Folder to Sync" msgstr "Ajouter un dossier à synchroniser" -#: crates/pdfs-gui/src/app/pages/devices.rs:699 +#: crates/pdfs-gui/src/app/pages/devices.rs:721 #: crates/pdfs-gui/src/app/pages/locations.rs:1559 #: crates/pdfs-gui/src/app/pages/locations.rs:1686 msgid "Syncing folder…" msgstr "Synchronisation du dossier…" -#: crates/pdfs-gui/src/app/pages/devices.rs:702 -#: crates/pdfs-gui/src/app/pages/devices.rs:706 +#: crates/pdfs-gui/src/app/pages/devices.rs:724 +#: crates/pdfs-gui/src/app/pages/devices.rs:728 msgid "Couldn't add folder" msgstr "Impossible d’ajouter le dossier" -#: crates/pdfs-gui/src/app/pages/devices.rs:740 -#: crates/pdfs-gui/src/app/pages/devices.rs:746 +#: crates/pdfs-gui/src/app/pages/devices.rs:762 +#: crates/pdfs-gui/src/app/pages/devices.rs:768 msgid "Couldn't list folders to restore" msgstr "Impossible de lister les dossiers à restaurer" -#: crates/pdfs-gui/src/app/pages/devices.rs:772 +#: crates/pdfs-gui/src/app/pages/devices.rs:794 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2358,19 +2382,19 @@ msgstr "" "Cochez les dossiers à synchroniser sur cette machine et ajustez " "l’emplacement de chacun." -#: crates/pdfs-gui/src/app/pages/devices.rs:798 +#: crates/pdfs-gui/src/app/pages/devices.rs:820 msgid "Restore Folders" msgstr "Restaurer les dossiers" -#: crates/pdfs-gui/src/app/pages/devices.rs:805 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Restore" msgstr "Restaurer" -#: crates/pdfs-gui/src/app/pages/devices.rs:838 +#: crates/pdfs-gui/src/app/pages/devices.rs:860 msgid "Restoring folders…" msgstr "Restauration des dossiers…" -#: crates/pdfs-gui/src/app/pages/devices.rs:850 +#: crates/pdfs-gui/src/app/pages/devices.rs:872 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2379,35 +2403,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:853 +#: crates/pdfs-gui/src/app/pages/devices.rs:875 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:862 +#: crates/pdfs-gui/src/app/pages/devices.rs:884 #, 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:865 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 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:889 +#: crates/pdfs-gui/src/app/pages/devices.rs:911 msgid "Folder will switch to online only" msgstr "Le dossier passera en ligne uniquement" -#: crates/pdfs-gui/src/app/pages/devices.rs:891 +#: crates/pdfs-gui/src/app/pages/devices.rs:913 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:895 -#: crates/pdfs-gui/src/app/pages/devices.rs:899 +#: crates/pdfs-gui/src/app/pages/devices.rs:917 +#: crates/pdfs-gui/src/app/pages/devices.rs:921 msgid "Couldn't change mode" msgstr "Impossible de changer de mode" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:953 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2424,7 +2448,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:937 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2438,68 +2462,68 @@ msgstr "" "synchronisés. Choisissez s’il faut aussi supprimer la copie dans Proton " "Drive." -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:964 msgid "Stop Syncing Folder?" msgstr "Arrêter la synchronisation du dossier ?" -#: crates/pdfs-gui/src/app/pages/devices.rs:947 +#: crates/pdfs-gui/src/app/pages/devices.rs:969 msgid "Also delete from Proton Drive" msgstr "Supprimer aussi de Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:949 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Deletes the only copy of these files." msgstr "Supprime l’unique copie de ces fichiers." -#: crates/pdfs-gui/src/app/pages/devices.rs:951 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 msgid "The local copy is unaffected." msgstr "La copie locale n’est pas affectée." -#: crates/pdfs-gui/src/app/pages/devices.rs:958 +#: crates/pdfs-gui/src/app/pages/devices.rs:980 msgid "Stop Syncing" msgstr "Arrêter la synchronisation" -#: crates/pdfs-gui/src/app/pages/devices.rs:971 +#: crates/pdfs-gui/src/app/pages/devices.rs:993 msgid "Stopped syncing folder" msgstr "Synchronisation du dossier arrêtée" -#: crates/pdfs-gui/src/app/pages/devices.rs:972 +#: crates/pdfs-gui/src/app/pages/devices.rs:994 msgid "Couldn't stop syncing the folder" msgstr "Impossible d’arrêter la synchronisation du dossier" -#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1005 msgid "Rename Computer" msgstr "Renommer l’ordinateur" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:985 +#: crates/pdfs-gui/src/app/pages/devices.rs:1007 #, rust-format msgid "Rename “{name}”." msgstr "Renommer « {name} »." -#: crates/pdfs-gui/src/app/pages/devices.rs:1010 -#: crates/pdfs-gui/src/app/pages/devices.rs:1022 +#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1044 msgid "Couldn't rename the computer" msgstr "Impossible de renommer l’ordinateur" -#: crates/pdfs-gui/src/app/pages/devices.rs:1011 +#: crates/pdfs-gui/src/app/pages/devices.rs:1033 msgid "A name is required." msgstr "Un nom est requis." -#: crates/pdfs-gui/src/app/pages/devices.rs:1021 +#: crates/pdfs-gui/src/app/pages/devices.rs:1043 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:1034 +#: crates/pdfs-gui/src/app/pages/devices.rs:1056 #, rust-format msgid "Type “{name}” to confirm" msgstr "Saisissez « {name} » pour confirmer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1039 +#: crates/pdfs-gui/src/app/pages/devices.rs:1061 msgid "Remove Computer?" msgstr "Retirer l’ordinateur ?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1042 +#: crates/pdfs-gui/src/app/pages/devices.rs:1064 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2514,26 +2538,26 @@ 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:1048 +#: crates/pdfs-gui/src/app/pages/devices.rs:1070 #: crates/pdfs-gui/src/app/pages/shared.rs:1025 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:634 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:876 msgid "Remove" msgstr "Retirer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1065 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 msgid "Computer removed" msgstr "Ordinateur retiré" -#: crates/pdfs-gui/src/app/pages/devices.rs:1066 +#: crates/pdfs-gui/src/app/pages/devices.rs:1088 msgid "Couldn't remove the computer" msgstr "Impossible de retirer l’ordinateur" -#: crates/pdfs-gui/src/app/pages/devices.rs:1084 +#: crates/pdfs-gui/src/app/pages/devices.rs:1106 msgid "Continue This Backup Here?" msgstr "Continuer cette sauvegarde ici ?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1087 +#: crates/pdfs-gui/src/app/pages/devices.rs:1109 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2552,25 +2576,25 @@ 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:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1114 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:1099 +#: crates/pdfs-gui/src/app/pages/devices.rs:1121 #, rust-format msgid "Now backing up as “{name}”" msgstr "Sauvegarde désormais en tant que « {name} »" -#: crates/pdfs-gui/src/app/pages/devices.rs:1108 +#: crates/pdfs-gui/src/app/pages/devices.rs:1130 msgid "Couldn't continue that backup here" msgstr "Impossible de continuer cette sauvegarde ici" -#: crates/pdfs-gui/src/app/pages/devices.rs:1124 +#: crates/pdfs-gui/src/app/pages/devices.rs:1146 msgid "Sync With This Computer?" msgstr "Synchroniser avec cet ordinateur ?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#: crates/pdfs-gui/src/app/pages/devices.rs:1149 #, rust-format msgid "" "This computer and “{name}” keep the same folders in step. A change or a " @@ -2587,25 +2611,25 @@ msgstr "" "dans Proton Drive. Les dossiers déjà synchronisés ici restent où ils sont. " "Vous choisissez ensuite lesquels de ses dossiers synchroniser ici." -#: crates/pdfs-gui/src/app/pages/devices.rs:1132 +#: crates/pdfs-gui/src/app/pages/devices.rs:1154 msgid "Sync Here" msgstr "Synchroniser ici" #. Translators: {name} is the name of the other computer this one now syncs with. -#: crates/pdfs-gui/src/app/pages/devices.rs:1139 +#: crates/pdfs-gui/src/app/pages/devices.rs:1161 #, rust-format msgid "Now syncing with “{name}”" msgstr "Synchronisé désormais avec « {name} »" -#: crates/pdfs-gui/src/app/pages/devices.rs:1155 +#: crates/pdfs-gui/src/app/pages/devices.rs:1177 msgid "Couldn't sync with that computer" msgstr "Impossible de synchroniser avec cet ordinateur" -#: crates/pdfs-gui/src/app/pages/devices.rs:1187 +#: crates/pdfs-gui/src/app/pages/devices.rs:1209 msgid "Move Backup to This Computer?" msgstr "Déplacer la sauvegarde vers cet ordinateur ?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1190 +#: crates/pdfs-gui/src/app/pages/devices.rs:1212 #, rust-format msgid "" "The folders backed up by “{name}” move into this computer's backup in Proton " @@ -2622,12 +2646,12 @@ msgstr "" "disparaître." #. Translators: {name} is the name of the computer whose folders moved to this one. -#: crates/pdfs-gui/src/app/pages/devices.rs:1202 +#: crates/pdfs-gui/src/app/pages/devices.rs:1224 #, rust-format msgid "Moved the backup of “{name}”" msgstr "Sauvegarde de « {name} » déplacée" -#: crates/pdfs-gui/src/app/pages/devices.rs:1216 +#: crates/pdfs-gui/src/app/pages/devices.rs:1238 msgid "Couldn't move that backup" msgstr "Impossible de déplacer cette sauvegarde" @@ -2908,7 +2932,7 @@ msgid "Sync paused until you resume" msgstr "Synchronisation en pause jusqu’à la reprise" #: crates/pdfs-gui/src/app/pages/locations.rs:570 -#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:290 +#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:308 msgid "Sync paused" msgstr "Synchronisation en pause" @@ -3374,7 +3398,7 @@ msgid "paused" msgstr "en pause" #. Translators: a folder's files can't be opened right now. -#: crates/pdfs-gui/src/app/pages/locations.rs:1742 +#: crates/pdfs-gui/src/app/pages/locations.rs:1745 msgid "not available" msgstr "non disponible" @@ -5138,7 +5162,7 @@ msgstr "Reprise {time}" msgid "Until you resume" msgstr "Jusqu’à la reprise" -#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:292 +#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:310 #, rust-format msgid "{n} change needs attention" msgid_plural "{n} changes need attention" @@ -6375,52 +6399,57 @@ msgstr "Proton Drive ne répond pas" msgid "Signed in as {username} — locked" msgstr "Connecté en tant que {username} — verrouillé" -#: crates/pdfs-gui/src/main.rs:200 +#: crates/pdfs-gui/src/main.rs:209 +#, rust-format +msgid "Signed in as {username} — can't open your Drive" +msgstr "Connecté en tant que {username} — impossible d’ouvrir Proton Drive" + +#: crates/pdfs-gui/src/main.rs:218 #, rust-format msgid "Signed in as {username} — not connected" msgstr "Connecté en tant que {username} — non connecté" -#: crates/pdfs-gui/src/main.rs:205 +#: crates/pdfs-gui/src/main.rs:223 msgid "Not signed in" msgstr "Non connecté" #. Translators: {error} is an error message, in English. -#: crates/pdfs-gui/src/main.rs:208 +#: crates/pdfs-gui/src/main.rs:226 #, rust-format msgid "Error: {error}" msgstr "Erreur : {error}" -#: crates/pdfs-gui/src/main.rs:230 +#: crates/pdfs-gui/src/main.rs:248 msgid "Proton Drive sent an unexpected reply" msgstr "Proton Drive a envoyé une réponse inattendue" -#: crates/pdfs-gui/src/main.rs:284 +#: crates/pdfs-gui/src/main.rs:302 msgid "Session expired — sign in again" msgstr "Session expirée — reconnectez-vous" -#: crates/pdfs-gui/src/main.rs:286 +#: crates/pdfs-gui/src/main.rs:304 msgid "Sign-in not saved — unlock your keyring" msgstr "Connexion non enregistrée — déverrouillez votre trousseau" -#: crates/pdfs-gui/src/main.rs:289 +#: crates/pdfs-gui/src/main.rs:307 msgid "Update not finished — restart Proton Drive" msgstr "Mise à jour inachevée — redémarrez Proton Drive" -#: crates/pdfs-gui/src/main.rs:299 +#: crates/pdfs-gui/src/main.rs:317 #, rust-format msgid "Up to date in {mountpoint} ({n} pinned)" msgid_plural "Up to date in {mountpoint} ({n} pinned)" msgstr[0] "À jour dans {mountpoint} ({n} épinglé)" msgstr[1] "À jour dans {mountpoint} ({n} épinglés)" -#: crates/pdfs-gui/src/main.rs:306 +#: crates/pdfs-gui/src/main.rs:324 #, rust-format msgid "Syncing — {queued} ({n} pinned)" msgid_plural "Syncing — {queued} ({n} pinned)" msgstr[0] "Synchronisation — {queued} ({n} épinglé)" msgstr[1] "Synchronisation — {queued} ({n} épinglés)" -#: crates/pdfs-gui/src/main.rs:313 +#: crates/pdfs-gui/src/main.rs:331 #, rust-format msgid "Offline — cached files only ({n} pinned)" msgid_plural "Offline — cached files only ({n} pinned)" @@ -6428,60 +6457,60 @@ msgstr[0] "Hors ligne — fichiers en cache uniquement ({n} épinglé)" msgstr[1] "Hors ligne — fichiers en cache uniquement ({n} épinglés)" #. Translators: {queued} is a queue summary such as "3 uploads queued". -#: crates/pdfs-gui/src/main.rs:319 +#: crates/pdfs-gui/src/main.rs:337 #, rust-format msgid "Offline — {queued}" msgstr "Hors ligne — {queued}" #. Translators: a menu item that opens the list of failing changes. -#: crates/pdfs-gui/src/main.rs:358 +#: crates/pdfs-gui/src/main.rs:376 #, rust-format msgid "{n} issue — View" msgid_plural "{n} issues — View" msgstr[0] "{n} problème — Afficher" msgstr[1] "{n} problèmes — Afficher" -#: crates/pdfs-gui/src/main.rs:465 +#: crates/pdfs-gui/src/main.rs:483 msgid "Sign In…" msgstr "Se connecter…" -#: crates/pdfs-gui/src/main.rs:466 +#: crates/pdfs-gui/src/main.rs:484 msgid "Sign In Again…" msgstr "Se reconnecter…" -#: crates/pdfs-gui/src/main.rs:469 +#: crates/pdfs-gui/src/main.rs:487 msgid "Open Proton Drive" msgstr "Ouvrir Proton Drive" -#: crates/pdfs-gui/src/main.rs:473 +#: crates/pdfs-gui/src/main.rs:491 msgid "Restart Proton Drive" msgstr "Redémarrer Proton Drive" -#: crates/pdfs-gui/src/main.rs:482 +#: crates/pdfs-gui/src/main.rs:500 msgid "Open Folder" msgstr "Ouvrir le dossier" -#: crates/pdfs-gui/src/main.rs:494 +#: crates/pdfs-gui/src/main.rs:512 msgid "Resume Sync" msgstr "Reprendre la synchronisation" -#: crates/pdfs-gui/src/main.rs:496 +#: crates/pdfs-gui/src/main.rs:514 msgid "Pause Sync" msgstr "Mettre en pause la synchronisation" -#: crates/pdfs-gui/src/main.rs:515 +#: crates/pdfs-gui/src/main.rs:533 msgid "Connect" msgstr "Connecter" -#: crates/pdfs-gui/src/main.rs:522 +#: crates/pdfs-gui/src/main.rs:540 msgid "Hide Tray Icon" msgstr "Masquer l’icône de la zone de notification" -#: crates/pdfs-gui/src/main.rs:526 +#: crates/pdfs-gui/src/main.rs:544 msgid "Stop Proton Drive…" msgstr "Arrêter Proton Drive…" -#: crates/pdfs-gui/src/main.rs:530 +#: crates/pdfs-gui/src/main.rs:548 msgid "Quit" msgstr "Quitter" diff --git a/po/it.po b/po/it.po index c035843..b2358de 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-26 22:26+0100\n" +"POT-Creation-Date: 2026-10-31 19:26+0100\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: Italian\n" @@ -245,13 +245,13 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1917 #: crates/pdfs-gui/src/app/pages/browser.rs:2039 #: crates/pdfs-gui/src/app/pages/browser.rs:2540 -#: crates/pdfs-gui/src/app/pages/devices.rs:804 -#: crates/pdfs-gui/src/app/pages/devices.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:995 -#: crates/pdfs-gui/src/app/pages/devices.rs:1047 -#: crates/pdfs-gui/src/app/pages/devices.rs:1091 -#: crates/pdfs-gui/src/app/pages/devices.rs:1131 -#: crates/pdfs-gui/src/app/pages/devices.rs:1194 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 +#: crates/pdfs-gui/src/app/pages/devices.rs:979 +#: crates/pdfs-gui/src/app/pages/devices.rs:1017 +#: crates/pdfs-gui/src/app/pages/devices.rs:1069 +#: crates/pdfs-gui/src/app/pages/devices.rs:1113 +#: crates/pdfs-gui/src/app/pages/devices.rs:1153 +#: crates/pdfs-gui/src/app/pages/devices.rs:1216 #: crates/pdfs-gui/src/app/pages/locations.rs:850 #: crates/pdfs-gui/src/app/pages/locations.rs:1054 #: crates/pdfs-gui/src/app/pages/locations.rs:1537 @@ -346,7 +346,7 @@ msgstr "Griglia / elenco" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1896 #: crates/pdfs-gui/src/app/pages/browser.rs:1918 -#: crates/pdfs-gui/src/app/pages/devices.rs:996 +#: crates/pdfs-gui/src/app/pages/devices.rs:1018 #: crates/pdfs-gui/src/app/widgets/details.rs:161 msgid "Rename" msgstr "Rinomina" @@ -420,14 +420,27 @@ msgstr "Non connesso" msgid "The Proton Drive service isn't running." msgstr "Il servizio di Proton Drive non è in esecuzione." -#: crates/pdfs-gui/src/app/main.rs:1803 +#: crates/pdfs-gui/src/app/main.rs:1813 +msgid "Can't open your Drive" +msgstr "Impossibile aprire Proton Drive" + +#: crates/pdfs-gui/src/app/main.rs:1816 +#, rust-format +msgid "" +"The Proton Drive service keeps failing to start and tries again on its own. " +"The last error was: {error}" +msgstr "" +"Il servizio di Proton Drive non riesce ad avviarsi e riprova da solo. Ultimo " +"errore: {error}" + +#: crates/pdfs-gui/src/app/main.rs:1824 #: crates/pdfs-gui/src/app/pages/status.rs:355 #: crates/pdfs-gui/src/hud/mod.rs:269 crates/pdfs-gui/src/hud/mod.rs:579 -#: crates/pdfs-gui/src/main.rs:475 crates/pdfs-gui/src/main.rs:517 +#: crates/pdfs-gui/src/main.rs:493 crates/pdfs-gui/src/main.rs:535 msgid "Connecting…" msgstr "Connessione…" -#: crates/pdfs-gui/src/app/main.rs:1804 +#: crates/pdfs-gui/src/app/main.rs:1825 msgid "Waiting for the Proton Drive service to start." msgstr "In attesa dell’avvio del servizio di Proton Drive." @@ -678,7 +691,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:839 +#: crates/pdfs-gui/src/app/pages/devices.rs:861 msgid "Couldn't restore folders" msgstr "Impossibile ripristinare le cartelle" @@ -1273,12 +1286,12 @@ msgstr "Impossibile caricare gli album" #: crates/pdfs-gui/src/app/pages/browser.rs:1873 #: crates/pdfs-gui/src/app/pages/browser.rs:2500 #: crates/pdfs-gui/src/app/pages/browser.rs:2650 -#: crates/pdfs-gui/src/app/pages/devices.rs:707 -#: crates/pdfs-gui/src/app/pages/devices.rs:747 -#: crates/pdfs-gui/src/app/pages/devices.rs:900 -#: crates/pdfs-gui/src/app/pages/devices.rs:1170 -#: crates/pdfs-gui/src/app/pages/devices.rs:1231 -#: crates/pdfs-gui/src/app/pages/devices.rs:1260 +#: crates/pdfs-gui/src/app/pages/devices.rs:729 +#: crates/pdfs-gui/src/app/pages/devices.rs:769 +#: crates/pdfs-gui/src/app/pages/devices.rs:922 +#: crates/pdfs-gui/src/app/pages/devices.rs:1192 +#: crates/pdfs-gui/src/app/pages/devices.rs:1253 +#: crates/pdfs-gui/src/app/pages/devices.rs:1282 #: crates/pdfs-gui/src/app/pages/duplicates.rs:221 #: crates/pdfs-gui/src/app/pages/locations.rs:577 #: crates/pdfs-gui/src/app/pages/locations.rs:824 @@ -1927,7 +1940,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1900 -#: crates/pdfs-gui/src/app/pages/devices.rs:989 +#: crates/pdfs-gui/src/app/pages/devices.rs:1011 msgid "New name" msgstr "Nuovo nome" @@ -1952,7 +1965,7 @@ msgstr "Scegli la cartella di destinazione." #: crates/pdfs-gui/src/app/pages/browser.rs:1989 #: crates/pdfs-gui/src/app/pages/browser.rs:1994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1195 +#: crates/pdfs-gui/src/app/pages/devices.rs:1217 msgid "Move Here" msgstr "Sposta qui" @@ -2275,76 +2288,87 @@ 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:608 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 +#, rust-format +msgid "{folders} · {n} waiting for its first sync" +msgid_plural "{folders} · {n} waiting for their first sync" +msgstr[0] "{folders} · {n} attende la prima sincronizzazione" +msgstr[1] "{folders} · {n} attendono la prima sincronizzazione" + +#: crates/pdfs-gui/src/app/pages/devices.rs:621 #, 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:619 +#: crates/pdfs-gui/src/app/pages/devices.rs:640 msgid "syncing…" msgstr "sincronizzazione…" -#: crates/pdfs-gui/src/app/pages/devices.rs:620 +#: crates/pdfs-gui/src/app/pages/devices.rs:641 msgid "sync error" msgstr "errore di sincronizzazione" -#: crates/pdfs-gui/src/app/pages/devices.rs:621 +#: crates/pdfs-gui/src/app/pages/devices.rs:642 msgid "needs attention" msgstr "richiede attenzione" -#: crates/pdfs-gui/src/app/pages/devices.rs:622 +#: crates/pdfs-gui/src/app/pages/devices.rs:643 +msgid "waiting for first sync" +msgstr "in attesa della prima sincronizzazione" + +#: crates/pdfs-gui/src/app/pages/devices.rs:644 msgid "up to date" msgstr "aggiornato" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 msgid "checking for changes…" msgstr "controllo delle modifiche…" -#: crates/pdfs-gui/src/app/pages/devices.rs:638 +#: crates/pdfs-gui/src/app/pages/devices.rs:660 #, rust-format msgid "checking for changes — {n} item checked" msgid_plural "checking for changes — {n} items checked" msgstr[0] "controllo delle modifiche: {n} elemento controllato" msgstr[1] "controllo delle modifiche: {n} elementi controllati" -#: crates/pdfs-gui/src/app/pages/devices.rs:645 +#: crates/pdfs-gui/src/app/pages/devices.rs:667 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "controllo delle modifiche: {done} di {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:657 +#: crates/pdfs-gui/src/app/pages/devices.rs:679 #, rust-format msgid "syncing {done} of {total}" msgstr "sincronizzazione {done} di {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:663 +#: crates/pdfs-gui/src/app/pages/devices.rs:685 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "sincronizzazione di {file}: {done} di {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:675 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Add Folder to Sync" msgstr "Aggiungi cartella da sincronizzare" -#: crates/pdfs-gui/src/app/pages/devices.rs:699 +#: crates/pdfs-gui/src/app/pages/devices.rs:721 #: crates/pdfs-gui/src/app/pages/locations.rs:1559 #: crates/pdfs-gui/src/app/pages/locations.rs:1686 msgid "Syncing folder…" msgstr "Sincronizzazione della cartella…" -#: crates/pdfs-gui/src/app/pages/devices.rs:702 -#: crates/pdfs-gui/src/app/pages/devices.rs:706 +#: crates/pdfs-gui/src/app/pages/devices.rs:724 +#: crates/pdfs-gui/src/app/pages/devices.rs:728 msgid "Couldn't add folder" msgstr "Impossibile aggiungere la cartella" -#: crates/pdfs-gui/src/app/pages/devices.rs:740 -#: crates/pdfs-gui/src/app/pages/devices.rs:746 +#: crates/pdfs-gui/src/app/pages/devices.rs:762 +#: crates/pdfs-gui/src/app/pages/devices.rs:768 msgid "Couldn't list folders to restore" msgstr "Impossibile elencare le cartelle da ripristinare" -#: crates/pdfs-gui/src/app/pages/devices.rs:772 +#: crates/pdfs-gui/src/app/pages/devices.rs:794 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2352,19 +2376,19 @@ msgstr "" "Seleziona le cartelle da sincronizzare su questa macchina e regola dove deve " "trovarsi ciascuna." -#: crates/pdfs-gui/src/app/pages/devices.rs:798 +#: crates/pdfs-gui/src/app/pages/devices.rs:820 msgid "Restore Folders" msgstr "Ripristina cartelle" -#: crates/pdfs-gui/src/app/pages/devices.rs:805 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Restore" msgstr "Ripristina" -#: crates/pdfs-gui/src/app/pages/devices.rs:838 +#: crates/pdfs-gui/src/app/pages/devices.rs:860 msgid "Restoring folders…" msgstr "Ripristino delle cartelle…" -#: crates/pdfs-gui/src/app/pages/devices.rs:850 +#: crates/pdfs-gui/src/app/pages/devices.rs:872 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2373,39 +2397,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:853 +#: crates/pdfs-gui/src/app/pages/devices.rs:875 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:862 +#: crates/pdfs-gui/src/app/pages/devices.rs:884 #, 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:865 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 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:889 +#: crates/pdfs-gui/src/app/pages/devices.rs:911 msgid "Folder will switch to online only" msgstr "La cartella passerà a solo online" -#: crates/pdfs-gui/src/app/pages/devices.rs:891 +#: crates/pdfs-gui/src/app/pages/devices.rs:913 msgid "Folder will download and stay synced" msgstr "La cartella verrà scaricata e resterà sincronizzata" -#: crates/pdfs-gui/src/app/pages/devices.rs:895 -#: crates/pdfs-gui/src/app/pages/devices.rs:899 +#: crates/pdfs-gui/src/app/pages/devices.rs:917 +#: crates/pdfs-gui/src/app/pages/devices.rs:921 msgid "Couldn't change mode" msgstr "Impossibile cambiare modalità" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:953 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2423,7 +2447,7 @@ msgstr "" "cartella a Sincronizzata e attendi il completamento del download prima di " "rimuoverla." -#: crates/pdfs-gui/src/app/pages/devices.rs:937 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2436,68 +2460,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:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:964 msgid "Stop Syncing Folder?" msgstr "Interrompere la sincronizzazione della cartella?" -#: crates/pdfs-gui/src/app/pages/devices.rs:947 +#: crates/pdfs-gui/src/app/pages/devices.rs:969 msgid "Also delete from Proton Drive" msgstr "Elimina anche da Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:949 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Deletes the only copy of these files." msgstr "Elimina l'unica copia di questi file." -#: crates/pdfs-gui/src/app/pages/devices.rs:951 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 msgid "The local copy is unaffected." msgstr "La copia locale non viene modificata." -#: crates/pdfs-gui/src/app/pages/devices.rs:958 +#: crates/pdfs-gui/src/app/pages/devices.rs:980 msgid "Stop Syncing" msgstr "Interrompi sincronizzazione" -#: crates/pdfs-gui/src/app/pages/devices.rs:971 +#: crates/pdfs-gui/src/app/pages/devices.rs:993 msgid "Stopped syncing folder" msgstr "Sincronizzazione della cartella interrotta" -#: crates/pdfs-gui/src/app/pages/devices.rs:972 +#: crates/pdfs-gui/src/app/pages/devices.rs:994 msgid "Couldn't stop syncing the folder" msgstr "Impossibile interrompere la sincronizzazione della cartella" -#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1005 msgid "Rename Computer" msgstr "Rinomina computer" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:985 +#: crates/pdfs-gui/src/app/pages/devices.rs:1007 #, rust-format msgid "Rename “{name}”." msgstr "Rinomina “{name}”." -#: crates/pdfs-gui/src/app/pages/devices.rs:1010 -#: crates/pdfs-gui/src/app/pages/devices.rs:1022 +#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1044 msgid "Couldn't rename the computer" msgstr "Impossibile rinominare il computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1011 +#: crates/pdfs-gui/src/app/pages/devices.rs:1033 msgid "A name is required." msgstr "È richiesto un nome." -#: crates/pdfs-gui/src/app/pages/devices.rs:1021 +#: crates/pdfs-gui/src/app/pages/devices.rs:1043 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:1034 +#: crates/pdfs-gui/src/app/pages/devices.rs:1056 #, rust-format msgid "Type “{name}” to confirm" msgstr "Digita “{name}” per confermare" -#: crates/pdfs-gui/src/app/pages/devices.rs:1039 +#: crates/pdfs-gui/src/app/pages/devices.rs:1061 msgid "Remove Computer?" msgstr "Rimuovere il computer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1042 +#: crates/pdfs-gui/src/app/pages/devices.rs:1064 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2512,26 +2536,26 @@ 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:1048 +#: crates/pdfs-gui/src/app/pages/devices.rs:1070 #: crates/pdfs-gui/src/app/pages/shared.rs:1025 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:634 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:876 msgid "Remove" msgstr "Rimuovi" -#: crates/pdfs-gui/src/app/pages/devices.rs:1065 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 msgid "Computer removed" msgstr "Computer rimosso" -#: crates/pdfs-gui/src/app/pages/devices.rs:1066 +#: crates/pdfs-gui/src/app/pages/devices.rs:1088 msgid "Couldn't remove the computer" msgstr "Impossibile rimuovere il computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1084 +#: crates/pdfs-gui/src/app/pages/devices.rs:1106 msgid "Continue This Backup Here?" msgstr "Continuare questo backup qui?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1087 +#: crates/pdfs-gui/src/app/pages/devices.rs:1109 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2550,25 +2574,25 @@ 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:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1114 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:1099 +#: crates/pdfs-gui/src/app/pages/devices.rs:1121 #, rust-format msgid "Now backing up as “{name}”" msgstr "Ora il backup avviene come “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1108 +#: crates/pdfs-gui/src/app/pages/devices.rs:1130 msgid "Couldn't continue that backup here" msgstr "Impossibile continuare quel backup qui" -#: crates/pdfs-gui/src/app/pages/devices.rs:1124 +#: crates/pdfs-gui/src/app/pages/devices.rs:1146 msgid "Sync With This Computer?" msgstr "Sincronizzare con questo computer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#: crates/pdfs-gui/src/app/pages/devices.rs:1149 #, rust-format msgid "" "This computer and “{name}” keep the same folders in step. A change or a " @@ -2585,25 +2609,25 @@ msgstr "" "in Proton Drive. Le cartelle già sincronizzate qui restano dove sono. Poi " "scegli quali delle sue cartelle sincronizzare qui." -#: crates/pdfs-gui/src/app/pages/devices.rs:1132 +#: crates/pdfs-gui/src/app/pages/devices.rs:1154 msgid "Sync Here" msgstr "Sincronizza qui" #. Translators: {name} is the name of the other computer this one now syncs with. -#: crates/pdfs-gui/src/app/pages/devices.rs:1139 +#: crates/pdfs-gui/src/app/pages/devices.rs:1161 #, rust-format msgid "Now syncing with “{name}”" msgstr "Ora sincronizzato con “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1155 +#: crates/pdfs-gui/src/app/pages/devices.rs:1177 msgid "Couldn't sync with that computer" msgstr "Impossibile sincronizzare con quel computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1187 +#: crates/pdfs-gui/src/app/pages/devices.rs:1209 msgid "Move Backup to This Computer?" msgstr "Spostare il backup su questo computer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1190 +#: crates/pdfs-gui/src/app/pages/devices.rs:1212 #, rust-format msgid "" "The folders backed up by “{name}” move into this computer's backup in Proton " @@ -2620,12 +2644,12 @@ msgstr "" "cartelle." #. Translators: {name} is the name of the computer whose folders moved to this one. -#: crates/pdfs-gui/src/app/pages/devices.rs:1202 +#: crates/pdfs-gui/src/app/pages/devices.rs:1224 #, rust-format msgid "Moved the backup of “{name}”" msgstr "Backup di “{name}” spostato" -#: crates/pdfs-gui/src/app/pages/devices.rs:1216 +#: crates/pdfs-gui/src/app/pages/devices.rs:1238 msgid "Couldn't move that backup" msgstr "Impossibile spostare quel backup" @@ -2906,7 +2930,7 @@ msgid "Sync paused until you resume" msgstr "Sincronizzazione in pausa finché non riprendi" #: crates/pdfs-gui/src/app/pages/locations.rs:570 -#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:290 +#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:308 msgid "Sync paused" msgstr "Sincronizzazione in pausa" @@ -3366,7 +3390,7 @@ msgid "paused" msgstr "in pausa" #. Translators: a folder's files can't be opened right now. -#: crates/pdfs-gui/src/app/pages/locations.rs:1742 +#: crates/pdfs-gui/src/app/pages/locations.rs:1745 msgid "not available" msgstr "non disponibile" @@ -5114,7 +5138,7 @@ msgstr "Riprende {time}" msgid "Until you resume" msgstr "Finché non riprendi" -#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:292 +#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:310 #, rust-format msgid "{n} change needs attention" msgid_plural "{n} changes need attention" @@ -6351,52 +6375,57 @@ msgstr "Proton Drive non risponde" msgid "Signed in as {username} — locked" msgstr "Accesso effettuato come {username} — bloccato" -#: crates/pdfs-gui/src/main.rs:200 +#: crates/pdfs-gui/src/main.rs:209 +#, rust-format +msgid "Signed in as {username} — can't open your Drive" +msgstr "Accesso effettuato come {username} — impossibile aprire Proton Drive" + +#: crates/pdfs-gui/src/main.rs:218 #, rust-format msgid "Signed in as {username} — not connected" msgstr "Accesso effettuato come {username} — non connesso" -#: crates/pdfs-gui/src/main.rs:205 +#: crates/pdfs-gui/src/main.rs:223 msgid "Not signed in" msgstr "Accesso non eseguito" #. Translators: {error} is an error message, in English. -#: crates/pdfs-gui/src/main.rs:208 +#: crates/pdfs-gui/src/main.rs:226 #, rust-format msgid "Error: {error}" msgstr "Errore: {error}" -#: crates/pdfs-gui/src/main.rs:230 +#: crates/pdfs-gui/src/main.rs:248 msgid "Proton Drive sent an unexpected reply" msgstr "Proton Drive ha inviato una risposta inattesa" -#: crates/pdfs-gui/src/main.rs:284 +#: crates/pdfs-gui/src/main.rs:302 msgid "Session expired — sign in again" msgstr "Sessione scaduta: accedi di nuovo" -#: crates/pdfs-gui/src/main.rs:286 +#: crates/pdfs-gui/src/main.rs:304 msgid "Sign-in not saved — unlock your keyring" msgstr "Accesso non salvato: sblocca il portachiavi" -#: crates/pdfs-gui/src/main.rs:289 +#: crates/pdfs-gui/src/main.rs:307 msgid "Update not finished — restart Proton Drive" msgstr "Aggiornamento non completato: riavvia Proton Drive" -#: crates/pdfs-gui/src/main.rs:299 +#: crates/pdfs-gui/src/main.rs:317 #, rust-format msgid "Up to date in {mountpoint} ({n} pinned)" msgid_plural "Up to date in {mountpoint} ({n} pinned)" msgstr[0] "Aggiornato in {mountpoint} ({n} fissato)" msgstr[1] "Aggiornato in {mountpoint} ({n} fissati)" -#: crates/pdfs-gui/src/main.rs:306 +#: crates/pdfs-gui/src/main.rs:324 #, rust-format msgid "Syncing — {queued} ({n} pinned)" msgid_plural "Syncing — {queued} ({n} pinned)" msgstr[0] "Sincronizzazione: {queued} ({n} fissato)" msgstr[1] "Sincronizzazione: {queued} ({n} fissati)" -#: crates/pdfs-gui/src/main.rs:313 +#: crates/pdfs-gui/src/main.rs:331 #, rust-format msgid "Offline — cached files only ({n} pinned)" msgid_plural "Offline — cached files only ({n} pinned)" @@ -6404,60 +6433,60 @@ msgstr[0] "Offline: solo file in cache ({n} fissato)" msgstr[1] "Offline: solo file in cache ({n} fissati)" #. Translators: {queued} is a queue summary such as "3 uploads queued". -#: crates/pdfs-gui/src/main.rs:319 +#: crates/pdfs-gui/src/main.rs:337 #, rust-format msgid "Offline — {queued}" msgstr "Offline: {queued}" #. Translators: a menu item that opens the list of failing changes. -#: crates/pdfs-gui/src/main.rs:358 +#: crates/pdfs-gui/src/main.rs:376 #, rust-format msgid "{n} issue — View" msgid_plural "{n} issues — View" msgstr[0] "{n} problema: visualizza" msgstr[1] "{n} problemi: visualizza" -#: crates/pdfs-gui/src/main.rs:465 +#: crates/pdfs-gui/src/main.rs:483 msgid "Sign In…" msgstr "Accedi…" -#: crates/pdfs-gui/src/main.rs:466 +#: crates/pdfs-gui/src/main.rs:484 msgid "Sign In Again…" msgstr "Accedi di nuovo…" -#: crates/pdfs-gui/src/main.rs:469 +#: crates/pdfs-gui/src/main.rs:487 msgid "Open Proton Drive" msgstr "Apri Proton Drive" -#: crates/pdfs-gui/src/main.rs:473 +#: crates/pdfs-gui/src/main.rs:491 msgid "Restart Proton Drive" msgstr "Riavvia Proton Drive" -#: crates/pdfs-gui/src/main.rs:482 +#: crates/pdfs-gui/src/main.rs:500 msgid "Open Folder" msgstr "Apri cartella" -#: crates/pdfs-gui/src/main.rs:494 +#: crates/pdfs-gui/src/main.rs:512 msgid "Resume Sync" msgstr "Riprendi sincronizzazione" -#: crates/pdfs-gui/src/main.rs:496 +#: crates/pdfs-gui/src/main.rs:514 msgid "Pause Sync" msgstr "Metti in pausa sincronizzazione" -#: crates/pdfs-gui/src/main.rs:515 +#: crates/pdfs-gui/src/main.rs:533 msgid "Connect" msgstr "Connetti" -#: crates/pdfs-gui/src/main.rs:522 +#: crates/pdfs-gui/src/main.rs:540 msgid "Hide Tray Icon" msgstr "Nascondi icona nell'area di notifica" -#: crates/pdfs-gui/src/main.rs:526 +#: crates/pdfs-gui/src/main.rs:544 msgid "Stop Proton Drive…" msgstr "Arresta Proton Drive…" -#: crates/pdfs-gui/src/main.rs:530 +#: crates/pdfs-gui/src/main.rs:548 msgid "Quit" msgstr "Esci" diff --git a/po/nl.po b/po/nl.po index 1de0e6f..b572ca8 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-26 22:26+0100\n" +"POT-Creation-Date: 2026-10-31 19:26+0100\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: Dutch\n" @@ -246,13 +246,13 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1917 #: crates/pdfs-gui/src/app/pages/browser.rs:2039 #: crates/pdfs-gui/src/app/pages/browser.rs:2540 -#: crates/pdfs-gui/src/app/pages/devices.rs:804 -#: crates/pdfs-gui/src/app/pages/devices.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:995 -#: crates/pdfs-gui/src/app/pages/devices.rs:1047 -#: crates/pdfs-gui/src/app/pages/devices.rs:1091 -#: crates/pdfs-gui/src/app/pages/devices.rs:1131 -#: crates/pdfs-gui/src/app/pages/devices.rs:1194 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 +#: crates/pdfs-gui/src/app/pages/devices.rs:979 +#: crates/pdfs-gui/src/app/pages/devices.rs:1017 +#: crates/pdfs-gui/src/app/pages/devices.rs:1069 +#: crates/pdfs-gui/src/app/pages/devices.rs:1113 +#: crates/pdfs-gui/src/app/pages/devices.rs:1153 +#: crates/pdfs-gui/src/app/pages/devices.rs:1216 #: crates/pdfs-gui/src/app/pages/locations.rs:850 #: crates/pdfs-gui/src/app/pages/locations.rs:1054 #: crates/pdfs-gui/src/app/pages/locations.rs:1537 @@ -347,7 +347,7 @@ msgstr "Raster / lijst" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1896 #: crates/pdfs-gui/src/app/pages/browser.rs:1918 -#: crates/pdfs-gui/src/app/pages/devices.rs:996 +#: crates/pdfs-gui/src/app/pages/devices.rs:1018 #: crates/pdfs-gui/src/app/widgets/details.rs:161 msgid "Rename" msgstr "Hernoemen" @@ -421,14 +421,27 @@ msgstr "Niet verbonden" msgid "The Proton Drive service isn't running." msgstr "De Proton Drive-service draait niet." -#: crates/pdfs-gui/src/app/main.rs:1803 +#: crates/pdfs-gui/src/app/main.rs:1813 +msgid "Can't open your Drive" +msgstr "Kan Proton Drive niet openen" + +#: crates/pdfs-gui/src/app/main.rs:1816 +#, rust-format +msgid "" +"The Proton Drive service keeps failing to start and tries again on its own. " +"The last error was: {error}" +msgstr "" +"De Proton Drive-service kan steeds niet starten en probeert het zelf " +"opnieuw. Laatste fout: {error}" + +#: crates/pdfs-gui/src/app/main.rs:1824 #: crates/pdfs-gui/src/app/pages/status.rs:355 #: crates/pdfs-gui/src/hud/mod.rs:269 crates/pdfs-gui/src/hud/mod.rs:579 -#: crates/pdfs-gui/src/main.rs:475 crates/pdfs-gui/src/main.rs:517 +#: crates/pdfs-gui/src/main.rs:493 crates/pdfs-gui/src/main.rs:535 msgid "Connecting…" msgstr "Verbinden…" -#: crates/pdfs-gui/src/app/main.rs:1804 +#: crates/pdfs-gui/src/app/main.rs:1825 msgid "Waiting for the Proton Drive service to start." msgstr "Wachten tot de Proton Drive-service is gestart." @@ -679,7 +692,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:839 +#: crates/pdfs-gui/src/app/pages/devices.rs:861 msgid "Couldn't restore folders" msgstr "Kon mappen niet terugzetten" @@ -1269,12 +1282,12 @@ msgstr "Kon albums niet laden" #: crates/pdfs-gui/src/app/pages/browser.rs:1873 #: crates/pdfs-gui/src/app/pages/browser.rs:2500 #: crates/pdfs-gui/src/app/pages/browser.rs:2650 -#: crates/pdfs-gui/src/app/pages/devices.rs:707 -#: crates/pdfs-gui/src/app/pages/devices.rs:747 -#: crates/pdfs-gui/src/app/pages/devices.rs:900 -#: crates/pdfs-gui/src/app/pages/devices.rs:1170 -#: crates/pdfs-gui/src/app/pages/devices.rs:1231 -#: crates/pdfs-gui/src/app/pages/devices.rs:1260 +#: crates/pdfs-gui/src/app/pages/devices.rs:729 +#: crates/pdfs-gui/src/app/pages/devices.rs:769 +#: crates/pdfs-gui/src/app/pages/devices.rs:922 +#: crates/pdfs-gui/src/app/pages/devices.rs:1192 +#: crates/pdfs-gui/src/app/pages/devices.rs:1253 +#: crates/pdfs-gui/src/app/pages/devices.rs:1282 #: crates/pdfs-gui/src/app/pages/duplicates.rs:221 #: crates/pdfs-gui/src/app/pages/locations.rs:577 #: crates/pdfs-gui/src/app/pages/locations.rs:824 @@ -1921,7 +1934,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1900 -#: crates/pdfs-gui/src/app/pages/devices.rs:989 +#: crates/pdfs-gui/src/app/pages/devices.rs:1011 msgid "New name" msgstr "Nieuwe naam" @@ -1946,7 +1959,7 @@ msgstr "Kies de map waarnaar je wilt verplaatsen." #: crates/pdfs-gui/src/app/pages/browser.rs:1989 #: crates/pdfs-gui/src/app/pages/browser.rs:1994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1195 +#: crates/pdfs-gui/src/app/pages/devices.rs:1217 msgid "Move Here" msgstr "Hierheen verplaatsen" @@ -2269,76 +2282,87 @@ 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:608 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 +#, rust-format +msgid "{folders} · {n} waiting for its first sync" +msgid_plural "{folders} · {n} waiting for their first sync" +msgstr[0] "{folders} · {n} wacht op de eerste synchronisatie" +msgstr[1] "{folders} · {n} wachten op de eerste synchronisatie" + +#: crates/pdfs-gui/src/app/pages/devices.rs:621 #, 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:619 +#: crates/pdfs-gui/src/app/pages/devices.rs:640 msgid "syncing…" msgstr "synchroniseren…" -#: crates/pdfs-gui/src/app/pages/devices.rs:620 +#: crates/pdfs-gui/src/app/pages/devices.rs:641 msgid "sync error" msgstr "synchronisatiefout" -#: crates/pdfs-gui/src/app/pages/devices.rs:621 +#: crates/pdfs-gui/src/app/pages/devices.rs:642 msgid "needs attention" msgstr "vereist aandacht" -#: crates/pdfs-gui/src/app/pages/devices.rs:622 +#: crates/pdfs-gui/src/app/pages/devices.rs:643 +msgid "waiting for first sync" +msgstr "wacht op eerste synchronisatie" + +#: crates/pdfs-gui/src/app/pages/devices.rs:644 msgid "up to date" msgstr "bijgewerkt" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 msgid "checking for changes…" msgstr "controleren op wijzigingen…" -#: crates/pdfs-gui/src/app/pages/devices.rs:638 +#: crates/pdfs-gui/src/app/pages/devices.rs:660 #, rust-format msgid "checking for changes — {n} item checked" msgid_plural "checking for changes — {n} items checked" msgstr[0] "controleren op wijzigingen — {n} item gecontroleerd" msgstr[1] "controleren op wijzigingen — {n} items gecontroleerd" -#: crates/pdfs-gui/src/app/pages/devices.rs:645 +#: crates/pdfs-gui/src/app/pages/devices.rs:667 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "controleren op wijzigingen — {done} van {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:657 +#: crates/pdfs-gui/src/app/pages/devices.rs:679 #, rust-format msgid "syncing {done} of {total}" msgstr "synchroniseren: {done} van {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:663 +#: crates/pdfs-gui/src/app/pages/devices.rs:685 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "{file} synchroniseren — {done} van {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:675 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Add Folder to Sync" msgstr "Map toevoegen aan synchronisatie" -#: crates/pdfs-gui/src/app/pages/devices.rs:699 +#: crates/pdfs-gui/src/app/pages/devices.rs:721 #: crates/pdfs-gui/src/app/pages/locations.rs:1559 #: crates/pdfs-gui/src/app/pages/locations.rs:1686 msgid "Syncing folder…" msgstr "Map synchroniseren…" -#: crates/pdfs-gui/src/app/pages/devices.rs:702 -#: crates/pdfs-gui/src/app/pages/devices.rs:706 +#: crates/pdfs-gui/src/app/pages/devices.rs:724 +#: crates/pdfs-gui/src/app/pages/devices.rs:728 msgid "Couldn't add folder" msgstr "Kon map niet toevoegen" -#: crates/pdfs-gui/src/app/pages/devices.rs:740 -#: crates/pdfs-gui/src/app/pages/devices.rs:746 +#: crates/pdfs-gui/src/app/pages/devices.rs:762 +#: crates/pdfs-gui/src/app/pages/devices.rs:768 msgid "Couldn't list folders to restore" msgstr "Kon mappen om terug te zetten niet weergeven" -#: crates/pdfs-gui/src/app/pages/devices.rs:772 +#: crates/pdfs-gui/src/app/pages/devices.rs:794 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2346,19 +2370,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:798 +#: crates/pdfs-gui/src/app/pages/devices.rs:820 msgid "Restore Folders" msgstr "Mappen herstellen" -#: crates/pdfs-gui/src/app/pages/devices.rs:805 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Restore" msgstr "Terugzetten" -#: crates/pdfs-gui/src/app/pages/devices.rs:838 +#: crates/pdfs-gui/src/app/pages/devices.rs:860 msgid "Restoring folders…" msgstr "Mappen terugzetten…" -#: crates/pdfs-gui/src/app/pages/devices.rs:850 +#: crates/pdfs-gui/src/app/pages/devices.rs:872 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2367,38 +2391,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:853 +#: crates/pdfs-gui/src/app/pages/devices.rs:875 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:862 +#: crates/pdfs-gui/src/app/pages/devices.rs:884 #, 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:865 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 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:889 +#: crates/pdfs-gui/src/app/pages/devices.rs:911 msgid "Folder will switch to online only" msgstr "Map wordt alleen online" -#: crates/pdfs-gui/src/app/pages/devices.rs:891 +#: crates/pdfs-gui/src/app/pages/devices.rs:913 msgid "Folder will download and stay synced" msgstr "Map wordt gedownload en blijft gesynchroniseerd" -#: crates/pdfs-gui/src/app/pages/devices.rs:895 -#: crates/pdfs-gui/src/app/pages/devices.rs:899 +#: crates/pdfs-gui/src/app/pages/devices.rs:917 +#: crates/pdfs-gui/src/app/pages/devices.rs:921 msgid "Couldn't change mode" msgstr "Kon modus niet wijzigen" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:953 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2415,7 +2439,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:937 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2428,68 +2452,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:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:964 msgid "Stop Syncing Folder?" msgstr "Synchroniseren van map stoppen?" -#: crates/pdfs-gui/src/app/pages/devices.rs:947 +#: crates/pdfs-gui/src/app/pages/devices.rs:969 msgid "Also delete from Proton Drive" msgstr "Ook uit Proton Drive verwijderen" -#: crates/pdfs-gui/src/app/pages/devices.rs:949 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Deletes the only copy of these files." msgstr "Verwijdert de enige kopie van deze bestanden." -#: crates/pdfs-gui/src/app/pages/devices.rs:951 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 msgid "The local copy is unaffected." msgstr "De lokale kopie blijft ongewijzigd." -#: crates/pdfs-gui/src/app/pages/devices.rs:958 +#: crates/pdfs-gui/src/app/pages/devices.rs:980 msgid "Stop Syncing" msgstr "Synchroniseren stoppen" -#: crates/pdfs-gui/src/app/pages/devices.rs:971 +#: crates/pdfs-gui/src/app/pages/devices.rs:993 msgid "Stopped syncing folder" msgstr "Synchroniseren van map gestopt" -#: crates/pdfs-gui/src/app/pages/devices.rs:972 +#: crates/pdfs-gui/src/app/pages/devices.rs:994 msgid "Couldn't stop syncing the folder" msgstr "Kon het synchroniseren van de map niet stoppen" -#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1005 msgid "Rename Computer" msgstr "Computer hernoemen" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:985 +#: crates/pdfs-gui/src/app/pages/devices.rs:1007 #, rust-format msgid "Rename “{name}”." msgstr "“{name}” hernoemen." -#: crates/pdfs-gui/src/app/pages/devices.rs:1010 -#: crates/pdfs-gui/src/app/pages/devices.rs:1022 +#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1044 msgid "Couldn't rename the computer" msgstr "Kon de computer niet hernoemen" -#: crates/pdfs-gui/src/app/pages/devices.rs:1011 +#: crates/pdfs-gui/src/app/pages/devices.rs:1033 msgid "A name is required." msgstr "Een naam is vereist." -#: crates/pdfs-gui/src/app/pages/devices.rs:1021 +#: crates/pdfs-gui/src/app/pages/devices.rs:1043 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:1034 +#: crates/pdfs-gui/src/app/pages/devices.rs:1056 #, rust-format msgid "Type “{name}” to confirm" msgstr "Typ “{name}” om te bevestigen" -#: crates/pdfs-gui/src/app/pages/devices.rs:1039 +#: crates/pdfs-gui/src/app/pages/devices.rs:1061 msgid "Remove Computer?" msgstr "Computer verwijderen?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1042 +#: crates/pdfs-gui/src/app/pages/devices.rs:1064 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2504,26 +2528,26 @@ 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:1048 +#: crates/pdfs-gui/src/app/pages/devices.rs:1070 #: crates/pdfs-gui/src/app/pages/shared.rs:1025 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:634 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:876 msgid "Remove" msgstr "Verwijderen" -#: crates/pdfs-gui/src/app/pages/devices.rs:1065 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 msgid "Computer removed" msgstr "Computer verwijderd" -#: crates/pdfs-gui/src/app/pages/devices.rs:1066 +#: crates/pdfs-gui/src/app/pages/devices.rs:1088 msgid "Couldn't remove the computer" msgstr "Kon de computer niet verwijderen" -#: crates/pdfs-gui/src/app/pages/devices.rs:1084 +#: crates/pdfs-gui/src/app/pages/devices.rs:1106 msgid "Continue This Backup Here?" msgstr "Deze back-up hier voortzetten?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1087 +#: crates/pdfs-gui/src/app/pages/devices.rs:1109 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2542,25 +2566,25 @@ 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:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1114 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:1099 +#: crates/pdfs-gui/src/app/pages/devices.rs:1121 #, rust-format msgid "Now backing up as “{name}”" msgstr "Back-up nu als “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1108 +#: crates/pdfs-gui/src/app/pages/devices.rs:1130 msgid "Couldn't continue that backup here" msgstr "Kon die back-up hier niet voortzetten" -#: crates/pdfs-gui/src/app/pages/devices.rs:1124 +#: crates/pdfs-gui/src/app/pages/devices.rs:1146 msgid "Sync With This Computer?" msgstr "Synchroniseren met deze computer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#: crates/pdfs-gui/src/app/pages/devices.rs:1149 #, rust-format msgid "" "This computer and “{name}” keep the same folders in step. A change or a " @@ -2577,25 +2601,25 @@ msgstr "" "Proton Drive. Mappen die hier al worden gesynchroniseerd, blijven waar ze " "zijn. Daarna kiest u welke van zijn mappen u hier synchroniseert." -#: crates/pdfs-gui/src/app/pages/devices.rs:1132 +#: crates/pdfs-gui/src/app/pages/devices.rs:1154 msgid "Sync Here" msgstr "Hier synchroniseren" #. Translators: {name} is the name of the other computer this one now syncs with. -#: crates/pdfs-gui/src/app/pages/devices.rs:1139 +#: crates/pdfs-gui/src/app/pages/devices.rs:1161 #, rust-format msgid "Now syncing with “{name}”" msgstr "Nu gesynchroniseerd met “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1155 +#: crates/pdfs-gui/src/app/pages/devices.rs:1177 msgid "Couldn't sync with that computer" msgstr "Kon niet synchroniseren met die computer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1187 +#: crates/pdfs-gui/src/app/pages/devices.rs:1209 msgid "Move Backup to This Computer?" msgstr "Back-up naar deze computer verplaatsen?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1190 +#: crates/pdfs-gui/src/app/pages/devices.rs:1212 #, rust-format msgid "" "The folders backed up by “{name}” move into this computer's backup in Proton " @@ -2611,12 +2635,12 @@ msgstr "" "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:1202 +#: crates/pdfs-gui/src/app/pages/devices.rs:1224 #, rust-format msgid "Moved the backup of “{name}”" msgstr "Back-up van “{name}” verplaatst" -#: crates/pdfs-gui/src/app/pages/devices.rs:1216 +#: crates/pdfs-gui/src/app/pages/devices.rs:1238 msgid "Couldn't move that backup" msgstr "Kon die back-up niet verplaatsen" @@ -2896,7 +2920,7 @@ msgid "Sync paused until you resume" msgstr "Synchronisatie gepauzeerd totdat je hervat" #: crates/pdfs-gui/src/app/pages/locations.rs:570 -#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:290 +#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:308 msgid "Sync paused" msgstr "Synchronisatie gepauzeerd" @@ -3358,7 +3382,7 @@ msgid "paused" msgstr "gepauzeerd" #. Translators: a folder's files can't be opened right now. -#: crates/pdfs-gui/src/app/pages/locations.rs:1742 +#: crates/pdfs-gui/src/app/pages/locations.rs:1745 msgid "not available" msgstr "niet beschikbaar" @@ -5108,7 +5132,7 @@ msgstr "Hervat {time}" msgid "Until you resume" msgstr "Totdat je hervat" -#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:292 +#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:310 #, rust-format msgid "{n} change needs attention" msgid_plural "{n} changes need attention" @@ -6337,52 +6361,57 @@ msgstr "Proton Drive reageert niet" msgid "Signed in as {username} — locked" msgstr "Aangemeld als {username} — vergrendeld" -#: crates/pdfs-gui/src/main.rs:200 +#: crates/pdfs-gui/src/main.rs:209 +#, rust-format +msgid "Signed in as {username} — can't open your Drive" +msgstr "Aangemeld als {username} — kan Proton Drive niet openen" + +#: crates/pdfs-gui/src/main.rs:218 #, rust-format msgid "Signed in as {username} — not connected" msgstr "Aangemeld als {username} — niet verbonden" -#: crates/pdfs-gui/src/main.rs:205 +#: crates/pdfs-gui/src/main.rs:223 msgid "Not signed in" msgstr "Niet aangemeld" #. Translators: {error} is an error message, in English. -#: crates/pdfs-gui/src/main.rs:208 +#: crates/pdfs-gui/src/main.rs:226 #, rust-format msgid "Error: {error}" msgstr "Fout: {error}" -#: crates/pdfs-gui/src/main.rs:230 +#: crates/pdfs-gui/src/main.rs:248 msgid "Proton Drive sent an unexpected reply" msgstr "Proton Drive gaf een onverwacht antwoord" -#: crates/pdfs-gui/src/main.rs:284 +#: crates/pdfs-gui/src/main.rs:302 msgid "Session expired — sign in again" msgstr "Sessie verlopen — meld je opnieuw aan" -#: crates/pdfs-gui/src/main.rs:286 +#: crates/pdfs-gui/src/main.rs:304 msgid "Sign-in not saved — unlock your keyring" msgstr "Aanmelding niet opgeslagen — ontgrendel je sleutelbos" -#: crates/pdfs-gui/src/main.rs:289 +#: crates/pdfs-gui/src/main.rs:307 msgid "Update not finished — restart Proton Drive" msgstr "Update niet voltooid — start Proton Drive opnieuw" -#: crates/pdfs-gui/src/main.rs:299 +#: crates/pdfs-gui/src/main.rs:317 #, rust-format msgid "Up to date in {mountpoint} ({n} pinned)" msgid_plural "Up to date in {mountpoint} ({n} pinned)" msgstr[0] "Bijgewerkt in {mountpoint} ({n} vastgezet)" msgstr[1] "Bijgewerkt in {mountpoint} ({n} vastgezet)" -#: crates/pdfs-gui/src/main.rs:306 +#: crates/pdfs-gui/src/main.rs:324 #, rust-format msgid "Syncing — {queued} ({n} pinned)" msgid_plural "Syncing — {queued} ({n} pinned)" msgstr[0] "Synchroniseren — {queued} ({n} vastgezet)" msgstr[1] "Synchroniseren — {queued} ({n} vastgezet)" -#: crates/pdfs-gui/src/main.rs:313 +#: crates/pdfs-gui/src/main.rs:331 #, rust-format msgid "Offline — cached files only ({n} pinned)" msgid_plural "Offline — cached files only ({n} pinned)" @@ -6390,60 +6419,60 @@ msgstr[0] "Offline — alleen bestanden in cache ({n} vastgezet)" msgstr[1] "Offline — alleen bestanden in cache ({n} vastgezet)" #. Translators: {queued} is a queue summary such as "3 uploads queued". -#: crates/pdfs-gui/src/main.rs:319 +#: crates/pdfs-gui/src/main.rs:337 #, rust-format msgid "Offline — {queued}" msgstr "Offline — {queued}" #. Translators: a menu item that opens the list of failing changes. -#: crates/pdfs-gui/src/main.rs:358 +#: crates/pdfs-gui/src/main.rs:376 #, rust-format msgid "{n} issue — View" msgid_plural "{n} issues — View" msgstr[0] "{n} probleem — Bekijken" msgstr[1] "{n} problemen — Bekijken" -#: crates/pdfs-gui/src/main.rs:465 +#: crates/pdfs-gui/src/main.rs:483 msgid "Sign In…" msgstr "Aanmelden…" -#: crates/pdfs-gui/src/main.rs:466 +#: crates/pdfs-gui/src/main.rs:484 msgid "Sign In Again…" msgstr "Opnieuw aanmelden…" -#: crates/pdfs-gui/src/main.rs:469 +#: crates/pdfs-gui/src/main.rs:487 msgid "Open Proton Drive" msgstr "Proton Drive openen" -#: crates/pdfs-gui/src/main.rs:473 +#: crates/pdfs-gui/src/main.rs:491 msgid "Restart Proton Drive" msgstr "Proton Drive opnieuw starten" -#: crates/pdfs-gui/src/main.rs:482 +#: crates/pdfs-gui/src/main.rs:500 msgid "Open Folder" msgstr "Map openen" -#: crates/pdfs-gui/src/main.rs:494 +#: crates/pdfs-gui/src/main.rs:512 msgid "Resume Sync" msgstr "Synchronisatie hervatten" -#: crates/pdfs-gui/src/main.rs:496 +#: crates/pdfs-gui/src/main.rs:514 msgid "Pause Sync" msgstr "Synchronisatie pauzeren" -#: crates/pdfs-gui/src/main.rs:515 +#: crates/pdfs-gui/src/main.rs:533 msgid "Connect" msgstr "Verbinden" -#: crates/pdfs-gui/src/main.rs:522 +#: crates/pdfs-gui/src/main.rs:540 msgid "Hide Tray Icon" msgstr "Pictogram in systeemvak verbergen" -#: crates/pdfs-gui/src/main.rs:526 +#: crates/pdfs-gui/src/main.rs:544 msgid "Stop Proton Drive…" msgstr "Proton Drive stoppen…" -#: crates/pdfs-gui/src/main.rs:530 +#: crates/pdfs-gui/src/main.rs:548 msgid "Quit" msgstr "Afsluiten" diff --git a/po/pdfs.pot b/po/pdfs.pot index 90860e8..9812f4b 100644 --- a/po/pdfs.pot +++ b/po/pdfs.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: proton-drive-linux 3.6.0\n" "Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n" -"POT-Creation-Date: 2026-10-26 22:28+0100\n" +"POT-Creation-Date: 2026-10-31 19:26+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -222,13 +222,13 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1917 #: crates/pdfs-gui/src/app/pages/browser.rs:2039 #: crates/pdfs-gui/src/app/pages/browser.rs:2540 -#: crates/pdfs-gui/src/app/pages/devices.rs:804 -#: crates/pdfs-gui/src/app/pages/devices.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:995 -#: crates/pdfs-gui/src/app/pages/devices.rs:1047 -#: crates/pdfs-gui/src/app/pages/devices.rs:1091 -#: crates/pdfs-gui/src/app/pages/devices.rs:1131 -#: crates/pdfs-gui/src/app/pages/devices.rs:1194 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 +#: crates/pdfs-gui/src/app/pages/devices.rs:979 +#: crates/pdfs-gui/src/app/pages/devices.rs:1017 +#: crates/pdfs-gui/src/app/pages/devices.rs:1069 +#: crates/pdfs-gui/src/app/pages/devices.rs:1113 +#: crates/pdfs-gui/src/app/pages/devices.rs:1153 +#: crates/pdfs-gui/src/app/pages/devices.rs:1216 #: crates/pdfs-gui/src/app/pages/locations.rs:850 #: crates/pdfs-gui/src/app/pages/locations.rs:1054 #: crates/pdfs-gui/src/app/pages/locations.rs:1537 @@ -323,7 +323,7 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1896 #: crates/pdfs-gui/src/app/pages/browser.rs:1918 -#: crates/pdfs-gui/src/app/pages/devices.rs:996 +#: crates/pdfs-gui/src/app/pages/devices.rs:1018 #: crates/pdfs-gui/src/app/widgets/details.rs:161 msgid "Rename" msgstr "" @@ -397,14 +397,25 @@ msgstr "" msgid "The Proton Drive service isn't running." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1803 +#: crates/pdfs-gui/src/app/main.rs:1813 +msgid "Can't open your Drive" +msgstr "" + +#: crates/pdfs-gui/src/app/main.rs:1816 +#, rust-format +msgid "" +"The Proton Drive service keeps failing to start and tries again on its own. " +"The last error was: {error}" +msgstr "" + +#: crates/pdfs-gui/src/app/main.rs:1824 #: crates/pdfs-gui/src/app/pages/status.rs:355 #: crates/pdfs-gui/src/hud/mod.rs:269 crates/pdfs-gui/src/hud/mod.rs:579 -#: crates/pdfs-gui/src/main.rs:475 crates/pdfs-gui/src/main.rs:517 +#: crates/pdfs-gui/src/main.rs:493 crates/pdfs-gui/src/main.rs:535 msgid "Connecting…" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1804 +#: crates/pdfs-gui/src/app/main.rs:1825 msgid "Waiting for the Proton Drive service to start." msgstr "" @@ -649,7 +660,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:839 +#: crates/pdfs-gui/src/app/pages/devices.rs:861 msgid "Couldn't restore folders" msgstr "" @@ -1227,12 +1238,12 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1873 #: crates/pdfs-gui/src/app/pages/browser.rs:2500 #: crates/pdfs-gui/src/app/pages/browser.rs:2650 -#: crates/pdfs-gui/src/app/pages/devices.rs:707 -#: crates/pdfs-gui/src/app/pages/devices.rs:747 -#: crates/pdfs-gui/src/app/pages/devices.rs:900 -#: crates/pdfs-gui/src/app/pages/devices.rs:1170 -#: crates/pdfs-gui/src/app/pages/devices.rs:1231 -#: crates/pdfs-gui/src/app/pages/devices.rs:1260 +#: crates/pdfs-gui/src/app/pages/devices.rs:729 +#: crates/pdfs-gui/src/app/pages/devices.rs:769 +#: crates/pdfs-gui/src/app/pages/devices.rs:922 +#: crates/pdfs-gui/src/app/pages/devices.rs:1192 +#: crates/pdfs-gui/src/app/pages/devices.rs:1253 +#: crates/pdfs-gui/src/app/pages/devices.rs:1282 #: crates/pdfs-gui/src/app/pages/duplicates.rs:221 #: crates/pdfs-gui/src/app/pages/locations.rs:577 #: crates/pdfs-gui/src/app/pages/locations.rs:824 @@ -1875,7 +1886,7 @@ msgid "{status}. {message}" msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1900 -#: crates/pdfs-gui/src/app/pages/devices.rs:989 +#: crates/pdfs-gui/src/app/pages/devices.rs:1011 msgid "New name" msgstr "" @@ -1900,7 +1911,7 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1989 #: crates/pdfs-gui/src/app/pages/browser.rs:1994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1195 +#: crates/pdfs-gui/src/app/pages/devices.rs:1217 msgid "Move Here" msgstr "" @@ -2221,127 +2232,138 @@ msgid_plural "{n} folders backed up" msgstr[0] "" msgstr[1] "" -#: crates/pdfs-gui/src/app/pages/devices.rs:608 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 +#, rust-format +msgid "{folders} · {n} waiting for its first sync" +msgid_plural "{folders} · {n} waiting for their first sync" +msgstr[0] "" +msgstr[1] "" + +#: crates/pdfs-gui/src/app/pages/devices.rs:621 #, 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:619 +#: crates/pdfs-gui/src/app/pages/devices.rs:640 msgid "syncing…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:620 +#: crates/pdfs-gui/src/app/pages/devices.rs:641 msgid "sync error" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:621 +#: crates/pdfs-gui/src/app/pages/devices.rs:642 msgid "needs attention" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:622 +#: crates/pdfs-gui/src/app/pages/devices.rs:643 +msgid "waiting for first sync" +msgstr "" + +#: crates/pdfs-gui/src/app/pages/devices.rs:644 msgid "up to date" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 msgid "checking for changes…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:638 +#: crates/pdfs-gui/src/app/pages/devices.rs:660 #, rust-format msgid "checking for changes — {n} item checked" msgid_plural "checking for changes — {n} items checked" msgstr[0] "" msgstr[1] "" -#: crates/pdfs-gui/src/app/pages/devices.rs:645 +#: crates/pdfs-gui/src/app/pages/devices.rs:667 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:657 +#: crates/pdfs-gui/src/app/pages/devices.rs:679 #, rust-format msgid "syncing {done} of {total}" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:663 +#: crates/pdfs-gui/src/app/pages/devices.rs:685 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:675 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Add Folder to Sync" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:699 +#: crates/pdfs-gui/src/app/pages/devices.rs:721 #: crates/pdfs-gui/src/app/pages/locations.rs:1559 #: crates/pdfs-gui/src/app/pages/locations.rs:1686 msgid "Syncing folder…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:702 -#: crates/pdfs-gui/src/app/pages/devices.rs:706 +#: crates/pdfs-gui/src/app/pages/devices.rs:724 +#: crates/pdfs-gui/src/app/pages/devices.rs:728 msgid "Couldn't add folder" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:740 -#: crates/pdfs-gui/src/app/pages/devices.rs:746 +#: crates/pdfs-gui/src/app/pages/devices.rs:762 +#: crates/pdfs-gui/src/app/pages/devices.rs:768 msgid "Couldn't list folders to restore" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:772 +#: crates/pdfs-gui/src/app/pages/devices.rs:794 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:798 +#: crates/pdfs-gui/src/app/pages/devices.rs:820 msgid "Restore Folders" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:805 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Restore" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:838 +#: crates/pdfs-gui/src/app/pages/devices.rs:860 msgid "Restoring folders…" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:850 +#: crates/pdfs-gui/src/app/pages/devices.rs:872 #, 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:853 +#: crates/pdfs-gui/src/app/pages/devices.rs:875 msgid "These folders are backed up under this computer in Proton Drive." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:862 +#: crates/pdfs-gui/src/app/pages/devices.rs:884 #, rust-format msgid "Nothing to restore — “{name}”'s folders are all synced here." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:865 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 msgid "Nothing to restore — this computer's folders are all synced here." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:889 +#: crates/pdfs-gui/src/app/pages/devices.rs:911 msgid "Folder will switch to online only" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:891 +#: crates/pdfs-gui/src/app/pages/devices.rs:913 msgid "Folder will download and stay synced" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:895 -#: crates/pdfs-gui/src/app/pages/devices.rs:899 +#: crates/pdfs-gui/src/app/pages/devices.rs:917 +#: crates/pdfs-gui/src/app/pages/devices.rs:921 msgid "Couldn't change mode" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:953 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2352,7 +2374,7 @@ msgid "" "finish before removing it." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:937 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2361,68 +2383,68 @@ msgid "" "whether to also delete the copy in Proton Drive." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:964 msgid "Stop Syncing Folder?" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:947 +#: crates/pdfs-gui/src/app/pages/devices.rs:969 msgid "Also delete from Proton Drive" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:949 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Deletes the only copy of these files." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:951 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 msgid "The local copy is unaffected." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:958 +#: crates/pdfs-gui/src/app/pages/devices.rs:980 msgid "Stop Syncing" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:971 +#: crates/pdfs-gui/src/app/pages/devices.rs:993 msgid "Stopped syncing folder" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:972 +#: crates/pdfs-gui/src/app/pages/devices.rs:994 msgid "Couldn't stop syncing the folder" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1005 msgid "Rename Computer" msgstr "" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:985 +#: crates/pdfs-gui/src/app/pages/devices.rs:1007 #, rust-format msgid "Rename “{name}”." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1010 -#: crates/pdfs-gui/src/app/pages/devices.rs:1022 +#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1044 msgid "Couldn't rename the computer" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1011 +#: crates/pdfs-gui/src/app/pages/devices.rs:1033 msgid "A name is required." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1021 +#: crates/pdfs-gui/src/app/pages/devices.rs:1043 msgid "Computer renamed" msgstr "" #. Translators: {name} is the computer's name, which the user must type. -#: crates/pdfs-gui/src/app/pages/devices.rs:1034 +#: crates/pdfs-gui/src/app/pages/devices.rs:1056 #, rust-format msgid "Type “{name}” to confirm" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1039 +#: crates/pdfs-gui/src/app/pages/devices.rs:1061 msgid "Remove Computer?" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1042 +#: crates/pdfs-gui/src/app/pages/devices.rs:1064 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2432,26 +2454,26 @@ msgid "" "here." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1048 +#: crates/pdfs-gui/src/app/pages/devices.rs:1070 #: crates/pdfs-gui/src/app/pages/shared.rs:1025 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:634 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:876 msgid "Remove" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1065 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 msgid "Computer removed" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1066 +#: crates/pdfs-gui/src/app/pages/devices.rs:1088 msgid "Couldn't remove the computer" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1084 +#: crates/pdfs-gui/src/app/pages/devices.rs:1106 msgid "Continue This Backup Here?" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1087 +#: crates/pdfs-gui/src/app/pages/devices.rs:1109 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2463,25 +2485,25 @@ msgid "" "syncing." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1114 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:1099 +#: crates/pdfs-gui/src/app/pages/devices.rs:1121 #, rust-format msgid "Now backing up as “{name}”" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1108 +#: crates/pdfs-gui/src/app/pages/devices.rs:1130 msgid "Couldn't continue that backup here" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1124 +#: crates/pdfs-gui/src/app/pages/devices.rs:1146 msgid "Sync With This Computer?" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#: crates/pdfs-gui/src/app/pages/devices.rs:1149 #, rust-format msgid "" "This computer and “{name}” keep the same folders in step. A change or a " @@ -2492,25 +2514,25 @@ msgid "" "its folders to sync here." msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1132 +#: crates/pdfs-gui/src/app/pages/devices.rs:1154 msgid "Sync Here" msgstr "" #. Translators: {name} is the name of the other computer this one now syncs with. -#: crates/pdfs-gui/src/app/pages/devices.rs:1139 +#: crates/pdfs-gui/src/app/pages/devices.rs:1161 #, rust-format msgid "Now syncing with “{name}”" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1155 +#: crates/pdfs-gui/src/app/pages/devices.rs:1177 msgid "Couldn't sync with that computer" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1187 +#: crates/pdfs-gui/src/app/pages/devices.rs:1209 msgid "Move Backup to This Computer?" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1190 +#: crates/pdfs-gui/src/app/pages/devices.rs:1212 #, rust-format msgid "" "The folders backed up by “{name}” move into this computer's backup in Proton " @@ -2521,12 +2543,12 @@ msgid "" msgstr "" #. Translators: {name} is the name of the computer whose folders moved to this one. -#: crates/pdfs-gui/src/app/pages/devices.rs:1202 +#: crates/pdfs-gui/src/app/pages/devices.rs:1224 #, rust-format msgid "Moved the backup of “{name}”" msgstr "" -#: crates/pdfs-gui/src/app/pages/devices.rs:1216 +#: crates/pdfs-gui/src/app/pages/devices.rs:1238 msgid "Couldn't move that backup" msgstr "" @@ -2788,7 +2810,7 @@ msgid "Sync paused until you resume" msgstr "" #: crates/pdfs-gui/src/app/pages/locations.rs:570 -#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:290 +#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:308 msgid "Sync paused" msgstr "" @@ -3210,7 +3232,7 @@ msgid "paused" msgstr "" #. Translators: a folder's files can't be opened right now. -#: crates/pdfs-gui/src/app/pages/locations.rs:1742 +#: crates/pdfs-gui/src/app/pages/locations.rs:1745 msgid "not available" msgstr "" @@ -4883,7 +4905,7 @@ msgstr "" msgid "Until you resume" msgstr "" -#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:292 +#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:310 #, rust-format msgid "{n} change needs attention" msgid_plural "{n} changes need attention" @@ -6071,52 +6093,57 @@ msgstr "" msgid "Signed in as {username} — locked" msgstr "" -#: crates/pdfs-gui/src/main.rs:200 +#: crates/pdfs-gui/src/main.rs:209 +#, rust-format +msgid "Signed in as {username} — can't open your Drive" +msgstr "" + +#: crates/pdfs-gui/src/main.rs:218 #, rust-format msgid "Signed in as {username} — not connected" msgstr "" -#: crates/pdfs-gui/src/main.rs:205 +#: crates/pdfs-gui/src/main.rs:223 msgid "Not signed in" msgstr "" #. Translators: {error} is an error message, in English. -#: crates/pdfs-gui/src/main.rs:208 +#: crates/pdfs-gui/src/main.rs:226 #, rust-format msgid "Error: {error}" msgstr "" -#: crates/pdfs-gui/src/main.rs:230 +#: crates/pdfs-gui/src/main.rs:248 msgid "Proton Drive sent an unexpected reply" msgstr "" -#: crates/pdfs-gui/src/main.rs:284 +#: crates/pdfs-gui/src/main.rs:302 msgid "Session expired — sign in again" msgstr "" -#: crates/pdfs-gui/src/main.rs:286 +#: crates/pdfs-gui/src/main.rs:304 msgid "Sign-in not saved — unlock your keyring" msgstr "" -#: crates/pdfs-gui/src/main.rs:289 +#: crates/pdfs-gui/src/main.rs:307 msgid "Update not finished — restart Proton Drive" msgstr "" -#: crates/pdfs-gui/src/main.rs:299 +#: crates/pdfs-gui/src/main.rs:317 #, rust-format msgid "Up to date in {mountpoint} ({n} pinned)" msgid_plural "Up to date in {mountpoint} ({n} pinned)" msgstr[0] "" msgstr[1] "" -#: crates/pdfs-gui/src/main.rs:306 +#: crates/pdfs-gui/src/main.rs:324 #, rust-format msgid "Syncing — {queued} ({n} pinned)" msgid_plural "Syncing — {queued} ({n} pinned)" msgstr[0] "" msgstr[1] "" -#: crates/pdfs-gui/src/main.rs:313 +#: crates/pdfs-gui/src/main.rs:331 #, rust-format msgid "Offline — cached files only ({n} pinned)" msgid_plural "Offline — cached files only ({n} pinned)" @@ -6124,60 +6151,60 @@ msgstr[0] "" msgstr[1] "" #. Translators: {queued} is a queue summary such as "3 uploads queued". -#: crates/pdfs-gui/src/main.rs:319 +#: crates/pdfs-gui/src/main.rs:337 #, rust-format msgid "Offline — {queued}" msgstr "" #. Translators: a menu item that opens the list of failing changes. -#: crates/pdfs-gui/src/main.rs:358 +#: crates/pdfs-gui/src/main.rs:376 #, rust-format msgid "{n} issue — View" msgid_plural "{n} issues — View" msgstr[0] "" msgstr[1] "" -#: crates/pdfs-gui/src/main.rs:465 +#: crates/pdfs-gui/src/main.rs:483 msgid "Sign In…" msgstr "" -#: crates/pdfs-gui/src/main.rs:466 +#: crates/pdfs-gui/src/main.rs:484 msgid "Sign In Again…" msgstr "" -#: crates/pdfs-gui/src/main.rs:469 +#: crates/pdfs-gui/src/main.rs:487 msgid "Open Proton Drive" msgstr "" -#: crates/pdfs-gui/src/main.rs:473 +#: crates/pdfs-gui/src/main.rs:491 msgid "Restart Proton Drive" msgstr "" -#: crates/pdfs-gui/src/main.rs:482 +#: crates/pdfs-gui/src/main.rs:500 msgid "Open Folder" msgstr "" -#: crates/pdfs-gui/src/main.rs:494 +#: crates/pdfs-gui/src/main.rs:512 msgid "Resume Sync" msgstr "" -#: crates/pdfs-gui/src/main.rs:496 +#: crates/pdfs-gui/src/main.rs:514 msgid "Pause Sync" msgstr "" -#: crates/pdfs-gui/src/main.rs:515 +#: crates/pdfs-gui/src/main.rs:533 msgid "Connect" msgstr "" -#: crates/pdfs-gui/src/main.rs:522 +#: crates/pdfs-gui/src/main.rs:540 msgid "Hide Tray Icon" msgstr "" -#: crates/pdfs-gui/src/main.rs:526 +#: crates/pdfs-gui/src/main.rs:544 msgid "Stop Proton Drive…" msgstr "" -#: crates/pdfs-gui/src/main.rs:530 +#: crates/pdfs-gui/src/main.rs:548 msgid "Quit" msgstr "" diff --git a/po/pl.po b/po/pl.po index 8acb1fb..df4a6d5 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-26 22:26+0100\n" +"POT-Creation-Date: 2026-10-31 19:26+0100\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: Polish\n" @@ -250,13 +250,13 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1917 #: crates/pdfs-gui/src/app/pages/browser.rs:2039 #: crates/pdfs-gui/src/app/pages/browser.rs:2540 -#: crates/pdfs-gui/src/app/pages/devices.rs:804 -#: crates/pdfs-gui/src/app/pages/devices.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:995 -#: crates/pdfs-gui/src/app/pages/devices.rs:1047 -#: crates/pdfs-gui/src/app/pages/devices.rs:1091 -#: crates/pdfs-gui/src/app/pages/devices.rs:1131 -#: crates/pdfs-gui/src/app/pages/devices.rs:1194 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 +#: crates/pdfs-gui/src/app/pages/devices.rs:979 +#: crates/pdfs-gui/src/app/pages/devices.rs:1017 +#: crates/pdfs-gui/src/app/pages/devices.rs:1069 +#: crates/pdfs-gui/src/app/pages/devices.rs:1113 +#: crates/pdfs-gui/src/app/pages/devices.rs:1153 +#: crates/pdfs-gui/src/app/pages/devices.rs:1216 #: crates/pdfs-gui/src/app/pages/locations.rs:850 #: crates/pdfs-gui/src/app/pages/locations.rs:1054 #: crates/pdfs-gui/src/app/pages/locations.rs:1537 @@ -351,7 +351,7 @@ msgstr "Siatka/lista" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1896 #: crates/pdfs-gui/src/app/pages/browser.rs:1918 -#: crates/pdfs-gui/src/app/pages/devices.rs:996 +#: crates/pdfs-gui/src/app/pages/devices.rs:1018 #: crates/pdfs-gui/src/app/widgets/details.rs:161 msgid "Rename" msgstr "Zmień nazwę" @@ -425,14 +425,27 @@ msgstr "Brak połączenia" msgid "The Proton Drive service isn't running." msgstr "Usługa Proton Drive nie jest uruchomiona." -#: crates/pdfs-gui/src/app/main.rs:1803 +#: crates/pdfs-gui/src/app/main.rs:1813 +msgid "Can't open your Drive" +msgstr "Nie można otworzyć Proton Drive" + +#: crates/pdfs-gui/src/app/main.rs:1816 +#, rust-format +msgid "" +"The Proton Drive service keeps failing to start and tries again on its own. " +"The last error was: {error}" +msgstr "" +"Usługa Proton Drive nie może się uruchomić i sama ponawia próby. Ostatni " +"błąd: {error}" + +#: crates/pdfs-gui/src/app/main.rs:1824 #: crates/pdfs-gui/src/app/pages/status.rs:355 #: crates/pdfs-gui/src/hud/mod.rs:269 crates/pdfs-gui/src/hud/mod.rs:579 -#: crates/pdfs-gui/src/main.rs:475 crates/pdfs-gui/src/main.rs:517 +#: crates/pdfs-gui/src/main.rs:493 crates/pdfs-gui/src/main.rs:535 msgid "Connecting…" msgstr "Łączenie…" -#: crates/pdfs-gui/src/app/main.rs:1804 +#: crates/pdfs-gui/src/app/main.rs:1825 msgid "Waiting for the Proton Drive service to start." msgstr "Oczekiwanie na uruchomienie usługi Proton Drive." @@ -696,7 +709,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:839 +#: crates/pdfs-gui/src/app/pages/devices.rs:861 msgid "Couldn't restore folders" msgstr "Nie można przywrócić katalogów" @@ -1300,12 +1313,12 @@ msgstr "Nie można wczytać albumów" #: crates/pdfs-gui/src/app/pages/browser.rs:1873 #: crates/pdfs-gui/src/app/pages/browser.rs:2500 #: crates/pdfs-gui/src/app/pages/browser.rs:2650 -#: crates/pdfs-gui/src/app/pages/devices.rs:707 -#: crates/pdfs-gui/src/app/pages/devices.rs:747 -#: crates/pdfs-gui/src/app/pages/devices.rs:900 -#: crates/pdfs-gui/src/app/pages/devices.rs:1170 -#: crates/pdfs-gui/src/app/pages/devices.rs:1231 -#: crates/pdfs-gui/src/app/pages/devices.rs:1260 +#: crates/pdfs-gui/src/app/pages/devices.rs:729 +#: crates/pdfs-gui/src/app/pages/devices.rs:769 +#: crates/pdfs-gui/src/app/pages/devices.rs:922 +#: crates/pdfs-gui/src/app/pages/devices.rs:1192 +#: crates/pdfs-gui/src/app/pages/devices.rs:1253 +#: crates/pdfs-gui/src/app/pages/devices.rs:1282 #: crates/pdfs-gui/src/app/pages/duplicates.rs:221 #: crates/pdfs-gui/src/app/pages/locations.rs:577 #: crates/pdfs-gui/src/app/pages/locations.rs:824 @@ -1969,7 +1982,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1900 -#: crates/pdfs-gui/src/app/pages/devices.rs:989 +#: crates/pdfs-gui/src/app/pages/devices.rs:1011 msgid "New name" msgstr "Nowa nazwa" @@ -1994,7 +2007,7 @@ msgstr "Proszę wybrać katalog docelowy." #: crates/pdfs-gui/src/app/pages/browser.rs:1989 #: crates/pdfs-gui/src/app/pages/browser.rs:1994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1195 +#: crates/pdfs-gui/src/app/pages/devices.rs:1217 msgid "Move Here" msgstr "Przenieś tutaj" @@ -2327,7 +2340,15 @@ 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:608 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 +#, rust-format +msgid "{folders} · {n} waiting for its first sync" +msgid_plural "{folders} · {n} waiting for their first sync" +msgstr[0] "{folders} · {n} czeka na pierwszą synchronizację" +msgstr[1] "{folders} · {n} czekają na pierwszą synchronizację" +msgstr[2] "{folders} · {n} czeka na pierwszą synchronizację" + +#: crates/pdfs-gui/src/app/pages/devices.rs:621 #, rust-format msgid "{folders} · {n} needs attention" msgid_plural "{folders} · {n} need attention" @@ -2335,27 +2356,31 @@ 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:619 +#: crates/pdfs-gui/src/app/pages/devices.rs:640 msgid "syncing…" msgstr "synchronizowanie…" -#: crates/pdfs-gui/src/app/pages/devices.rs:620 +#: crates/pdfs-gui/src/app/pages/devices.rs:641 msgid "sync error" msgstr "błąd synchronizacji" -#: crates/pdfs-gui/src/app/pages/devices.rs:621 +#: crates/pdfs-gui/src/app/pages/devices.rs:642 msgid "needs attention" msgstr "wymaga uwagi" -#: crates/pdfs-gui/src/app/pages/devices.rs:622 +#: crates/pdfs-gui/src/app/pages/devices.rs:643 +msgid "waiting for first sync" +msgstr "oczekiwanie na pierwszą synchronizację" + +#: crates/pdfs-gui/src/app/pages/devices.rs:644 msgid "up to date" msgstr "aktualne" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 msgid "checking for changes…" msgstr "sprawdzanie zmian…" -#: crates/pdfs-gui/src/app/pages/devices.rs:638 +#: crates/pdfs-gui/src/app/pages/devices.rs:660 #, rust-format msgid "checking for changes — {n} item checked" msgid_plural "checking for changes — {n} items checked" @@ -2363,42 +2388,42 @@ msgstr[0] "sprawdzanie zmian — sprawdzono {n} element" msgstr[1] "sprawdzanie zmian — sprawdzono {n} elementy" msgstr[2] "sprawdzanie zmian — sprawdzono {n} elementów" -#: crates/pdfs-gui/src/app/pages/devices.rs:645 +#: crates/pdfs-gui/src/app/pages/devices.rs:667 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "sprawdzanie zmian — {done} z {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:657 +#: crates/pdfs-gui/src/app/pages/devices.rs:679 #, rust-format msgid "syncing {done} of {total}" msgstr "synchronizowanie {done} z {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:663 +#: crates/pdfs-gui/src/app/pages/devices.rs:685 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "synchronizowanie {file} — {done} z {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:675 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Add Folder to Sync" msgstr "Dodaj katalog do synchronizacji" -#: crates/pdfs-gui/src/app/pages/devices.rs:699 +#: crates/pdfs-gui/src/app/pages/devices.rs:721 #: crates/pdfs-gui/src/app/pages/locations.rs:1559 #: crates/pdfs-gui/src/app/pages/locations.rs:1686 msgid "Syncing folder…" msgstr "Synchronizowanie katalogu…" -#: crates/pdfs-gui/src/app/pages/devices.rs:702 -#: crates/pdfs-gui/src/app/pages/devices.rs:706 +#: crates/pdfs-gui/src/app/pages/devices.rs:724 +#: crates/pdfs-gui/src/app/pages/devices.rs:728 msgid "Couldn't add folder" msgstr "Nie można dodać katalogu" -#: crates/pdfs-gui/src/app/pages/devices.rs:740 -#: crates/pdfs-gui/src/app/pages/devices.rs:746 +#: crates/pdfs-gui/src/app/pages/devices.rs:762 +#: crates/pdfs-gui/src/app/pages/devices.rs:768 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:772 +#: crates/pdfs-gui/src/app/pages/devices.rs:794 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2406,19 +2431,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:798 +#: crates/pdfs-gui/src/app/pages/devices.rs:820 msgid "Restore Folders" msgstr "Przywróć katalogi" -#: crates/pdfs-gui/src/app/pages/devices.rs:805 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Restore" msgstr "Przywróć" -#: crates/pdfs-gui/src/app/pages/devices.rs:838 +#: crates/pdfs-gui/src/app/pages/devices.rs:860 msgid "Restoring folders…" msgstr "Przywracanie katalogów…" -#: crates/pdfs-gui/src/app/pages/devices.rs:850 +#: crates/pdfs-gui/src/app/pages/devices.rs:872 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2427,37 +2452,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:853 +#: crates/pdfs-gui/src/app/pages/devices.rs:875 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:862 +#: crates/pdfs-gui/src/app/pages/devices.rs:884 #, 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:865 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 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:889 +#: crates/pdfs-gui/src/app/pages/devices.rs:911 msgid "Folder will switch to online only" msgstr "Katalog przejdzie w tryb tylko online" -#: crates/pdfs-gui/src/app/pages/devices.rs:891 +#: crates/pdfs-gui/src/app/pages/devices.rs:913 msgid "Folder will download and stay synced" msgstr "Katalog zostanie pobrany i będzie synchronizowany" -#: crates/pdfs-gui/src/app/pages/devices.rs:895 -#: crates/pdfs-gui/src/app/pages/devices.rs:899 +#: crates/pdfs-gui/src/app/pages/devices.rs:917 +#: crates/pdfs-gui/src/app/pages/devices.rs:921 msgid "Couldn't change mode" msgstr "Nie można zmienić trybu" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:953 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2474,7 +2499,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:937 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2487,68 +2512,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:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:964 msgid "Stop Syncing Folder?" msgstr "Zatrzymać synchronizację katalogu?" -#: crates/pdfs-gui/src/app/pages/devices.rs:947 +#: crates/pdfs-gui/src/app/pages/devices.rs:969 msgid "Also delete from Proton Drive" msgstr "Usuń także z Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:949 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Deletes the only copy of these files." msgstr "Usuwa jedyną kopię tych plików." -#: crates/pdfs-gui/src/app/pages/devices.rs:951 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 msgid "The local copy is unaffected." msgstr "Lokalna kopia pozostanie bez zmian." -#: crates/pdfs-gui/src/app/pages/devices.rs:958 +#: crates/pdfs-gui/src/app/pages/devices.rs:980 msgid "Stop Syncing" msgstr "Zatrzymaj synchronizację" -#: crates/pdfs-gui/src/app/pages/devices.rs:971 +#: crates/pdfs-gui/src/app/pages/devices.rs:993 msgid "Stopped syncing folder" msgstr "Zatrzymano synchronizację katalogu" -#: crates/pdfs-gui/src/app/pages/devices.rs:972 +#: crates/pdfs-gui/src/app/pages/devices.rs:994 msgid "Couldn't stop syncing the folder" msgstr "Nie można zatrzymać synchronizacji katalogu" -#: crates/pdfs-gui/src/app/pages/devices.rs:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1005 msgid "Rename Computer" msgstr "Zmiana nazwy komputera" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:985 +#: crates/pdfs-gui/src/app/pages/devices.rs:1007 #, rust-format msgid "Rename “{name}”." msgstr "Proszę podać nową nazwę komputera „{name}”." -#: crates/pdfs-gui/src/app/pages/devices.rs:1010 -#: crates/pdfs-gui/src/app/pages/devices.rs:1022 +#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1044 msgid "Couldn't rename the computer" msgstr "Nie można zmienić nazwy komputera" -#: crates/pdfs-gui/src/app/pages/devices.rs:1011 +#: crates/pdfs-gui/src/app/pages/devices.rs:1033 msgid "A name is required." msgstr "Nazwa jest wymagana." -#: crates/pdfs-gui/src/app/pages/devices.rs:1021 +#: crates/pdfs-gui/src/app/pages/devices.rs:1043 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:1034 +#: crates/pdfs-gui/src/app/pages/devices.rs:1056 #, rust-format msgid "Type “{name}” to confirm" msgstr "Proszę wpisać „{name}”, aby potwierdzić" -#: crates/pdfs-gui/src/app/pages/devices.rs:1039 +#: crates/pdfs-gui/src/app/pages/devices.rs:1061 msgid "Remove Computer?" msgstr "Usunąć komputer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1042 +#: crates/pdfs-gui/src/app/pages/devices.rs:1064 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2563,26 +2588,26 @@ 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:1048 +#: crates/pdfs-gui/src/app/pages/devices.rs:1070 #: crates/pdfs-gui/src/app/pages/shared.rs:1025 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:634 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:876 msgid "Remove" msgstr "Usuń" -#: crates/pdfs-gui/src/app/pages/devices.rs:1065 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 msgid "Computer removed" msgstr "Usunięto komputer" -#: crates/pdfs-gui/src/app/pages/devices.rs:1066 +#: crates/pdfs-gui/src/app/pages/devices.rs:1088 msgid "Couldn't remove the computer" msgstr "Nie można usunąć komputera" -#: crates/pdfs-gui/src/app/pages/devices.rs:1084 +#: crates/pdfs-gui/src/app/pages/devices.rs:1106 msgid "Continue This Backup Here?" msgstr "Kontynuować tę kopię zapasową tutaj?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1087 +#: crates/pdfs-gui/src/app/pages/devices.rs:1109 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2601,25 +2626,25 @@ 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:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1114 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:1099 +#: crates/pdfs-gui/src/app/pages/devices.rs:1121 #, rust-format msgid "Now backing up as “{name}”" msgstr "Kopia zapasowa jest teraz tworzona jako „{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1108 +#: crates/pdfs-gui/src/app/pages/devices.rs:1130 msgid "Couldn't continue that backup here" msgstr "Nie można kontynuować tej kopii zapasowej tutaj" -#: crates/pdfs-gui/src/app/pages/devices.rs:1124 +#: crates/pdfs-gui/src/app/pages/devices.rs:1146 msgid "Sync With This Computer?" msgstr "Synchronizować z tym komputerem?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#: crates/pdfs-gui/src/app/pages/devices.rs:1149 #, rust-format msgid "" "This computer and “{name}” keep the same folders in step. A change or a " @@ -2636,25 +2661,25 @@ msgstr "" "Drive. Katalogi już tu synchronizowane zostaną tam, gdzie są. Następnie " "proszę wybrać, które z jego katalogów synchronizować tutaj." -#: crates/pdfs-gui/src/app/pages/devices.rs:1132 +#: crates/pdfs-gui/src/app/pages/devices.rs:1154 msgid "Sync Here" msgstr "Synchronizuj tutaj" #. Translators: {name} is the name of the other computer this one now syncs with. -#: crates/pdfs-gui/src/app/pages/devices.rs:1139 +#: crates/pdfs-gui/src/app/pages/devices.rs:1161 #, rust-format msgid "Now syncing with “{name}”" msgstr "Teraz synchronizowane z „{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1155 +#: crates/pdfs-gui/src/app/pages/devices.rs:1177 msgid "Couldn't sync with that computer" msgstr "Nie można synchronizować z tym komputerem" -#: crates/pdfs-gui/src/app/pages/devices.rs:1187 +#: crates/pdfs-gui/src/app/pages/devices.rs:1209 msgid "Move Backup to This Computer?" msgstr "Przenieść kopię zapasową na ten komputer?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1190 +#: crates/pdfs-gui/src/app/pages/devices.rs:1212 #, rust-format msgid "" "The folders backed up by “{name}” move into this computer's backup in Proton " @@ -2672,12 +2697,12 @@ msgstr "" "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:1202 +#: crates/pdfs-gui/src/app/pages/devices.rs:1224 #, rust-format msgid "Moved the backup of “{name}”" msgstr "Przeniesiono kopię zapasową „{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1216 +#: crates/pdfs-gui/src/app/pages/devices.rs:1238 msgid "Couldn't move that backup" msgstr "Nie można przenieść tej kopii zapasowej" @@ -2961,7 +2986,7 @@ msgid "Sync paused until you resume" msgstr "Wstrzymano synchronizację do czasu wznowienia" #: crates/pdfs-gui/src/app/pages/locations.rs:570 -#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:290 +#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:308 msgid "Sync paused" msgstr "Wstrzymano synchronizację" @@ -3423,7 +3448,7 @@ msgid "paused" msgstr "wstrzymany" #. Translators: a folder's files can't be opened right now. -#: crates/pdfs-gui/src/app/pages/locations.rs:1742 +#: crates/pdfs-gui/src/app/pages/locations.rs:1745 msgid "not available" msgstr "niedostępne" @@ -5188,7 +5213,7 @@ msgstr "Wznowienie {time}" msgid "Until you resume" msgstr "Do czasu wznowienia" -#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:292 +#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:310 #, rust-format msgid "{n} change needs attention" msgid_plural "{n} changes need attention" @@ -6439,38 +6464,43 @@ msgstr "Proton Drive nie odpowiada" msgid "Signed in as {username} — locked" msgstr "Zalogowano jako {username} — zablokowane" -#: crates/pdfs-gui/src/main.rs:200 +#: crates/pdfs-gui/src/main.rs:209 +#, rust-format +msgid "Signed in as {username} — can't open your Drive" +msgstr "Zalogowano jako {username} — nie można otworzyć Proton Drive" + +#: crates/pdfs-gui/src/main.rs:218 #, rust-format msgid "Signed in as {username} — not connected" msgstr "Zalogowano jako {username} — brak połączenia" -#: crates/pdfs-gui/src/main.rs:205 +#: crates/pdfs-gui/src/main.rs:223 msgid "Not signed in" msgstr "Nie zalogowano" #. Translators: {error} is an error message, in English. -#: crates/pdfs-gui/src/main.rs:208 +#: crates/pdfs-gui/src/main.rs:226 #, rust-format msgid "Error: {error}" msgstr "Błąd: {error}" -#: crates/pdfs-gui/src/main.rs:230 +#: crates/pdfs-gui/src/main.rs:248 msgid "Proton Drive sent an unexpected reply" msgstr "Proton Drive wysłał nieoczekiwaną odpowiedź" -#: crates/pdfs-gui/src/main.rs:284 +#: crates/pdfs-gui/src/main.rs:302 msgid "Session expired — sign in again" msgstr "Sesja wygasła — zaloguj się ponownie" -#: crates/pdfs-gui/src/main.rs:286 +#: crates/pdfs-gui/src/main.rs:304 msgid "Sign-in not saved — unlock your keyring" msgstr "Logowanie nie zostało zapisane — odblokuj bazę kluczy" -#: crates/pdfs-gui/src/main.rs:289 +#: crates/pdfs-gui/src/main.rs:307 msgid "Update not finished — restart Proton Drive" msgstr "Aktualizacja niedokończona — uruchom ponownie Proton Drive" -#: crates/pdfs-gui/src/main.rs:299 +#: crates/pdfs-gui/src/main.rs:317 #, rust-format msgid "Up to date in {mountpoint} ({n} pinned)" msgid_plural "Up to date in {mountpoint} ({n} pinned)" @@ -6478,7 +6508,7 @@ msgstr[0] "Aktualne w {mountpoint} ({n} przypięty)" msgstr[1] "Aktualne w {mountpoint} ({n} przypięte)" msgstr[2] "Aktualne w {mountpoint} ({n} przypiętych)" -#: crates/pdfs-gui/src/main.rs:306 +#: crates/pdfs-gui/src/main.rs:324 #, rust-format msgid "Syncing — {queued} ({n} pinned)" msgid_plural "Syncing — {queued} ({n} pinned)" @@ -6486,7 +6516,7 @@ msgstr[0] "Synchronizowanie — {queued} (przypięte: {n})" msgstr[1] "Synchronizowanie — {queued} (przypięte: {n})" msgstr[2] "Synchronizowanie — {queued} (przypięte: {n})" -#: crates/pdfs-gui/src/main.rs:313 +#: crates/pdfs-gui/src/main.rs:331 #, rust-format msgid "Offline — cached files only ({n} pinned)" msgid_plural "Offline — cached files only ({n} pinned)" @@ -6495,13 +6525,13 @@ msgstr[1] "Offline — tylko pliki w pamięci podręcznej (przypięte: {n})" msgstr[2] "Offline — tylko pliki w pamięci podręcznej (przypięte: {n})" #. Translators: {queued} is a queue summary such as "3 uploads queued". -#: crates/pdfs-gui/src/main.rs:319 +#: crates/pdfs-gui/src/main.rs:337 #, rust-format msgid "Offline — {queued}" msgstr "Offline — {queued}" #. Translators: a menu item that opens the list of failing changes. -#: crates/pdfs-gui/src/main.rs:358 +#: crates/pdfs-gui/src/main.rs:376 #, rust-format msgid "{n} issue — View" msgid_plural "{n} issues — View" @@ -6509,47 +6539,47 @@ msgstr[0] "{n} problem — wyświetl" msgstr[1] "{n} problemy — wyświetl" msgstr[2] "{n} problemów — wyświetl" -#: crates/pdfs-gui/src/main.rs:465 +#: crates/pdfs-gui/src/main.rs:483 msgid "Sign In…" msgstr "Zaloguj się…" -#: crates/pdfs-gui/src/main.rs:466 +#: crates/pdfs-gui/src/main.rs:484 msgid "Sign In Again…" msgstr "Zaloguj się ponownie…" -#: crates/pdfs-gui/src/main.rs:469 +#: crates/pdfs-gui/src/main.rs:487 msgid "Open Proton Drive" msgstr "Otwórz Proton Drive" -#: crates/pdfs-gui/src/main.rs:473 +#: crates/pdfs-gui/src/main.rs:491 msgid "Restart Proton Drive" msgstr "Uruchom ponownie Proton Drive" -#: crates/pdfs-gui/src/main.rs:482 +#: crates/pdfs-gui/src/main.rs:500 msgid "Open Folder" msgstr "Otwórz katalog" -#: crates/pdfs-gui/src/main.rs:494 +#: crates/pdfs-gui/src/main.rs:512 msgid "Resume Sync" msgstr "Wznów synchronizację" -#: crates/pdfs-gui/src/main.rs:496 +#: crates/pdfs-gui/src/main.rs:514 msgid "Pause Sync" msgstr "Wstrzymaj synchronizację" -#: crates/pdfs-gui/src/main.rs:515 +#: crates/pdfs-gui/src/main.rs:533 msgid "Connect" msgstr "Połącz" -#: crates/pdfs-gui/src/main.rs:522 +#: crates/pdfs-gui/src/main.rs:540 msgid "Hide Tray Icon" msgstr "Ukryj ikonę w obszarze powiadamiania" -#: crates/pdfs-gui/src/main.rs:526 +#: crates/pdfs-gui/src/main.rs:544 msgid "Stop Proton Drive…" msgstr "Zatrzymaj Proton Drive…" -#: crates/pdfs-gui/src/main.rs:530 +#: crates/pdfs-gui/src/main.rs:548 msgid "Quit" msgstr "Zakończ" diff --git a/po/pt_BR.po b/po/pt_BR.po index 56b0d45..344871b 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-26 22:26+0100\n" +"POT-Creation-Date: 2026-10-31 19:26+0100\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: Portuguese (Brazil)\n" @@ -245,13 +245,13 @@ msgstr "" #: crates/pdfs-gui/src/app/pages/browser.rs:1917 #: crates/pdfs-gui/src/app/pages/browser.rs:2039 #: crates/pdfs-gui/src/app/pages/browser.rs:2540 -#: crates/pdfs-gui/src/app/pages/devices.rs:804 -#: crates/pdfs-gui/src/app/pages/devices.rs:957 -#: crates/pdfs-gui/src/app/pages/devices.rs:995 -#: crates/pdfs-gui/src/app/pages/devices.rs:1047 -#: crates/pdfs-gui/src/app/pages/devices.rs:1091 -#: crates/pdfs-gui/src/app/pages/devices.rs:1131 -#: crates/pdfs-gui/src/app/pages/devices.rs:1194 +#: crates/pdfs-gui/src/app/pages/devices.rs:826 +#: crates/pdfs-gui/src/app/pages/devices.rs:979 +#: crates/pdfs-gui/src/app/pages/devices.rs:1017 +#: crates/pdfs-gui/src/app/pages/devices.rs:1069 +#: crates/pdfs-gui/src/app/pages/devices.rs:1113 +#: crates/pdfs-gui/src/app/pages/devices.rs:1153 +#: crates/pdfs-gui/src/app/pages/devices.rs:1216 #: crates/pdfs-gui/src/app/pages/locations.rs:850 #: crates/pdfs-gui/src/app/pages/locations.rs:1054 #: crates/pdfs-gui/src/app/pages/locations.rs:1537 @@ -346,7 +346,7 @@ msgstr "Grade / lista" #: crates/pdfs-gui/src/app/pages/albums.rs:298 #: crates/pdfs-gui/src/app/pages/browser.rs:1896 #: crates/pdfs-gui/src/app/pages/browser.rs:1918 -#: crates/pdfs-gui/src/app/pages/devices.rs:996 +#: crates/pdfs-gui/src/app/pages/devices.rs:1018 #: crates/pdfs-gui/src/app/widgets/details.rs:161 msgid "Rename" msgstr "Renomear" @@ -420,14 +420,27 @@ msgstr "Não conectado" msgid "The Proton Drive service isn't running." msgstr "O serviço do Proton Drive não está em execução." -#: crates/pdfs-gui/src/app/main.rs:1803 +#: crates/pdfs-gui/src/app/main.rs:1813 +msgid "Can't open your Drive" +msgstr "Não foi possível abrir o Proton Drive" + +#: crates/pdfs-gui/src/app/main.rs:1816 +#, rust-format +msgid "" +"The Proton Drive service keeps failing to start and tries again on its own. " +"The last error was: {error}" +msgstr "" +"O serviço do Proton Drive não consegue iniciar e tenta de novo sozinho. " +"Último erro: {error}" + +#: crates/pdfs-gui/src/app/main.rs:1824 #: crates/pdfs-gui/src/app/pages/status.rs:355 #: crates/pdfs-gui/src/hud/mod.rs:269 crates/pdfs-gui/src/hud/mod.rs:579 -#: crates/pdfs-gui/src/main.rs:475 crates/pdfs-gui/src/main.rs:517 +#: crates/pdfs-gui/src/main.rs:493 crates/pdfs-gui/src/main.rs:535 msgid "Connecting…" msgstr "Conectando…" -#: crates/pdfs-gui/src/app/main.rs:1804 +#: crates/pdfs-gui/src/app/main.rs:1825 msgid "Waiting for the Proton Drive service to start." msgstr "Aguardando o serviço do Proton Drive iniciar." @@ -678,7 +691,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:839 +#: crates/pdfs-gui/src/app/pages/devices.rs:861 msgid "Couldn't restore folders" msgstr "Não foi possível restaurar as pastas" @@ -1270,12 +1283,12 @@ msgstr "Não foi possível carregar os álbuns" #: crates/pdfs-gui/src/app/pages/browser.rs:1873 #: crates/pdfs-gui/src/app/pages/browser.rs:2500 #: crates/pdfs-gui/src/app/pages/browser.rs:2650 -#: crates/pdfs-gui/src/app/pages/devices.rs:707 -#: crates/pdfs-gui/src/app/pages/devices.rs:747 -#: crates/pdfs-gui/src/app/pages/devices.rs:900 -#: crates/pdfs-gui/src/app/pages/devices.rs:1170 -#: crates/pdfs-gui/src/app/pages/devices.rs:1231 -#: crates/pdfs-gui/src/app/pages/devices.rs:1260 +#: crates/pdfs-gui/src/app/pages/devices.rs:729 +#: crates/pdfs-gui/src/app/pages/devices.rs:769 +#: crates/pdfs-gui/src/app/pages/devices.rs:922 +#: crates/pdfs-gui/src/app/pages/devices.rs:1192 +#: crates/pdfs-gui/src/app/pages/devices.rs:1253 +#: crates/pdfs-gui/src/app/pages/devices.rs:1282 #: crates/pdfs-gui/src/app/pages/duplicates.rs:221 #: crates/pdfs-gui/src/app/pages/locations.rs:577 #: crates/pdfs-gui/src/app/pages/locations.rs:824 @@ -1921,7 +1934,7 @@ msgid "{status}. {message}" msgstr "{status}. {message}" #: crates/pdfs-gui/src/app/pages/browser.rs:1900 -#: crates/pdfs-gui/src/app/pages/devices.rs:989 +#: crates/pdfs-gui/src/app/pages/devices.rs:1011 msgid "New name" msgstr "Novo nome" @@ -1946,7 +1959,7 @@ msgstr "Escolha a pasta de destino." #: crates/pdfs-gui/src/app/pages/browser.rs:1989 #: crates/pdfs-gui/src/app/pages/browser.rs:1994 -#: crates/pdfs-gui/src/app/pages/devices.rs:1195 +#: crates/pdfs-gui/src/app/pages/devices.rs:1217 msgid "Move Here" msgstr "Mover para cá" @@ -2268,76 +2281,87 @@ 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:608 +#: crates/pdfs-gui/src/app/pages/devices.rs:607 +#, rust-format +msgid "{folders} · {n} waiting for its first sync" +msgid_plural "{folders} · {n} waiting for their first sync" +msgstr[0] "{folders} · {n} aguarda a primeira sincronização" +msgstr[1] "{folders} · {n} aguardam a primeira sincronização" + +#: crates/pdfs-gui/src/app/pages/devices.rs:621 #, 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:619 +#: crates/pdfs-gui/src/app/pages/devices.rs:640 msgid "syncing…" msgstr "sincronizando…" -#: crates/pdfs-gui/src/app/pages/devices.rs:620 +#: crates/pdfs-gui/src/app/pages/devices.rs:641 msgid "sync error" msgstr "erro de sincronização" -#: crates/pdfs-gui/src/app/pages/devices.rs:621 +#: crates/pdfs-gui/src/app/pages/devices.rs:642 msgid "needs attention" msgstr "requer atenção" -#: crates/pdfs-gui/src/app/pages/devices.rs:622 +#: crates/pdfs-gui/src/app/pages/devices.rs:643 +msgid "waiting for first sync" +msgstr "aguardando a primeira sincronização" + +#: crates/pdfs-gui/src/app/pages/devices.rs:644 msgid "up to date" msgstr "atualizado" -#: crates/pdfs-gui/src/app/pages/devices.rs:632 +#: crates/pdfs-gui/src/app/pages/devices.rs:654 msgid "checking for changes…" msgstr "verificando alterações…" -#: crates/pdfs-gui/src/app/pages/devices.rs:638 +#: crates/pdfs-gui/src/app/pages/devices.rs:660 #, rust-format msgid "checking for changes — {n} item checked" msgid_plural "checking for changes — {n} items checked" msgstr[0] "verificando alterações — {n} item verificado" msgstr[1] "verificando alterações — {n} itens verificados" -#: crates/pdfs-gui/src/app/pages/devices.rs:645 +#: crates/pdfs-gui/src/app/pages/devices.rs:667 #, rust-format msgid "checking for changes — {done} of {total}" msgstr "verificando alterações — {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:657 +#: crates/pdfs-gui/src/app/pages/devices.rs:679 #, rust-format msgid "syncing {done} of {total}" msgstr "sincronizando {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:663 +#: crates/pdfs-gui/src/app/pages/devices.rs:685 #, rust-format msgid "syncing {file} — {done} of {total}" msgstr "sincronizando {file} — {done} de {total}" -#: crates/pdfs-gui/src/app/pages/devices.rs:675 +#: crates/pdfs-gui/src/app/pages/devices.rs:697 msgid "Add Folder to Sync" msgstr "Adicionar pasta à sincronização" -#: crates/pdfs-gui/src/app/pages/devices.rs:699 +#: crates/pdfs-gui/src/app/pages/devices.rs:721 #: crates/pdfs-gui/src/app/pages/locations.rs:1559 #: crates/pdfs-gui/src/app/pages/locations.rs:1686 msgid "Syncing folder…" msgstr "Sincronizando pasta…" -#: crates/pdfs-gui/src/app/pages/devices.rs:702 -#: crates/pdfs-gui/src/app/pages/devices.rs:706 +#: crates/pdfs-gui/src/app/pages/devices.rs:724 +#: crates/pdfs-gui/src/app/pages/devices.rs:728 msgid "Couldn't add folder" msgstr "Não foi possível adicionar a pasta" -#: crates/pdfs-gui/src/app/pages/devices.rs:740 -#: crates/pdfs-gui/src/app/pages/devices.rs:746 +#: crates/pdfs-gui/src/app/pages/devices.rs:762 +#: crates/pdfs-gui/src/app/pages/devices.rs:768 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:772 +#: crates/pdfs-gui/src/app/pages/devices.rs:794 msgid "" "Tick the folders to sync to this machine, and adjust where each one should " "live." @@ -2345,19 +2369,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:798 +#: crates/pdfs-gui/src/app/pages/devices.rs:820 msgid "Restore Folders" msgstr "Restaurar pastas" -#: crates/pdfs-gui/src/app/pages/devices.rs:805 +#: crates/pdfs-gui/src/app/pages/devices.rs:827 msgid "Restore" msgstr "Restaurar" -#: crates/pdfs-gui/src/app/pages/devices.rs:838 +#: crates/pdfs-gui/src/app/pages/devices.rs:860 msgid "Restoring folders…" msgstr "Restaurando pastas…" -#: crates/pdfs-gui/src/app/pages/devices.rs:850 +#: crates/pdfs-gui/src/app/pages/devices.rs:872 #, rust-format msgid "" "These folders are backed up from “{name}”. Restored folders keep syncing " @@ -2366,36 +2390,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:853 +#: crates/pdfs-gui/src/app/pages/devices.rs:875 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:862 +#: crates/pdfs-gui/src/app/pages/devices.rs:884 #, 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:865 +#: crates/pdfs-gui/src/app/pages/devices.rs:887 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:889 +#: crates/pdfs-gui/src/app/pages/devices.rs:911 msgid "Folder will switch to online only" msgstr "A pasta passará a ser somente online" -#: crates/pdfs-gui/src/app/pages/devices.rs:891 +#: crates/pdfs-gui/src/app/pages/devices.rs:913 msgid "Folder will download and stay synced" msgstr "A pasta será baixada e permanecerá sincronizada" -#: crates/pdfs-gui/src/app/pages/devices.rs:895 -#: crates/pdfs-gui/src/app/pages/devices.rs:899 +#: crates/pdfs-gui/src/app/pages/devices.rs:917 +#: crates/pdfs-gui/src/app/pages/devices.rs:921 msgid "Couldn't change mode" msgstr "Não foi possível alterar o modo" -#: crates/pdfs-gui/src/app/pages/devices.rs:931 +#: crates/pdfs-gui/src/app/pages/devices.rs:953 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2412,7 +2436,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:937 +#: crates/pdfs-gui/src/app/pages/devices.rs:959 #, rust-format msgid "" "Stop syncing “{path}”?\n" @@ -2425,68 +2449,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:942 +#: crates/pdfs-gui/src/app/pages/devices.rs:964 msgid "Stop Syncing Folder?" msgstr "Parar de sincronizar a pasta?" -#: crates/pdfs-gui/src/app/pages/devices.rs:947 +#: crates/pdfs-gui/src/app/pages/devices.rs:969 msgid "Also delete from Proton Drive" msgstr "Também excluir do Proton Drive" -#: crates/pdfs-gui/src/app/pages/devices.rs:949 +#: crates/pdfs-gui/src/app/pages/devices.rs:971 msgid "Deletes the only copy of these files." msgstr "Exclui a única cópia destes arquivos." -#: crates/pdfs-gui/src/app/pages/devices.rs:951 +#: crates/pdfs-gui/src/app/pages/devices.rs:973 msgid "The local copy is unaffected." msgstr "A cópia local não é afetada." -#: crates/pdfs-gui/src/app/pages/devices.rs:958 +#: crates/pdfs-gui/src/app/pages/devices.rs:980 msgid "Stop Syncing" msgstr "Parar de sincronizar" -#: crates/pdfs-gui/src/app/pages/devices.rs:971 +#: crates/pdfs-gui/src/app/pages/devices.rs:993 msgid "Stopped syncing folder" msgstr "A pasta deixou de ser sincronizada" -#: crates/pdfs-gui/src/app/pages/devices.rs:972 +#: crates/pdfs-gui/src/app/pages/devices.rs:994 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:983 +#: crates/pdfs-gui/src/app/pages/devices.rs:1005 msgid "Rename Computer" msgstr "Renomear computador" #. Translators: {name} is the computer's current name. -#: crates/pdfs-gui/src/app/pages/devices.rs:985 +#: crates/pdfs-gui/src/app/pages/devices.rs:1007 #, rust-format msgid "Rename “{name}”." msgstr "Renomear “{name}”." -#: crates/pdfs-gui/src/app/pages/devices.rs:1010 -#: crates/pdfs-gui/src/app/pages/devices.rs:1022 +#: crates/pdfs-gui/src/app/pages/devices.rs:1032 +#: crates/pdfs-gui/src/app/pages/devices.rs:1044 msgid "Couldn't rename the computer" msgstr "Não foi possível renomear o computador" -#: crates/pdfs-gui/src/app/pages/devices.rs:1011 +#: crates/pdfs-gui/src/app/pages/devices.rs:1033 msgid "A name is required." msgstr "Um nome é obrigatório." -#: crates/pdfs-gui/src/app/pages/devices.rs:1021 +#: crates/pdfs-gui/src/app/pages/devices.rs:1043 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:1034 +#: crates/pdfs-gui/src/app/pages/devices.rs:1056 #, rust-format msgid "Type “{name}” to confirm" msgstr "Digite “{name}” para confirmar" -#: crates/pdfs-gui/src/app/pages/devices.rs:1039 +#: crates/pdfs-gui/src/app/pages/devices.rs:1061 msgid "Remove Computer?" msgstr "Remover computador?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1042 +#: crates/pdfs-gui/src/app/pages/devices.rs:1064 #, rust-format msgid "" "Remove “{name}” from this account?\n" @@ -2501,26 +2525,26 @@ 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:1048 +#: crates/pdfs-gui/src/app/pages/devices.rs:1070 #: crates/pdfs-gui/src/app/pages/shared.rs:1025 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:634 #: crates/pdfs-gui/src/app/widgets/share_dialog.rs:876 msgid "Remove" msgstr "Remover" -#: crates/pdfs-gui/src/app/pages/devices.rs:1065 +#: crates/pdfs-gui/src/app/pages/devices.rs:1087 msgid "Computer removed" msgstr "Computador removido" -#: crates/pdfs-gui/src/app/pages/devices.rs:1066 +#: crates/pdfs-gui/src/app/pages/devices.rs:1088 msgid "Couldn't remove the computer" msgstr "Não foi possível remover o computador" -#: crates/pdfs-gui/src/app/pages/devices.rs:1084 +#: crates/pdfs-gui/src/app/pages/devices.rs:1106 msgid "Continue This Backup Here?" msgstr "Continuar este backup aqui?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1087 +#: crates/pdfs-gui/src/app/pages/devices.rs:1109 #, rust-format msgid "" "This computer takes over the backup of “{name}”, for example after a " @@ -2539,25 +2563,25 @@ msgstr "" "movidas. Depois, use “Restaurar neste computador” para recuperar o que " "“{name}” sincronizava." -#: crates/pdfs-gui/src/app/pages/devices.rs:1092 +#: crates/pdfs-gui/src/app/pages/devices.rs:1114 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:1099 +#: crates/pdfs-gui/src/app/pages/devices.rs:1121 #, rust-format msgid "Now backing up as “{name}”" msgstr "Agora fazendo backup como “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1108 +#: crates/pdfs-gui/src/app/pages/devices.rs:1130 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:1124 +#: crates/pdfs-gui/src/app/pages/devices.rs:1146 msgid "Sync With This Computer?" msgstr "Sincronizar com este computador?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1127 +#: crates/pdfs-gui/src/app/pages/devices.rs:1149 #, rust-format msgid "" "This computer and “{name}” keep the same folders in step. A change or a " @@ -2574,25 +2598,25 @@ msgstr "" "Drive. As pastas já sincronizadas aqui ficam onde estão. Em seguida, você " "escolhe quais pastas dele sincronizar aqui." -#: crates/pdfs-gui/src/app/pages/devices.rs:1132 +#: crates/pdfs-gui/src/app/pages/devices.rs:1154 msgid "Sync Here" msgstr "Sincronizar aqui" #. Translators: {name} is the name of the other computer this one now syncs with. -#: crates/pdfs-gui/src/app/pages/devices.rs:1139 +#: crates/pdfs-gui/src/app/pages/devices.rs:1161 #, rust-format msgid "Now syncing with “{name}”" msgstr "Agora sincronizando com “{name}”" -#: crates/pdfs-gui/src/app/pages/devices.rs:1155 +#: crates/pdfs-gui/src/app/pages/devices.rs:1177 msgid "Couldn't sync with that computer" msgstr "Não foi possível sincronizar com esse computador" -#: crates/pdfs-gui/src/app/pages/devices.rs:1187 +#: crates/pdfs-gui/src/app/pages/devices.rs:1209 msgid "Move Backup to This Computer?" msgstr "Mover o backup para este computador?" -#: crates/pdfs-gui/src/app/pages/devices.rs:1190 +#: crates/pdfs-gui/src/app/pages/devices.rs:1212 #, rust-format msgid "" "The folders backed up by “{name}” move into this computer's backup in Proton " @@ -2609,12 +2633,12 @@ msgstr "" "pastas sumirem." #. Translators: {name} is the name of the computer whose folders moved to this one. -#: crates/pdfs-gui/src/app/pages/devices.rs:1202 +#: crates/pdfs-gui/src/app/pages/devices.rs:1224 #, rust-format msgid "Moved the backup of “{name}”" msgstr "Backup de “{name}” movido" -#: crates/pdfs-gui/src/app/pages/devices.rs:1216 +#: crates/pdfs-gui/src/app/pages/devices.rs:1238 msgid "Couldn't move that backup" msgstr "Não foi possível mover esse backup" @@ -2892,7 +2916,7 @@ msgid "Sync paused until you resume" msgstr "Sincronização pausada até você retomar" #: crates/pdfs-gui/src/app/pages/locations.rs:570 -#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:290 +#: crates/pdfs-gui/src/app/pages/status.rs:1445 crates/pdfs-gui/src/main.rs:308 msgid "Sync paused" msgstr "Sincronização pausada" @@ -3351,7 +3375,7 @@ msgid "paused" msgstr "pausada" #. Translators: a folder's files can't be opened right now. -#: crates/pdfs-gui/src/app/pages/locations.rs:1742 +#: crates/pdfs-gui/src/app/pages/locations.rs:1745 msgid "not available" msgstr "indisponível" @@ -5093,7 +5117,7 @@ msgstr "Retoma {time}" msgid "Until you resume" msgstr "Até você retomar" -#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:292 +#: crates/pdfs-gui/src/app/pages/status.rs:1464 crates/pdfs-gui/src/main.rs:310 #, rust-format msgid "{n} change needs attention" msgid_plural "{n} changes need attention" @@ -6324,52 +6348,57 @@ msgstr "O Proton Drive não está respondendo" msgid "Signed in as {username} — locked" msgstr "Conectado como {username} — bloqueado" -#: crates/pdfs-gui/src/main.rs:200 +#: crates/pdfs-gui/src/main.rs:209 +#, rust-format +msgid "Signed in as {username} — can't open your Drive" +msgstr "Conectado como {username} — não foi possível abrir o Proton Drive" + +#: crates/pdfs-gui/src/main.rs:218 #, rust-format msgid "Signed in as {username} — not connected" msgstr "Conectado como {username} — sem conexão" -#: crates/pdfs-gui/src/main.rs:205 +#: crates/pdfs-gui/src/main.rs:223 msgid "Not signed in" msgstr "Não conectado" #. Translators: {error} is an error message, in English. -#: crates/pdfs-gui/src/main.rs:208 +#: crates/pdfs-gui/src/main.rs:226 #, rust-format msgid "Error: {error}" msgstr "Erro: {error}" -#: crates/pdfs-gui/src/main.rs:230 +#: crates/pdfs-gui/src/main.rs:248 msgid "Proton Drive sent an unexpected reply" msgstr "O Proton Drive enviou uma resposta inesperada" -#: crates/pdfs-gui/src/main.rs:284 +#: crates/pdfs-gui/src/main.rs:302 msgid "Session expired — sign in again" msgstr "Sessão expirada — entre novamente" -#: crates/pdfs-gui/src/main.rs:286 +#: crates/pdfs-gui/src/main.rs:304 msgid "Sign-in not saved — unlock your keyring" msgstr "Login não salvo — desbloqueie o chaveiro" -#: crates/pdfs-gui/src/main.rs:289 +#: crates/pdfs-gui/src/main.rs:307 msgid "Update not finished — restart Proton Drive" msgstr "Atualização não concluída — reinicie o Proton Drive" -#: crates/pdfs-gui/src/main.rs:299 +#: crates/pdfs-gui/src/main.rs:317 #, rust-format msgid "Up to date in {mountpoint} ({n} pinned)" msgid_plural "Up to date in {mountpoint} ({n} pinned)" msgstr[0] "Atualizado em {mountpoint} ({n} fixado)" msgstr[1] "Atualizado em {mountpoint} ({n} fixados)" -#: crates/pdfs-gui/src/main.rs:306 +#: crates/pdfs-gui/src/main.rs:324 #, rust-format msgid "Syncing — {queued} ({n} pinned)" msgid_plural "Syncing — {queued} ({n} pinned)" msgstr[0] "Sincronizando — {queued} ({n} fixado)" msgstr[1] "Sincronizando — {queued} ({n} fixados)" -#: crates/pdfs-gui/src/main.rs:313 +#: crates/pdfs-gui/src/main.rs:331 #, rust-format msgid "Offline — cached files only ({n} pinned)" msgid_plural "Offline — cached files only ({n} pinned)" @@ -6377,60 +6406,60 @@ msgstr[0] "Offline — somente arquivos em cache ({n} fixado)" msgstr[1] "Offline — somente arquivos em cache ({n} fixados)" #. Translators: {queued} is a queue summary such as "3 uploads queued". -#: crates/pdfs-gui/src/main.rs:319 +#: crates/pdfs-gui/src/main.rs:337 #, rust-format msgid "Offline — {queued}" msgstr "Offline — {queued}" #. Translators: a menu item that opens the list of failing changes. -#: crates/pdfs-gui/src/main.rs:358 +#: crates/pdfs-gui/src/main.rs:376 #, rust-format msgid "{n} issue — View" msgid_plural "{n} issues — View" msgstr[0] "{n} problema — Ver" msgstr[1] "{n} problemas — Ver" -#: crates/pdfs-gui/src/main.rs:465 +#: crates/pdfs-gui/src/main.rs:483 msgid "Sign In…" msgstr "Entrar…" -#: crates/pdfs-gui/src/main.rs:466 +#: crates/pdfs-gui/src/main.rs:484 msgid "Sign In Again…" msgstr "Entrar novamente…" -#: crates/pdfs-gui/src/main.rs:469 +#: crates/pdfs-gui/src/main.rs:487 msgid "Open Proton Drive" msgstr "Abrir o Proton Drive" -#: crates/pdfs-gui/src/main.rs:473 +#: crates/pdfs-gui/src/main.rs:491 msgid "Restart Proton Drive" msgstr "Reiniciar o Proton Drive" -#: crates/pdfs-gui/src/main.rs:482 +#: crates/pdfs-gui/src/main.rs:500 msgid "Open Folder" msgstr "Abrir pasta" -#: crates/pdfs-gui/src/main.rs:494 +#: crates/pdfs-gui/src/main.rs:512 msgid "Resume Sync" msgstr "Retomar sincronização" -#: crates/pdfs-gui/src/main.rs:496 +#: crates/pdfs-gui/src/main.rs:514 msgid "Pause Sync" msgstr "Pausar sincronização" -#: crates/pdfs-gui/src/main.rs:515 +#: crates/pdfs-gui/src/main.rs:533 msgid "Connect" msgstr "Conectar" -#: crates/pdfs-gui/src/main.rs:522 +#: crates/pdfs-gui/src/main.rs:540 msgid "Hide Tray Icon" msgstr "Ocultar ícone da bandeja" -#: crates/pdfs-gui/src/main.rs:526 +#: crates/pdfs-gui/src/main.rs:544 msgid "Stop Proton Drive…" msgstr "Parar o Proton Drive…" -#: crates/pdfs-gui/src/main.rs:530 +#: crates/pdfs-gui/src/main.rs:548 msgid "Quit" msgstr "Sair"