From 179b6555c4f343cd583caebf9300bab7e22738e4 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 16 Oct 2026 17:21:46 +0200 Subject: [PATCH] fix(gui): keep a saved sign-in when the keyring can't be read The app read the session with `auth::load().ok()`, so a locked keyring, a Secret Service that was not up yet or any D-Bus error showed the sign-in form as if nobody were signed in. The session was still saved (B253). Keyring errors are now kept apart from "not signed in". The login page shows a banner saying the saved sign-in could not be read, with a Retry button. The app tries again after 2, 5, 10 and 30 seconds and opens on Files once the keyring answers. The login page's header also loses the back button the collapsed split gave it. Translated into every shipped language. --- crates/pdfs-gui/src/app/main.rs | 13 +- crates/pdfs-gui/src/app/pages/login.rs | 88 +++++++++- docs/BUGS.md | 26 +++ po/de.po | 217 +++++++++++++------------ po/en_GB.po | 217 +++++++++++++------------ po/es.po | 217 +++++++++++++------------ po/fr.po | 217 +++++++++++++------------ po/it.po | 217 +++++++++++++------------ po/nl.po | 217 +++++++++++++------------ po/pdfs.pot | 216 ++++++++++++------------ po/pl.po | 217 +++++++++++++------------ po/pt_BR.po | 217 +++++++++++++------------ 12 files changed, 1129 insertions(+), 950 deletions(-) diff --git a/crates/pdfs-gui/src/app/main.rs b/crates/pdfs-gui/src/app/main.rs index c67232b..164410a 100644 --- a/crates/pdfs-gui/src/app/main.rs +++ b/crates/pdfs-gui/src/app/main.rs @@ -345,8 +345,12 @@ fn build_window(app: &adw::Application) { login_frame.add_top_bar(&{ let header = adw::HeaderBar::new(); header.set_show_title(false); + // The split is collapsed while signed out; going "back" would only + // uncover a sidebar with nowhere to go. + header.set_show_back_button(false); header }); + login_frame.add_top_bar(&login_widgets.keyring_banner); login_frame.set_content(Some(&login_page)); stack.add_named(&login_frame, Some("login")); stack.add_named(&browser_page, Some("browser")); @@ -383,13 +387,17 @@ fn build_window(app: &adw::Application) { let toasts = adw::ToastOverlay::new(); toasts.set_child(Some(&split)); + let (startup_session, keyring_error) = match read_session() { + Ok(session) => (session, None), + Err(e) => (None, Some(e)), + }; let ui = Rc::new(Ui { dirs, stack: stack.clone(), toasts: toasts.clone(), busy: Cell::new(0), opening: RefCell::new(HashSet::new()), - session: RefCell::new(auth::load().ok()), + session: RefCell::new(startup_session), unlock_offered: Cell::new(false), mounted: RefCell::new(false), sidebar: sidebar_list.clone(), @@ -648,6 +656,9 @@ fn build_window(app: &adw::Application) { }, }); wire_login(&ui); + if let Some(e) = keyring_error { + keyring_unreadable(&ui, &e); + } let ui_status = ui.clone(); main_widgets .status_button diff --git a/crates/pdfs-gui/src/app/pages/login.rs b/crates/pdfs-gui/src/app/pages/login.rs index 9c1151c..e7b0dc8 100644 --- a/crates/pdfs-gui/src/app/pages/login.rs +++ b/crates/pdfs-gui/src/app/pages/login.rs @@ -9,6 +9,9 @@ pub(crate) struct LoginState { /// Spins inside the Sign in button while a sign-in is running. pub(crate) login_spinner: Spinner, pub(crate) login_status: gtk4::Label, + /// Shown over the form while a saved sign-in exists but the keyring would + /// not hand it over; see [`keyring_unreadable`]. + pub(crate) keyring_banner: adw::Banner, } /// Where the login page's account links go. @@ -87,6 +90,12 @@ pub(crate) fn build_login_page() -> (gtk4::Widget, LoginState) { .child(&inner) .build(); let scroll = gtk4::ScrolledWindow::builder().child(&clamp).build(); + let keyring_banner = adw::Banner::builder() + .title(gettext( + "Couldn't read your saved sign-in. Unlock the system keyring, then try again.", + )) + .button_label(gettext("Retry")) + .build(); ( scroll.upcast(), @@ -96,10 +105,80 @@ pub(crate) fn build_login_page() -> (gtk4::Widget, LoginState) { login_button, login_spinner, login_status, + keyring_banner, }, ) } +/// The stored session, `None` when there is none, or the keyring's error when +/// it could not be read. A locked or absent keyring is not a sign-out: the +/// session is still saved, and signing in again would only replace it. +pub(crate) fn read_session() -> pdfs_core::Result> { + match auth::load() { + Ok(session) => Ok(Some(session)), + Err(pdfs_core::Error::NotLoggedIn) => Ok(None), + Err(e) => Err(e), + } +} + +/// How long to wait before each automatic retry of an unreadable keyring. A +/// keyring daemon started with the session is usually up within seconds. +const KEYRING_RETRIES: [u32; 4] = [2, 5, 10, 30]; + +/// The keyring refused to hand over the saved session. Keep the login page up, +/// but say why, and pick the session up as soon as the keyring answers: on its +/// own a few times, and whenever the user presses Retry. +pub(crate) fn keyring_unreadable(ui: &Rc, error: &pdfs_core::Error) { + tracing::warn!("cannot read the saved session: {error}"); + let banner = &ui.login.keyring_banner; + if banner.is_revealed() { + return; + } + banner.set_revealed(true); + let ui_retry = ui.clone(); + let handler = banner.connect_button_clicked(move |_| retry_session(&ui_retry)); + // Disconnected once the banner goes, so a later failure wires it afresh. + let handler = RefCell::new(Some(handler)); + banner.connect_revealed_notify(move |banner| { + if !banner.is_revealed() + && let Some(handler) = handler.borrow_mut().take() + { + banner.disconnect(handler); + } + }); + let ui = ui.clone(); + glib::spawn_future_local(async move { + for seconds in KEYRING_RETRIES { + glib::timeout_future_seconds(seconds).await; + if !ui.login.keyring_banner.is_revealed() { + return; + } + retry_session(&ui); + } + }); +} + +/// Read the saved session again after [`keyring_unreadable`]. Found: show the +/// signed-in window. Gone: the plain login page. Still unreadable: keep waiting. +fn retry_session(ui: &Rc) { + let ui = ui.clone(); + glib::spawn_future_local(async move { + let Ok(result) = gio::spawn_blocking(read_session).await else { + return; + }; + match result { + Ok(session) => { + ui.login.keyring_banner.set_revealed(false); + if session.is_some() && ui.session.borrow().is_none() { + *ui.session.borrow_mut() = session; + refresh(&ui); + } + } + Err(e) => tracing::debug!("the keyring still cannot be read: {e}"), + } + }); +} + /// Connect the sign-in button: read the fields, run [`auth::login`] on a worker /// thread, and report the outcome back on the main loop. pub(crate) fn wire_login(ui: &Rc) { @@ -202,10 +281,11 @@ pub(crate) fn wire_login(ui: &Rc) { // Read on a worker: a keyring that has to be unlocked first // answers only once the user has, and the window must not // freeze until then. - *ui.session.borrow_mut() = gio::spawn_blocking(|| auth::load().ok()) - .await - .ok() - .flatten(); + match gio::spawn_blocking(read_session).await { + Ok(Ok(session)) => *ui.session.borrow_mut() = session, + Ok(Err(e)) => keyring_unreadable(&ui, &e), + Err(_) => {} + } // Enable+start the mount service now that we have a session. let _ = gio::spawn_blocking(service::enable_start).await; refresh(&ui); diff --git a/docs/BUGS.md b/docs/BUGS.md index 6e2f8c1..92b99e3 100644 --- a/docs/BUGS.md +++ b/docs/BUGS.md @@ -100,6 +100,7 @@ a live account since. | Entry | Title | |---|---| +| [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 | | [B252](#b252--computers-sharing-a-device-overwrite-each-others-profile) | Computers sharing a device overwrite each other's profile | | [B251](#b251--two-computers-can-make-the-same-conflict-copy-name) | Two computers can make the same conflict copy name | | [B250](#b250--a-revision-made-during-a-mirror-download-never-arrives) | A revision made during a mirror download never arrives | @@ -355,6 +356,31 @@ a live account since. Newest first. +### B253 — The app shows the sign-in page when the keyring is locked + +**Status:** Fixed, not verified live +**Found:** 2026-10-16, user report: "the login each time you reboot or logout and back in". +**Where:** `build_window` in `crates/pdfs-gui/src/app/main.rs`; `wire_login` in +`crates/pdfs-gui/src/app/pages/login.rs` + +**Repro:** Sign in, then start `pdfs-app` while the Secret Service is locked, not yet running, +or unreachable. The app shows the sign-in form as if nobody were signed in. The saved session +is still in the keyring, and the daemon waits for it. + +**Cause.** The app read the session with `auth::load().ok()`, which turns every keyring error +into "signed out". Only `NotLoggedIn` means that. The tray already told the two apart. + +**Fix.** `read_session` keeps `NotLoggedIn` apart from keyring errors. On an error the login +page shows a banner saying the keyring could not be read, with a Retry button. The app reads +the keyring again after 2, 5, 10 and 30 seconds and on Retry, and opens on Files once it +answers, without asking for a sign-in. A sign-in whose session cannot be read back gets the +same banner. + +**Test:** None automated; the GTK window has no test harness. Checked by hand with +`DBUS_SESSION_BUS_ADDRESS` pointing nowhere: the banner shows over the form. + +--- + ### B252 — Computers sharing a device overwrite each other's profile **Status:** Fixed, not verified live diff --git a/po/de.po b/po/de.po index e717399..778e824 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-16 16:02+0200\n" +"POT-Creation-Date: 2026-10-16 17:09+0200\n" "PO-Revision-Date: 2026-09-23 12:00+0200\n" "Last-Translator: Proton Drive for Linux contributors\n" "Language-Team: German\n" @@ -57,7 +57,7 @@ msgstr "%-d. %b %Y" msgid "%A, %B %-d, %Y, %H:%M" msgstr "%A, %-d. %B %Y, %H:%M" -#: crates/pdfs-gui/src/app/main.rs:767 +#: crates/pdfs-gui/src/app/main.rs:849 #: crates/pdfs-gui/src/app/pages/activity_text.rs:539 #: crates/pdfs-gui/src/app/pages/browser.rs:454 #: crates/pdfs-gui/src/app/pages/browser.rs:833 @@ -70,74 +70,74 @@ msgstr "%A, %-d. %B %Y, %H:%M" msgid "My files" msgstr "Meine Dateien" -#: crates/pdfs-gui/src/app/main.rs:770 crates/pdfs-gui/src/app/main.rs:1415 +#: crates/pdfs-gui/src/app/main.rs:852 crates/pdfs-gui/src/app/main.rs:1517 #: crates/pdfs-gui/src/app/pages/albums.rs:638 #: crates/pdfs-gui/src/app/pages/photos.rs:845 #: crates/pdfs-gui/src/app/pages/places.rs:441 msgid "Photos" msgstr "Fotos" -#: crates/pdfs-gui/src/app/main.rs:775 +#: crates/pdfs-gui/src/app/main.rs:857 #: crates/pdfs-gui/src/app/pages/shared.rs:168 #: crates/pdfs-gui/src/app/pages/shared.rs:484 msgid "Shared with me" msgstr "Mit mir geteilt" -#: crates/pdfs-gui/src/app/main.rs:780 +#: crates/pdfs-gui/src/app/main.rs:862 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120 msgid "Shared by me" msgstr "Von mir geteilt" -#: crates/pdfs-gui/src/app/main.rs:783 +#: crates/pdfs-gui/src/app/main.rs:865 #: crates/pdfs-gui/src/app/pages/devices.rs:140 #: crates/pdfs-gui/src/app/pages/devices.rs:493 msgid "Computers" msgstr "Computer" -#: crates/pdfs-gui/src/app/main.rs:784 +#: crates/pdfs-gui/src/app/main.rs:866 #: crates/pdfs-gui/src/app/pages/trash.rs:101 msgid "Trash" msgstr "Papierkorb" #. Translators: sidebar entry for the page listing this computer's synced folders. -#: crates/pdfs-gui/src/app/main.rs:786 +#: crates/pdfs-gui/src/app/main.rs:868 msgid "Sync" msgstr "Synchronisierung" #. Translators: a failed action's toast. {what} says what failed, such as #. "Couldn't rename"; {detail} is the reason, usually untranslated text #. from the mount service. -#: crates/pdfs-gui/src/app/main.rs:1112 +#: crates/pdfs-gui/src/app/main.rs:1214 #: crates/pdfs-gui/src/app/pages/takeout.rs:714 #, rust-format msgid "{what}: {detail}" msgstr "{what}: {detail}" -#: crates/pdfs-gui/src/app/main.rs:1135 +#: crates/pdfs-gui/src/app/main.rs:1237 msgid "You're offline" msgstr "Keine Internetverbindung" -#: crates/pdfs-gui/src/app/main.rs:1136 +#: crates/pdfs-gui/src/app/main.rs:1238 msgid "That's not there any more" msgstr "Das ist nicht mehr vorhanden" -#: crates/pdfs-gui/src/app/main.rs:1137 +#: crates/pdfs-gui/src/app/main.rs:1239 msgid "You don't have access to that" msgstr "Kein Zugriff darauf" -#: crates/pdfs-gui/src/app/main.rs:1138 +#: crates/pdfs-gui/src/app/main.rs:1240 msgid "Something changed this first" msgstr "Etwas anderes hat dies zuerst geändert" -#: crates/pdfs-gui/src/app/main.rs:1139 +#: crates/pdfs-gui/src/app/main.rs:1241 msgid "Your Proton Drive is full" msgstr "Das Proton Drive ist voll" -#: crates/pdfs-gui/src/app/main.rs:1226 +#: crates/pdfs-gui/src/app/main.rs:1328 msgid "Proton Drive for Linux" msgstr "Proton Drive for Linux" -#: crates/pdfs-gui/src/app/main.rs:1234 +#: crates/pdfs-gui/src/app/main.rs:1336 msgid "" "Files-on-demand Proton Drive for the Linux desktop.\n" "\n" @@ -149,23 +149,23 @@ msgstr "" "Inoffizieller Client – nicht mit Proton AG verbunden, von Proton AG " "unterstützt oder befürwortet." -#: crates/pdfs-gui/src/app/main.rs:1239 +#: crates/pdfs-gui/src/app/main.rs:1341 msgid "Source Code" msgstr "Quellcode" -#: crates/pdfs-gui/src/app/main.rs:1243 +#: crates/pdfs-gui/src/app/main.rs:1345 msgid "Proton Terms of Service" msgstr "Nutzungsbedingungen von Proton" -#: crates/pdfs-gui/src/app/main.rs:1247 +#: crates/pdfs-gui/src/app/main.rs:1349 msgid "Proton Privacy Policy" msgstr "Datenschutzerklärung von Proton" -#: crates/pdfs-gui/src/app/main.rs:1252 +#: crates/pdfs-gui/src/app/main.rs:1354 msgid "Disclaimer" msgstr "Haftungsausschluss" -#: crates/pdfs-gui/src/app/main.rs:1256 +#: crates/pdfs-gui/src/app/main.rs:1358 msgid "" "This is an unofficial, community-built client. It is not affiliated with, " "endorsed by, or supported by Proton AG.\n" @@ -190,7 +190,7 @@ msgstr "" "bereitgestellt. Daten, deren Verlust nicht hinnehmbar ist, sollten " "zusätzlich gesichert werden." -#: crates/pdfs-gui/src/app/main.rs:1264 +#: crates/pdfs-gui/src/app/main.rs:1366 msgid "" "Places are named with data from GeoNames, licensed under GeoNames, lizenziert unter CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1272 +#: crates/pdfs-gui/src/app/main.rs:1374 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -210,7 +210,7 @@ msgstr "" "href=\"https://www.naturalearthdata.com/\">Natural Earth gezeichnet, die " "gemeinfrei sind." -#: crates/pdfs-gui/src/app/main.rs:1280 +#: crates/pdfs-gui/src/app/main.rs:1382 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, licensed under CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1272 +#: crates/pdfs-gui/src/app/main.rs:1374 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -207,7 +207,7 @@ msgstr "" "\">Natural Earth land outlines and country borders, which are in the " "public domain." -#: crates/pdfs-gui/src/app/main.rs:1280 +#: crates/pdfs-gui/src/app/main.rs:1382 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, con licencia CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1272 +#: crates/pdfs-gui/src/app/main.rs:1374 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -208,7 +208,7 @@ msgstr "" "fronteras de países de Natural " "Earth, que son de dominio público." -#: crates/pdfs-gui/src/app/main.rs:1280 +#: crates/pdfs-gui/src/app/main.rs:1382 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, sous licence CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1272 +#: crates/pdfs-gui/src/app/main.rs:1374 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -209,7 +209,7 @@ msgstr "" "frontières des pays de Natural " "Earth, qui sont dans le domaine public." -#: crates/pdfs-gui/src/app/main.rs:1280 +#: crates/pdfs-gui/src/app/main.rs:1382 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, con licenza CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1272 +#: crates/pdfs-gui/src/app/main.rs:1374 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -207,7 +207,7 @@ msgstr "" "confini nazionali di Natural " "Earth, che sono di pubblico dominio." -#: crates/pdfs-gui/src/app/main.rs:1280 +#: crates/pdfs-gui/src/app/main.rs:1382 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, gelicentieerd onder CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1272 +#: crates/pdfs-gui/src/app/main.rs:1374 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -208,7 +208,7 @@ msgstr "" "href=\"https://www.naturalearthdata.com/\">Natural Earth, die in het " "publieke domein vallen." -#: crates/pdfs-gui/src/app/main.rs:1280 +#: crates/pdfs-gui/src/app/main.rs:1382 msgid "" "The street map, when turned on, comes from OpenFreeMap in the \n" "Language-Team: LANGUAGE \n" @@ -60,7 +60,7 @@ msgstr "" msgid "%A, %B %-d, %Y, %H:%M" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:767 +#: crates/pdfs-gui/src/app/main.rs:849 #: crates/pdfs-gui/src/app/pages/activity_text.rs:539 #: crates/pdfs-gui/src/app/pages/browser.rs:454 #: crates/pdfs-gui/src/app/pages/browser.rs:833 @@ -73,74 +73,74 @@ msgstr "" msgid "My files" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:770 crates/pdfs-gui/src/app/main.rs:1415 +#: crates/pdfs-gui/src/app/main.rs:852 crates/pdfs-gui/src/app/main.rs:1517 #: crates/pdfs-gui/src/app/pages/albums.rs:638 #: crates/pdfs-gui/src/app/pages/photos.rs:845 #: crates/pdfs-gui/src/app/pages/places.rs:441 msgid "Photos" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:775 +#: crates/pdfs-gui/src/app/main.rs:857 #: crates/pdfs-gui/src/app/pages/shared.rs:168 #: crates/pdfs-gui/src/app/pages/shared.rs:484 msgid "Shared with me" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:780 +#: crates/pdfs-gui/src/app/main.rs:862 #: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120 msgid "Shared by me" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:783 +#: crates/pdfs-gui/src/app/main.rs:865 #: crates/pdfs-gui/src/app/pages/devices.rs:140 #: crates/pdfs-gui/src/app/pages/devices.rs:493 msgid "Computers" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:784 +#: crates/pdfs-gui/src/app/main.rs:866 #: crates/pdfs-gui/src/app/pages/trash.rs:101 msgid "Trash" msgstr "" #. Translators: sidebar entry for the page listing this computer's synced folders. -#: crates/pdfs-gui/src/app/main.rs:786 +#: crates/pdfs-gui/src/app/main.rs:868 msgid "Sync" msgstr "" #. Translators: a failed action's toast. {what} says what failed, such as #. "Couldn't rename"; {detail} is the reason, usually untranslated text #. from the mount service. -#: crates/pdfs-gui/src/app/main.rs:1112 +#: crates/pdfs-gui/src/app/main.rs:1214 #: crates/pdfs-gui/src/app/pages/takeout.rs:714 #, rust-format msgid "{what}: {detail}" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1135 +#: crates/pdfs-gui/src/app/main.rs:1237 msgid "You're offline" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1136 +#: crates/pdfs-gui/src/app/main.rs:1238 msgid "That's not there any more" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1137 +#: crates/pdfs-gui/src/app/main.rs:1239 msgid "You don't have access to that" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1138 +#: crates/pdfs-gui/src/app/main.rs:1240 msgid "Something changed this first" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1139 +#: crates/pdfs-gui/src/app/main.rs:1241 msgid "Your Proton Drive is full" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1226 +#: crates/pdfs-gui/src/app/main.rs:1328 msgid "Proton Drive for Linux" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1234 +#: crates/pdfs-gui/src/app/main.rs:1336 msgid "" "Files-on-demand Proton Drive for the Linux desktop.\n" "\n" @@ -148,23 +148,23 @@ msgid "" "AG." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1239 +#: crates/pdfs-gui/src/app/main.rs:1341 msgid "Source Code" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1243 +#: crates/pdfs-gui/src/app/main.rs:1345 msgid "Proton Terms of Service" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1247 +#: crates/pdfs-gui/src/app/main.rs:1349 msgid "Proton Privacy Policy" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1252 +#: crates/pdfs-gui/src/app/main.rs:1354 msgid "Disclaimer" msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1256 +#: crates/pdfs-gui/src/app/main.rs:1358 msgid "" "This is an unofficial, community-built client. It is not affiliated with, " "endorsed by, or supported by Proton AG.\n" @@ -177,21 +177,21 @@ msgid "" "of any data you cannot afford to lose." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1264 +#: crates/pdfs-gui/src/app/main.rs:1366 msgid "" "Places are named with data from GeoNames, licensed under CC BY 4.0." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1272 +#: crates/pdfs-gui/src/app/main.rs:1374 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " "public domain." msgstr "" -#: crates/pdfs-gui/src/app/main.rs:1280 +#: crates/pdfs-gui/src/app/main.rs:1382 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames udostępnionych na licencji CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1272 +#: crates/pdfs-gui/src/app/main.rs:1374 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -211,7 +211,7 @@ msgstr "" "www.naturalearthdata.com/\">Natural Earth, które należą do domeny " "publicznej." -#: crates/pdfs-gui/src/app/main.rs:1280 +#: crates/pdfs-gui/src/app/main.rs:1382 msgid "" "The street map, when turned on, comes from OpenFreeMap in the GeoNames, licensed under GeoNames, licenciados sob CC BY 4.0." -#: crates/pdfs-gui/src/app/main.rs:1272 +#: crates/pdfs-gui/src/app/main.rs:1374 msgid "" "The places map is drawn from Natural Earth land outlines and country borders, which are in the " @@ -207,7 +207,7 @@ msgstr "" "fronteiras de países do Natural Earth, que são de domínio público." -#: crates/pdfs-gui/src/app/main.rs:1280 +#: crates/pdfs-gui/src/app/main.rs:1382 msgid "" "The street map, when turned on, comes from OpenFreeMap in the