diff --git a/Cargo.lock b/Cargo.lock index fd93ea10..7ca2cdae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2214,6 +2214,7 @@ dependencies = [ "windows-sys 0.61.2", "winresource", "x11rb", + "zip", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3f3a10ec..36cd74f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,8 @@ base64 = "0.22" percent-encoding = "2" data-url = "0.3" html5ever = "0.40" +# Snapshot archives one verified database snapshot for the document provider. +zip = { version = "9.0.4", default-features = false } cssparser = "0.38" # Match Blitz's Stylo version when adapting resolved CSS colors and gradients. style = { package = "stylo", version = "=0.22.0" } diff --git a/lang/de/LC_MESSAGES/flectar-mail.po b/lang/de/LC_MESSAGES/flectar-mail.po index f1808c7a..c45ba670 100644 --- a/lang/de/LC_MESSAGES/flectar-mail.po +++ b/lang/de/LC_MESSAGES/flectar-mail.po @@ -186,7 +186,7 @@ msgstr "Gesprächsverlauf verfügbar" msgid "New address-book entry" msgstr "Neuer Adressbuch-Eintrag" -#: ui/app.slint:121 ui/app.slint:847 ui/components/composer-fields.slint:66 +#: ui/app.slint:121 ui/app.slint:845 ui/components/composer-fields.slint:66 #: ui/dialogs/compose-dialog.slint:37 msgid "No connected account" msgstr "Kein verknüpftes Konto" @@ -201,11 +201,11 @@ msgstr[1] "{n} Interaktionen" msgid "Last contacted {} {}, {}" msgstr "" -#: ui/app.slint:128 ui/app.slint:361 +#: ui/app.slint:128 ui/app.slint:359 msgid "Loading local data…" msgstr "Lade lokale Daten…" -#: ui/app.slint:129 ui/app.slint:362 +#: ui/app.slint:129 ui/app.slint:360 msgid "Local data ready." msgstr "Lokale Daten bereit." @@ -666,750 +666,742 @@ msgid "Choose where to export the database snapshot" msgstr "" #: ui/app.slint:289 -msgid "The platform host does not provide document export." -msgstr "" - -#: ui/app.slint:290 -msgid "The platform host does not provide document import." -msgstr "" - -#: ui/app.slint:291 msgid "Backup exported. Passwords and OAuth tokens were not included." msgstr "" -#: ui/app.slint:292 +#: ui/app.slint:290 msgid "Could not export backup: {}" msgstr "" -#: ui/app.slint:293 +#: ui/app.slint:291 msgid "Local data storage is unavailable. Retry startup." msgstr "" -#: ui/app.slint:294 +#: ui/app.slint:292 msgid "Creating verified database snapshot…" msgstr "" -#: ui/app.slint:295 +#: ui/app.slint:293 msgid "Backup imported. All {} account configurations already existed." msgstr "" -#: ui/app.slint:296 +#: ui/app.slint:294 msgid "" "Imported {} account configurations. Open Accounts and sign in to each one." msgstr "" -#: ui/app.slint:297 +#: ui/app.slint:295 msgid "Could not import backup: {}" msgstr "" -#: ui/app.slint:298 +#: ui/app.slint:296 msgid "All local Flectar Mail data was deleted." msgstr "" -#: ui/app.slint:299 +#: ui/app.slint:297 msgid "Could not delete all local data: {}" msgstr "" -#: ui/app.slint:300 +#: ui/app.slint:298 msgid "Deleting local accounts and cached data…" msgstr "" -#: ui/app.slint:305 +#: ui/app.slint:303 msgid "Background mail refresh failed: {}" msgstr "" -#: ui/app.slint:306 +#: ui/app.slint:304 msgid "No conversations in this folder" msgstr "" -#: ui/app.slint:307 +#: ui/app.slint:305 msgid "No conversations match this search" msgstr "" -#: ui/app.slint:308 +#: ui/app.slint:306 msgid "Mail core is ready for account synchronization and message actions." msgstr "" -#: ui/app.slint:309 +#: ui/app.slint:307 msgid "Large message rendered in compatibility mode." msgstr "" -#: ui/app.slint:310 +#: ui/app.slint:308 msgid "Software email render failed: {}" msgstr "" -#: ui/app.slint:311 +#: ui/app.slint:309 msgid "Rendered with software fallback after GPU error: {}" msgstr "" -#: ui/app.slint:312 +#: ui/app.slint:310 msgid "Blitz render failed (GPU: {}; software: {})" msgstr "" -#: ui/app.slint:317 +#: ui/app.slint:315 msgid "Add an email address, then save the contact." msgstr "" -#: ui/app.slint:318 +#: ui/app.slint:316 msgid "Enter a valid email address before saving." msgstr "" -#: ui/app.slint:319 +#: ui/app.slint:317 msgid "Could not save: contact storage is unavailable." msgstr "" -#: ui/app.slint:320 +#: ui/app.slint:318 msgid "Could not save contact: {}" msgstr "" -#: ui/app.slint:321 +#: ui/app.slint:319 msgid "Contact saved." msgstr "" -#: ui/app.slint:322 +#: ui/app.slint:320 msgid "Saved to contacts." msgstr "" -#: ui/app.slint:323 +#: ui/app.slint:321 msgid "Could not delete: contact storage is unavailable." msgstr "" -#: ui/app.slint:324 +#: ui/app.slint:322 msgid "Could not delete contact: {}" msgstr "" -#: ui/app.slint:325 +#: ui/app.slint:323 msgid "Contact deleted." msgstr "" -#: ui/app.slint:326 +#: ui/app.slint:324 msgid "Could not load contacts: {}" msgstr "" -#: ui/app.slint:327 +#: ui/app.slint:325 msgid "Local contact storage is still starting…" msgstr "" -#: ui/app.slint:328 +#: ui/app.slint:326 msgid "Loading contacts…" msgstr "" -#: ui/app.slint:333 ui/components/calendar-agenda.slint:233 +#: ui/app.slint:331 ui/components/calendar-agenda.slint:233 msgid "Local calendar" msgstr "Lokaler Kalender" -#: ui/app.slint:334 ui/components/calendar.slint:271 +#: ui/app.slint:332 ui/components/calendar.slint:271 #: ui/components/calendar.slint:319 ui/components/calendar.slint:434 msgid "Calendar" msgstr "" -#: ui/app.slint:335 +#: ui/app.slint:333 msgid "Could not load calendar: {}" msgstr "" -#: ui/app.slint:336 +#: ui/app.slint:334 msgid "Calendar shown and syncing." msgstr "" -#: ui/app.slint:337 +#: ui/app.slint:335 msgid "Calendar hidden and sync paused." msgstr "" -#: ui/app.slint:338 +#: ui/app.slint:336 msgid "Calendar update failed: {}" msgstr "" -#: ui/app.slint:339 +#: ui/app.slint:337 msgid "Only events created in Flectar Mail can be edited." msgstr "" -#: ui/app.slint:340 +#: ui/app.slint:338 msgid "Only events created in Flectar Mail can be deleted." msgstr "" -#: ui/app.slint:341 +#: ui/app.slint:339 msgid "Calendar storage is unavailable." msgstr "" -#: ui/app.slint:342 +#: ui/app.slint:340 msgid "Could not delete event: {}" msgstr "" -#: ui/app.slint:343 +#: ui/app.slint:341 msgid "Event deleted, but refresh failed: {}" msgstr "" -#: ui/app.slint:344 +#: ui/app.slint:342 msgid "Event deleted." msgstr "" -#: ui/app.slint:345 +#: ui/app.slint:343 msgid "Use YYYY-MM-DD and a 24-hour time such as 09:30." msgstr "" -#: ui/app.slint:346 +#: ui/app.slint:344 msgid "Event duration must be a number of minutes." msgstr "" -#: ui/app.slint:347 +#: ui/app.slint:345 msgid "Add a title and choose a duration from 15 to 1440 minutes." msgstr "" -#: ui/app.slint:348 +#: ui/app.slint:346 msgid "That local time does not exist in the current time zone." msgstr "" -#: ui/app.slint:349 +#: ui/app.slint:347 msgid "Could not save event: {}" msgstr "" -#: ui/app.slint:350 +#: ui/app.slint:348 msgid "Event saved, but refresh failed: {}" msgstr "" -#: ui/app.slint:351 +#: ui/app.slint:349 msgid "Event saved. Provider sync will run when enabled." msgstr "" -#: ui/app.slint:356 +#: ui/app.slint:354 msgid "Email resource render failed: {}" msgstr "" -#: ui/app.slint:357 +#: ui/app.slint:355 msgid "Message refresh failed: {}" msgstr "" -#: ui/app.slint:358 +#: ui/app.slint:356 msgid "Message load failed: {}" msgstr "" -#: ui/app.slint:359 +#: ui/app.slint:357 msgid "" "Your local mailbox could not be opened. No account operation was started. " "Retry, or copy the diagnostics for support." msgstr "" -#: ui/app.slint:360 +#: ui/app.slint:358 msgid "Local storage startup failed." msgstr "" -#: ui/app.slint:367 +#: ui/app.slint:365 msgid "Could not identify the dragged account." msgstr "" -#: ui/app.slint:368 +#: ui/app.slint:366 msgid "Account ordering requires local mail data." msgstr "" -#: ui/app.slint:369 +#: ui/app.slint:367 msgid "Could not find the account to reorder." msgstr "" -#: ui/app.slint:370 +#: ui/app.slint:368 msgid "Saving account order…" msgstr "" -#: ui/app.slint:371 +#: ui/app.slint:369 msgid "Sent copy settings require local mail data." msgstr "Die Einstellungen für gesendete Kopien erfordern lokale E-Mail-Daten." -#: ui/app.slint:372 +#: ui/app.slint:370 msgid "Updating Sent copy settings…" msgstr "Einstellungen für gesendete Kopien werden aktualisiert…" -#: ui/app.slint:373 +#: ui/app.slint:371 msgid "Sent copy settings updated." msgstr "Einstellungen für gesendete Kopien aktualisiert." -#: ui/app.slint:374 +#: ui/app.slint:372 msgid "Could not update Sent copy settings: {}" msgstr "" "Einstellungen für gesendete Kopien konnten nicht aktualisiert werden: {}" -#: ui/app.slint:375 +#: ui/app.slint:373 msgid "Mail history requires local mail data." msgstr "" -#: ui/app.slint:376 +#: ui/app.slint:374 msgid "Updating mail history…" msgstr "" -#: ui/app.slint:377 +#: ui/app.slint:375 msgid "Remote images are unavailable in this minimal build." msgstr "" -#: ui/app.slint:378 +#: ui/app.slint:376 msgid "Could not save remote-image setting: {}" msgstr "" -#: ui/app.slint:379 +#: ui/app.slint:377 msgid "Remote sender icons unavailable: {}" msgstr "" -#: ui/app.slint:380 +#: ui/app.slint:378 msgid "Remote images unavailable: {}" msgstr "" -#: ui/app.slint:381 +#: ui/app.slint:379 msgid "Remote body images and sender favicons enabled." msgstr "" -#: ui/app.slint:382 +#: ui/app.slint:380 msgid "Remote images blocked." msgstr "" -#: ui/app.slint:383 +#: ui/app.slint:381 msgid "Could not refresh message images: {}" msgstr "" -#: ui/app.slint:384 +#: ui/app.slint:382 msgid "Remote-image loading is disabled." msgstr "" -#: ui/app.slint:385 +#: ui/app.slint:383 msgid "Could not load message images: {}" msgstr "" -#: ui/app.slint:386 +#: ui/app.slint:384 msgid "Sync finished, refresh failed: {}" msgstr "" -#: ui/app.slint:387 +#: ui/app.slint:385 msgid "Sync complete." msgstr "" -#: ui/app.slint:388 +#: ui/app.slint:386 msgid "Sync failed: {}" msgstr "" -#: ui/app.slint:389 +#: ui/app.slint:387 msgid "Local mail data is unavailable; preview data is read-only." msgstr "" -#: ui/app.slint:390 +#: ui/app.slint:388 msgid "Synchronizing accounts…" msgstr "" -#: ui/app.slint:395 +#: ui/app.slint:393 msgid "Message action completed." msgstr "" -#: ui/app.slint:396 +#: ui/app.slint:394 msgid "" "Permanent deletion queued. Mail will disappear after the server confirms." msgstr "" -#: ui/app.slint:397 +#: ui/app.slint:395 msgid "Message action failed: {}" msgstr "" -#: ui/app.slint:398 +#: ui/app.slint:396 msgid "Message action failed: message is no longer available" msgstr "" -#: ui/app.slint:399 +#: ui/app.slint:397 msgid "Label added." msgstr "" -#: ui/app.slint:400 +#: ui/app.slint:398 msgid "Label removed." msgstr "" -#: ui/app.slint:401 +#: ui/app.slint:399 msgid "Label created and added." msgstr "" -#: ui/app.slint:402 +#: ui/app.slint:400 msgid "Label created." msgstr "" -#: ui/app.slint:403 +#: ui/app.slint:401 msgid "Label updated." msgstr "" -#: ui/app.slint:404 +#: ui/app.slint:402 msgid "Label deleted." msgstr "" -#: ui/app.slint:405 +#: ui/app.slint:403 msgid "Could not update label: {}" msgstr "" -#: ui/app.slint:406 +#: ui/app.slint:404 msgid "Could not save label: {}" msgstr "" -#: ui/app.slint:407 +#: ui/app.slint:405 msgid "Could not delete label: {}" msgstr "" -#: ui/app.slint:408 +#: ui/app.slint:406 msgid "Folder created." msgstr "" -#: ui/app.slint:409 +#: ui/app.slint:407 msgid "Folder renamed." msgstr "" -#: ui/app.slint:410 +#: ui/app.slint:408 msgid "Folder deleted." msgstr "" -#: ui/app.slint:411 +#: ui/app.slint:409 msgid "Could not create folder: {}" msgstr "" -#: ui/app.slint:412 +#: ui/app.slint:410 msgid "Could not rename folder: {}" msgstr "" -#: ui/app.slint:413 +#: ui/app.slint:411 msgid "Could not delete folder: {}" msgstr "" -#: ui/app.slint:414 +#: ui/app.slint:412 msgid "Could not close preview: {}" msgstr "" -#: ui/app.slint:415 +#: ui/app.slint:413 msgid "Opened the system print view." msgstr "" -#: ui/app.slint:416 +#: ui/app.slint:414 msgid "Could not print message: {}" msgstr "" -#: ui/app.slint:417 +#: ui/app.slint:415 msgid "Opened the message in your browser." msgstr "" -#: ui/app.slint:418 +#: ui/app.slint:416 msgid "Could not open message in browser: {}" msgstr "" -#: ui/app.slint:419 +#: ui/app.slint:417 msgid "Connect an account before replying or forwarding." msgstr "" -#: ui/app.slint:420 +#: ui/app.slint:418 msgid "Choose a message first." msgstr "" -#: ui/app.slint:421 +#: ui/app.slint:419 msgid "Only drafts can be reopened for editing." msgstr "" -#: ui/app.slint:422 +#: ui/app.slint:420 msgid "Could not open draft: {}" msgstr "" -#: ui/app.slint:423 +#: ui/app.slint:421 msgid "Could not open draft: invalid account identifier" msgstr "" -#: ui/app.slint:424 +#: ui/app.slint:422 msgid "Connected account" msgstr "" -#: ui/app.slint:425 +#: ui/app.slint:423 msgid "Could not open composer: {}" msgstr "" -#: ui/app.slint:426 +#: ui/app.slint:424 msgid "Could not open composer: invalid account identifier" msgstr "" -#: ui/app.slint:431 +#: ui/app.slint:429 msgid "Attach files to this email" msgstr "" -#: ui/app.slint:432 -msgid "The platform host does not provide a document picker." +#: ui/app.slint:430 +msgid "Could not add attachments: {}" msgstr "" -#: ui/app.slint:433 +#: ui/app.slint:431 msgid "Added 1 attachment." msgstr "" -#: ui/app.slint:434 +#: ui/app.slint:432 msgid "Added {} attachments." msgstr "" -#: ui/app.slint:435 +#: ui/app.slint:433 msgid "" "Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB " "total limit)." msgstr "" -#: ui/app.slint:436 +#: ui/app.slint:434 msgid "Attachment removed." msgstr "" -#: ui/app.slint:437 +#: ui/app.slint:435 msgid "Choose the account this message should be sent from." msgstr "" -#: ui/app.slint:438 +#: ui/app.slint:436 msgid "Choose a verified sender identity for this message." msgstr "" -#: ui/app.slint:439 +#: ui/app.slint:437 msgid "Connect an account before saving or sending mail." msgstr "" -#: ui/app.slint:440 +#: ui/app.slint:438 msgid "Message queued for delivery (action {})." msgstr "" -#: ui/app.slint:441 +#: ui/app.slint:439 msgid "Sending message…" msgstr "" -#: ui/app.slint:442 +#: ui/app.slint:440 msgid "Message sent." msgstr "" -#: ui/app.slint:443 +#: ui/app.slint:441 msgid "Message was not sent: {} Your draft is preserved." msgstr "" -#: ui/app.slint:444 +#: ui/app.slint:442 msgid "Draft saved locally (#{})." msgstr "" -#: ui/app.slint:445 +#: ui/app.slint:443 msgid "Compose failed: {}" msgstr "" -#: ui/app.slint:446 +#: ui/app.slint:444 msgid "Connect an account before managing email templates." msgstr "" -#: ui/app.slint:447 +#: ui/app.slint:445 msgid "Could not load email templates: {}" msgstr "" -#: ui/app.slint:448 +#: ui/app.slint:446 msgid "Enter a template name." msgstr "" -#: ui/app.slint:449 +#: ui/app.slint:447 msgid "Add a subject or message to the template." msgstr "" -#: ui/app.slint:450 +#: ui/app.slint:448 msgid "Email template saved." msgstr "" -#: ui/app.slint:451 +#: ui/app.slint:449 msgid "Could not save email template: {}" msgstr "" -#: ui/app.slint:452 +#: ui/app.slint:450 msgid "Email template deleted." msgstr "" -#: ui/app.slint:453 +#: ui/app.slint:451 msgid "Could not delete email template: {}" msgstr "" -#: ui/app.slint:454 +#: ui/app.slint:452 msgid "Email template is no longer available." msgstr "" -#: ui/app.slint:455 +#: ui/app.slint:453 msgid "Email template inserted." msgstr "" -#: ui/app.slint:456 +#: ui/app.slint:454 msgid "Email template inserted, but its usage could not be saved: {}" msgstr "" -#: ui/app.slint:457 +#: ui/app.slint:455 msgid "IMAP port must be between 1 and 65535." msgstr "" -#: ui/app.slint:458 +#: ui/app.slint:456 msgid "SMTP port must be between 1 and 65535." msgstr "" -#: ui/app.slint:459 +#: ui/app.slint:457 msgid "Local mail data is unavailable. Retry startup." msgstr "" -#: ui/app.slint:460 +#: ui/app.slint:458 msgid "Discovering JMAP and checking capabilities…" msgstr "" -#: ui/app.slint:461 +#: ui/app.slint:459 msgid "Certificate imported. Connect to verify the server." msgstr "" -#: ui/app.slint:462 +#: ui/app.slint:460 msgid "Could not load account preferences: {}" msgstr "" -#: ui/app.slint:463 +#: ui/app.slint:461 msgid "" "Your edited signature was preserved. Choose No signature to keep it as " "message text before inserting another." msgstr "" -#: ui/app.slint:464 +#: ui/app.slint:462 msgid "Certificate import failed: {}" msgstr "" -#: ui/app.slint:465 +#: ui/app.slint:463 msgid "Paste the PEM certificate into the certificate field." msgstr "" -#: ui/app.slint:466 +#: ui/app.slint:464 msgid "Testing IMAP and SMTP connections…" msgstr "" -#: ui/app.slint:471 +#: ui/app.slint:469 msgid "" "Another browser authorization is already in progress; finish it or choose " "Cancel." msgstr "" -#: ui/app.slint:472 ui/dialogs/settings-dialog.slint:1643 +#: ui/app.slint:470 ui/dialogs/settings-dialog.slint:1643 #: ui/dialogs/settings-dialog.slint:1686 ui/views/onboarding-view.slint:152 msgid "Add app keys in Sign-in settings to enable this provider." msgstr "" -#: ui/app.slint:473 +#: ui/app.slint:471 msgid "Choose Gmail or Outlook OAuth." msgstr "" -#: ui/app.slint:474 +#: ui/app.slint:472 msgid "Complete sign-in in your browser, or choose Cancel here." msgstr "" -#: ui/app.slint:475 +#: ui/app.slint:473 msgid "Cancelling browser sign-in…" msgstr "" -#: ui/app.slint:476 +#: ui/app.slint:474 msgid "Finishing browser sign-in…" msgstr "" -#: ui/app.slint:477 +#: ui/app.slint:475 msgid "Could not use that browser address: {}" msgstr "" -#: ui/app.slint:478 +#: ui/app.slint:476 msgid "Calendar core is unavailable." msgstr "" -#: ui/app.slint:479 +#: ui/app.slint:477 msgid "Account no longer exists." msgstr "" -#: ui/app.slint:480 +#: ui/app.slint:478 msgid "Authorize calendar access in your browser." msgstr "" -#: ui/app.slint:481 +#: ui/app.slint:479 msgid "OAuth app keys saved." msgstr "" -#: ui/app.slint:482 +#: ui/app.slint:480 msgid "{} OAuth settings saved." msgstr "" -#: ui/app.slint:483 +#: ui/app.slint:481 msgid "OAuth settings failed: {}" msgstr "" -#: ui/app.slint:484 +#: ui/app.slint:482 msgid "Mail refresh failed: {}" msgstr "" -#: ui/app.slint:485 +#: ui/app.slint:483 msgid "Mail filter failed: {}" msgstr "" -#: ui/app.slint:486 +#: ui/app.slint:484 msgid "Mail pagination failed: {}" msgstr "" -#: ui/app.slint:487 +#: ui/app.slint:485 msgid "Could not mark message as read: {}" msgstr "" -#: ui/app.slint:488 ui/views/mail-reading-pane.slint:223 +#: ui/app.slint:486 ui/views/mail-reading-pane.slint:223 #: ui/views/mail-reading-pane.slint:1162 msgid "Loading message…" msgstr "" -#: ui/app.slint:494 ui/components/workspace-navigation.slint:551 +#: ui/app.slint:492 ui/components/workspace-navigation.slint:551 #: ui/tray.slint:9 ui/tray.slint:10 msgid "Flectar Mail" msgstr "" -#: ui/app.slint:1065 ui/app.slint:1078 +#: ui/app.slint:1063 ui/app.slint:1076 msgid "Global Labels (local only)" msgstr "" -#: ui/app.slint:1938 +#: ui/app.slint:1936 msgid "Empty Trash?" msgstr "" -#: ui/app.slint:1938 ui/views/files-view.slint:538 +#: ui/app.slint:1936 ui/views/files-view.slint:538 msgid "Delete permanently?" msgstr "" -#: ui/app.slint:1942 +#: ui/app.slint:1940 msgid "" "Permanently delete all messages in Trash across every connected account?" msgstr "" -#: ui/app.slint:1943 +#: ui/app.slint:1941 msgid "Permanently delete all messages in this account’s Trash?" msgstr "" -#: ui/app.slint:1945 +#: ui/app.slint:1943 msgid "Permanently delete the selected conversations from Trash?" msgstr "" -#: ui/app.slint:1946 +#: ui/app.slint:1944 msgid "Permanently delete this conversation from Trash?" msgstr "" -#: ui/app.slint:1947 ui/components/calendar.slint:926 +#: ui/app.slint:1945 ui/components/calendar.slint:926 msgid "This action cannot be undone." msgstr "" -#: ui/app.slint:1948 +#: ui/app.slint:1946 msgid "Empty Trash" msgstr "" -#: ui/app.slint:1948 ui/views/files-view.slint:620 +#: ui/app.slint:1946 ui/views/files-view.slint:620 msgid "Delete permanently" msgstr "" -#: ui/app.slint:2111 +#: ui/app.slint:2109 msgid "Mailbox startup failed" msgstr "" -#: ui/app.slint:2117 +#: ui/app.slint:2115 msgid "Flectar Mail could not open local storage" msgstr "" -#: ui/app.slint:2130 +#: ui/app.slint:2128 msgid "Diagnostic ID: {}" msgstr "" -#: ui/app.slint:2133 +#: ui/app.slint:2131 msgid "Diagnostic ID {}" msgstr "" -#: ui/app.slint:2138 +#: ui/app.slint:2136 msgid "Retry" msgstr "" -#: ui/app.slint:2146 ui/components/copyable-diagnostic.slint:38 +#: ui/app.slint:2144 ui/components/copyable-diagnostic.slint:38 msgid "Copy diagnostics" msgstr "" diff --git a/lang/es/LC_MESSAGES/flectar-mail.po b/lang/es/LC_MESSAGES/flectar-mail.po index 0878c9b2..95440476 100644 --- a/lang/es/LC_MESSAGES/flectar-mail.po +++ b/lang/es/LC_MESSAGES/flectar-mail.po @@ -186,7 +186,7 @@ msgstr "Historial de conversaciones disponible" msgid "New address-book entry" msgstr "Nueva entrada de contactos" -#: ui/app.slint:121 ui/app.slint:847 ui/components/composer-fields.slint:66 +#: ui/app.slint:121 ui/app.slint:845 ui/components/composer-fields.slint:66 #: ui/dialogs/compose-dialog.slint:37 msgid "No connected account" msgstr "No hay ninguna cuenta conectada" @@ -201,11 +201,11 @@ msgstr[1] "{n} interacciones" msgid "Last contacted {} {}, {}" msgstr "Último contacto: {1} de {0} de {2}" -#: ui/app.slint:128 ui/app.slint:361 +#: ui/app.slint:128 ui/app.slint:359 msgid "Loading local data…" msgstr "Cargando datos locales…" -#: ui/app.slint:129 ui/app.slint:362 +#: ui/app.slint:129 ui/app.slint:360 msgid "Local data ready." msgstr "Datos locales listos." @@ -680,236 +680,228 @@ msgid "Choose where to export the database snapshot" msgstr "Elige dónde exportar la instantánea de la base de datos" #: ui/app.slint:289 -msgid "The platform host does not provide document export." -msgstr "La plataforma no permite exportar documentos." - -#: ui/app.slint:290 -msgid "The platform host does not provide document import." -msgstr "La plataforma no permite importar documentos." - -#: ui/app.slint:291 msgid "Backup exported. Passwords and OAuth tokens were not included." msgstr "" "Copia de seguridad exportada. No se incluyeron contraseñas ni tokens de " "OAuth." -#: ui/app.slint:292 +#: ui/app.slint:290 msgid "Could not export backup: {}" msgstr "No se pudo exportar la copia de seguridad: {}" -#: ui/app.slint:293 +#: ui/app.slint:291 msgid "Local data storage is unavailable. Retry startup." msgstr "" "El almacenamiento de datos locales no está disponible. Vuelve a intentar el " "inicio." -#: ui/app.slint:294 +#: ui/app.slint:292 msgid "Creating verified database snapshot…" msgstr "Creando una instantánea verificada de la base de datos…" -#: ui/app.slint:295 +#: ui/app.slint:293 msgid "Backup imported. All {} account configurations already existed." msgstr "" "Copia de seguridad importada. Las {} configuraciones de cuenta ya existían." -#: ui/app.slint:296 +#: ui/app.slint:294 msgid "" "Imported {} account configurations. Open Accounts and sign in to each one." msgstr "" "Se importaron {} configuraciones de cuenta. Abre Cuentas e inicia sesión en " "cada una." -#: ui/app.slint:297 +#: ui/app.slint:295 msgid "Could not import backup: {}" msgstr "No se pudo importar la copia de seguridad: {}" -#: ui/app.slint:298 +#: ui/app.slint:296 msgid "All local Flectar Mail data was deleted." msgstr "Se eliminaron todos los datos locales de Flectar Mail." -#: ui/app.slint:299 +#: ui/app.slint:297 msgid "Could not delete all local data: {}" msgstr "No se pudieron eliminar todos los datos locales: {}" -#: ui/app.slint:300 +#: ui/app.slint:298 msgid "Deleting local accounts and cached data…" msgstr "Eliminando las cuentas locales y los datos en caché…" -#: ui/app.slint:305 +#: ui/app.slint:303 msgid "Background mail refresh failed: {}" msgstr "Falló la actualización del correo en segundo plano: {}" -#: ui/app.slint:306 +#: ui/app.slint:304 msgid "No conversations in this folder" msgstr "No hay conversaciones en esta carpeta" -#: ui/app.slint:307 +#: ui/app.slint:305 msgid "No conversations match this search" msgstr "Ninguna conversación coincide con esta búsqueda" -#: ui/app.slint:308 +#: ui/app.slint:306 msgid "Mail core is ready for account synchronization and message actions." msgstr "" "El núcleo de correo está listo para sincronizar cuentas y realizar acciones " "con mensajes." -#: ui/app.slint:309 +#: ui/app.slint:307 msgid "Large message rendered in compatibility mode." msgstr "Mensaje grande renderizado en modo de compatibilidad." -#: ui/app.slint:310 +#: ui/app.slint:308 msgid "Software email render failed: {}" msgstr "Falló la renderización por software del correo: {}" -#: ui/app.slint:311 +#: ui/app.slint:309 msgid "Rendered with software fallback after GPU error: {}" msgstr "Renderizado por software tras un error de la GPU: {}" -#: ui/app.slint:312 +#: ui/app.slint:310 msgid "Blitz render failed (GPU: {}; software: {})" msgstr "Falló la renderización de Blitz (GPU: {}; software: {})" -#: ui/app.slint:317 +#: ui/app.slint:315 msgid "Add an email address, then save the contact." msgstr "Añade una dirección de correo y guarda el contacto." -#: ui/app.slint:318 +#: ui/app.slint:316 msgid "Enter a valid email address before saving." msgstr "Introduce una dirección de correo válida antes de guardar." -#: ui/app.slint:319 +#: ui/app.slint:317 msgid "Could not save: contact storage is unavailable." msgstr "No se pudo guardar: el almacenamiento de contactos no está disponible." -#: ui/app.slint:320 +#: ui/app.slint:318 msgid "Could not save contact: {}" msgstr "No se pudo guardar el contacto: {}" -#: ui/app.slint:321 +#: ui/app.slint:319 msgid "Contact saved." msgstr "Contacto guardado." -#: ui/app.slint:322 +#: ui/app.slint:320 msgid "Saved to contacts." msgstr "Guardado en contactos." -#: ui/app.slint:323 +#: ui/app.slint:321 msgid "Could not delete: contact storage is unavailable." msgstr "" "No se pudo eliminar: el almacenamiento de contactos no está disponible." -#: ui/app.slint:324 +#: ui/app.slint:322 msgid "Could not delete contact: {}" msgstr "No se pudo eliminar el contacto: {}" -#: ui/app.slint:325 +#: ui/app.slint:323 msgid "Contact deleted." msgstr "Contacto eliminado." -#: ui/app.slint:326 +#: ui/app.slint:324 msgid "Could not load contacts: {}" msgstr "No se pudieron cargar los contactos: {}" -#: ui/app.slint:327 +#: ui/app.slint:325 msgid "Local contact storage is still starting…" msgstr "El almacenamiento local de contactos aún se está iniciando…" -#: ui/app.slint:328 +#: ui/app.slint:326 msgid "Loading contacts…" msgstr "Cargando contactos…" -#: ui/app.slint:333 ui/components/calendar-agenda.slint:233 +#: ui/app.slint:331 ui/components/calendar-agenda.slint:233 msgid "Local calendar" msgstr "Calendario local" -#: ui/app.slint:334 ui/components/calendar.slint:271 +#: ui/app.slint:332 ui/components/calendar.slint:271 #: ui/components/calendar.slint:319 ui/components/calendar.slint:434 msgid "Calendar" msgstr "Calendario" -#: ui/app.slint:335 +#: ui/app.slint:333 msgid "Could not load calendar: {}" msgstr "No se pudo cargar el calendario: {}" -#: ui/app.slint:336 +#: ui/app.slint:334 msgid "Calendar shown and syncing." msgstr "Calendario visible y sincronizándose." -#: ui/app.slint:337 +#: ui/app.slint:335 msgid "Calendar hidden and sync paused." msgstr "Calendario oculto y sincronización en pausa." -#: ui/app.slint:338 +#: ui/app.slint:336 msgid "Calendar update failed: {}" msgstr "Falló la actualización del calendario: {}" -#: ui/app.slint:339 +#: ui/app.slint:337 msgid "Only events created in Flectar Mail can be edited." msgstr "Solo se pueden editar los eventos creados en Flectar Mail." -#: ui/app.slint:340 +#: ui/app.slint:338 msgid "Only events created in Flectar Mail can be deleted." msgstr "Solo se pueden eliminar los eventos creados en Flectar Mail." -#: ui/app.slint:341 +#: ui/app.slint:339 msgid "Calendar storage is unavailable." msgstr "El almacenamiento del calendario no está disponible." -#: ui/app.slint:342 +#: ui/app.slint:340 msgid "Could not delete event: {}" msgstr "No se pudo eliminar el evento: {}" -#: ui/app.slint:343 +#: ui/app.slint:341 msgid "Event deleted, but refresh failed: {}" msgstr "El evento se eliminó, pero falló la actualización: {}" -#: ui/app.slint:344 +#: ui/app.slint:342 msgid "Event deleted." msgstr "Evento eliminado." -#: ui/app.slint:345 +#: ui/app.slint:343 msgid "Use YYYY-MM-DD and a 24-hour time such as 09:30." msgstr "Usa AAAA-MM-DD y una hora en formato de 24 horas, como 09:30." -#: ui/app.slint:346 +#: ui/app.slint:344 msgid "Event duration must be a number of minutes." msgstr "La duración del evento debe ser un número de minutos." -#: ui/app.slint:347 +#: ui/app.slint:345 msgid "Add a title and choose a duration from 15 to 1440 minutes." msgstr "Añade un título y elige una duración de entre 15 y 1440 minutos." -#: ui/app.slint:348 +#: ui/app.slint:346 msgid "That local time does not exist in the current time zone." msgstr "Esa hora local no existe en la zona horaria actual." -#: ui/app.slint:349 +#: ui/app.slint:347 msgid "Could not save event: {}" msgstr "No se pudo guardar el evento: {}" -#: ui/app.slint:350 +#: ui/app.slint:348 msgid "Event saved, but refresh failed: {}" msgstr "El evento se guardó, pero falló la actualización: {}" -#: ui/app.slint:351 +#: ui/app.slint:349 msgid "Event saved. Provider sync will run when enabled." msgstr "" "Evento guardado. La sincronización del proveedor se ejecutará cuando esté " "activada." -#: ui/app.slint:356 +#: ui/app.slint:354 msgid "Email resource render failed: {}" msgstr "Falló la renderización de un recurso del correo: {}" -#: ui/app.slint:357 +#: ui/app.slint:355 msgid "Message refresh failed: {}" msgstr "Falló la actualización del mensaje: {}" -#: ui/app.slint:358 +#: ui/app.slint:356 msgid "Message load failed: {}" msgstr "Falló la carga del mensaje: {}" -#: ui/app.slint:359 +#: ui/app.slint:357 msgid "" "Your local mailbox could not be opened. No account operation was started. " "Retry, or copy the diagnostics for support." @@ -917,257 +909,257 @@ msgstr "" "No se pudo abrir tu buzón local. No se inició ninguna operación de cuenta. " "Vuelve a intentarlo o copia el diagnóstico para solicitar asistencia." -#: ui/app.slint:360 +#: ui/app.slint:358 msgid "Local storage startup failed." msgstr "Falló el inicio del almacenamiento local." -#: ui/app.slint:367 +#: ui/app.slint:365 msgid "Could not identify the dragged account." msgstr "No se pudo identificar la cuenta arrastrada." -#: ui/app.slint:368 +#: ui/app.slint:366 msgid "Account ordering requires local mail data." msgstr "Para ordenar las cuentas se necesitan los datos de correo locales." -#: ui/app.slint:369 +#: ui/app.slint:367 msgid "Could not find the account to reorder." msgstr "No se encontró la cuenta que se iba a reordenar." -#: ui/app.slint:370 +#: ui/app.slint:368 msgid "Saving account order…" msgstr "Guardando el orden de las cuentas…" -#: ui/app.slint:371 +#: ui/app.slint:369 msgid "Sent copy settings require local mail data." msgstr "La configuración de copias enviadas requiere datos de correo locales." -#: ui/app.slint:372 +#: ui/app.slint:370 msgid "Updating Sent copy settings…" msgstr "Actualizando la configuración de copias enviadas…" -#: ui/app.slint:373 +#: ui/app.slint:371 msgid "Sent copy settings updated." msgstr "Configuración de copias enviadas actualizada." -#: ui/app.slint:374 +#: ui/app.slint:372 msgid "Could not update Sent copy settings: {}" msgstr "No se pudo actualizar la configuración de copias enviadas: {}" -#: ui/app.slint:375 +#: ui/app.slint:373 msgid "Mail history requires local mail data." msgstr "El historial de correo necesita los datos de correo locales." -#: ui/app.slint:376 +#: ui/app.slint:374 msgid "Updating mail history…" msgstr "Actualizando el historial de correo…" -#: ui/app.slint:377 +#: ui/app.slint:375 msgid "Remote images are unavailable in this minimal build." msgstr "Las imágenes remotas no están disponibles en esta compilación mínima." -#: ui/app.slint:378 +#: ui/app.slint:376 msgid "Could not save remote-image setting: {}" msgstr "No se pudo guardar el ajuste de imágenes remotas: {}" -#: ui/app.slint:379 +#: ui/app.slint:377 msgid "Remote sender icons unavailable: {}" msgstr "Los iconos remotos de los remitentes no están disponibles: {}" -#: ui/app.slint:380 +#: ui/app.slint:378 msgid "Remote images unavailable: {}" msgstr "Las imágenes remotas no están disponibles: {}" -#: ui/app.slint:381 +#: ui/app.slint:379 msgid "Remote body images and sender favicons enabled." msgstr "Imágenes remotas del cuerpo e iconos de remitentes activados." -#: ui/app.slint:382 +#: ui/app.slint:380 msgid "Remote images blocked." msgstr "Imágenes remotas bloqueadas." -#: ui/app.slint:383 +#: ui/app.slint:381 msgid "Could not refresh message images: {}" msgstr "No se pudieron actualizar las imágenes del mensaje: {}" -#: ui/app.slint:384 +#: ui/app.slint:382 msgid "Remote-image loading is disabled." msgstr "La carga de imágenes remotas está desactivada." -#: ui/app.slint:385 +#: ui/app.slint:383 msgid "Could not load message images: {}" msgstr "No se pudieron cargar las imágenes del mensaje: {}" -#: ui/app.slint:386 +#: ui/app.slint:384 msgid "Sync finished, refresh failed: {}" msgstr "La sincronización terminó, pero falló la actualización: {}" -#: ui/app.slint:387 +#: ui/app.slint:385 msgid "Sync complete." msgstr "Sincronización completada." -#: ui/app.slint:388 +#: ui/app.slint:386 msgid "Sync failed: {}" msgstr "Falló la sincronización: {}" -#: ui/app.slint:389 +#: ui/app.slint:387 msgid "Local mail data is unavailable; preview data is read-only." msgstr "" "Los datos de correo locales no están disponibles; los datos de vista previa " "son de solo lectura." -#: ui/app.slint:390 +#: ui/app.slint:388 msgid "Synchronizing accounts…" msgstr "Sincronizando cuentas…" -#: ui/app.slint:395 +#: ui/app.slint:393 msgid "Message action completed." msgstr "Acción del mensaje completada." -#: ui/app.slint:396 +#: ui/app.slint:394 msgid "" "Permanent deletion queued. Mail will disappear after the server confirms." msgstr "" "Eliminación permanente en cola. Los mensajes desaparecerán cuando el " "servidor la confirme." -#: ui/app.slint:397 +#: ui/app.slint:395 msgid "Message action failed: {}" msgstr "Falló la acción del mensaje: {}" -#: ui/app.slint:398 +#: ui/app.slint:396 msgid "Message action failed: message is no longer available" msgstr "Falló la acción del mensaje: el mensaje ya no está disponible" -#: ui/app.slint:399 +#: ui/app.slint:397 msgid "Label added." msgstr "Etiqueta agregada." -#: ui/app.slint:400 +#: ui/app.slint:398 msgid "Label removed." msgstr "Etiqueta eliminada." -#: ui/app.slint:401 +#: ui/app.slint:399 msgid "Label created and added." msgstr "Etiqueta creada y agregada." -#: ui/app.slint:402 +#: ui/app.slint:400 msgid "Label created." msgstr "Etiqueta creada." -#: ui/app.slint:403 +#: ui/app.slint:401 msgid "Label updated." msgstr "Etiqueta actualizada." -#: ui/app.slint:404 +#: ui/app.slint:402 msgid "Label deleted." msgstr "Etiqueta eliminada." -#: ui/app.slint:405 +#: ui/app.slint:403 msgid "Could not update label: {}" msgstr "No se pudo actualizar la etiqueta: {}" -#: ui/app.slint:406 +#: ui/app.slint:404 msgid "Could not save label: {}" msgstr "No se pudo guardar la etiqueta: {}" -#: ui/app.slint:407 +#: ui/app.slint:405 msgid "Could not delete label: {}" msgstr "No se pudo eliminar la etiqueta: {}" -#: ui/app.slint:408 +#: ui/app.slint:406 msgid "Folder created." msgstr "Carpeta creada." -#: ui/app.slint:409 +#: ui/app.slint:407 msgid "Folder renamed." msgstr "Carpeta renombrada." -#: ui/app.slint:410 +#: ui/app.slint:408 msgid "Folder deleted." msgstr "Carpeta eliminada." -#: ui/app.slint:411 +#: ui/app.slint:409 msgid "Could not create folder: {}" msgstr "No se pudo crear la carpeta: {}" -#: ui/app.slint:412 +#: ui/app.slint:410 msgid "Could not rename folder: {}" msgstr "No se pudo renombrar la carpeta: {}" -#: ui/app.slint:413 +#: ui/app.slint:411 msgid "Could not delete folder: {}" msgstr "No se pudo eliminar la carpeta: {}" -#: ui/app.slint:414 +#: ui/app.slint:412 msgid "Could not close preview: {}" msgstr "No se pudo cerrar la vista previa: {}" -#: ui/app.slint:415 +#: ui/app.slint:413 msgid "Opened the system print view." msgstr "Se abrió la vista de impresión del sistema." -#: ui/app.slint:416 +#: ui/app.slint:414 msgid "Could not print message: {}" msgstr "No se pudo imprimir el mensaje: {}" -#: ui/app.slint:417 +#: ui/app.slint:415 msgid "Opened the message in your browser." msgstr "Se abrió el mensaje en el navegador." -#: ui/app.slint:418 +#: ui/app.slint:416 msgid "Could not open message in browser: {}" msgstr "No se pudo abrir el mensaje en el navegador: {}" -#: ui/app.slint:419 +#: ui/app.slint:417 msgid "Connect an account before replying or forwarding." msgstr "Conecta una cuenta antes de responder o reenviar." -#: ui/app.slint:420 +#: ui/app.slint:418 msgid "Choose a message first." msgstr "Elige primero un mensaje." -#: ui/app.slint:421 +#: ui/app.slint:419 msgid "Only drafts can be reopened for editing." msgstr "Solo se pueden volver a abrir borradores para editarlos." -#: ui/app.slint:422 +#: ui/app.slint:420 msgid "Could not open draft: {}" msgstr "No se pudo abrir el borrador: {}" -#: ui/app.slint:423 +#: ui/app.slint:421 msgid "Could not open draft: invalid account identifier" msgstr "" "No se pudo abrir el borrador: el identificador de la cuenta no es válido" -#: ui/app.slint:424 +#: ui/app.slint:422 msgid "Connected account" msgstr "Cuenta conectada" -#: ui/app.slint:425 +#: ui/app.slint:423 msgid "Could not open composer: {}" msgstr "No se pudo abrir el editor: {}" -#: ui/app.slint:426 +#: ui/app.slint:424 msgid "Could not open composer: invalid account identifier" msgstr "No se pudo abrir el editor: el identificador de la cuenta no es válido" -#: ui/app.slint:431 +#: ui/app.slint:429 msgid "Attach files to this email" msgstr "Adjuntar archivos a este correo" -#: ui/app.slint:432 -msgid "The platform host does not provide a document picker." -msgstr "La plataforma no proporciona un selector de documentos." +#: ui/app.slint:430 +msgid "Could not add attachments: {}" +msgstr "No se pudieron añadir los archivos adjuntos: {}" -#: ui/app.slint:433 +#: ui/app.slint:431 msgid "Added 1 attachment." msgstr "Se añadió 1 archivo adjunto." -#: ui/app.slint:434 +#: ui/app.slint:432 msgid "Added {} attachments." msgstr "Se añadieron {} archivos adjuntos." -#: ui/app.slint:435 +#: ui/app.slint:433 msgid "" "Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB " "total limit)." @@ -1175,117 +1167,117 @@ msgstr "" "Archivos adjuntos añadidos: {}. Omitidos: {} (duplicados, ilegibles o por " "superar el límite total de 25 MB)." -#: ui/app.slint:436 +#: ui/app.slint:434 msgid "Attachment removed." msgstr "Archivo adjunto eliminado." -#: ui/app.slint:437 +#: ui/app.slint:435 msgid "Choose the account this message should be sent from." msgstr "Elige la cuenta desde la que se debe enviar este mensaje." -#: ui/app.slint:438 +#: ui/app.slint:436 msgid "Choose a verified sender identity for this message." msgstr "Elige una identidad de remitente verificada para este mensaje." -#: ui/app.slint:439 +#: ui/app.slint:437 msgid "Connect an account before saving or sending mail." msgstr "Conecta una cuenta antes de guardar o enviar correo." -#: ui/app.slint:440 +#: ui/app.slint:438 msgid "Message queued for delivery (action {})." msgstr "Mensaje en cola para su entrega (acción {})." -#: ui/app.slint:441 +#: ui/app.slint:439 msgid "Sending message…" msgstr "Enviando mensaje…" -#: ui/app.slint:442 +#: ui/app.slint:440 msgid "Message sent." msgstr "Mensaje enviado." -#: ui/app.slint:443 +#: ui/app.slint:441 msgid "Message was not sent: {} Your draft is preserved." msgstr "El mensaje no se envió: {} El borrador se ha conservado." -#: ui/app.slint:444 +#: ui/app.slint:442 msgid "Draft saved locally (#{})." msgstr "Borrador guardado localmente (#{})." -#: ui/app.slint:445 +#: ui/app.slint:443 msgid "Compose failed: {}" msgstr "Falló la redacción: {}" -#: ui/app.slint:446 +#: ui/app.slint:444 msgid "Connect an account before managing email templates." msgstr "Conecta una cuenta antes de gestionar plantillas de correo." -#: ui/app.slint:447 +#: ui/app.slint:445 msgid "Could not load email templates: {}" msgstr "No se pudieron cargar las plantillas de correo: {}" -#: ui/app.slint:448 +#: ui/app.slint:446 msgid "Enter a template name." msgstr "Escribe un nombre para la plantilla." -#: ui/app.slint:449 +#: ui/app.slint:447 msgid "Add a subject or message to the template." msgstr "Añade un asunto o mensaje a la plantilla." -#: ui/app.slint:450 +#: ui/app.slint:448 msgid "Email template saved." msgstr "Plantilla de correo guardada." -#: ui/app.slint:451 +#: ui/app.slint:449 msgid "Could not save email template: {}" msgstr "No se pudo guardar la plantilla de correo: {}" -#: ui/app.slint:452 +#: ui/app.slint:450 msgid "Email template deleted." msgstr "Plantilla de correo eliminada." -#: ui/app.slint:453 +#: ui/app.slint:451 msgid "Could not delete email template: {}" msgstr "No se pudo eliminar la plantilla de correo: {}" -#: ui/app.slint:454 +#: ui/app.slint:452 msgid "Email template is no longer available." msgstr "La plantilla de correo ya no está disponible." -#: ui/app.slint:455 +#: ui/app.slint:453 msgid "Email template inserted." msgstr "Plantilla de correo insertada." -#: ui/app.slint:456 +#: ui/app.slint:454 msgid "Email template inserted, but its usage could not be saved: {}" msgstr "Plantilla de correo insertada, pero no se pudo guardar su uso: {}" -#: ui/app.slint:457 +#: ui/app.slint:455 msgid "IMAP port must be between 1 and 65535." msgstr "El puerto IMAP debe estar entre 1 y 65535." -#: ui/app.slint:458 +#: ui/app.slint:456 msgid "SMTP port must be between 1 and 65535." msgstr "El puerto SMTP debe estar entre 1 y 65535." -#: ui/app.slint:459 +#: ui/app.slint:457 msgid "Local mail data is unavailable. Retry startup." msgstr "" "Los datos de correo locales no están disponibles. Vuelve a intentar el " "inicio." -#: ui/app.slint:460 +#: ui/app.slint:458 msgid "Discovering JMAP and checking capabilities…" msgstr "Detectando JMAP y comprobando sus capacidades…" -#: ui/app.slint:461 +#: ui/app.slint:459 msgid "Certificate imported. Connect to verify the server." msgstr "Certificado importado. Conecta para verificar el servidor." -#: ui/app.slint:462 +#: ui/app.slint:460 msgid "Could not load account preferences: {}" msgstr "No se pudieron cargar las preferencias de la cuenta: {}" -#: ui/app.slint:463 +#: ui/app.slint:461 msgid "" "Your edited signature was preserved. Choose No signature to keep it as " "message text before inserting another." @@ -1293,19 +1285,19 @@ msgstr "" "Se conservó la firma que editaste. Elige Sin firma para mantenerla como " "texto del mensaje antes de insertar otra." -#: ui/app.slint:464 +#: ui/app.slint:462 msgid "Certificate import failed: {}" msgstr "Error al importar el certificado: {}" -#: ui/app.slint:465 +#: ui/app.slint:463 msgid "Paste the PEM certificate into the certificate field." msgstr "Pega el certificado PEM en el campo del certificado." -#: ui/app.slint:466 +#: ui/app.slint:464 msgid "Testing IMAP and SMTP connections…" msgstr "Comprobando las conexiones IMAP y SMTP…" -#: ui/app.slint:471 +#: ui/app.slint:469 msgid "" "Another browser authorization is already in progress; finish it or choose " "Cancel." @@ -1313,150 +1305,150 @@ msgstr "" "Ya hay otra autorización del navegador en curso; termínala o selecciona " "Cancelar." -#: ui/app.slint:472 ui/dialogs/settings-dialog.slint:1643 +#: ui/app.slint:470 ui/dialogs/settings-dialog.slint:1643 #: ui/dialogs/settings-dialog.slint:1686 ui/views/onboarding-view.slint:152 msgid "Add app keys in Sign-in settings to enable this provider." msgstr "" "Añade claves de aplicación en los ajustes de inicio de sesión para habilitar " "este proveedor." -#: ui/app.slint:473 +#: ui/app.slint:471 msgid "Choose Gmail or Outlook OAuth." msgstr "Elige OAuth de Gmail o Outlook." -#: ui/app.slint:474 +#: ui/app.slint:472 msgid "Complete sign-in in your browser, or choose Cancel here." msgstr "" "Completa el inicio de sesión en el navegador o selecciona Cancelar aquí." -#: ui/app.slint:475 +#: ui/app.slint:473 msgid "Cancelling browser sign-in…" msgstr "Cancelando el inicio de sesión en el navegador…" -#: ui/app.slint:476 +#: ui/app.slint:474 msgid "Finishing browser sign-in…" msgstr "Finalizando el inicio de sesión en el navegador…" -#: ui/app.slint:477 +#: ui/app.slint:475 msgid "Could not use that browser address: {}" msgstr "No se pudo usar esa dirección del navegador: {}" -#: ui/app.slint:478 +#: ui/app.slint:476 msgid "Calendar core is unavailable." msgstr "El núcleo del calendario no está disponible." -#: ui/app.slint:479 +#: ui/app.slint:477 msgid "Account no longer exists." msgstr "La cuenta ya no existe." -#: ui/app.slint:480 +#: ui/app.slint:478 msgid "Authorize calendar access in your browser." msgstr "Autoriza el acceso al calendario en el navegador." -#: ui/app.slint:481 +#: ui/app.slint:479 msgid "OAuth app keys saved." msgstr "Claves de la aplicación OAuth guardadas." -#: ui/app.slint:482 +#: ui/app.slint:480 msgid "{} OAuth settings saved." msgstr "Ajustes de OAuth de {} guardados." -#: ui/app.slint:483 +#: ui/app.slint:481 msgid "OAuth settings failed: {}" msgstr "Fallaron los ajustes de OAuth: {}" -#: ui/app.slint:484 +#: ui/app.slint:482 msgid "Mail refresh failed: {}" msgstr "Falló la actualización del correo: {}" -#: ui/app.slint:485 +#: ui/app.slint:483 msgid "Mail filter failed: {}" msgstr "Falló el filtro de correo: {}" -#: ui/app.slint:486 +#: ui/app.slint:484 msgid "Mail pagination failed: {}" msgstr "Falló la paginación del correo: {}" -#: ui/app.slint:487 +#: ui/app.slint:485 msgid "Could not mark message as read: {}" msgstr "No se pudo marcar el mensaje como leído: {}" -#: ui/app.slint:488 ui/views/mail-reading-pane.slint:223 +#: ui/app.slint:486 ui/views/mail-reading-pane.slint:223 #: ui/views/mail-reading-pane.slint:1162 msgid "Loading message…" msgstr "Cargando mensaje…" -#: ui/app.slint:494 ui/components/workspace-navigation.slint:551 +#: ui/app.slint:492 ui/components/workspace-navigation.slint:551 #: ui/tray.slint:9 ui/tray.slint:10 msgid "Flectar Mail" msgstr "Flectar Mail" -#: ui/app.slint:1065 ui/app.slint:1078 +#: ui/app.slint:1063 ui/app.slint:1076 msgid "Global Labels (local only)" msgstr "Etiquetas globales (solo locales)" -#: ui/app.slint:1938 +#: ui/app.slint:1936 msgid "Empty Trash?" msgstr "¿Vaciar la papelera?" -#: ui/app.slint:1938 ui/views/files-view.slint:538 +#: ui/app.slint:1936 ui/views/files-view.slint:538 msgid "Delete permanently?" msgstr "¿Eliminar permanentemente?" -#: ui/app.slint:1942 +#: ui/app.slint:1940 msgid "" "Permanently delete all messages in Trash across every connected account?" msgstr "" "¿Eliminar permanentemente todos los mensajes de la papelera de todas las " "cuentas conectadas?" -#: ui/app.slint:1943 +#: ui/app.slint:1941 msgid "Permanently delete all messages in this account’s Trash?" msgstr "" "¿Eliminar permanentemente todos los mensajes de la papelera de esta cuenta?" -#: ui/app.slint:1945 +#: ui/app.slint:1943 msgid "Permanently delete the selected conversations from Trash?" msgstr "" "¿Eliminar permanentemente las conversaciones seleccionadas de la papelera?" -#: ui/app.slint:1946 +#: ui/app.slint:1944 msgid "Permanently delete this conversation from Trash?" msgstr "¿Eliminar permanentemente esta conversación de la papelera?" -#: ui/app.slint:1947 ui/components/calendar.slint:926 +#: ui/app.slint:1945 ui/components/calendar.slint:926 msgid "This action cannot be undone." msgstr "Esta acción no se puede deshacer." -#: ui/app.slint:1948 +#: ui/app.slint:1946 msgid "Empty Trash" msgstr "Vaciar la papelera" -#: ui/app.slint:1948 ui/views/files-view.slint:620 +#: ui/app.slint:1946 ui/views/files-view.slint:620 msgid "Delete permanently" msgstr "Eliminar permanentemente" -#: ui/app.slint:2111 +#: ui/app.slint:2109 msgid "Mailbox startup failed" msgstr "Error al iniciar el buzón" -#: ui/app.slint:2117 +#: ui/app.slint:2115 msgid "Flectar Mail could not open local storage" msgstr "Flectar Mail no pudo abrir el almacenamiento local" -#: ui/app.slint:2130 +#: ui/app.slint:2128 msgid "Diagnostic ID: {}" msgstr "ID de diagnóstico: {}" -#: ui/app.slint:2133 +#: ui/app.slint:2131 msgid "Diagnostic ID {}" msgstr "ID de diagnóstico {}" -#: ui/app.slint:2138 +#: ui/app.slint:2136 msgid "Retry" msgstr "Reintentar" -#: ui/app.slint:2146 ui/components/copyable-diagnostic.slint:38 +#: ui/app.slint:2144 ui/components/copyable-diagnostic.slint:38 msgid "Copy diagnostics" msgstr "Copiar datos de diagnóstico" diff --git a/lang/flectar-mail.pot b/lang/flectar-mail.pot index 69579671..33264175 100644 --- a/lang/flectar-mail.pot +++ b/lang/flectar-mail.pot @@ -184,8 +184,8 @@ msgstr "" msgid "New address-book entry" msgstr "" -#: ui/app.slint:121 ui/app.slint:847 ui/components/composer-fields.slint:66 -#: ui/app.slint:121 ui/app.slint:847 ui/components/composer-fields.slint:66 ui/dialogs/compose-dialog.slint:37 +#: ui/app.slint:121 ui/app.slint:845 ui/components/composer-fields.slint:66 +#: ui/app.slint:121 ui/app.slint:845 ui/components/composer-fields.slint:66 ui/dialogs/compose-dialog.slint:37 msgid "No connected account" msgstr "" @@ -199,11 +199,11 @@ msgstr[1] "" msgid "Last contacted {} {}, {}" msgstr "" -#: ui/app.slint:128 ui/app.slint:361 +#: ui/app.slint:128 ui/app.slint:359 msgid "Loading local data…" msgstr "" -#: ui/app.slint:129 ui/app.slint:362 +#: ui/app.slint:129 ui/app.slint:360 msgid "Local data ready." msgstr "" @@ -664,759 +664,751 @@ msgid "Choose where to export the database snapshot" msgstr "" #: ui/app.slint:289 -msgid "The platform host does not provide document export." -msgstr "" - -#: ui/app.slint:290 -msgid "The platform host does not provide document import." -msgstr "" - -#: ui/app.slint:291 msgid "Backup exported. Passwords and OAuth tokens were not included." msgstr "" -#: ui/app.slint:292 +#: ui/app.slint:290 msgid "Could not export backup: {}" msgstr "" -#: ui/app.slint:293 +#: ui/app.slint:291 msgid "Local data storage is unavailable. Retry startup." msgstr "" -#: ui/app.slint:294 +#: ui/app.slint:292 msgid "Creating verified database snapshot…" msgstr "" -#: ui/app.slint:295 +#: ui/app.slint:293 msgid "Backup imported. All {} account configurations already existed." msgstr "" -#: ui/app.slint:296 +#: ui/app.slint:294 msgid "" "Imported {} account configurations. Open Accounts and sign in to each one." msgstr "" -#: ui/app.slint:297 +#: ui/app.slint:295 msgid "Could not import backup: {}" msgstr "" -#: ui/app.slint:298 +#: ui/app.slint:296 msgid "All local Flectar Mail data was deleted." msgstr "" -#: ui/app.slint:299 +#: ui/app.slint:297 msgid "Could not delete all local data: {}" msgstr "" -#: ui/app.slint:300 +#: ui/app.slint:298 msgid "Deleting local accounts and cached data…" msgstr "" -#: ui/app.slint:305 +#: ui/app.slint:303 msgid "Background mail refresh failed: {}" msgstr "" -#: ui/app.slint:306 +#: ui/app.slint:304 msgid "No conversations in this folder" msgstr "" -#: ui/app.slint:307 +#: ui/app.slint:305 msgid "No conversations match this search" msgstr "" -#: ui/app.slint:308 +#: ui/app.slint:306 msgid "Mail core is ready for account synchronization and message actions." msgstr "" -#: ui/app.slint:309 +#: ui/app.slint:307 msgid "Large message rendered in compatibility mode." msgstr "" -#: ui/app.slint:310 +#: ui/app.slint:308 msgid "Software email render failed: {}" msgstr "" -#: ui/app.slint:311 +#: ui/app.slint:309 msgid "Rendered with software fallback after GPU error: {}" msgstr "" -#: ui/app.slint:312 +#: ui/app.slint:310 msgid "Blitz render failed (GPU: {}; software: {})" msgstr "" -#: ui/app.slint:317 +#: ui/app.slint:315 msgid "Add an email address, then save the contact." msgstr "" -#: ui/app.slint:318 +#: ui/app.slint:316 msgid "Enter a valid email address before saving." msgstr "" -#: ui/app.slint:319 +#: ui/app.slint:317 msgid "Could not save: contact storage is unavailable." msgstr "" -#: ui/app.slint:320 +#: ui/app.slint:318 msgid "Could not save contact: {}" msgstr "" -#: ui/app.slint:321 +#: ui/app.slint:319 msgid "Contact saved." msgstr "" -#: ui/app.slint:322 +#: ui/app.slint:320 msgid "Saved to contacts." msgstr "" -#: ui/app.slint:323 +#: ui/app.slint:321 msgid "Could not delete: contact storage is unavailable." msgstr "" -#: ui/app.slint:324 +#: ui/app.slint:322 msgid "Could not delete contact: {}" msgstr "" -#: ui/app.slint:325 +#: ui/app.slint:323 msgid "Contact deleted." msgstr "" -#: ui/app.slint:326 +#: ui/app.slint:324 msgid "Could not load contacts: {}" msgstr "" -#: ui/app.slint:327 +#: ui/app.slint:325 msgid "Local contact storage is still starting…" msgstr "" -#: ui/app.slint:328 +#: ui/app.slint:326 msgid "Loading contacts…" msgstr "" -#: ui/app.slint:333 ui/components/calendar-agenda.slint:233 -#: ui/app.slint:333 ui/components/calendar-agenda.slint:233 +#: ui/app.slint:331 ui/components/calendar-agenda.slint:233 +#: ui/app.slint:331 ui/components/calendar-agenda.slint:233 msgid "Local calendar" msgstr "" -#: ui/app.slint:334 ui/components/calendar.slint:271 -#: ui/app.slint:334 ui/components/calendar.slint:271 ui/components/calendar.slint:319 -#: ui/app.slint:334 ui/components/calendar.slint:271 ui/components/calendar.slint:319 ui/components/calendar.slint:434 -#: ui/app.slint:334 ui/components/calendar.slint:271 ui/components/calendar.slint:319 ui/components/calendar.slint:434 -#: ui/app.slint:334 ui/components/calendar.slint:271 ui/components/calendar.slint:319 ui/components/calendar.slint:434 -#: ui/app.slint:334 ui/components/calendar.slint:271 ui/components/calendar.slint:319 ui/components/calendar.slint:434 -#: ui/app.slint:334 ui/components/calendar.slint:271 ui/components/calendar.slint:319 ui/components/calendar.slint:434 +#: ui/app.slint:332 ui/components/calendar.slint:271 +#: ui/app.slint:332 ui/components/calendar.slint:271 ui/components/calendar.slint:319 +#: ui/app.slint:332 ui/components/calendar.slint:271 ui/components/calendar.slint:319 ui/components/calendar.slint:434 +#: ui/app.slint:332 ui/components/calendar.slint:271 ui/components/calendar.slint:319 ui/components/calendar.slint:434 +#: ui/app.slint:332 ui/components/calendar.slint:271 ui/components/calendar.slint:319 ui/components/calendar.slint:434 +#: ui/app.slint:332 ui/components/calendar.slint:271 ui/components/calendar.slint:319 ui/components/calendar.slint:434 +#: ui/app.slint:332 ui/components/calendar.slint:271 ui/components/calendar.slint:319 ui/components/calendar.slint:434 msgid "Calendar" msgstr "" -#: ui/app.slint:335 +#: ui/app.slint:333 msgid "Could not load calendar: {}" msgstr "" -#: ui/app.slint:336 +#: ui/app.slint:334 msgid "Calendar shown and syncing." msgstr "" -#: ui/app.slint:337 +#: ui/app.slint:335 msgid "Calendar hidden and sync paused." msgstr "" -#: ui/app.slint:338 +#: ui/app.slint:336 msgid "Calendar update failed: {}" msgstr "" -#: ui/app.slint:339 +#: ui/app.slint:337 msgid "Only events created in Flectar Mail can be edited." msgstr "" -#: ui/app.slint:340 +#: ui/app.slint:338 msgid "Only events created in Flectar Mail can be deleted." msgstr "" -#: ui/app.slint:341 +#: ui/app.slint:339 msgid "Calendar storage is unavailable." msgstr "" -#: ui/app.slint:342 +#: ui/app.slint:340 msgid "Could not delete event: {}" msgstr "" -#: ui/app.slint:343 +#: ui/app.slint:341 msgid "Event deleted, but refresh failed: {}" msgstr "" -#: ui/app.slint:344 +#: ui/app.slint:342 msgid "Event deleted." msgstr "" -#: ui/app.slint:345 +#: ui/app.slint:343 msgid "Use YYYY-MM-DD and a 24-hour time such as 09:30." msgstr "" -#: ui/app.slint:346 +#: ui/app.slint:344 msgid "Event duration must be a number of minutes." msgstr "" -#: ui/app.slint:347 +#: ui/app.slint:345 msgid "Add a title and choose a duration from 15 to 1440 minutes." msgstr "" -#: ui/app.slint:348 +#: ui/app.slint:346 msgid "That local time does not exist in the current time zone." msgstr "" -#: ui/app.slint:349 +#: ui/app.slint:347 msgid "Could not save event: {}" msgstr "" -#: ui/app.slint:350 +#: ui/app.slint:348 msgid "Event saved, but refresh failed: {}" msgstr "" -#: ui/app.slint:351 +#: ui/app.slint:349 msgid "Event saved. Provider sync will run when enabled." msgstr "" -#: ui/app.slint:356 +#: ui/app.slint:354 msgid "Email resource render failed: {}" msgstr "" -#: ui/app.slint:357 +#: ui/app.slint:355 msgid "Message refresh failed: {}" msgstr "" -#: ui/app.slint:358 +#: ui/app.slint:356 msgid "Message load failed: {}" msgstr "" -#: ui/app.slint:359 +#: ui/app.slint:357 msgid "" "Your local mailbox could not be opened. No account operation was started. " "Retry, or copy the diagnostics for support." msgstr "" -#: ui/app.slint:360 +#: ui/app.slint:358 msgid "Local storage startup failed." msgstr "" -#: ui/app.slint:367 +#: ui/app.slint:365 msgid "Could not identify the dragged account." msgstr "" -#: ui/app.slint:368 +#: ui/app.slint:366 msgid "Account ordering requires local mail data." msgstr "" -#: ui/app.slint:369 +#: ui/app.slint:367 msgid "Could not find the account to reorder." msgstr "" -#: ui/app.slint:370 +#: ui/app.slint:368 msgid "Saving account order…" msgstr "" -#: ui/app.slint:371 +#: ui/app.slint:369 msgid "Sent copy settings require local mail data." msgstr "" -#: ui/app.slint:372 +#: ui/app.slint:370 msgid "Updating Sent copy settings…" msgstr "" -#: ui/app.slint:373 +#: ui/app.slint:371 msgid "Sent copy settings updated." msgstr "" -#: ui/app.slint:374 +#: ui/app.slint:372 msgid "Could not update Sent copy settings: {}" msgstr "" -#: ui/app.slint:375 +#: ui/app.slint:373 msgid "Mail history requires local mail data." msgstr "" -#: ui/app.slint:376 +#: ui/app.slint:374 msgid "Updating mail history…" msgstr "" -#: ui/app.slint:377 +#: ui/app.slint:375 msgid "Remote images are unavailable in this minimal build." msgstr "" -#: ui/app.slint:378 +#: ui/app.slint:376 msgid "Could not save remote-image setting: {}" msgstr "" -#: ui/app.slint:379 +#: ui/app.slint:377 msgid "Remote sender icons unavailable: {}" msgstr "" -#: ui/app.slint:380 +#: ui/app.slint:378 msgid "Remote images unavailable: {}" msgstr "" -#: ui/app.slint:381 +#: ui/app.slint:379 msgid "Remote body images and sender favicons enabled." msgstr "" -#: ui/app.slint:382 +#: ui/app.slint:380 msgid "Remote images blocked." msgstr "" -#: ui/app.slint:383 +#: ui/app.slint:381 msgid "Could not refresh message images: {}" msgstr "" -#: ui/app.slint:384 +#: ui/app.slint:382 msgid "Remote-image loading is disabled." msgstr "" -#: ui/app.slint:385 +#: ui/app.slint:383 msgid "Could not load message images: {}" msgstr "" -#: ui/app.slint:386 +#: ui/app.slint:384 msgid "Sync finished, refresh failed: {}" msgstr "" -#: ui/app.slint:387 +#: ui/app.slint:385 msgid "Sync complete." msgstr "" -#: ui/app.slint:388 +#: ui/app.slint:386 msgid "Sync failed: {}" msgstr "" -#: ui/app.slint:389 +#: ui/app.slint:387 msgid "Local mail data is unavailable; preview data is read-only." msgstr "" -#: ui/app.slint:390 +#: ui/app.slint:388 msgid "Synchronizing accounts…" msgstr "" -#: ui/app.slint:395 +#: ui/app.slint:393 msgid "Message action completed." msgstr "" -#: ui/app.slint:396 +#: ui/app.slint:394 msgid "" "Permanent deletion queued. Mail will disappear after the server confirms." msgstr "" -#: ui/app.slint:397 +#: ui/app.slint:395 msgid "Message action failed: {}" msgstr "" -#: ui/app.slint:398 +#: ui/app.slint:396 msgid "Message action failed: message is no longer available" msgstr "" -#: ui/app.slint:399 +#: ui/app.slint:397 msgid "Label added." msgstr "" -#: ui/app.slint:400 +#: ui/app.slint:398 msgid "Label removed." msgstr "" -#: ui/app.slint:401 +#: ui/app.slint:399 msgid "Label created and added." msgstr "" -#: ui/app.slint:402 +#: ui/app.slint:400 msgid "Label created." msgstr "" -#: ui/app.slint:403 +#: ui/app.slint:401 msgid "Label updated." msgstr "" -#: ui/app.slint:404 +#: ui/app.slint:402 msgid "Label deleted." msgstr "" -#: ui/app.slint:405 +#: ui/app.slint:403 msgid "Could not update label: {}" msgstr "" -#: ui/app.slint:406 +#: ui/app.slint:404 msgid "Could not save label: {}" msgstr "" -#: ui/app.slint:407 +#: ui/app.slint:405 msgid "Could not delete label: {}" msgstr "" -#: ui/app.slint:408 +#: ui/app.slint:406 msgid "Folder created." msgstr "" -#: ui/app.slint:409 +#: ui/app.slint:407 msgid "Folder renamed." msgstr "" -#: ui/app.slint:410 +#: ui/app.slint:408 msgid "Folder deleted." msgstr "" -#: ui/app.slint:411 +#: ui/app.slint:409 msgid "Could not create folder: {}" msgstr "" -#: ui/app.slint:412 +#: ui/app.slint:410 msgid "Could not rename folder: {}" msgstr "" -#: ui/app.slint:413 +#: ui/app.slint:411 msgid "Could not delete folder: {}" msgstr "" -#: ui/app.slint:414 +#: ui/app.slint:412 msgid "Could not close preview: {}" msgstr "" -#: ui/app.slint:415 +#: ui/app.slint:413 msgid "Opened the system print view." msgstr "" -#: ui/app.slint:416 +#: ui/app.slint:414 msgid "Could not print message: {}" msgstr "" -#: ui/app.slint:417 +#: ui/app.slint:415 msgid "Opened the message in your browser." msgstr "" -#: ui/app.slint:418 +#: ui/app.slint:416 msgid "Could not open message in browser: {}" msgstr "" -#: ui/app.slint:419 +#: ui/app.slint:417 msgid "Connect an account before replying or forwarding." msgstr "" -#: ui/app.slint:420 +#: ui/app.slint:418 msgid "Choose a message first." msgstr "" -#: ui/app.slint:421 +#: ui/app.slint:419 msgid "Only drafts can be reopened for editing." msgstr "" -#: ui/app.slint:422 +#: ui/app.slint:420 msgid "Could not open draft: {}" msgstr "" -#: ui/app.slint:423 +#: ui/app.slint:421 msgid "Could not open draft: invalid account identifier" msgstr "" -#: ui/app.slint:424 +#: ui/app.slint:422 msgid "Connected account" msgstr "" -#: ui/app.slint:425 +#: ui/app.slint:423 msgid "Could not open composer: {}" msgstr "" -#: ui/app.slint:426 +#: ui/app.slint:424 msgid "Could not open composer: invalid account identifier" msgstr "" -#: ui/app.slint:431 +#: ui/app.slint:429 msgid "Attach files to this email" msgstr "" -#: ui/app.slint:432 -msgid "The platform host does not provide a document picker." +#: ui/app.slint:430 +msgid "Could not add attachments: {}" msgstr "" -#: ui/app.slint:433 +#: ui/app.slint:431 msgid "Added 1 attachment." msgstr "" -#: ui/app.slint:434 +#: ui/app.slint:432 msgid "Added {} attachments." msgstr "" -#: ui/app.slint:435 +#: ui/app.slint:433 msgid "" "Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB " "total limit)." msgstr "" -#: ui/app.slint:436 +#: ui/app.slint:434 msgid "Attachment removed." msgstr "" -#: ui/app.slint:437 +#: ui/app.slint:435 msgid "Choose the account this message should be sent from." msgstr "" -#: ui/app.slint:438 +#: ui/app.slint:436 msgid "Choose a verified sender identity for this message." msgstr "" -#: ui/app.slint:439 +#: ui/app.slint:437 msgid "Connect an account before saving or sending mail." msgstr "" -#: ui/app.slint:440 +#: ui/app.slint:438 msgid "Message queued for delivery (action {})." msgstr "" -#: ui/app.slint:441 +#: ui/app.slint:439 msgid "Sending message…" msgstr "" -#: ui/app.slint:442 +#: ui/app.slint:440 msgid "Message sent." msgstr "" -#: ui/app.slint:443 +#: ui/app.slint:441 msgid "Message was not sent: {} Your draft is preserved." msgstr "" -#: ui/app.slint:444 +#: ui/app.slint:442 msgid "Draft saved locally (#{})." msgstr "" -#: ui/app.slint:445 +#: ui/app.slint:443 msgid "Compose failed: {}" msgstr "" -#: ui/app.slint:446 +#: ui/app.slint:444 msgid "Connect an account before managing email templates." msgstr "" -#: ui/app.slint:447 +#: ui/app.slint:445 msgid "Could not load email templates: {}" msgstr "" -#: ui/app.slint:448 +#: ui/app.slint:446 msgid "Enter a template name." msgstr "" -#: ui/app.slint:449 +#: ui/app.slint:447 msgid "Add a subject or message to the template." msgstr "" -#: ui/app.slint:450 +#: ui/app.slint:448 msgid "Email template saved." msgstr "" -#: ui/app.slint:451 +#: ui/app.slint:449 msgid "Could not save email template: {}" msgstr "" -#: ui/app.slint:452 +#: ui/app.slint:450 msgid "Email template deleted." msgstr "" -#: ui/app.slint:453 +#: ui/app.slint:451 msgid "Could not delete email template: {}" msgstr "" -#: ui/app.slint:454 +#: ui/app.slint:452 msgid "Email template is no longer available." msgstr "" -#: ui/app.slint:455 +#: ui/app.slint:453 msgid "Email template inserted." msgstr "" -#: ui/app.slint:456 +#: ui/app.slint:454 msgid "Email template inserted, but its usage could not be saved: {}" msgstr "" -#: ui/app.slint:457 +#: ui/app.slint:455 msgid "IMAP port must be between 1 and 65535." msgstr "" -#: ui/app.slint:458 +#: ui/app.slint:456 msgid "SMTP port must be between 1 and 65535." msgstr "" -#: ui/app.slint:459 +#: ui/app.slint:457 msgid "Local mail data is unavailable. Retry startup." msgstr "" -#: ui/app.slint:460 +#: ui/app.slint:458 msgid "Discovering JMAP and checking capabilities…" msgstr "" -#: ui/app.slint:461 +#: ui/app.slint:459 msgid "Certificate imported. Connect to verify the server." msgstr "" -#: ui/app.slint:462 +#: ui/app.slint:460 msgid "Could not load account preferences: {}" msgstr "" -#: ui/app.slint:463 +#: ui/app.slint:461 msgid "" "Your edited signature was preserved. Choose No signature to keep it as " "message text before inserting another." msgstr "" -#: ui/app.slint:464 +#: ui/app.slint:462 msgid "Certificate import failed: {}" msgstr "" -#: ui/app.slint:465 +#: ui/app.slint:463 msgid "Paste the PEM certificate into the certificate field." msgstr "" -#: ui/app.slint:466 +#: ui/app.slint:464 msgid "Testing IMAP and SMTP connections…" msgstr "" -#: ui/app.slint:471 +#: ui/app.slint:469 msgid "" "Another browser authorization is already in progress; finish it or choose " "Cancel." msgstr "" -#: ui/app.slint:472 ui/dialogs/settings-dialog.slint:1643 -#: ui/app.slint:472 ui/dialogs/settings-dialog.slint:1643 ui/dialogs/settings-dialog.slint:1686 -#: ui/app.slint:472 ui/dialogs/settings-dialog.slint:1643 ui/dialogs/settings-dialog.slint:1686 ui/views/onboarding-view.slint:152 -#: ui/app.slint:472 ui/dialogs/settings-dialog.slint:1643 ui/dialogs/settings-dialog.slint:1686 ui/views/onboarding-view.slint:152 +#: ui/app.slint:470 ui/dialogs/settings-dialog.slint:1643 +#: ui/app.slint:470 ui/dialogs/settings-dialog.slint:1643 ui/dialogs/settings-dialog.slint:1686 +#: ui/app.slint:470 ui/dialogs/settings-dialog.slint:1643 ui/dialogs/settings-dialog.slint:1686 ui/views/onboarding-view.slint:152 +#: ui/app.slint:470 ui/dialogs/settings-dialog.slint:1643 ui/dialogs/settings-dialog.slint:1686 ui/views/onboarding-view.slint:152 msgid "Add app keys in Sign-in settings to enable this provider." msgstr "" -#: ui/app.slint:473 +#: ui/app.slint:471 msgid "Choose Gmail or Outlook OAuth." msgstr "" -#: ui/app.slint:474 +#: ui/app.slint:472 msgid "Complete sign-in in your browser, or choose Cancel here." msgstr "" -#: ui/app.slint:475 +#: ui/app.slint:473 msgid "Cancelling browser sign-in…" msgstr "" -#: ui/app.slint:476 +#: ui/app.slint:474 msgid "Finishing browser sign-in…" msgstr "" -#: ui/app.slint:477 +#: ui/app.slint:475 msgid "Could not use that browser address: {}" msgstr "" -#: ui/app.slint:478 +#: ui/app.slint:476 msgid "Calendar core is unavailable." msgstr "" -#: ui/app.slint:479 +#: ui/app.slint:477 msgid "Account no longer exists." msgstr "" -#: ui/app.slint:480 +#: ui/app.slint:478 msgid "Authorize calendar access in your browser." msgstr "" -#: ui/app.slint:481 +#: ui/app.slint:479 msgid "OAuth app keys saved." msgstr "" -#: ui/app.slint:482 +#: ui/app.slint:480 msgid "{} OAuth settings saved." msgstr "" -#: ui/app.slint:483 +#: ui/app.slint:481 msgid "OAuth settings failed: {}" msgstr "" -#: ui/app.slint:484 +#: ui/app.slint:482 msgid "Mail refresh failed: {}" msgstr "" -#: ui/app.slint:485 +#: ui/app.slint:483 msgid "Mail filter failed: {}" msgstr "" -#: ui/app.slint:486 +#: ui/app.slint:484 msgid "Mail pagination failed: {}" msgstr "" -#: ui/app.slint:487 +#: ui/app.slint:485 msgid "Could not mark message as read: {}" msgstr "" -#: ui/app.slint:488 ui/views/mail-reading-pane.slint:223 -#: ui/app.slint:488 ui/views/mail-reading-pane.slint:223 ui/views/mail-reading-pane.slint:1162 +#: ui/app.slint:486 ui/views/mail-reading-pane.slint:223 +#: ui/app.slint:486 ui/views/mail-reading-pane.slint:223 ui/views/mail-reading-pane.slint:1162 msgid "Loading message…" msgstr "" -#: ui/app.slint:494 ui/components/workspace-navigation.slint:551 -#: ui/app.slint:494 ui/components/workspace-navigation.slint:551 ui/tray.slint:9 ui/tray.slint:10 -#: ui/app.slint:494 ui/components/workspace-navigation.slint:551 ui/tray.slint:9 ui/tray.slint:10 +#: ui/app.slint:492 ui/components/workspace-navigation.slint:551 +#: ui/app.slint:492 ui/components/workspace-navigation.slint:551 ui/tray.slint:9 ui/tray.slint:10 +#: ui/app.slint:492 ui/components/workspace-navigation.slint:551 ui/tray.slint:9 ui/tray.slint:10 msgid "Flectar Mail" msgstr "" -#: ui/app.slint:1065 ui/app.slint:1078 +#: ui/app.slint:1063 ui/app.slint:1076 msgid "Global Labels (local only)" msgstr "" -#: ui/app.slint:1938 +#: ui/app.slint:1936 msgid "Empty Trash?" msgstr "" -#: ui/app.slint:1938 ui/views/files-view.slint:538 +#: ui/app.slint:1936 ui/views/files-view.slint:538 msgid "Delete permanently?" msgstr "" -#: ui/app.slint:1942 +#: ui/app.slint:1940 msgid "Permanently delete all messages in Trash across every connected account?" msgstr "" -#: ui/app.slint:1943 +#: ui/app.slint:1941 msgid "Permanently delete all messages in this account’s Trash?" msgstr "" -#: ui/app.slint:1945 +#: ui/app.slint:1943 msgid "Permanently delete the selected conversations from Trash?" msgstr "" -#: ui/app.slint:1946 +#: ui/app.slint:1944 msgid "Permanently delete this conversation from Trash?" msgstr "" -#: ui/app.slint:1947 ui/components/calendar.slint:926 -#: ui/app.slint:1947 ui/components/calendar.slint:926 +#: ui/app.slint:1945 ui/components/calendar.slint:926 +#: ui/app.slint:1945 ui/components/calendar.slint:926 msgid "This action cannot be undone." msgstr "" -#: ui/app.slint:1948 +#: ui/app.slint:1946 msgid "Empty Trash" msgstr "" -#: ui/app.slint:1948 ui/views/files-view.slint:620 -#: ui/app.slint:1948 ui/views/files-view.slint:620 +#: ui/app.slint:1946 ui/views/files-view.slint:620 +#: ui/app.slint:1946 ui/views/files-view.slint:620 msgid "Delete permanently" msgstr "" -#: ui/app.slint:2111 +#: ui/app.slint:2109 msgid "Mailbox startup failed" msgstr "" -#: ui/app.slint:2117 +#: ui/app.slint:2115 msgid "Flectar Mail could not open local storage" msgstr "" -#: ui/app.slint:2130 +#: ui/app.slint:2128 msgid "Diagnostic ID: {}" msgstr "" -#: ui/app.slint:2133 +#: ui/app.slint:2131 msgid "Diagnostic ID {}" msgstr "" -#: ui/app.slint:2138 +#: ui/app.slint:2136 msgid "Retry" msgstr "" -#: ui/app.slint:2146 ui/components/copyable-diagnostic.slint:38 +#: ui/app.slint:2144 ui/components/copyable-diagnostic.slint:38 msgid "Copy diagnostics" msgstr "" diff --git a/lang/fr/LC_MESSAGES/flectar-mail.po b/lang/fr/LC_MESSAGES/flectar-mail.po index c2b3a68f..9c4b2dcf 100644 --- a/lang/fr/LC_MESSAGES/flectar-mail.po +++ b/lang/fr/LC_MESSAGES/flectar-mail.po @@ -184,7 +184,7 @@ msgstr "" msgid "New address-book entry" msgstr "" -#: ui/app.slint:121 ui/app.slint:847 ui/components/composer-fields.slint:66 +#: ui/app.slint:121 ui/app.slint:845 ui/components/composer-fields.slint:66 #: ui/dialogs/compose-dialog.slint:37 msgid "No connected account" msgstr "" @@ -199,11 +199,11 @@ msgstr[1] "" msgid "Last contacted {} {}, {}" msgstr "" -#: ui/app.slint:128 ui/app.slint:361 +#: ui/app.slint:128 ui/app.slint:359 msgid "Loading local data…" msgstr "" -#: ui/app.slint:129 ui/app.slint:362 +#: ui/app.slint:129 ui/app.slint:360 msgid "Local data ready." msgstr "" @@ -664,751 +664,743 @@ msgid "Choose where to export the database snapshot" msgstr "" #: ui/app.slint:289 -msgid "The platform host does not provide document export." -msgstr "" - -#: ui/app.slint:290 -msgid "The platform host does not provide document import." -msgstr "" - -#: ui/app.slint:291 msgid "Backup exported. Passwords and OAuth tokens were not included." msgstr "" -#: ui/app.slint:292 +#: ui/app.slint:290 msgid "Could not export backup: {}" msgstr "" -#: ui/app.slint:293 +#: ui/app.slint:291 msgid "Local data storage is unavailable. Retry startup." msgstr "" -#: ui/app.slint:294 +#: ui/app.slint:292 msgid "Creating verified database snapshot…" msgstr "" -#: ui/app.slint:295 +#: ui/app.slint:293 msgid "Backup imported. All {} account configurations already existed." msgstr "" -#: ui/app.slint:296 +#: ui/app.slint:294 msgid "" "Imported {} account configurations. Open Accounts and sign in to each one." msgstr "" -#: ui/app.slint:297 +#: ui/app.slint:295 msgid "Could not import backup: {}" msgstr "" -#: ui/app.slint:298 +#: ui/app.slint:296 msgid "All local Flectar Mail data was deleted." msgstr "" -#: ui/app.slint:299 +#: ui/app.slint:297 msgid "Could not delete all local data: {}" msgstr "" -#: ui/app.slint:300 +#: ui/app.slint:298 msgid "Deleting local accounts and cached data…" msgstr "" -#: ui/app.slint:305 +#: ui/app.slint:303 msgid "Background mail refresh failed: {}" msgstr "" -#: ui/app.slint:306 +#: ui/app.slint:304 msgid "No conversations in this folder" msgstr "" -#: ui/app.slint:307 +#: ui/app.slint:305 msgid "No conversations match this search" msgstr "" -#: ui/app.slint:308 +#: ui/app.slint:306 msgid "Mail core is ready for account synchronization and message actions." msgstr "" -#: ui/app.slint:309 +#: ui/app.slint:307 msgid "Large message rendered in compatibility mode." msgstr "" -#: ui/app.slint:310 +#: ui/app.slint:308 msgid "Software email render failed: {}" msgstr "" -#: ui/app.slint:311 +#: ui/app.slint:309 msgid "Rendered with software fallback after GPU error: {}" msgstr "" -#: ui/app.slint:312 +#: ui/app.slint:310 msgid "Blitz render failed (GPU: {}; software: {})" msgstr "" -#: ui/app.slint:317 +#: ui/app.slint:315 msgid "Add an email address, then save the contact." msgstr "" -#: ui/app.slint:318 +#: ui/app.slint:316 msgid "Enter a valid email address before saving." msgstr "" -#: ui/app.slint:319 +#: ui/app.slint:317 msgid "Could not save: contact storage is unavailable." msgstr "" -#: ui/app.slint:320 +#: ui/app.slint:318 msgid "Could not save contact: {}" msgstr "" -#: ui/app.slint:321 +#: ui/app.slint:319 msgid "Contact saved." msgstr "" -#: ui/app.slint:322 +#: ui/app.slint:320 msgid "Saved to contacts." msgstr "" -#: ui/app.slint:323 +#: ui/app.slint:321 msgid "Could not delete: contact storage is unavailable." msgstr "" -#: ui/app.slint:324 +#: ui/app.slint:322 msgid "Could not delete contact: {}" msgstr "" -#: ui/app.slint:325 +#: ui/app.slint:323 msgid "Contact deleted." msgstr "" -#: ui/app.slint:326 +#: ui/app.slint:324 msgid "Could not load contacts: {}" msgstr "" -#: ui/app.slint:327 +#: ui/app.slint:325 msgid "Local contact storage is still starting…" msgstr "" -#: ui/app.slint:328 +#: ui/app.slint:326 msgid "Loading contacts…" msgstr "" -#: ui/app.slint:333 ui/components/calendar-agenda.slint:233 +#: ui/app.slint:331 ui/components/calendar-agenda.slint:233 msgid "Local calendar" msgstr "" -#: ui/app.slint:334 ui/components/calendar.slint:271 +#: ui/app.slint:332 ui/components/calendar.slint:271 #: ui/components/calendar.slint:319 ui/components/calendar.slint:434 msgid "Calendar" msgstr "" -#: ui/app.slint:335 +#: ui/app.slint:333 msgid "Could not load calendar: {}" msgstr "" -#: ui/app.slint:336 +#: ui/app.slint:334 msgid "Calendar shown and syncing." msgstr "" -#: ui/app.slint:337 +#: ui/app.slint:335 msgid "Calendar hidden and sync paused." msgstr "" -#: ui/app.slint:338 +#: ui/app.slint:336 msgid "Calendar update failed: {}" msgstr "" -#: ui/app.slint:339 +#: ui/app.slint:337 msgid "Only events created in Flectar Mail can be edited." msgstr "" -#: ui/app.slint:340 +#: ui/app.slint:338 msgid "Only events created in Flectar Mail can be deleted." msgstr "" -#: ui/app.slint:341 +#: ui/app.slint:339 msgid "Calendar storage is unavailable." msgstr "" -#: ui/app.slint:342 +#: ui/app.slint:340 msgid "Could not delete event: {}" msgstr "" -#: ui/app.slint:343 +#: ui/app.slint:341 msgid "Event deleted, but refresh failed: {}" msgstr "" -#: ui/app.slint:344 +#: ui/app.slint:342 msgid "Event deleted." msgstr "" -#: ui/app.slint:345 +#: ui/app.slint:343 msgid "Use YYYY-MM-DD and a 24-hour time such as 09:30." msgstr "" -#: ui/app.slint:346 +#: ui/app.slint:344 msgid "Event duration must be a number of minutes." msgstr "" -#: ui/app.slint:347 +#: ui/app.slint:345 msgid "Add a title and choose a duration from 15 to 1440 minutes." msgstr "" -#: ui/app.slint:348 +#: ui/app.slint:346 msgid "That local time does not exist in the current time zone." msgstr "" -#: ui/app.slint:349 +#: ui/app.slint:347 msgid "Could not save event: {}" msgstr "" -#: ui/app.slint:350 +#: ui/app.slint:348 msgid "Event saved, but refresh failed: {}" msgstr "" -#: ui/app.slint:351 +#: ui/app.slint:349 msgid "Event saved. Provider sync will run when enabled." msgstr "" -#: ui/app.slint:356 +#: ui/app.slint:354 msgid "Email resource render failed: {}" msgstr "" -#: ui/app.slint:357 +#: ui/app.slint:355 msgid "Message refresh failed: {}" msgstr "" -#: ui/app.slint:358 +#: ui/app.slint:356 msgid "Message load failed: {}" msgstr "" -#: ui/app.slint:359 +#: ui/app.slint:357 msgid "" "Your local mailbox could not be opened. No account operation was started. " "Retry, or copy the diagnostics for support." msgstr "" -#: ui/app.slint:360 +#: ui/app.slint:358 msgid "Local storage startup failed." msgstr "" -#: ui/app.slint:367 +#: ui/app.slint:365 msgid "Could not identify the dragged account." msgstr "" -#: ui/app.slint:368 +#: ui/app.slint:366 msgid "Account ordering requires local mail data." msgstr "" -#: ui/app.slint:369 +#: ui/app.slint:367 msgid "Could not find the account to reorder." msgstr "" -#: ui/app.slint:370 +#: ui/app.slint:368 msgid "Saving account order…" msgstr "" -#: ui/app.slint:371 +#: ui/app.slint:369 msgid "Sent copy settings require local mail data." msgstr "" "Les paramètres des copies envoyées nécessitent des données de messagerie " "locales." -#: ui/app.slint:372 +#: ui/app.slint:370 msgid "Updating Sent copy settings…" msgstr "Mise à jour des paramètres des copies envoyées…" -#: ui/app.slint:373 +#: ui/app.slint:371 msgid "Sent copy settings updated." msgstr "Paramètres des copies envoyées mis à jour." -#: ui/app.slint:374 +#: ui/app.slint:372 msgid "Could not update Sent copy settings: {}" msgstr "Impossible de mettre à jour les paramètres des copies envoyées : {}" -#: ui/app.slint:375 +#: ui/app.slint:373 msgid "Mail history requires local mail data." msgstr "" -#: ui/app.slint:376 +#: ui/app.slint:374 msgid "Updating mail history…" msgstr "" -#: ui/app.slint:377 +#: ui/app.slint:375 msgid "Remote images are unavailable in this minimal build." msgstr "" -#: ui/app.slint:378 +#: ui/app.slint:376 msgid "Could not save remote-image setting: {}" msgstr "" -#: ui/app.slint:379 +#: ui/app.slint:377 msgid "Remote sender icons unavailable: {}" msgstr "" -#: ui/app.slint:380 +#: ui/app.slint:378 msgid "Remote images unavailable: {}" msgstr "" -#: ui/app.slint:381 +#: ui/app.slint:379 msgid "Remote body images and sender favicons enabled." msgstr "" -#: ui/app.slint:382 +#: ui/app.slint:380 msgid "Remote images blocked." msgstr "" -#: ui/app.slint:383 +#: ui/app.slint:381 msgid "Could not refresh message images: {}" msgstr "" -#: ui/app.slint:384 +#: ui/app.slint:382 msgid "Remote-image loading is disabled." msgstr "" -#: ui/app.slint:385 +#: ui/app.slint:383 msgid "Could not load message images: {}" msgstr "" -#: ui/app.slint:386 +#: ui/app.slint:384 msgid "Sync finished, refresh failed: {}" msgstr "" -#: ui/app.slint:387 +#: ui/app.slint:385 msgid "Sync complete." msgstr "" -#: ui/app.slint:388 +#: ui/app.slint:386 msgid "Sync failed: {}" msgstr "" -#: ui/app.slint:389 +#: ui/app.slint:387 msgid "Local mail data is unavailable; preview data is read-only." msgstr "" -#: ui/app.slint:390 +#: ui/app.slint:388 msgid "Synchronizing accounts…" msgstr "" -#: ui/app.slint:395 +#: ui/app.slint:393 msgid "Message action completed." msgstr "" -#: ui/app.slint:396 +#: ui/app.slint:394 msgid "" "Permanent deletion queued. Mail will disappear after the server confirms." msgstr "" -#: ui/app.slint:397 +#: ui/app.slint:395 msgid "Message action failed: {}" msgstr "" -#: ui/app.slint:398 +#: ui/app.slint:396 msgid "Message action failed: message is no longer available" msgstr "" -#: ui/app.slint:399 +#: ui/app.slint:397 msgid "Label added." msgstr "" -#: ui/app.slint:400 +#: ui/app.slint:398 msgid "Label removed." msgstr "" -#: ui/app.slint:401 +#: ui/app.slint:399 msgid "Label created and added." msgstr "" -#: ui/app.slint:402 +#: ui/app.slint:400 msgid "Label created." msgstr "" -#: ui/app.slint:403 +#: ui/app.slint:401 msgid "Label updated." msgstr "" -#: ui/app.slint:404 +#: ui/app.slint:402 msgid "Label deleted." msgstr "" -#: ui/app.slint:405 +#: ui/app.slint:403 msgid "Could not update label: {}" msgstr "" -#: ui/app.slint:406 +#: ui/app.slint:404 msgid "Could not save label: {}" msgstr "" -#: ui/app.slint:407 +#: ui/app.slint:405 msgid "Could not delete label: {}" msgstr "" -#: ui/app.slint:408 +#: ui/app.slint:406 msgid "Folder created." msgstr "" -#: ui/app.slint:409 +#: ui/app.slint:407 msgid "Folder renamed." msgstr "" -#: ui/app.slint:410 +#: ui/app.slint:408 msgid "Folder deleted." msgstr "" -#: ui/app.slint:411 +#: ui/app.slint:409 msgid "Could not create folder: {}" msgstr "" -#: ui/app.slint:412 +#: ui/app.slint:410 msgid "Could not rename folder: {}" msgstr "" -#: ui/app.slint:413 +#: ui/app.slint:411 msgid "Could not delete folder: {}" msgstr "" -#: ui/app.slint:414 +#: ui/app.slint:412 msgid "Could not close preview: {}" msgstr "" -#: ui/app.slint:415 +#: ui/app.slint:413 msgid "Opened the system print view." msgstr "" -#: ui/app.slint:416 +#: ui/app.slint:414 msgid "Could not print message: {}" msgstr "" -#: ui/app.slint:417 +#: ui/app.slint:415 msgid "Opened the message in your browser." msgstr "" -#: ui/app.slint:418 +#: ui/app.slint:416 msgid "Could not open message in browser: {}" msgstr "" -#: ui/app.slint:419 +#: ui/app.slint:417 msgid "Connect an account before replying or forwarding." msgstr "" -#: ui/app.slint:420 +#: ui/app.slint:418 msgid "Choose a message first." msgstr "" -#: ui/app.slint:421 +#: ui/app.slint:419 msgid "Only drafts can be reopened for editing." msgstr "" -#: ui/app.slint:422 +#: ui/app.slint:420 msgid "Could not open draft: {}" msgstr "" -#: ui/app.slint:423 +#: ui/app.slint:421 msgid "Could not open draft: invalid account identifier" msgstr "" -#: ui/app.slint:424 +#: ui/app.slint:422 msgid "Connected account" msgstr "" -#: ui/app.slint:425 +#: ui/app.slint:423 msgid "Could not open composer: {}" msgstr "" -#: ui/app.slint:426 +#: ui/app.slint:424 msgid "Could not open composer: invalid account identifier" msgstr "" -#: ui/app.slint:431 +#: ui/app.slint:429 msgid "Attach files to this email" msgstr "" -#: ui/app.slint:432 -msgid "The platform host does not provide a document picker." +#: ui/app.slint:430 +msgid "Could not add attachments: {}" msgstr "" -#: ui/app.slint:433 +#: ui/app.slint:431 msgid "Added 1 attachment." msgstr "" -#: ui/app.slint:434 +#: ui/app.slint:432 msgid "Added {} attachments." msgstr "" -#: ui/app.slint:435 +#: ui/app.slint:433 msgid "" "Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB " "total limit)." msgstr "" -#: ui/app.slint:436 +#: ui/app.slint:434 msgid "Attachment removed." msgstr "" -#: ui/app.slint:437 +#: ui/app.slint:435 msgid "Choose the account this message should be sent from." msgstr "" -#: ui/app.slint:438 +#: ui/app.slint:436 msgid "Choose a verified sender identity for this message." msgstr "" -#: ui/app.slint:439 +#: ui/app.slint:437 msgid "Connect an account before saving or sending mail." msgstr "" -#: ui/app.slint:440 +#: ui/app.slint:438 msgid "Message queued for delivery (action {})." msgstr "" -#: ui/app.slint:441 +#: ui/app.slint:439 msgid "Sending message…" msgstr "" -#: ui/app.slint:442 +#: ui/app.slint:440 msgid "Message sent." msgstr "" -#: ui/app.slint:443 +#: ui/app.slint:441 msgid "Message was not sent: {} Your draft is preserved." msgstr "" -#: ui/app.slint:444 +#: ui/app.slint:442 msgid "Draft saved locally (#{})." msgstr "" -#: ui/app.slint:445 +#: ui/app.slint:443 msgid "Compose failed: {}" msgstr "" -#: ui/app.slint:446 +#: ui/app.slint:444 msgid "Connect an account before managing email templates." msgstr "" -#: ui/app.slint:447 +#: ui/app.slint:445 msgid "Could not load email templates: {}" msgstr "" -#: ui/app.slint:448 +#: ui/app.slint:446 msgid "Enter a template name." msgstr "" -#: ui/app.slint:449 +#: ui/app.slint:447 msgid "Add a subject or message to the template." msgstr "" -#: ui/app.slint:450 +#: ui/app.slint:448 msgid "Email template saved." msgstr "" -#: ui/app.slint:451 +#: ui/app.slint:449 msgid "Could not save email template: {}" msgstr "" -#: ui/app.slint:452 +#: ui/app.slint:450 msgid "Email template deleted." msgstr "" -#: ui/app.slint:453 +#: ui/app.slint:451 msgid "Could not delete email template: {}" msgstr "" -#: ui/app.slint:454 +#: ui/app.slint:452 msgid "Email template is no longer available." msgstr "" -#: ui/app.slint:455 +#: ui/app.slint:453 msgid "Email template inserted." msgstr "" -#: ui/app.slint:456 +#: ui/app.slint:454 msgid "Email template inserted, but its usage could not be saved: {}" msgstr "" -#: ui/app.slint:457 +#: ui/app.slint:455 msgid "IMAP port must be between 1 and 65535." msgstr "" -#: ui/app.slint:458 +#: ui/app.slint:456 msgid "SMTP port must be between 1 and 65535." msgstr "" -#: ui/app.slint:459 +#: ui/app.slint:457 msgid "Local mail data is unavailable. Retry startup." msgstr "" -#: ui/app.slint:460 +#: ui/app.slint:458 msgid "Discovering JMAP and checking capabilities…" msgstr "" -#: ui/app.slint:461 +#: ui/app.slint:459 msgid "Certificate imported. Connect to verify the server." msgstr "" -#: ui/app.slint:462 +#: ui/app.slint:460 msgid "Could not load account preferences: {}" msgstr "" -#: ui/app.slint:463 +#: ui/app.slint:461 msgid "" "Your edited signature was preserved. Choose No signature to keep it as " "message text before inserting another." msgstr "" -#: ui/app.slint:464 +#: ui/app.slint:462 msgid "Certificate import failed: {}" msgstr "" -#: ui/app.slint:465 +#: ui/app.slint:463 msgid "Paste the PEM certificate into the certificate field." msgstr "" -#: ui/app.slint:466 +#: ui/app.slint:464 msgid "Testing IMAP and SMTP connections…" msgstr "" -#: ui/app.slint:471 +#: ui/app.slint:469 msgid "" "Another browser authorization is already in progress; finish it or choose " "Cancel." msgstr "" -#: ui/app.slint:472 ui/dialogs/settings-dialog.slint:1643 +#: ui/app.slint:470 ui/dialogs/settings-dialog.slint:1643 #: ui/dialogs/settings-dialog.slint:1686 ui/views/onboarding-view.slint:152 msgid "Add app keys in Sign-in settings to enable this provider." msgstr "" -#: ui/app.slint:473 +#: ui/app.slint:471 msgid "Choose Gmail or Outlook OAuth." msgstr "" -#: ui/app.slint:474 +#: ui/app.slint:472 msgid "Complete sign-in in your browser, or choose Cancel here." msgstr "" -#: ui/app.slint:475 +#: ui/app.slint:473 msgid "Cancelling browser sign-in…" msgstr "" -#: ui/app.slint:476 +#: ui/app.slint:474 msgid "Finishing browser sign-in…" msgstr "" -#: ui/app.slint:477 +#: ui/app.slint:475 msgid "Could not use that browser address: {}" msgstr "" -#: ui/app.slint:478 +#: ui/app.slint:476 msgid "Calendar core is unavailable." msgstr "" -#: ui/app.slint:479 +#: ui/app.slint:477 msgid "Account no longer exists." msgstr "" -#: ui/app.slint:480 +#: ui/app.slint:478 msgid "Authorize calendar access in your browser." msgstr "" -#: ui/app.slint:481 +#: ui/app.slint:479 msgid "OAuth app keys saved." msgstr "" -#: ui/app.slint:482 +#: ui/app.slint:480 msgid "{} OAuth settings saved." msgstr "" -#: ui/app.slint:483 +#: ui/app.slint:481 msgid "OAuth settings failed: {}" msgstr "" -#: ui/app.slint:484 +#: ui/app.slint:482 msgid "Mail refresh failed: {}" msgstr "" -#: ui/app.slint:485 +#: ui/app.slint:483 msgid "Mail filter failed: {}" msgstr "" -#: ui/app.slint:486 +#: ui/app.slint:484 msgid "Mail pagination failed: {}" msgstr "" -#: ui/app.slint:487 +#: ui/app.slint:485 msgid "Could not mark message as read: {}" msgstr "" -#: ui/app.slint:488 ui/views/mail-reading-pane.slint:223 +#: ui/app.slint:486 ui/views/mail-reading-pane.slint:223 #: ui/views/mail-reading-pane.slint:1162 msgid "Loading message…" msgstr "" -#: ui/app.slint:494 ui/components/workspace-navigation.slint:551 +#: ui/app.slint:492 ui/components/workspace-navigation.slint:551 #: ui/tray.slint:9 ui/tray.slint:10 msgid "Flectar Mail" msgstr "" -#: ui/app.slint:1065 ui/app.slint:1078 +#: ui/app.slint:1063 ui/app.slint:1076 msgid "Global Labels (local only)" msgstr "" -#: ui/app.slint:1938 +#: ui/app.slint:1936 msgid "Empty Trash?" msgstr "" -#: ui/app.slint:1938 ui/views/files-view.slint:538 +#: ui/app.slint:1936 ui/views/files-view.slint:538 msgid "Delete permanently?" msgstr "" -#: ui/app.slint:1942 +#: ui/app.slint:1940 msgid "" "Permanently delete all messages in Trash across every connected account?" msgstr "" -#: ui/app.slint:1943 +#: ui/app.slint:1941 msgid "Permanently delete all messages in this account’s Trash?" msgstr "" -#: ui/app.slint:1945 +#: ui/app.slint:1943 msgid "Permanently delete the selected conversations from Trash?" msgstr "" -#: ui/app.slint:1946 +#: ui/app.slint:1944 msgid "Permanently delete this conversation from Trash?" msgstr "" -#: ui/app.slint:1947 ui/components/calendar.slint:926 +#: ui/app.slint:1945 ui/components/calendar.slint:926 msgid "This action cannot be undone." msgstr "" -#: ui/app.slint:1948 +#: ui/app.slint:1946 msgid "Empty Trash" msgstr "" -#: ui/app.slint:1948 ui/views/files-view.slint:620 +#: ui/app.slint:1946 ui/views/files-view.slint:620 msgid "Delete permanently" msgstr "" -#: ui/app.slint:2111 +#: ui/app.slint:2109 msgid "Mailbox startup failed" msgstr "" -#: ui/app.slint:2117 +#: ui/app.slint:2115 msgid "Flectar Mail could not open local storage" msgstr "" -#: ui/app.slint:2130 +#: ui/app.slint:2128 msgid "Diagnostic ID: {}" msgstr "" -#: ui/app.slint:2133 +#: ui/app.slint:2131 msgid "Diagnostic ID {}" msgstr "" -#: ui/app.slint:2138 +#: ui/app.slint:2136 msgid "Retry" msgstr "" -#: ui/app.slint:2146 ui/components/copyable-diagnostic.slint:38 +#: ui/app.slint:2144 ui/components/copyable-diagnostic.slint:38 msgid "Copy diagnostics" msgstr "" diff --git a/lang/ru/LC_MESSAGES/flectar-mail.po b/lang/ru/LC_MESSAGES/flectar-mail.po index 70b2917f..d725fed3 100644 --- a/lang/ru/LC_MESSAGES/flectar-mail.po +++ b/lang/ru/LC_MESSAGES/flectar-mail.po @@ -188,7 +188,7 @@ msgstr "История переписки доступна" msgid "New address-book entry" msgstr "Новая запись в адресной книге" -#: ui/app.slint:121 ui/app.slint:847 ui/components/composer-fields.slint:66 +#: ui/app.slint:121 ui/app.slint:845 ui/components/composer-fields.slint:66 #: ui/dialogs/compose-dialog.slint:37 msgid "No connected account" msgstr "Нет подключённой учётной записи" @@ -204,11 +204,11 @@ msgstr[2] "{n} взаимодействий" msgid "Last contacted {} {}, {}" msgstr "Последний контакт: {1} {0} {2}" -#: ui/app.slint:128 ui/app.slint:361 +#: ui/app.slint:128 ui/app.slint:359 msgid "Loading local data…" msgstr "Загрузка локальных данных…" -#: ui/app.slint:129 ui/app.slint:362 +#: ui/app.slint:129 ui/app.slint:360 msgid "Local data ready." msgstr "Локальные данные готовы." @@ -670,231 +670,223 @@ msgid "Choose where to export the database snapshot" msgstr "Выберите место для экспорта снимка базы данных" #: ui/app.slint:289 -msgid "The platform host does not provide document export." -msgstr "Платформа не предоставляет экспорт документов." - -#: ui/app.slint:290 -msgid "The platform host does not provide document import." -msgstr "Платформа не предоставляет импорт документов." - -#: ui/app.slint:291 msgid "Backup exported. Passwords and OAuth tokens were not included." msgstr "Резервная копия экспортирована. Пароли и токены OAuth не включены." -#: ui/app.slint:292 +#: ui/app.slint:290 msgid "Could not export backup: {}" msgstr "Не удалось экспортировать резервную копию: {}" -#: ui/app.slint:293 +#: ui/app.slint:291 msgid "Local data storage is unavailable. Retry startup." msgstr "Локальное хранилище данных недоступно. Повторите запуск." -#: ui/app.slint:294 +#: ui/app.slint:292 msgid "Creating verified database snapshot…" msgstr "Создание проверенного снимка базы данных…" -#: ui/app.slint:295 +#: ui/app.slint:293 msgid "Backup imported. All {} account configurations already existed." msgstr "" "Резервная копия импортирована. Все настройки учётных записей уже имелись " "({})." -#: ui/app.slint:296 +#: ui/app.slint:294 msgid "" "Imported {} account configurations. Open Accounts and sign in to each one." msgstr "" "Импортировано настроек учётных записей: {}. Откройте раздел «Учётные записи» " "и войдите в каждую." -#: ui/app.slint:297 +#: ui/app.slint:295 msgid "Could not import backup: {}" msgstr "Не удалось импортировать резервную копию: {}" -#: ui/app.slint:298 +#: ui/app.slint:296 msgid "All local Flectar Mail data was deleted." msgstr "Все локальные данные Flectar Mail удалены." -#: ui/app.slint:299 +#: ui/app.slint:297 msgid "Could not delete all local data: {}" msgstr "Не удалось удалить все локальные данные: {}" -#: ui/app.slint:300 +#: ui/app.slint:298 msgid "Deleting local accounts and cached data…" msgstr "Удаление локальных учётных записей и кешированных данных…" -#: ui/app.slint:305 +#: ui/app.slint:303 msgid "Background mail refresh failed: {}" msgstr "Фоновое обновление почты не удалось: {}" -#: ui/app.slint:306 +#: ui/app.slint:304 msgid "No conversations in this folder" msgstr "В этой папке нет переписок" -#: ui/app.slint:307 +#: ui/app.slint:305 msgid "No conversations match this search" msgstr "Нет переписок, соответствующих этому поиску" -#: ui/app.slint:308 +#: ui/app.slint:306 msgid "Mail core is ready for account synchronization and message actions." msgstr "" "Почтовое ядро готово к синхронизации учётных записей и действиям с " "сообщениями." -#: ui/app.slint:309 +#: ui/app.slint:307 msgid "Large message rendered in compatibility mode." msgstr "Большое сообщение отображено в режиме совместимости." -#: ui/app.slint:310 +#: ui/app.slint:308 msgid "Software email render failed: {}" msgstr "Ошибка программной отрисовки письма: {}" -#: ui/app.slint:311 +#: ui/app.slint:309 msgid "Rendered with software fallback after GPU error: {}" msgstr "Отображено программно после ошибки GPU: {}" -#: ui/app.slint:312 +#: ui/app.slint:310 msgid "Blitz render failed (GPU: {}; software: {})" msgstr "Ошибка отрисовки Blitz (GPU: {}; программно: {})" -#: ui/app.slint:317 +#: ui/app.slint:315 msgid "Add an email address, then save the contact." msgstr "Добавьте адрес электронной почты, затем сохраните контакт." -#: ui/app.slint:318 +#: ui/app.slint:316 msgid "Enter a valid email address before saving." msgstr "Введите корректный адрес электронной почты перед сохранением." -#: ui/app.slint:319 +#: ui/app.slint:317 msgid "Could not save: contact storage is unavailable." msgstr "Не удалось сохранить: хранилище контактов недоступно." -#: ui/app.slint:320 +#: ui/app.slint:318 msgid "Could not save contact: {}" msgstr "Не удалось сохранить контакт: {}" -#: ui/app.slint:321 +#: ui/app.slint:319 msgid "Contact saved." msgstr "Контакт сохранён." -#: ui/app.slint:322 +#: ui/app.slint:320 msgid "Saved to contacts." msgstr "Сохранено в контактах." -#: ui/app.slint:323 +#: ui/app.slint:321 msgid "Could not delete: contact storage is unavailable." msgstr "Не удалось удалить: хранилище контактов недоступно." -#: ui/app.slint:324 +#: ui/app.slint:322 msgid "Could not delete contact: {}" msgstr "Не удалось удалить контакт: {}" -#: ui/app.slint:325 +#: ui/app.slint:323 msgid "Contact deleted." msgstr "Контакт удалён." -#: ui/app.slint:326 +#: ui/app.slint:324 msgid "Could not load contacts: {}" msgstr "Не удалось загрузить контакты: {}" -#: ui/app.slint:327 +#: ui/app.slint:325 msgid "Local contact storage is still starting…" msgstr "Локальное хранилище контактов ещё запускается…" -#: ui/app.slint:328 +#: ui/app.slint:326 msgid "Loading contacts…" msgstr "Загрузка контактов…" -#: ui/app.slint:333 ui/components/calendar-agenda.slint:233 +#: ui/app.slint:331 ui/components/calendar-agenda.slint:233 msgid "Local calendar" msgstr "Локальный календарь" -#: ui/app.slint:334 ui/components/calendar.slint:271 +#: ui/app.slint:332 ui/components/calendar.slint:271 #: ui/components/calendar.slint:319 ui/components/calendar.slint:434 msgid "Calendar" msgstr "Календарь" -#: ui/app.slint:335 +#: ui/app.slint:333 msgid "Could not load calendar: {}" msgstr "Не удалось загрузить календарь: {}" -#: ui/app.slint:336 +#: ui/app.slint:334 msgid "Calendar shown and syncing." msgstr "Календарь отображён и синхронизируется." -#: ui/app.slint:337 +#: ui/app.slint:335 msgid "Calendar hidden and sync paused." msgstr "Календарь скрыт, синхронизация приостановлена." -#: ui/app.slint:338 +#: ui/app.slint:336 msgid "Calendar update failed: {}" msgstr "Обновление календаря не удалось: {}" -#: ui/app.slint:339 +#: ui/app.slint:337 msgid "Only events created in Flectar Mail can be edited." msgstr "Редактировать можно только события, созданные в Flectar Mail." -#: ui/app.slint:340 +#: ui/app.slint:338 msgid "Only events created in Flectar Mail can be deleted." msgstr "Удалять можно только события, созданные в Flectar Mail." -#: ui/app.slint:341 +#: ui/app.slint:339 msgid "Calendar storage is unavailable." msgstr "Хранилище календаря недоступно." -#: ui/app.slint:342 +#: ui/app.slint:340 msgid "Could not delete event: {}" msgstr "Не удалось удалить событие: {}" -#: ui/app.slint:343 +#: ui/app.slint:341 msgid "Event deleted, but refresh failed: {}" msgstr "Событие удалено, но обновление не удалось: {}" -#: ui/app.slint:344 +#: ui/app.slint:342 msgid "Event deleted." msgstr "Событие удалено." -#: ui/app.slint:345 +#: ui/app.slint:343 msgid "Use YYYY-MM-DD and a 24-hour time such as 09:30." msgstr "Используйте формат ГГГГ-ММ-ДД и 24-часовое время, например 09:30." -#: ui/app.slint:346 +#: ui/app.slint:344 msgid "Event duration must be a number of minutes." msgstr "Продолжительность события должна быть указана в минутах." -#: ui/app.slint:347 +#: ui/app.slint:345 msgid "Add a title and choose a duration from 15 to 1440 minutes." msgstr "Добавьте название и выберите продолжительность от 15 до 1440 минут." -#: ui/app.slint:348 +#: ui/app.slint:346 msgid "That local time does not exist in the current time zone." msgstr "Такое местное время не существует в текущем часовом поясе." -#: ui/app.slint:349 +#: ui/app.slint:347 msgid "Could not save event: {}" msgstr "Не удалось сохранить событие: {}" -#: ui/app.slint:350 +#: ui/app.slint:348 msgid "Event saved, but refresh failed: {}" msgstr "Событие сохранено, но обновление не удалось: {}" -#: ui/app.slint:351 +#: ui/app.slint:349 msgid "Event saved. Provider sync will run when enabled." msgstr "" "Событие сохранено. Синхронизация с провайдером выполнится при включении." -#: ui/app.slint:356 +#: ui/app.slint:354 msgid "Email resource render failed: {}" msgstr "Ошибка отрисовки ресурса письма: {}" -#: ui/app.slint:357 +#: ui/app.slint:355 msgid "Message refresh failed: {}" msgstr "Обновление сообщения не удалось: {}" -#: ui/app.slint:358 +#: ui/app.slint:356 msgid "Message load failed: {}" msgstr "Загрузка сообщения не удалась: {}" -#: ui/app.slint:359 +#: ui/app.slint:357 msgid "" "Your local mailbox could not be opened. No account operation was started. " "Retry, or copy the diagnostics for support." @@ -903,258 +895,258 @@ msgstr "" "запускалась. Повторите попытку или скопируйте диагностику для службы " "поддержки." -#: ui/app.slint:360 +#: ui/app.slint:358 msgid "Local storage startup failed." msgstr "Запуск локального хранилища не удался." -#: ui/app.slint:367 +#: ui/app.slint:365 msgid "Could not identify the dragged account." msgstr "Не удалось определить перетаскиваемую учётную запись." -#: ui/app.slint:368 +#: ui/app.slint:366 msgid "Account ordering requires local mail data." msgstr "Для упорядочивания учётных записей требуются локальные данные почты." -#: ui/app.slint:369 +#: ui/app.slint:367 msgid "Could not find the account to reorder." msgstr "Не удалось найти учётную запись для перестановки." -#: ui/app.slint:370 +#: ui/app.slint:368 msgid "Saving account order…" msgstr "Сохранение порядка учётных записей…" -#: ui/app.slint:371 +#: ui/app.slint:369 msgid "Sent copy settings require local mail data." msgstr "Для настройки копий отправленных писем нужны локальные данные почты." -#: ui/app.slint:372 +#: ui/app.slint:370 msgid "Updating Sent copy settings…" msgstr "Обновление настроек копий отправленных писем…" -#: ui/app.slint:373 +#: ui/app.slint:371 msgid "Sent copy settings updated." msgstr "Настройки копий отправленных писем обновлены." -#: ui/app.slint:374 +#: ui/app.slint:372 msgid "Could not update Sent copy settings: {}" msgstr "Не удалось обновить настройки копий отправленных писем: {}" -#: ui/app.slint:375 +#: ui/app.slint:373 msgid "Mail history requires local mail data." msgstr "Для истории почты требуются локальные данные почты." -#: ui/app.slint:376 +#: ui/app.slint:374 msgid "Updating mail history…" msgstr "Обновление истории почты…" -#: ui/app.slint:377 +#: ui/app.slint:375 msgid "Remote images are unavailable in this minimal build." msgstr "Внешние изображения недоступны в этой минимальной сборке." -#: ui/app.slint:378 +#: ui/app.slint:376 msgid "Could not save remote-image setting: {}" msgstr "Не удалось сохранить настройку внешних изображений: {}" -#: ui/app.slint:379 +#: ui/app.slint:377 msgid "Remote sender icons unavailable: {}" msgstr "Внешние значки отправителей недоступны: {}" -#: ui/app.slint:380 +#: ui/app.slint:378 msgid "Remote images unavailable: {}" msgstr "Внешние изображения недоступны: {}" -#: ui/app.slint:381 +#: ui/app.slint:379 msgid "Remote body images and sender favicons enabled." msgstr "" "Загрузка внешних изображений в письмах и значков отправителей включена." -#: ui/app.slint:382 +#: ui/app.slint:380 msgid "Remote images blocked." msgstr "Внешние изображения заблокированы." -#: ui/app.slint:383 +#: ui/app.slint:381 msgid "Could not refresh message images: {}" msgstr "Не удалось обновить изображения сообщения: {}" -#: ui/app.slint:384 +#: ui/app.slint:382 msgid "Remote-image loading is disabled." msgstr "Загрузка внешних изображений отключена." -#: ui/app.slint:385 +#: ui/app.slint:383 msgid "Could not load message images: {}" msgstr "Не удалось загрузить изображения сообщения: {}" -#: ui/app.slint:386 +#: ui/app.slint:384 msgid "Sync finished, refresh failed: {}" msgstr "Синхронизация завершена, обновление не удалось: {}" -#: ui/app.slint:387 +#: ui/app.slint:385 msgid "Sync complete." msgstr "Синхронизация завершена." -#: ui/app.slint:388 +#: ui/app.slint:386 msgid "Sync failed: {}" msgstr "Синхронизация не удалась: {}" -#: ui/app.slint:389 +#: ui/app.slint:387 msgid "Local mail data is unavailable; preview data is read-only." msgstr "" "Локальные данные почты недоступны; данные предпросмотра доступны только для " "чтения." -#: ui/app.slint:390 +#: ui/app.slint:388 msgid "Synchronizing accounts…" msgstr "Синхронизация учётных записей…" -#: ui/app.slint:395 +#: ui/app.slint:393 msgid "Message action completed." msgstr "Действие с сообщением выполнено." -#: ui/app.slint:396 +#: ui/app.slint:394 msgid "" "Permanent deletion queued. Mail will disappear after the server confirms." msgstr "" "Безвозвратное удаление запланировано. Письма исчезнут после подтверждения " "сервера." -#: ui/app.slint:397 +#: ui/app.slint:395 msgid "Message action failed: {}" msgstr "Действие с сообщением не удалось: {}" -#: ui/app.slint:398 +#: ui/app.slint:396 msgid "Message action failed: message is no longer available" msgstr "Действие с сообщением не удалось: сообщение больше недоступно" -#: ui/app.slint:399 +#: ui/app.slint:397 msgid "Label added." msgstr "Метка добавлена." -#: ui/app.slint:400 +#: ui/app.slint:398 msgid "Label removed." msgstr "Метка удалена." -#: ui/app.slint:401 +#: ui/app.slint:399 msgid "Label created and added." msgstr "Метка создана и добавлена." -#: ui/app.slint:402 +#: ui/app.slint:400 msgid "Label created." msgstr "Метка создана." -#: ui/app.slint:403 +#: ui/app.slint:401 msgid "Label updated." msgstr "Метка обновлена." -#: ui/app.slint:404 +#: ui/app.slint:402 msgid "Label deleted." msgstr "Метка удалена." -#: ui/app.slint:405 +#: ui/app.slint:403 msgid "Could not update label: {}" msgstr "Не удалось обновить метку: {}" -#: ui/app.slint:406 +#: ui/app.slint:404 msgid "Could not save label: {}" msgstr "Не удалось сохранить метку: {}" -#: ui/app.slint:407 +#: ui/app.slint:405 msgid "Could not delete label: {}" msgstr "Не удалось удалить метку: {}" -#: ui/app.slint:408 +#: ui/app.slint:406 msgid "Folder created." msgstr "Папка создана." -#: ui/app.slint:409 +#: ui/app.slint:407 msgid "Folder renamed." msgstr "Папка переименована." -#: ui/app.slint:410 +#: ui/app.slint:408 msgid "Folder deleted." msgstr "Папка удалена." -#: ui/app.slint:411 +#: ui/app.slint:409 msgid "Could not create folder: {}" msgstr "Не удалось создать папку: {}" -#: ui/app.slint:412 +#: ui/app.slint:410 msgid "Could not rename folder: {}" msgstr "Не удалось переименовать папку: {}" -#: ui/app.slint:413 +#: ui/app.slint:411 msgid "Could not delete folder: {}" msgstr "Не удалось удалить папку: {}" -#: ui/app.slint:414 +#: ui/app.slint:412 msgid "Could not close preview: {}" msgstr "Не удалось закрыть предпросмотр: {}" -#: ui/app.slint:415 +#: ui/app.slint:413 msgid "Opened the system print view." msgstr "Открыт системный просмотр печати." -#: ui/app.slint:416 +#: ui/app.slint:414 msgid "Could not print message: {}" msgstr "Не удалось напечатать сообщение: {}" -#: ui/app.slint:417 +#: ui/app.slint:415 msgid "Opened the message in your browser." msgstr "Сообщение открыто в браузере." -#: ui/app.slint:418 +#: ui/app.slint:416 msgid "Could not open message in browser: {}" msgstr "Не удалось открыть сообщение в браузере: {}" -#: ui/app.slint:419 +#: ui/app.slint:417 msgid "Connect an account before replying or forwarding." msgstr "Подключите учётную запись перед ответом или пересылкой." -#: ui/app.slint:420 +#: ui/app.slint:418 msgid "Choose a message first." msgstr "Сначала выберите сообщение." -#: ui/app.slint:421 +#: ui/app.slint:419 msgid "Only drafts can be reopened for editing." msgstr "Только черновики можно снова открыть для редактирования." -#: ui/app.slint:422 +#: ui/app.slint:420 msgid "Could not open draft: {}" msgstr "Не удалось открыть черновик: {}" -#: ui/app.slint:423 +#: ui/app.slint:421 msgid "Could not open draft: invalid account identifier" msgstr "Не удалось открыть черновик: неверный идентификатор учётной записи" -#: ui/app.slint:424 +#: ui/app.slint:422 msgid "Connected account" msgstr "Подключённая учётная запись" -#: ui/app.slint:425 +#: ui/app.slint:423 msgid "Could not open composer: {}" msgstr "Не удалось открыть редактор письма: {}" -#: ui/app.slint:426 +#: ui/app.slint:424 msgid "Could not open composer: invalid account identifier" msgstr "" "Не удалось открыть редактор письма: неверный идентификатор учётной записи" -#: ui/app.slint:431 +#: ui/app.slint:429 msgid "Attach files to this email" msgstr "Прикрепить файлы к этому письму" -#: ui/app.slint:432 -msgid "The platform host does not provide a document picker." -msgstr "Платформа не предоставляет выбор документов." +#: ui/app.slint:430 +msgid "Could not add attachments: {}" +msgstr "Не удалось добавить вложения: {}" -#: ui/app.slint:433 +#: ui/app.slint:431 msgid "Added 1 attachment." msgstr "Добавлено одно вложение." -#: ui/app.slint:434 +#: ui/app.slint:432 msgid "Added {} attachments." msgstr "Добавлено вложений: {}." -#: ui/app.slint:435 +#: ui/app.slint:433 msgid "" "Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB " "total limit)." @@ -1162,116 +1154,116 @@ msgstr "" "Добавлено вложений: {}. Пропущено: {} (дубликаты, недоступные для чтения или " "превышающие общий лимит 25 МБ)." -#: ui/app.slint:436 +#: ui/app.slint:434 msgid "Attachment removed." msgstr "Вложение удалено." -#: ui/app.slint:437 +#: ui/app.slint:435 msgid "Choose the account this message should be sent from." msgstr "Выберите учётную запись, с которой нужно отправить это сообщение." -#: ui/app.slint:438 +#: ui/app.slint:436 msgid "Choose a verified sender identity for this message." msgstr "Выберите подтверждённые данные отправителя для этого письма." -#: ui/app.slint:439 +#: ui/app.slint:437 msgid "Connect an account before saving or sending mail." msgstr "Подключите учётную запись перед сохранением или отправкой почты." -#: ui/app.slint:440 +#: ui/app.slint:438 msgid "Message queued for delivery (action {})." msgstr "Сообщение поставлено в очередь на отправку (действие {})." -#: ui/app.slint:441 +#: ui/app.slint:439 msgid "Sending message…" msgstr "Отправка сообщения…" -#: ui/app.slint:442 +#: ui/app.slint:440 msgid "Message sent." msgstr "Сообщение отправлено." -#: ui/app.slint:443 +#: ui/app.slint:441 msgid "Message was not sent: {} Your draft is preserved." msgstr "Сообщение не отправлено: {} Ваш черновик сохранён." -#: ui/app.slint:444 +#: ui/app.slint:442 msgid "Draft saved locally (#{})." msgstr "Черновик сохранён локально (#{})." -#: ui/app.slint:445 +#: ui/app.slint:443 msgid "Compose failed: {}" msgstr "Создание письма не удалось: {}" -#: ui/app.slint:446 +#: ui/app.slint:444 msgid "Connect an account before managing email templates." msgstr "Подключите учётную запись перед управлением шаблонами писем." -#: ui/app.slint:447 +#: ui/app.slint:445 msgid "Could not load email templates: {}" msgstr "Не удалось загрузить шаблоны писем: {}" -#: ui/app.slint:448 +#: ui/app.slint:446 msgid "Enter a template name." msgstr "Введите имя шаблона." -#: ui/app.slint:449 +#: ui/app.slint:447 msgid "Add a subject or message to the template." msgstr "Добавьте тему или сообщение в шаблон." -#: ui/app.slint:450 +#: ui/app.slint:448 msgid "Email template saved." msgstr "Шаблон письма сохранён." -#: ui/app.slint:451 +#: ui/app.slint:449 msgid "Could not save email template: {}" msgstr "Не удалось сохранить шаблон письма: {}" -#: ui/app.slint:452 +#: ui/app.slint:450 msgid "Email template deleted." msgstr "Шаблон письма удалён." -#: ui/app.slint:453 +#: ui/app.slint:451 msgid "Could not delete email template: {}" msgstr "Не удалось удалить шаблон письма: {}" -#: ui/app.slint:454 +#: ui/app.slint:452 msgid "Email template is no longer available." msgstr "Шаблон письма больше недоступен." -#: ui/app.slint:455 +#: ui/app.slint:453 msgid "Email template inserted." msgstr "Шаблон письма вставлен." -#: ui/app.slint:456 +#: ui/app.slint:454 msgid "Email template inserted, but its usage could not be saved: {}" msgstr "" "Шаблон письма вставлен, но сведения о его использовании не сохранены: {}" -#: ui/app.slint:457 +#: ui/app.slint:455 msgid "IMAP port must be between 1 and 65535." msgstr "Порт IMAP должен быть в диапазоне от 1 до 65535." -#: ui/app.slint:458 +#: ui/app.slint:456 msgid "SMTP port must be between 1 and 65535." msgstr "Порт SMTP должен быть в диапазоне от 1 до 65535." -#: ui/app.slint:459 +#: ui/app.slint:457 msgid "Local mail data is unavailable. Retry startup." msgstr "Локальные данные почты недоступны. Повторите запуск." -#: ui/app.slint:460 +#: ui/app.slint:458 msgid "Discovering JMAP and checking capabilities…" msgstr "Обнаружение JMAP и проверка возможностей…" -#: ui/app.slint:461 +#: ui/app.slint:459 msgid "Certificate imported. Connect to verify the server." msgstr "Сертификат импортирован. Подключитесь для проверки сервера." -#: ui/app.slint:462 +#: ui/app.slint:460 msgid "Could not load account preferences: {}" msgstr "Не удалось загрузить настройки учётной записи: {}" -#: ui/app.slint:463 +#: ui/app.slint:461 msgid "" "Your edited signature was preserved. Choose No signature to keep it as " "message text before inserting another." @@ -1279,19 +1271,19 @@ msgstr "" "Ваша изменённая подпись сохранена. Выберите «Без подписи», чтобы оставить её " "как текст сообщения перед вставкой другой." -#: ui/app.slint:464 +#: ui/app.slint:462 msgid "Certificate import failed: {}" msgstr "Импорт сертификата не удался: {}" -#: ui/app.slint:465 +#: ui/app.slint:463 msgid "Paste the PEM certificate into the certificate field." msgstr "Вставьте сертификат PEM в поле сертификата." -#: ui/app.slint:466 +#: ui/app.slint:464 msgid "Testing IMAP and SMTP connections…" msgstr "Проверка подключений IMAP и SMTP…" -#: ui/app.slint:471 +#: ui/app.slint:469 msgid "" "Another browser authorization is already in progress; finish it or choose " "Cancel." @@ -1299,147 +1291,147 @@ msgstr "" "Другая авторизация в браузере уже выполняется; завершите её или выберите " "«Отмена»." -#: ui/app.slint:472 ui/dialogs/settings-dialog.slint:1643 +#: ui/app.slint:470 ui/dialogs/settings-dialog.slint:1643 #: ui/dialogs/settings-dialog.slint:1686 ui/views/onboarding-view.slint:152 msgid "Add app keys in Sign-in settings to enable this provider." msgstr "" "Добавьте ключи приложения в настройках входа, чтобы включить этого " "провайдера." -#: ui/app.slint:473 +#: ui/app.slint:471 msgid "Choose Gmail or Outlook OAuth." msgstr "Выберите OAuth Gmail или Outlook." -#: ui/app.slint:474 +#: ui/app.slint:472 msgid "Complete sign-in in your browser, or choose Cancel here." msgstr "Завершите вход в браузере или выберите «Отмена» здесь." -#: ui/app.slint:475 +#: ui/app.slint:473 msgid "Cancelling browser sign-in…" msgstr "Отмена входа через браузер…" -#: ui/app.slint:476 +#: ui/app.slint:474 msgid "Finishing browser sign-in…" msgstr "Завершение входа через браузер…" -#: ui/app.slint:477 +#: ui/app.slint:475 msgid "Could not use that browser address: {}" msgstr "Не удалось использовать этот адрес браузера: {}" -#: ui/app.slint:478 +#: ui/app.slint:476 msgid "Calendar core is unavailable." msgstr "Ядро календаря недоступно." -#: ui/app.slint:479 +#: ui/app.slint:477 msgid "Account no longer exists." msgstr "Учётная запись больше не существует." -#: ui/app.slint:480 +#: ui/app.slint:478 msgid "Authorize calendar access in your browser." msgstr "Авторизуйте доступ к календарю в браузере." -#: ui/app.slint:481 +#: ui/app.slint:479 msgid "OAuth app keys saved." msgstr "Ключи приложения OAuth сохранены." -#: ui/app.slint:482 +#: ui/app.slint:480 msgid "{} OAuth settings saved." msgstr "Настройки OAuth {} сохранены." -#: ui/app.slint:483 +#: ui/app.slint:481 msgid "OAuth settings failed: {}" msgstr "Настройки OAuth не удались: {}" -#: ui/app.slint:484 +#: ui/app.slint:482 msgid "Mail refresh failed: {}" msgstr "Обновление почты не удалось: {}" -#: ui/app.slint:485 +#: ui/app.slint:483 msgid "Mail filter failed: {}" msgstr "Фильтрация почты не удалась: {}" -#: ui/app.slint:486 +#: ui/app.slint:484 msgid "Mail pagination failed: {}" msgstr "Постраничная навигация по почте не удалась: {}" -#: ui/app.slint:487 +#: ui/app.slint:485 msgid "Could not mark message as read: {}" msgstr "Не удалось отметить сообщение как прочитанное: {}" -#: ui/app.slint:488 ui/views/mail-reading-pane.slint:223 +#: ui/app.slint:486 ui/views/mail-reading-pane.slint:223 #: ui/views/mail-reading-pane.slint:1162 msgid "Loading message…" msgstr "Загрузка сообщения…" -#: ui/app.slint:494 ui/components/workspace-navigation.slint:551 +#: ui/app.slint:492 ui/components/workspace-navigation.slint:551 #: ui/tray.slint:9 ui/tray.slint:10 msgid "Flectar Mail" msgstr "Flectar Mail" -#: ui/app.slint:1065 ui/app.slint:1078 +#: ui/app.slint:1063 ui/app.slint:1076 msgid "Global Labels (local only)" msgstr "Глобальные метки (только локально)" -#: ui/app.slint:1938 +#: ui/app.slint:1936 msgid "Empty Trash?" msgstr "Очистить корзину?" -#: ui/app.slint:1938 ui/views/files-view.slint:538 +#: ui/app.slint:1936 ui/views/files-view.slint:538 msgid "Delete permanently?" msgstr "Удалить безвозвратно?" -#: ui/app.slint:1942 +#: ui/app.slint:1940 msgid "" "Permanently delete all messages in Trash across every connected account?" msgstr "" "Безвозвратно удалить все письма из корзины во всех подключённых учётных " "записях?" -#: ui/app.slint:1943 +#: ui/app.slint:1941 msgid "Permanently delete all messages in this account’s Trash?" msgstr "Безвозвратно удалить все письма из корзины этой учётной записи?" -#: ui/app.slint:1945 +#: ui/app.slint:1943 msgid "Permanently delete the selected conversations from Trash?" msgstr "Безвозвратно удалить выбранные переписки из корзины?" -#: ui/app.slint:1946 +#: ui/app.slint:1944 msgid "Permanently delete this conversation from Trash?" msgstr "Безвозвратно удалить эту переписку из корзины?" -#: ui/app.slint:1947 ui/components/calendar.slint:926 +#: ui/app.slint:1945 ui/components/calendar.slint:926 msgid "This action cannot be undone." msgstr "Это действие нельзя отменить." -#: ui/app.slint:1948 +#: ui/app.slint:1946 msgid "Empty Trash" msgstr "Очистить корзину" -#: ui/app.slint:1948 ui/views/files-view.slint:620 +#: ui/app.slint:1946 ui/views/files-view.slint:620 msgid "Delete permanently" msgstr "Удалить безвозвратно" -#: ui/app.slint:2111 +#: ui/app.slint:2109 msgid "Mailbox startup failed" msgstr "Запуск почтового ящика не удался" -#: ui/app.slint:2117 +#: ui/app.slint:2115 msgid "Flectar Mail could not open local storage" msgstr "Flectar Mail не смог открыть локальное хранилище" -#: ui/app.slint:2130 +#: ui/app.slint:2128 msgid "Diagnostic ID: {}" msgstr "Идентификатор диагностики: {}" -#: ui/app.slint:2133 +#: ui/app.slint:2131 msgid "Diagnostic ID {}" msgstr "Идентификатор диагностики {}" -#: ui/app.slint:2138 +#: ui/app.slint:2136 msgid "Retry" msgstr "Повторить" -#: ui/app.slint:2146 ui/components/copyable-diagnostic.slint:38 +#: ui/app.slint:2144 ui/components/copyable-diagnostic.slint:38 msgid "Copy diagnostics" msgstr "Копировать диагностику" diff --git a/lang/tr/LC_MESSAGES/flectar-mail.po b/lang/tr/LC_MESSAGES/flectar-mail.po index 29f22342..10a547b3 100644 --- a/lang/tr/LC_MESSAGES/flectar-mail.po +++ b/lang/tr/LC_MESSAGES/flectar-mail.po @@ -184,7 +184,7 @@ msgstr "Konuşma geçmişi mevcut" msgid "New address-book entry" msgstr "Yeni adres defteri kaydı" -#: ui/app.slint:121 ui/app.slint:847 ui/components/composer-fields.slint:66 +#: ui/app.slint:121 ui/app.slint:845 ui/components/composer-fields.slint:66 #: ui/dialogs/compose-dialog.slint:37 msgid "No connected account" msgstr "Bağlı hesap yok" @@ -199,11 +199,11 @@ msgstr[1] "{n} etkileşim" msgid "Last contacted {} {}, {}" msgstr "Son iletişim: {1} {0} {2}" -#: ui/app.slint:128 ui/app.slint:361 +#: ui/app.slint:128 ui/app.slint:359 msgid "Loading local data…" msgstr "Yerel veriler yükleniyor…" -#: ui/app.slint:129 ui/app.slint:362 +#: ui/app.slint:129 ui/app.slint:360 msgid "Local data ready." msgstr "Yerel veriler hazır." @@ -666,227 +666,219 @@ msgid "Choose where to export the database snapshot" msgstr "Veritabanı anlık görüntüsünün nereye aktarılacağını seçin" #: ui/app.slint:289 -msgid "The platform host does not provide document export." -msgstr "Platform ana bilgisayarı belge dışa aktarma sağlamıyor." - -#: ui/app.slint:290 -msgid "The platform host does not provide document import." -msgstr "Platform ana bilgisayarı belge içe aktarma sağlamıyor." - -#: ui/app.slint:291 msgid "Backup exported. Passwords and OAuth tokens were not included." msgstr "Yedek dışa aktarıldı. Parolalar ve OAuth belirteçleri dahil edilmedi." -#: ui/app.slint:292 +#: ui/app.slint:290 msgid "Could not export backup: {}" msgstr "Yedek dışa aktarılamadı: {}" -#: ui/app.slint:293 +#: ui/app.slint:291 msgid "Local data storage is unavailable. Retry startup." msgstr "Yerel veri deposu kullanılamıyor. Başlatmayı yeniden deneyin." -#: ui/app.slint:294 +#: ui/app.slint:292 msgid "Creating verified database snapshot…" msgstr "Doğrulanmış veritabanı anlık görüntüsü oluşturuluyor…" -#: ui/app.slint:295 +#: ui/app.slint:293 msgid "Backup imported. All {} account configurations already existed." msgstr "Yedek içe aktarıldı. Tüm {} hesap yapılandırması zaten mevcuttu." -#: ui/app.slint:296 +#: ui/app.slint:294 msgid "" "Imported {} account configurations. Open Accounts and sign in to each one." msgstr "" "{} hesap yapılandırması içe aktarıldı. Hesaplar'ı açın ve her biriyle oturum " "açın." -#: ui/app.slint:297 +#: ui/app.slint:295 msgid "Could not import backup: {}" msgstr "Yedek içe aktarılamadı: {}" -#: ui/app.slint:298 +#: ui/app.slint:296 msgid "All local Flectar Mail data was deleted." msgstr "Tüm yerel Flectar Mail verileri silindi." -#: ui/app.slint:299 +#: ui/app.slint:297 msgid "Could not delete all local data: {}" msgstr "Tüm yerel veriler silinemedi: {}" -#: ui/app.slint:300 +#: ui/app.slint:298 msgid "Deleting local accounts and cached data…" msgstr "Yerel hesaplar ve önbellek verileri siliniyor…" -#: ui/app.slint:305 +#: ui/app.slint:303 msgid "Background mail refresh failed: {}" msgstr "Arka plan e-posta yenilemesi başarısız: {}" -#: ui/app.slint:306 +#: ui/app.slint:304 msgid "No conversations in this folder" msgstr "Bu klasörde konuşma yok" -#: ui/app.slint:307 +#: ui/app.slint:305 msgid "No conversations match this search" msgstr "Bu aramayla eşleşen konuşma yok" -#: ui/app.slint:308 +#: ui/app.slint:306 msgid "Mail core is ready for account synchronization and message actions." msgstr "E-posta çekirdeği hesap eşitlemesi ve ileti işlemleri için hazır." -#: ui/app.slint:309 +#: ui/app.slint:307 msgid "Large message rendered in compatibility mode." msgstr "Büyük ileti uyumluluk modunda görüntülendi." -#: ui/app.slint:310 +#: ui/app.slint:308 msgid "Software email render failed: {}" msgstr "E-postanın yazılımla görüntülenmesi başarısız oldu: {}" -#: ui/app.slint:311 +#: ui/app.slint:309 msgid "Rendered with software fallback after GPU error: {}" msgstr "GPU hatasından sonra yazılım yedeğiyle görüntülendi: {}" -#: ui/app.slint:312 +#: ui/app.slint:310 msgid "Blitz render failed (GPU: {}; software: {})" msgstr "Blitz oluşturması başarısız (GPU: {}; yazılım: {})" -#: ui/app.slint:317 +#: ui/app.slint:315 msgid "Add an email address, then save the contact." msgstr "Bir e-posta adresi ekleyin, ardından kişiyi kaydedin." -#: ui/app.slint:318 +#: ui/app.slint:316 msgid "Enter a valid email address before saving." msgstr "Kaydetmeden önce geçerli bir e-posta adresi girin." -#: ui/app.slint:319 +#: ui/app.slint:317 msgid "Could not save: contact storage is unavailable." msgstr "Kaydedilemedi: kişi deposu kullanılamıyor." -#: ui/app.slint:320 +#: ui/app.slint:318 msgid "Could not save contact: {}" msgstr "Kişi kaydedilemedi: {}" -#: ui/app.slint:321 +#: ui/app.slint:319 msgid "Contact saved." msgstr "Kişi kaydedildi." -#: ui/app.slint:322 +#: ui/app.slint:320 msgid "Saved to contacts." msgstr "Kişilere kaydedildi." -#: ui/app.slint:323 +#: ui/app.slint:321 msgid "Could not delete: contact storage is unavailable." msgstr "Silinemedi: kişi deposu kullanılamıyor." -#: ui/app.slint:324 +#: ui/app.slint:322 msgid "Could not delete contact: {}" msgstr "Kişi silinemedi: {}" -#: ui/app.slint:325 +#: ui/app.slint:323 msgid "Contact deleted." msgstr "Kişi silindi." -#: ui/app.slint:326 +#: ui/app.slint:324 msgid "Could not load contacts: {}" msgstr "Kişiler yüklenemedi: {}" -#: ui/app.slint:327 +#: ui/app.slint:325 msgid "Local contact storage is still starting…" msgstr "Yerel kişi deposu hâlâ başlıyor…" -#: ui/app.slint:328 +#: ui/app.slint:326 msgid "Loading contacts…" msgstr "Kişiler yükleniyor…" -#: ui/app.slint:333 ui/components/calendar-agenda.slint:233 +#: ui/app.slint:331 ui/components/calendar-agenda.slint:233 msgid "Local calendar" msgstr "Yerel takvim" -#: ui/app.slint:334 ui/components/calendar.slint:271 +#: ui/app.slint:332 ui/components/calendar.slint:271 #: ui/components/calendar.slint:319 ui/components/calendar.slint:434 msgid "Calendar" msgstr "Takvim" -#: ui/app.slint:335 +#: ui/app.slint:333 msgid "Could not load calendar: {}" msgstr "Takvim yüklenemedi: {}" -#: ui/app.slint:336 +#: ui/app.slint:334 msgid "Calendar shown and syncing." msgstr "Takvim gösteriliyor ve eşitleniyor." -#: ui/app.slint:337 +#: ui/app.slint:335 msgid "Calendar hidden and sync paused." msgstr "Takvim gizlendi ve eşitleme duraklatıldı." -#: ui/app.slint:338 +#: ui/app.slint:336 msgid "Calendar update failed: {}" msgstr "Takvim güncellemesi başarısız: {}" -#: ui/app.slint:339 +#: ui/app.slint:337 msgid "Only events created in Flectar Mail can be edited." msgstr "Yalnızca Flectar Mail'de oluşturulan etkinlikler düzenlenebilir." -#: ui/app.slint:340 +#: ui/app.slint:338 msgid "Only events created in Flectar Mail can be deleted." msgstr "Yalnızca Flectar Mail'de oluşturulan etkinlikler silinebilir." -#: ui/app.slint:341 +#: ui/app.slint:339 msgid "Calendar storage is unavailable." msgstr "Takvim deposu kullanılamıyor." -#: ui/app.slint:342 +#: ui/app.slint:340 msgid "Could not delete event: {}" msgstr "Etkinlik silinemedi: {}" -#: ui/app.slint:343 +#: ui/app.slint:341 msgid "Event deleted, but refresh failed: {}" msgstr "Etkinlik silindi, ancak yenileme başarısız: {}" -#: ui/app.slint:344 +#: ui/app.slint:342 msgid "Event deleted." msgstr "Etkinlik silindi." -#: ui/app.slint:345 +#: ui/app.slint:343 msgid "Use YYYY-MM-DD and a 24-hour time such as 09:30." msgstr "YYYY-AA-GG biçimini ve 09:30 gibi 24 saatlik bir saat kullanın." -#: ui/app.slint:346 +#: ui/app.slint:344 msgid "Event duration must be a number of minutes." msgstr "Etkinlik süresi dakika cinsinden bir sayı olmalıdır." -#: ui/app.slint:347 +#: ui/app.slint:345 msgid "Add a title and choose a duration from 15 to 1440 minutes." msgstr "Bir başlık ekleyin ve 15 ile 1440 dakika arasında bir süre seçin." -#: ui/app.slint:348 +#: ui/app.slint:346 msgid "That local time does not exist in the current time zone." msgstr "Bu yerel saat geçerli saat diliminde mevcut değil." -#: ui/app.slint:349 +#: ui/app.slint:347 msgid "Could not save event: {}" msgstr "Etkinlik kaydedilemedi: {}" -#: ui/app.slint:350 +#: ui/app.slint:348 msgid "Event saved, but refresh failed: {}" msgstr "Etkinlik kaydedildi, ancak yenileme başarısız: {}" -#: ui/app.slint:351 +#: ui/app.slint:349 msgid "Event saved. Provider sync will run when enabled." msgstr "" "Etkinlik kaydedildi. Sağlayıcı eşitlemesi etkinleştirildiğinde çalışacak." -#: ui/app.slint:356 +#: ui/app.slint:354 msgid "Email resource render failed: {}" msgstr "E-posta kaynağı görüntülenemedi: {}" -#: ui/app.slint:357 +#: ui/app.slint:355 msgid "Message refresh failed: {}" msgstr "İleti yenilemesi başarısız: {}" -#: ui/app.slint:358 +#: ui/app.slint:356 msgid "Message load failed: {}" msgstr "İleti yüklenemedi: {}" -#: ui/app.slint:359 +#: ui/app.slint:357 msgid "" "Your local mailbox could not be opened. No account operation was started. " "Retry, or copy the diagnostics for support." @@ -894,252 +886,252 @@ msgstr "" "Yerel posta kutunuz açılamadı. Hiçbir hesap işlemi başlatılmadı. Yeniden " "deneyin veya destek için tanılama bilgilerini kopyalayın." -#: ui/app.slint:360 +#: ui/app.slint:358 msgid "Local storage startup failed." msgstr "Yerel depolama başlatılamadı." -#: ui/app.slint:367 +#: ui/app.slint:365 msgid "Could not identify the dragged account." msgstr "Sürüklenen hesap tanımlanamadı." -#: ui/app.slint:368 +#: ui/app.slint:366 msgid "Account ordering requires local mail data." msgstr "Hesap sıralaması yerel e-posta verisi gerektirir." -#: ui/app.slint:369 +#: ui/app.slint:367 msgid "Could not find the account to reorder." msgstr "Yeniden sıralanacak hesap bulunamadı." -#: ui/app.slint:370 +#: ui/app.slint:368 msgid "Saving account order…" msgstr "Hesap sırası kaydediliyor…" -#: ui/app.slint:371 +#: ui/app.slint:369 msgid "Sent copy settings require local mail data." msgstr "Gönderilen kopya ayarları yerel posta verileri gerektirir." -#: ui/app.slint:372 +#: ui/app.slint:370 msgid "Updating Sent copy settings…" msgstr "Gönderilen kopya ayarları güncelleniyor…" -#: ui/app.slint:373 +#: ui/app.slint:371 msgid "Sent copy settings updated." msgstr "Gönderilen kopya ayarları güncellendi." -#: ui/app.slint:374 +#: ui/app.slint:372 msgid "Could not update Sent copy settings: {}" msgstr "Gönderilen kopya ayarları güncellenemedi: {}" -#: ui/app.slint:375 +#: ui/app.slint:373 msgid "Mail history requires local mail data." msgstr "E-posta geçmişi yerel e-posta verisi gerektirir." -#: ui/app.slint:376 +#: ui/app.slint:374 msgid "Updating mail history…" msgstr "E-posta geçmişi güncelleniyor…" -#: ui/app.slint:377 +#: ui/app.slint:375 msgid "Remote images are unavailable in this minimal build." msgstr "Uzak görseller bu minimal derlemede kullanılamıyor." -#: ui/app.slint:378 +#: ui/app.slint:376 msgid "Could not save remote-image setting: {}" msgstr "Uzak görsel ayarı kaydedilemedi: {}" -#: ui/app.slint:379 +#: ui/app.slint:377 msgid "Remote sender icons unavailable: {}" msgstr "Uzak gönderen simgeleri kullanılamıyor: {}" -#: ui/app.slint:380 +#: ui/app.slint:378 msgid "Remote images unavailable: {}" msgstr "Uzak görseller kullanılamıyor: {}" -#: ui/app.slint:381 +#: ui/app.slint:379 msgid "Remote body images and sender favicons enabled." msgstr "Gövde içi uzak görseller ve gönderen favikonları etkinleştirildi." -#: ui/app.slint:382 +#: ui/app.slint:380 msgid "Remote images blocked." msgstr "Uzak görseller engellendi." -#: ui/app.slint:383 +#: ui/app.slint:381 msgid "Could not refresh message images: {}" msgstr "İleti görselleri yenilenemedi: {}" -#: ui/app.slint:384 +#: ui/app.slint:382 msgid "Remote-image loading is disabled." msgstr "Uzak görsel yükleme devre dışı." -#: ui/app.slint:385 +#: ui/app.slint:383 msgid "Could not load message images: {}" msgstr "İleti görselleri yüklenemedi: {}" -#: ui/app.slint:386 +#: ui/app.slint:384 msgid "Sync finished, refresh failed: {}" msgstr "Eşitleme tamamlandı, yenileme başarısız: {}" -#: ui/app.slint:387 +#: ui/app.slint:385 msgid "Sync complete." msgstr "Eşitleme tamamlandı." -#: ui/app.slint:388 +#: ui/app.slint:386 msgid "Sync failed: {}" msgstr "Eşitleme başarısız: {}" -#: ui/app.slint:389 +#: ui/app.slint:387 msgid "Local mail data is unavailable; preview data is read-only." msgstr "Yerel e-posta verisi kullanılamıyor; önizleme verisi salt okunur." -#: ui/app.slint:390 +#: ui/app.slint:388 msgid "Synchronizing accounts…" msgstr "Hesaplar eşitleniyor…" -#: ui/app.slint:395 +#: ui/app.slint:393 msgid "Message action completed." msgstr "İleti işlemi tamamlandı." -#: ui/app.slint:396 +#: ui/app.slint:394 msgid "" "Permanent deletion queued. Mail will disappear after the server confirms." msgstr "" -#: ui/app.slint:397 +#: ui/app.slint:395 msgid "Message action failed: {}" msgstr "İleti işlemi başarısız: {}" -#: ui/app.slint:398 +#: ui/app.slint:396 msgid "Message action failed: message is no longer available" msgstr "İleti işlemi başarısız: ileti artık mevcut değil" -#: ui/app.slint:399 +#: ui/app.slint:397 msgid "Label added." msgstr "Etiket eklendi." -#: ui/app.slint:400 +#: ui/app.slint:398 msgid "Label removed." msgstr "Etiket kaldırıldı." -#: ui/app.slint:401 +#: ui/app.slint:399 msgid "Label created and added." msgstr "Etiket oluşturuldu ve eklendi." -#: ui/app.slint:402 +#: ui/app.slint:400 msgid "Label created." msgstr "Etiket oluşturuldu." -#: ui/app.slint:403 +#: ui/app.slint:401 msgid "Label updated." msgstr "Etiket güncellendi." -#: ui/app.slint:404 +#: ui/app.slint:402 msgid "Label deleted." msgstr "Etiket silindi." -#: ui/app.slint:405 +#: ui/app.slint:403 msgid "Could not update label: {}" msgstr "Etiket güncellenemedi: {}" -#: ui/app.slint:406 +#: ui/app.slint:404 msgid "Could not save label: {}" msgstr "Etiket kaydedilemedi: {}" -#: ui/app.slint:407 +#: ui/app.slint:405 msgid "Could not delete label: {}" msgstr "Etiket silinemedi: {}" -#: ui/app.slint:408 +#: ui/app.slint:406 msgid "Folder created." msgstr "Klasör oluşturuldu." -#: ui/app.slint:409 +#: ui/app.slint:407 msgid "Folder renamed." msgstr "Klasör yeniden adlandırıldı." -#: ui/app.slint:410 +#: ui/app.slint:408 msgid "Folder deleted." msgstr "Klasör silindi." -#: ui/app.slint:411 +#: ui/app.slint:409 msgid "Could not create folder: {}" msgstr "Klasör oluşturulamadı: {}" -#: ui/app.slint:412 +#: ui/app.slint:410 msgid "Could not rename folder: {}" msgstr "Klasör yeniden adlandırılamadı: {}" -#: ui/app.slint:413 +#: ui/app.slint:411 msgid "Could not delete folder: {}" msgstr "Klasör silinemedi: {}" -#: ui/app.slint:414 +#: ui/app.slint:412 msgid "Could not close preview: {}" msgstr "Önizleme kapatılamadı: {}" -#: ui/app.slint:415 +#: ui/app.slint:413 msgid "Opened the system print view." msgstr "Sistem yazdırma görünümü açıldı." -#: ui/app.slint:416 +#: ui/app.slint:414 msgid "Could not print message: {}" msgstr "İleti yazdırılamadı: {}" -#: ui/app.slint:417 +#: ui/app.slint:415 msgid "Opened the message in your browser." msgstr "İleti tarayıcınızda açıldı." -#: ui/app.slint:418 +#: ui/app.slint:416 msgid "Could not open message in browser: {}" msgstr "İleti tarayıcıda açılamadı: {}" -#: ui/app.slint:419 +#: ui/app.slint:417 msgid "Connect an account before replying or forwarding." msgstr "Yanıtlamadan veya iletmeden önce bir hesap bağlayın." -#: ui/app.slint:420 +#: ui/app.slint:418 msgid "Choose a message first." msgstr "Önce bir ileti seçin." -#: ui/app.slint:421 +#: ui/app.slint:419 msgid "Only drafts can be reopened for editing." msgstr "Yalnızca taslaklar düzenlenmek üzere yeniden açılabilir." -#: ui/app.slint:422 +#: ui/app.slint:420 msgid "Could not open draft: {}" msgstr "Taslak açılamadı: {}" -#: ui/app.slint:423 +#: ui/app.slint:421 msgid "Could not open draft: invalid account identifier" msgstr "Taslak açılamadı: geçersiz hesap tanımlayıcısı" -#: ui/app.slint:424 +#: ui/app.slint:422 msgid "Connected account" msgstr "Bağlı hesap" -#: ui/app.slint:425 +#: ui/app.slint:423 msgid "Could not open composer: {}" msgstr "Oluşturucu açılamadı: {}" -#: ui/app.slint:426 +#: ui/app.slint:424 msgid "Could not open composer: invalid account identifier" msgstr "Oluşturucu açılamadı: geçersiz hesap tanımlayıcısı" -#: ui/app.slint:431 +#: ui/app.slint:429 msgid "Attach files to this email" msgstr "Bu e-postaya dosya ekleyin" -#: ui/app.slint:432 -msgid "The platform host does not provide a document picker." -msgstr "Platform ana bilgisayarı belge seçici sağlamıyor." +#: ui/app.slint:430 +msgid "Could not add attachments: {}" +msgstr "Ekler eklenemedi: {}" -#: ui/app.slint:433 +#: ui/app.slint:431 msgid "Added 1 attachment." msgstr "1 ek eklendi." -#: ui/app.slint:434 +#: ui/app.slint:432 msgid "Added {} attachments." msgstr "{} ek eklendi." -#: ui/app.slint:435 +#: ui/app.slint:433 msgid "" "Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB " "total limit)." @@ -1147,115 +1139,115 @@ msgstr "" "Eklenen ekler: {}. Atlananlar: {} (yinelenen, okunamayan veya toplam 25 MB " "sınırını aşan)." -#: ui/app.slint:436 +#: ui/app.slint:434 msgid "Attachment removed." msgstr "Ek kaldırıldı." -#: ui/app.slint:437 +#: ui/app.slint:435 msgid "Choose the account this message should be sent from." msgstr "Bu iletinin hangi hesaptan gönderileceğini seçin." -#: ui/app.slint:438 +#: ui/app.slint:436 msgid "Choose a verified sender identity for this message." msgstr "Bu ileti için doğrulanmış bir gönderen kimliği seçin." -#: ui/app.slint:439 +#: ui/app.slint:437 msgid "Connect an account before saving or sending mail." msgstr "E-posta kaydetmeden veya göndermeden önce bir hesap bağlayın." -#: ui/app.slint:440 +#: ui/app.slint:438 msgid "Message queued for delivery (action {})." msgstr "İleti gönderilmek üzere kuyruğa alındı (eylem {})." -#: ui/app.slint:441 +#: ui/app.slint:439 msgid "Sending message…" msgstr "İleti gönderiliyor…" -#: ui/app.slint:442 +#: ui/app.slint:440 msgid "Message sent." msgstr "İleti gönderildi." -#: ui/app.slint:443 +#: ui/app.slint:441 msgid "Message was not sent: {} Your draft is preserved." msgstr "İleti gönderilemedi: {} Taslağınız korunuyor." -#: ui/app.slint:444 +#: ui/app.slint:442 msgid "Draft saved locally (#{})." msgstr "Taslak yerel olarak kaydedildi (#{})." -#: ui/app.slint:445 +#: ui/app.slint:443 msgid "Compose failed: {}" msgstr "Oluşturma başarısız: {}" -#: ui/app.slint:446 +#: ui/app.slint:444 msgid "Connect an account before managing email templates." msgstr "E-posta şablonlarını yönetmeden önce bir hesap bağlayın." -#: ui/app.slint:447 +#: ui/app.slint:445 msgid "Could not load email templates: {}" msgstr "E-posta şablonları yüklenemedi: {}" -#: ui/app.slint:448 +#: ui/app.slint:446 msgid "Enter a template name." msgstr "Bir şablon adı girin." -#: ui/app.slint:449 +#: ui/app.slint:447 msgid "Add a subject or message to the template." msgstr "Şablona bir konu veya ileti ekleyin." -#: ui/app.slint:450 +#: ui/app.slint:448 msgid "Email template saved." msgstr "E-posta şablonu kaydedildi." -#: ui/app.slint:451 +#: ui/app.slint:449 msgid "Could not save email template: {}" msgstr "E-posta şablonu kaydedilemedi: {}" -#: ui/app.slint:452 +#: ui/app.slint:450 msgid "Email template deleted." msgstr "E-posta şablonu silindi." -#: ui/app.slint:453 +#: ui/app.slint:451 msgid "Could not delete email template: {}" msgstr "E-posta şablonu silinemedi: {}" -#: ui/app.slint:454 +#: ui/app.slint:452 msgid "Email template is no longer available." msgstr "E-posta şablonu artık mevcut değil." -#: ui/app.slint:455 +#: ui/app.slint:453 msgid "Email template inserted." msgstr "E-posta şablonu eklendi." -#: ui/app.slint:456 +#: ui/app.slint:454 msgid "Email template inserted, but its usage could not be saved: {}" msgstr "E-posta şablonu eklendi, ancak kullanımı kaydedilemedi: {}" -#: ui/app.slint:457 +#: ui/app.slint:455 msgid "IMAP port must be between 1 and 65535." msgstr "IMAP bağlantı noktası 1 ile 65535 arasında olmalıdır." -#: ui/app.slint:458 +#: ui/app.slint:456 msgid "SMTP port must be between 1 and 65535." msgstr "SMTP bağlantı noktası 1 ile 65535 arasında olmalıdır." -#: ui/app.slint:459 +#: ui/app.slint:457 msgid "Local mail data is unavailable. Retry startup." msgstr "Yerel e-posta verisi kullanılamıyor. Başlatmayı yeniden deneyin." -#: ui/app.slint:460 +#: ui/app.slint:458 msgid "Discovering JMAP and checking capabilities…" msgstr "JMAP keşfediliyor ve yetenekler denetleniyor…" -#: ui/app.slint:461 +#: ui/app.slint:459 msgid "Certificate imported. Connect to verify the server." msgstr "Sertifika içe aktarıldı. Sunucuyu doğrulamak için bağlanın." -#: ui/app.slint:462 +#: ui/app.slint:460 msgid "Could not load account preferences: {}" msgstr "Hesap tercihleri yüklenemedi: {}" -#: ui/app.slint:463 +#: ui/app.slint:461 msgid "" "Your edited signature was preserved. Choose No signature to keep it as " "message text before inserting another." @@ -1263,19 +1255,19 @@ msgstr "" "Düzenlediğiniz imza korundu. Başka birini eklemeden önce onu ileti metni " "olarak tutmak için İmza yok'u seçin." -#: ui/app.slint:464 +#: ui/app.slint:462 msgid "Certificate import failed: {}" msgstr "Sertifika içe aktarılamadı: {}" -#: ui/app.slint:465 +#: ui/app.slint:463 msgid "Paste the PEM certificate into the certificate field." msgstr "PEM sertifikasını sertifika alanına yapıştırın." -#: ui/app.slint:466 +#: ui/app.slint:464 msgid "Testing IMAP and SMTP connections…" msgstr "IMAP ve SMTP bağlantıları sınanıyor…" -#: ui/app.slint:471 +#: ui/app.slint:469 msgid "" "Another browser authorization is already in progress; finish it or choose " "Cancel." @@ -1283,145 +1275,145 @@ msgstr "" "Başka bir tarayıcı yetkilendirmesi zaten sürüyor; onu bitirin veya İptal'i " "seçin." -#: ui/app.slint:472 ui/dialogs/settings-dialog.slint:1643 +#: ui/app.slint:470 ui/dialogs/settings-dialog.slint:1643 #: ui/dialogs/settings-dialog.slint:1686 ui/views/onboarding-view.slint:152 msgid "Add app keys in Sign-in settings to enable this provider." msgstr "" "Bu sağlayıcıyı etkinleştirmek için Oturum açma ayarlarında uygulama " "anahtarları ekleyin." -#: ui/app.slint:473 +#: ui/app.slint:471 msgid "Choose Gmail or Outlook OAuth." msgstr "Gmail veya Outlook OAuth seçin." -#: ui/app.slint:474 +#: ui/app.slint:472 msgid "Complete sign-in in your browser, or choose Cancel here." msgstr "Oturum açmayı tarayıcınızda tamamlayın veya burada İptal'i seçin." -#: ui/app.slint:475 +#: ui/app.slint:473 msgid "Cancelling browser sign-in…" msgstr "Tarayıcıyla oturum açma iptal ediliyor…" -#: ui/app.slint:476 +#: ui/app.slint:474 msgid "Finishing browser sign-in…" msgstr "Tarayıcıyla oturum açma tamamlanıyor…" -#: ui/app.slint:477 +#: ui/app.slint:475 msgid "Could not use that browser address: {}" msgstr "O tarayıcı adresi kullanılamadı: {}" -#: ui/app.slint:478 +#: ui/app.slint:476 msgid "Calendar core is unavailable." msgstr "Takvim çekirdeği kullanılamıyor." -#: ui/app.slint:479 +#: ui/app.slint:477 msgid "Account no longer exists." msgstr "Hesap artık yok." -#: ui/app.slint:480 +#: ui/app.slint:478 msgid "Authorize calendar access in your browser." msgstr "Takvim erişimini tarayıcınızda yetkilendirin." -#: ui/app.slint:481 +#: ui/app.slint:479 msgid "OAuth app keys saved." msgstr "OAuth uygulama anahtarları kaydedildi." -#: ui/app.slint:482 +#: ui/app.slint:480 msgid "{} OAuth settings saved." msgstr "{} OAuth ayarları kaydedildi." -#: ui/app.slint:483 +#: ui/app.slint:481 msgid "OAuth settings failed: {}" msgstr "OAuth ayarları başarısız: {}" -#: ui/app.slint:484 +#: ui/app.slint:482 msgid "Mail refresh failed: {}" msgstr "E-posta yenilemesi başarısız: {}" -#: ui/app.slint:485 +#: ui/app.slint:483 msgid "Mail filter failed: {}" msgstr "E-posta filtresi başarısız: {}" -#: ui/app.slint:486 +#: ui/app.slint:484 msgid "Mail pagination failed: {}" msgstr "E-posta sayfalaması başarısız: {}" -#: ui/app.slint:487 +#: ui/app.slint:485 msgid "Could not mark message as read: {}" msgstr "İleti okundu olarak işaretlenemedi: {}" -#: ui/app.slint:488 ui/views/mail-reading-pane.slint:223 +#: ui/app.slint:486 ui/views/mail-reading-pane.slint:223 #: ui/views/mail-reading-pane.slint:1162 msgid "Loading message…" msgstr "İleti yükleniyor…" -#: ui/app.slint:494 ui/components/workspace-navigation.slint:551 +#: ui/app.slint:492 ui/components/workspace-navigation.slint:551 #: ui/tray.slint:9 ui/tray.slint:10 msgid "Flectar Mail" msgstr "Flectar Mail" -#: ui/app.slint:1065 ui/app.slint:1078 +#: ui/app.slint:1063 ui/app.slint:1076 msgid "Global Labels (local only)" msgstr "Genel Etiketler (yalnızca yerel)" -#: ui/app.slint:1938 +#: ui/app.slint:1936 msgid "Empty Trash?" msgstr "" -#: ui/app.slint:1938 ui/views/files-view.slint:538 +#: ui/app.slint:1936 ui/views/files-view.slint:538 msgid "Delete permanently?" msgstr "Kalıcı olarak silinsin mi?" -#: ui/app.slint:1942 +#: ui/app.slint:1940 msgid "" "Permanently delete all messages in Trash across every connected account?" msgstr "" -#: ui/app.slint:1943 +#: ui/app.slint:1941 msgid "Permanently delete all messages in this account’s Trash?" msgstr "" -#: ui/app.slint:1945 +#: ui/app.slint:1943 msgid "Permanently delete the selected conversations from Trash?" msgstr "" -#: ui/app.slint:1946 +#: ui/app.slint:1944 msgid "Permanently delete this conversation from Trash?" msgstr "" -#: ui/app.slint:1947 ui/components/calendar.slint:926 +#: ui/app.slint:1945 ui/components/calendar.slint:926 msgid "This action cannot be undone." msgstr "Bu işlem geri alınamaz." -#: ui/app.slint:1948 +#: ui/app.slint:1946 msgid "Empty Trash" msgstr "" -#: ui/app.slint:1948 ui/views/files-view.slint:620 +#: ui/app.slint:1946 ui/views/files-view.slint:620 msgid "Delete permanently" msgstr "Kalıcı olarak sil" -#: ui/app.slint:2111 +#: ui/app.slint:2109 msgid "Mailbox startup failed" msgstr "Posta kutusu başlatılamadı" -#: ui/app.slint:2117 +#: ui/app.slint:2115 msgid "Flectar Mail could not open local storage" msgstr "Flectar Mail yerel depolamayı açamadı" -#: ui/app.slint:2130 +#: ui/app.slint:2128 msgid "Diagnostic ID: {}" msgstr "Tanılama kimliği: {}" -#: ui/app.slint:2133 +#: ui/app.slint:2131 msgid "Diagnostic ID {}" msgstr "Tanılama kimliği {}" -#: ui/app.slint:2138 +#: ui/app.slint:2136 msgid "Retry" msgstr "Yeniden dene" -#: ui/app.slint:2146 ui/components/copyable-diagnostic.slint:38 +#: ui/app.slint:2144 ui/components/copyable-diagnostic.slint:38 msgid "Copy diagnostics" msgstr "Tanılama bilgilerini kopyala" diff --git a/lang/zh_Hans/LC_MESSAGES/flectar-mail.po b/lang/zh_Hans/LC_MESSAGES/flectar-mail.po index dc1409cd..dee8af0b 100644 --- a/lang/zh_Hans/LC_MESSAGES/flectar-mail.po +++ b/lang/zh_Hans/LC_MESSAGES/flectar-mail.po @@ -186,7 +186,7 @@ msgstr "有往来邮件记录" msgid "New address-book entry" msgstr "新建通讯录条目" -#: ui/app.slint:121 ui/app.slint:847 ui/components/composer-fields.slint:66 +#: ui/app.slint:121 ui/app.slint:845 ui/components/composer-fields.slint:66 #: ui/dialogs/compose-dialog.slint:37 msgid "No connected account" msgstr "未连接账户" @@ -200,11 +200,11 @@ msgstr[0] "{n} 次互动" msgid "Last contacted {} {}, {}" msgstr "上次联系于 {}年{}月{}日" -#: ui/app.slint:128 ui/app.slint:361 +#: ui/app.slint:128 ui/app.slint:359 msgid "Loading local data…" msgstr "正在加载本地数据…" -#: ui/app.slint:129 ui/app.slint:362 +#: ui/app.slint:129 ui/app.slint:360 msgid "Local data ready." msgstr "本地数据已就绪。" @@ -665,590 +665,582 @@ msgid "Choose where to export the database snapshot" msgstr "选择数据库快照的导出位置" #: ui/app.slint:289 -msgid "The platform host does not provide document export." -msgstr "当前平台环境不支持导出文档。" - -#: ui/app.slint:290 -msgid "The platform host does not provide document import." -msgstr "当前平台环境不支持导入文档。" - -#: ui/app.slint:291 msgid "Backup exported. Passwords and OAuth tokens were not included." msgstr "备份已导出,其中不包含密码和 OAuth 令牌。" -#: ui/app.slint:292 +#: ui/app.slint:290 msgid "Could not export backup: {}" msgstr "无法导出备份:{}" -#: ui/app.slint:293 +#: ui/app.slint:291 msgid "Local data storage is unavailable. Retry startup." msgstr "本地数据存储不可用,请重新启动。" -#: ui/app.slint:294 +#: ui/app.slint:292 msgid "Creating verified database snapshot…" msgstr "正在创建已验证的数据库快照…" -#: ui/app.slint:295 +#: ui/app.slint:293 msgid "Backup imported. All {} account configurations already existed." msgstr "备份已导入。{} 个账户配置均已存在。" -#: ui/app.slint:296 +#: ui/app.slint:294 msgid "" "Imported {} account configurations. Open Accounts and sign in to each one." msgstr "已导入 {} 个账户配置。请打开“账户”并逐一登录。" -#: ui/app.slint:297 +#: ui/app.slint:295 msgid "Could not import backup: {}" msgstr "无法导入备份:{}" -#: ui/app.slint:298 +#: ui/app.slint:296 msgid "All local Flectar Mail data was deleted." msgstr "Flectar Mail 的全部本地数据已删除。" -#: ui/app.slint:299 +#: ui/app.slint:297 msgid "Could not delete all local data: {}" msgstr "无法删除全部本地数据:{}" -#: ui/app.slint:300 +#: ui/app.slint:298 msgid "Deleting local accounts and cached data…" msgstr "正在删除本地账户和缓存数据…" -#: ui/app.slint:305 +#: ui/app.slint:303 msgid "Background mail refresh failed: {}" msgstr "后台邮件刷新失败:{}" -#: ui/app.slint:306 +#: ui/app.slint:304 msgid "No conversations in this folder" msgstr "此文件夹中没有会话" -#: ui/app.slint:307 +#: ui/app.slint:305 msgid "No conversations match this search" msgstr "没有符合该搜索条件的会话" -#: ui/app.slint:308 +#: ui/app.slint:306 msgid "Mail core is ready for account synchronization and message actions." msgstr "邮件核心已就绪,可进行账户同步和邮件操作。" -#: ui/app.slint:309 +#: ui/app.slint:307 msgid "Large message rendered in compatibility mode." msgstr "大型邮件已使用兼容模式渲染。" -#: ui/app.slint:310 +#: ui/app.slint:308 msgid "Software email render failed: {}" msgstr "软件方式渲染邮件失败:{}" -#: ui/app.slint:311 +#: ui/app.slint:309 msgid "Rendered with software fallback after GPU error: {}" msgstr "GPU 出错后已改用软件方式渲染:{}" -#: ui/app.slint:312 +#: ui/app.slint:310 msgid "Blitz render failed (GPU: {}; software: {})" msgstr "Blitz 渲染失败(GPU:{};软件:{})" -#: ui/app.slint:317 +#: ui/app.slint:315 msgid "Add an email address, then save the contact." msgstr "请先填写电子邮件地址,再保存联系人。" -#: ui/app.slint:318 +#: ui/app.slint:316 msgid "Enter a valid email address before saving." msgstr "保存前请填写有效的电子邮件地址。" -#: ui/app.slint:319 +#: ui/app.slint:317 msgid "Could not save: contact storage is unavailable." msgstr "无法保存:联系人存储不可用。" -#: ui/app.slint:320 +#: ui/app.slint:318 msgid "Could not save contact: {}" msgstr "无法保存联系人:{}" -#: ui/app.slint:321 +#: ui/app.slint:319 msgid "Contact saved." msgstr "联系人已保存。" -#: ui/app.slint:322 +#: ui/app.slint:320 msgid "Saved to contacts." msgstr "已保存到联系人。" -#: ui/app.slint:323 +#: ui/app.slint:321 msgid "Could not delete: contact storage is unavailable." msgstr "无法删除:联系人存储不可用。" -#: ui/app.slint:324 +#: ui/app.slint:322 msgid "Could not delete contact: {}" msgstr "无法删除联系人:{}" -#: ui/app.slint:325 +#: ui/app.slint:323 msgid "Contact deleted." msgstr "联系人已删除。" -#: ui/app.slint:326 +#: ui/app.slint:324 msgid "Could not load contacts: {}" msgstr "无法加载联系人:{}" -#: ui/app.slint:327 +#: ui/app.slint:325 msgid "Local contact storage is still starting…" msgstr "本地联系人存储仍在启动中…" -#: ui/app.slint:328 +#: ui/app.slint:326 msgid "Loading contacts…" msgstr "正在加载联系人…" -#: ui/app.slint:333 ui/components/calendar-agenda.slint:233 +#: ui/app.slint:331 ui/components/calendar-agenda.slint:233 msgid "Local calendar" msgstr "本地日历" -#: ui/app.slint:334 ui/components/calendar.slint:271 +#: ui/app.slint:332 ui/components/calendar.slint:271 #: ui/components/calendar.slint:319 ui/components/calendar.slint:434 msgid "Calendar" msgstr "日历" -#: ui/app.slint:335 +#: ui/app.slint:333 msgid "Could not load calendar: {}" msgstr "无法加载日历:{}" -#: ui/app.slint:336 +#: ui/app.slint:334 msgid "Calendar shown and syncing." msgstr "日历已显示,正在同步。" -#: ui/app.slint:337 +#: ui/app.slint:335 msgid "Calendar hidden and sync paused." msgstr "日历已隐藏,同步已暂停。" -#: ui/app.slint:338 +#: ui/app.slint:336 msgid "Calendar update failed: {}" msgstr "日历更新失败:{}" -#: ui/app.slint:339 +#: ui/app.slint:337 msgid "Only events created in Flectar Mail can be edited." msgstr "只能编辑在 Flectar Mail 中创建的日程。" -#: ui/app.slint:340 +#: ui/app.slint:338 msgid "Only events created in Flectar Mail can be deleted." msgstr "只能删除在 Flectar Mail 中创建的日程。" -#: ui/app.slint:341 +#: ui/app.slint:339 msgid "Calendar storage is unavailable." msgstr "日历存储不可用。" -#: ui/app.slint:342 +#: ui/app.slint:340 msgid "Could not delete event: {}" msgstr "无法删除日程:{}" -#: ui/app.slint:343 +#: ui/app.slint:341 msgid "Event deleted, but refresh failed: {}" msgstr "日程已删除,但刷新失败:{}" -#: ui/app.slint:344 +#: ui/app.slint:342 msgid "Event deleted." msgstr "日程已删除。" -#: ui/app.slint:345 +#: ui/app.slint:343 msgid "Use YYYY-MM-DD and a 24-hour time such as 09:30." msgstr "请使用 YYYY-MM-DD 格式的日期和 24 小时制时间,例如 09:30。" -#: ui/app.slint:346 +#: ui/app.slint:344 msgid "Event duration must be a number of minutes." msgstr "日程时长必须是以分钟为单位的数字。" -#: ui/app.slint:347 +#: ui/app.slint:345 msgid "Add a title and choose a duration from 15 to 1440 minutes." msgstr "请填写标题,并选择 15 至 1440 分钟之间的时长。" -#: ui/app.slint:348 +#: ui/app.slint:346 msgid "That local time does not exist in the current time zone." msgstr "该本地时间在当前时区不存在。" -#: ui/app.slint:349 +#: ui/app.slint:347 msgid "Could not save event: {}" msgstr "无法保存日程:{}" -#: ui/app.slint:350 +#: ui/app.slint:348 msgid "Event saved, but refresh failed: {}" msgstr "日程已保存,但刷新失败:{}" -#: ui/app.slint:351 +#: ui/app.slint:349 msgid "Event saved. Provider sync will run when enabled." msgstr "日程已保存。启用服务商同步后将自动同步。" -#: ui/app.slint:356 +#: ui/app.slint:354 msgid "Email resource render failed: {}" msgstr "邮件资源渲染失败:{}" -#: ui/app.slint:357 +#: ui/app.slint:355 msgid "Message refresh failed: {}" msgstr "邮件刷新失败:{}" -#: ui/app.slint:358 +#: ui/app.slint:356 msgid "Message load failed: {}" msgstr "邮件加载失败:{}" -#: ui/app.slint:359 +#: ui/app.slint:357 msgid "" "Your local mailbox could not be opened. No account operation was started. " "Retry, or copy the diagnostics for support." msgstr "" "无法打开本地邮箱,未执行任何账户操作。请重试,或复制诊断信息以寻求支持。" -#: ui/app.slint:360 +#: ui/app.slint:358 msgid "Local storage startup failed." msgstr "本地存储启动失败。" -#: ui/app.slint:367 +#: ui/app.slint:365 msgid "Could not identify the dragged account." msgstr "无法识别所拖动的账户。" -#: ui/app.slint:368 +#: ui/app.slint:366 msgid "Account ordering requires local mail data." msgstr "调整账户顺序需要本地邮件数据。" -#: ui/app.slint:369 +#: ui/app.slint:367 msgid "Could not find the account to reorder." msgstr "找不到要调整顺序的账户。" -#: ui/app.slint:370 +#: ui/app.slint:368 msgid "Saving account order…" msgstr "正在保存账户顺序…" -#: ui/app.slint:371 +#: ui/app.slint:369 msgid "Sent copy settings require local mail data." msgstr "已发送邮件副本设置需要本地邮件数据。" -#: ui/app.slint:372 +#: ui/app.slint:370 msgid "Updating Sent copy settings…" msgstr "正在更新已发送邮件副本设置…" -#: ui/app.slint:373 +#: ui/app.slint:371 msgid "Sent copy settings updated." msgstr "已发送邮件副本设置已更新。" -#: ui/app.slint:374 +#: ui/app.slint:372 msgid "Could not update Sent copy settings: {}" msgstr "无法更新已发送邮件副本设置:{}" -#: ui/app.slint:375 +#: ui/app.slint:373 msgid "Mail history requires local mail data." msgstr "设置邮件同步范围需要本地邮件数据。" -#: ui/app.slint:376 +#: ui/app.slint:374 msgid "Updating mail history…" msgstr "正在更新邮件同步范围…" -#: ui/app.slint:377 +#: ui/app.slint:375 msgid "Remote images are unavailable in this minimal build." msgstr "当前精简版本不支持远程图片。" -#: ui/app.slint:378 +#: ui/app.slint:376 msgid "Could not save remote-image setting: {}" msgstr "无法保存远程图片设置:{}" -#: ui/app.slint:379 +#: ui/app.slint:377 msgid "Remote sender icons unavailable: {}" msgstr "远程发件人图标不可用:{}" -#: ui/app.slint:380 +#: ui/app.slint:378 msgid "Remote images unavailable: {}" msgstr "远程图片不可用:{}" -#: ui/app.slint:381 +#: ui/app.slint:379 msgid "Remote body images and sender favicons enabled." msgstr "已启用正文远程图片和发件人网站图标。" -#: ui/app.slint:382 +#: ui/app.slint:380 msgid "Remote images blocked." msgstr "远程图片已阻止。" -#: ui/app.slint:383 +#: ui/app.slint:381 msgid "Could not refresh message images: {}" msgstr "无法刷新邮件图片:{}" -#: ui/app.slint:384 +#: ui/app.slint:382 msgid "Remote-image loading is disabled." msgstr "远程图片加载已禁用。" -#: ui/app.slint:385 +#: ui/app.slint:383 msgid "Could not load message images: {}" msgstr "无法加载邮件图片:{}" -#: ui/app.slint:386 +#: ui/app.slint:384 msgid "Sync finished, refresh failed: {}" msgstr "同步已完成,但刷新失败:{}" -#: ui/app.slint:387 +#: ui/app.slint:385 msgid "Sync complete." msgstr "同步完成。" -#: ui/app.slint:388 +#: ui/app.slint:386 msgid "Sync failed: {}" msgstr "同步失败:{}" -#: ui/app.slint:389 +#: ui/app.slint:387 msgid "Local mail data is unavailable; preview data is read-only." msgstr "本地邮件数据不可用,预览数据为只读。" -#: ui/app.slint:390 +#: ui/app.slint:388 msgid "Synchronizing accounts…" msgstr "正在同步账户…" -#: ui/app.slint:395 +#: ui/app.slint:393 msgid "Message action completed." msgstr "邮件操作已完成。" -#: ui/app.slint:396 +#: ui/app.slint:394 msgid "" "Permanent deletion queued. Mail will disappear after the server confirms." msgstr "" -#: ui/app.slint:397 +#: ui/app.slint:395 msgid "Message action failed: {}" msgstr "邮件操作失败:{}" -#: ui/app.slint:398 +#: ui/app.slint:396 msgid "Message action failed: message is no longer available" msgstr "邮件操作失败:邮件已不存在" -#: ui/app.slint:399 +#: ui/app.slint:397 msgid "Label added." msgstr "标签已添加。" -#: ui/app.slint:400 +#: ui/app.slint:398 msgid "Label removed." msgstr "标签已移除。" -#: ui/app.slint:401 +#: ui/app.slint:399 msgid "Label created and added." msgstr "标签已创建并添加。" -#: ui/app.slint:402 +#: ui/app.slint:400 msgid "Label created." msgstr "标签已创建。" -#: ui/app.slint:403 +#: ui/app.slint:401 msgid "Label updated." msgstr "标签已更新。" -#: ui/app.slint:404 +#: ui/app.slint:402 msgid "Label deleted." msgstr "标签已删除。" -#: ui/app.slint:405 +#: ui/app.slint:403 msgid "Could not update label: {}" msgstr "无法更新标签:{}" -#: ui/app.slint:406 +#: ui/app.slint:404 msgid "Could not save label: {}" msgstr "无法保存标签:{}" -#: ui/app.slint:407 +#: ui/app.slint:405 msgid "Could not delete label: {}" msgstr "无法删除标签:{}" -#: ui/app.slint:408 +#: ui/app.slint:406 msgid "Folder created." msgstr "文件夹已创建。" -#: ui/app.slint:409 +#: ui/app.slint:407 msgid "Folder renamed." msgstr "文件夹已重命名。" -#: ui/app.slint:410 +#: ui/app.slint:408 msgid "Folder deleted." msgstr "文件夹已删除。" -#: ui/app.slint:411 +#: ui/app.slint:409 msgid "Could not create folder: {}" msgstr "无法创建文件夹:{}" -#: ui/app.slint:412 +#: ui/app.slint:410 msgid "Could not rename folder: {}" msgstr "无法重命名文件夹:{}" -#: ui/app.slint:413 +#: ui/app.slint:411 msgid "Could not delete folder: {}" msgstr "无法删除文件夹:{}" -#: ui/app.slint:414 +#: ui/app.slint:412 msgid "Could not close preview: {}" msgstr "无法关闭预览:{}" -#: ui/app.slint:415 +#: ui/app.slint:413 msgid "Opened the system print view." msgstr "已打开系统打印视图。" -#: ui/app.slint:416 +#: ui/app.slint:414 msgid "Could not print message: {}" msgstr "无法打印邮件:{}" -#: ui/app.slint:417 +#: ui/app.slint:415 msgid "Opened the message in your browser." msgstr "已在浏览器中打开该邮件。" -#: ui/app.slint:418 +#: ui/app.slint:416 msgid "Could not open message in browser: {}" msgstr "无法在浏览器中打开邮件:{}" -#: ui/app.slint:419 +#: ui/app.slint:417 msgid "Connect an account before replying or forwarding." msgstr "请先连接账户再进行回复或转发。" -#: ui/app.slint:420 +#: ui/app.slint:418 msgid "Choose a message first." msgstr "请先选择一封邮件。" -#: ui/app.slint:421 +#: ui/app.slint:419 msgid "Only drafts can be reopened for editing." msgstr "只有草稿箱中的邮件可以重新编辑。" -#: ui/app.slint:422 +#: ui/app.slint:420 msgid "Could not open draft: {}" msgstr "无法打开草稿:{}" -#: ui/app.slint:423 +#: ui/app.slint:421 msgid "Could not open draft: invalid account identifier" msgstr "无法打开草稿:账户标识无效" -#: ui/app.slint:424 +#: ui/app.slint:422 msgid "Connected account" msgstr "已连接账户" -#: ui/app.slint:425 +#: ui/app.slint:423 msgid "Could not open composer: {}" msgstr "无法打开邮件编辑器:{}" -#: ui/app.slint:426 +#: ui/app.slint:424 msgid "Could not open composer: invalid account identifier" msgstr "无法打开邮件编辑器:账户标识无效" -#: ui/app.slint:431 +#: ui/app.slint:429 msgid "Attach files to this email" msgstr "为此邮件添加附件" -#: ui/app.slint:432 -msgid "The platform host does not provide a document picker." -msgstr "当前平台环境不支持文件选择器。" +#: ui/app.slint:430 +msgid "Could not add attachments: {}" +msgstr "无法添加附件:{}" -#: ui/app.slint:433 +#: ui/app.slint:431 msgid "Added 1 attachment." msgstr "已添加 1 个附件。" -#: ui/app.slint:434 +#: ui/app.slint:432 msgid "Added {} attachments." msgstr "已添加 {} 个附件。" -#: ui/app.slint:435 +#: ui/app.slint:433 msgid "" "Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB " "total limit)." msgstr "已添加 {} 个附件,跳过 {} 个(重复、无法读取或超过 25 MB 总量上限)。" -#: ui/app.slint:436 +#: ui/app.slint:434 msgid "Attachment removed." msgstr "附件已移除。" -#: ui/app.slint:437 +#: ui/app.slint:435 msgid "Choose the account this message should be sent from." msgstr "选择发送此邮件的账户。" -#: ui/app.slint:438 +#: ui/app.slint:436 msgid "Choose a verified sender identity for this message." msgstr "为此邮件选择已验证的发件人身份。" -#: ui/app.slint:439 +#: ui/app.slint:437 msgid "Connect an account before saving or sending mail." msgstr "请先连接账户再保存或发送邮件。" -#: ui/app.slint:440 +#: ui/app.slint:438 msgid "Message queued for delivery (action {})." msgstr "邮件已加入发送队列(操作 {})。" -#: ui/app.slint:441 +#: ui/app.slint:439 msgid "Sending message…" msgstr "正在发送邮件…" -#: ui/app.slint:442 +#: ui/app.slint:440 msgid "Message sent." msgstr "邮件已发送。" -#: ui/app.slint:443 +#: ui/app.slint:441 msgid "Message was not sent: {} Your draft is preserved." msgstr "邮件未发送:{} 草稿已保留。" -#: ui/app.slint:444 +#: ui/app.slint:442 msgid "Draft saved locally (#{})." msgstr "草稿已保存到本地(#{})。" -#: ui/app.slint:445 +#: ui/app.slint:443 msgid "Compose failed: {}" msgstr "撰写邮件失败:{}" -#: ui/app.slint:446 +#: ui/app.slint:444 msgid "Connect an account before managing email templates." msgstr "请先连接账户再管理邮件模板。" -#: ui/app.slint:447 +#: ui/app.slint:445 msgid "Could not load email templates: {}" msgstr "无法加载邮件模板:{}" -#: ui/app.slint:448 +#: ui/app.slint:446 msgid "Enter a template name." msgstr "请输入模板名称。" -#: ui/app.slint:449 +#: ui/app.slint:447 msgid "Add a subject or message to the template." msgstr "请为模板添加主题或正文。" -#: ui/app.slint:450 +#: ui/app.slint:448 msgid "Email template saved." msgstr "邮件模板已保存。" -#: ui/app.slint:451 +#: ui/app.slint:449 msgid "Could not save email template: {}" msgstr "无法保存邮件模板:{}" -#: ui/app.slint:452 +#: ui/app.slint:450 msgid "Email template deleted." msgstr "邮件模板已删除。" -#: ui/app.slint:453 +#: ui/app.slint:451 msgid "Could not delete email template: {}" msgstr "无法删除邮件模板:{}" -#: ui/app.slint:454 +#: ui/app.slint:452 msgid "Email template is no longer available." msgstr "邮件模板已不存在。" -#: ui/app.slint:455 +#: ui/app.slint:453 msgid "Email template inserted." msgstr "邮件模板已插入。" -#: ui/app.slint:456 +#: ui/app.slint:454 msgid "Email template inserted, but its usage could not be saved: {}" msgstr "邮件模板已插入,但无法记录其使用情况:{}" -#: ui/app.slint:457 +#: ui/app.slint:455 msgid "IMAP port must be between 1 and 65535." msgstr "IMAP 端口必须在 1 到 65535 之间。" -#: ui/app.slint:458 +#: ui/app.slint:456 msgid "SMTP port must be between 1 and 65535." msgstr "SMTP 端口必须在 1 到 65535 之间。" -#: ui/app.slint:459 +#: ui/app.slint:457 msgid "Local mail data is unavailable. Retry startup." msgstr "本地邮件数据不可用,请重新启动。" -#: ui/app.slint:460 +#: ui/app.slint:458 msgid "Discovering JMAP and checking capabilities…" msgstr "正在发现 JMAP 并检查服务能力…" -#: ui/app.slint:461 +#: ui/app.slint:459 msgid "Certificate imported. Connect to verify the server." msgstr "证书已导入。请连接以验证服务器。" -#: ui/app.slint:462 +#: ui/app.slint:460 msgid "Could not load account preferences: {}" msgstr "无法加载账户偏好设置:{}" -#: ui/app.slint:463 +#: ui/app.slint:461 msgid "" "Your edited signature was preserved. Choose No signature to keep it as " "message text before inserting another." @@ -1256,161 +1248,161 @@ msgstr "" "您编辑过的签名已保留。插入其他签名前,请选择“不使用签名”,以便将其保留为邮件" "正文。" -#: ui/app.slint:464 +#: ui/app.slint:462 msgid "Certificate import failed: {}" msgstr "证书导入失败:{}" -#: ui/app.slint:465 +#: ui/app.slint:463 msgid "Paste the PEM certificate into the certificate field." msgstr "请将 PEM 证书粘贴到证书字段中。" -#: ui/app.slint:466 +#: ui/app.slint:464 msgid "Testing IMAP and SMTP connections…" msgstr "正在测试 IMAP 和 SMTP 连接…" -#: ui/app.slint:471 +#: ui/app.slint:469 msgid "" "Another browser authorization is already in progress; finish it or choose " "Cancel." msgstr "已有另一项浏览器授权正在进行,请完成它或选择“取消”。" -#: ui/app.slint:472 ui/dialogs/settings-dialog.slint:1643 +#: ui/app.slint:470 ui/dialogs/settings-dialog.slint:1643 #: ui/dialogs/settings-dialog.slint:1686 ui/views/onboarding-view.slint:152 msgid "Add app keys in Sign-in settings to enable this provider." msgstr "请在“登录设置”中添加应用密钥以启用此服务商。" -#: ui/app.slint:473 +#: ui/app.slint:471 msgid "Choose Gmail or Outlook OAuth." msgstr "请选择 Gmail 或 Outlook OAuth。" -#: ui/app.slint:474 +#: ui/app.slint:472 msgid "Complete sign-in in your browser, or choose Cancel here." msgstr "请在浏览器中完成登录,或在此处选择“取消”。" -#: ui/app.slint:475 +#: ui/app.slint:473 msgid "Cancelling browser sign-in…" msgstr "正在取消浏览器登录…" -#: ui/app.slint:476 +#: ui/app.slint:474 msgid "Finishing browser sign-in…" msgstr "正在完成浏览器登录…" -#: ui/app.slint:477 +#: ui/app.slint:475 msgid "Could not use that browser address: {}" msgstr "无法使用该浏览器地址:{}" -#: ui/app.slint:478 +#: ui/app.slint:476 msgid "Calendar core is unavailable." msgstr "日历核心不可用。" -#: ui/app.slint:479 +#: ui/app.slint:477 msgid "Account no longer exists." msgstr "账户已不存在。" -#: ui/app.slint:480 +#: ui/app.slint:478 msgid "Authorize calendar access in your browser." msgstr "请在浏览器中授权日历访问。" -#: ui/app.slint:481 +#: ui/app.slint:479 msgid "OAuth app keys saved." msgstr "OAuth 应用密钥已保存。" -#: ui/app.slint:482 +#: ui/app.slint:480 msgid "{} OAuth settings saved." msgstr "{} OAuth 设置已保存。" -#: ui/app.slint:483 +#: ui/app.slint:481 msgid "OAuth settings failed: {}" msgstr "OAuth 设置失败:{}" -#: ui/app.slint:484 +#: ui/app.slint:482 msgid "Mail refresh failed: {}" msgstr "邮件刷新失败:{}" -#: ui/app.slint:485 +#: ui/app.slint:483 msgid "Mail filter failed: {}" msgstr "邮件筛选失败:{}" -#: ui/app.slint:486 +#: ui/app.slint:484 msgid "Mail pagination failed: {}" msgstr "邮件分页失败:{}" -#: ui/app.slint:487 +#: ui/app.slint:485 msgid "Could not mark message as read: {}" msgstr "无法将邮件标记为已读:{}" -#: ui/app.slint:488 ui/views/mail-reading-pane.slint:223 +#: ui/app.slint:486 ui/views/mail-reading-pane.slint:223 #: ui/views/mail-reading-pane.slint:1162 msgid "Loading message…" msgstr "正在加载邮件…" -#: ui/app.slint:494 ui/components/workspace-navigation.slint:551 +#: ui/app.slint:492 ui/components/workspace-navigation.slint:551 #: ui/tray.slint:9 ui/tray.slint:10 msgid "Flectar Mail" msgstr "Flectar Mail" -#: ui/app.slint:1065 ui/app.slint:1078 +#: ui/app.slint:1063 ui/app.slint:1076 msgid "Global Labels (local only)" msgstr "全局标签(仅本地)" -#: ui/app.slint:1938 +#: ui/app.slint:1936 msgid "Empty Trash?" msgstr "" -#: ui/app.slint:1938 ui/views/files-view.slint:538 +#: ui/app.slint:1936 ui/views/files-view.slint:538 msgid "Delete permanently?" msgstr "永久删除?" -#: ui/app.slint:1942 +#: ui/app.slint:1940 msgid "" "Permanently delete all messages in Trash across every connected account?" msgstr "" -#: ui/app.slint:1943 +#: ui/app.slint:1941 msgid "Permanently delete all messages in this account’s Trash?" msgstr "" -#: ui/app.slint:1945 +#: ui/app.slint:1943 msgid "Permanently delete the selected conversations from Trash?" msgstr "" -#: ui/app.slint:1946 +#: ui/app.slint:1944 msgid "Permanently delete this conversation from Trash?" msgstr "" -#: ui/app.slint:1947 ui/components/calendar.slint:926 +#: ui/app.slint:1945 ui/components/calendar.slint:926 msgid "This action cannot be undone." msgstr "此操作无法撤销。" -#: ui/app.slint:1948 +#: ui/app.slint:1946 msgid "Empty Trash" msgstr "" -#: ui/app.slint:1948 ui/views/files-view.slint:620 +#: ui/app.slint:1946 ui/views/files-view.slint:620 msgid "Delete permanently" msgstr "永久删除" -#: ui/app.slint:2111 +#: ui/app.slint:2109 msgid "Mailbox startup failed" msgstr "邮箱启动失败" -#: ui/app.slint:2117 +#: ui/app.slint:2115 msgid "Flectar Mail could not open local storage" msgstr "Flectar Mail 无法打开本地存储" -#: ui/app.slint:2130 +#: ui/app.slint:2128 msgid "Diagnostic ID: {}" msgstr "诊断 ID:{}" -#: ui/app.slint:2133 +#: ui/app.slint:2131 msgid "Diagnostic ID {}" msgstr "诊断 ID {}" -#: ui/app.slint:2138 +#: ui/app.slint:2136 msgid "Retry" msgstr "重试" -#: ui/app.slint:2146 ui/components/copyable-diagnostic.slint:38 +#: ui/app.slint:2144 ui/components/copyable-diagnostic.slint:38 msgid "Copy diagnostics" msgstr "复制诊断信息" diff --git a/platform/README.md b/platform/README.md index 11e7b4d9..ff0bda19 100644 --- a/platform/README.md +++ b/platform/README.md @@ -71,14 +71,24 @@ provides these crate-internal items, which shared code calls without a `cfg`: Hosts that launch through `src/desktop.rs` (desktop and iOS) also provide `platform::launch_context()`. +`DocumentProvider::export()` receives files from two app-private places: +generated files below the cache directory, and database snapshots, which are +staged as one zip in `snapshot-staging` beside the data directory because Core +refuses to write snapshots inside the data and cache roots. A host that +restricts export sources must accept both. + +Shared code starts every picker and export through `documents::run_request`, +which runs the provider future on the Tokio runtime while the UI event loop +keeps going. A host that needs another threading policy while its picker is +up changes that one function. + ### Known gaps -- Compose attachments, account backups and database snapshots still use - desktop file dialogs directly. On `mobile` they report that the host has no - picker. Routing them through `DocumentProvider` will make them available to - every host. - iOS has no Keychain-backed `CredentialStore` yet and uses `UnavailableCredentialStore`. +- Mobile document transfers are capped at 512 MiB, so Android and iOS cannot + export database snapshots of larger mailboxes yet. The export fails before + the picker opens. ## Adding a platform diff --git a/platform/android/Cargo.lock b/platform/android/Cargo.lock index 01f78309..e05111b8 100644 --- a/platform/android/Cargo.lock +++ b/platform/android/Cargo.lock @@ -1794,6 +1794,7 @@ dependencies = [ "windows-sys 0.61.2", "winresource", "x11rb", + "zip", ] [[package]] @@ -6945,6 +6946,12 @@ dependencies = [ "serde", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typenum" version = "1.20.1" @@ -8501,6 +8508,19 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "zip" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a954114b47a54df49f83d2722814e86b23d4bb1b54a1b90fb7d92b41619ddc22" +dependencies = [ + "crc32fast", + "indexmap", + "memchr", + "nix", + "typed-path", +] + [[package]] name = "zmij" version = "1.0.23" diff --git a/src/compose_controller.rs b/src/compose_controller.rs index 433a12d2..2effde40 100644 --- a/src/compose_controller.rs +++ b/src/compose_controller.rs @@ -542,11 +542,14 @@ pub(super) fn complete_compose_recipient( pub(super) fn clear_compose( app: &AppWindow, files: &Rc>>, + session: &Cell, document: &Rc>, editor: &Rc>, contacts: &Rc>>, ) { files.borrow_mut().clear(); + // A picker still open for this composer must not attach to the next one. + session.set(session.get().wrapping_add(1)); app.global::() .invoke_composer_reset(); document.borrow_mut().reset(); diff --git a/src/data_controller.rs b/src/data_controller.rs index e59d910f..3d28693e 100644 --- a/src/data_controller.rs +++ b/src/data_controller.rs @@ -12,7 +12,7 @@ use flectar_mail_core::models::{ normalized_workspace_list_pane_width, }; use serde::{Deserialize, Serialize}; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; const MAX_ACCOUNT_BACKUP_BYTES: u64 = 1024 * 1024; const STORAGE_CATEGORY_COUNT: usize = 5; @@ -341,34 +341,334 @@ fn read_account_backup(path: &std::path::Path) -> Result, String> { Ok(bytes) } -#[cfg(desktop)] -fn pick_backup_export_path(title: String, filter: String) -> Option { - rfd::FileDialog::new() - .set_title(title) - .set_file_name(format!( - "flectar-mail-backup-{}.json", - Local::now().format("%Y-%m-%d") - )) - .add_filter(filter, &["json"]) - .save_file() -} +/// Applies an account backup the host already imported. The caller keeps the +/// imported document alive until this returns. +fn import_account_backup_document( + app: &AppWindow, + path: &std::path::Path, + core: &CoreMailSource, + state: &Rc>, + runtime: &Rc, +) { + let result = (|| -> Result<(usize, Settings, Vec, Vec), String> { + let bytes = read_account_backup(path)?; + let backup: serde_json::Value = + serde_json::from_slice(&bytes).map_err(|error| format!("invalid JSON: {error}"))?; + if backup.get("format").and_then(|value| value.as_str()) + != Some("flectar-mail-account-backup") + { + return Err("this is not a Flectar Mail account backup".into()); + } + if backup.get("version").and_then(|value| value.as_u64()) != Some(1) { + return Err("this backup version is not supported".into()); + } + if backup + .get("includesCredentials") + .and_then(|value| value.as_bool()) + == Some(true) + { + return Err("backups containing credentials are not accepted".into()); + } + let configs = serde_json::from_value::>( + backup + .get("accounts") + .cloned() + .ok_or_else(|| "backup has no accounts list".to_owned())?, + ) + .map_err(|error| format!("invalid account data: {error}"))?; + if configs.len() > 100 { + return Err("backup contains more than 100 accounts".into()); + } + let imported_emails = configs + .iter() + .map(|config| config.email.trim().to_ascii_lowercase()) + .collect::>(); + let portable_profiles = portable_mail_profiles(&backup, &imported_emails)?; + let has_portable_profiles = portable_profiles.is_some(); + let portable_profiles = portable_profiles.unwrap_or_default(); + let portable_colors = portable_account_colors(&backup, &imported_emails)?; + let has_portable_colors = portable_colors.is_some(); + let portable_colors = portable_colors.unwrap_or_default(); -#[cfg(mobile)] -fn pick_backup_export_path(_title: String, _filter: String) -> Option { - None + let mut settings = runtime.block_on(core.load_settings())?; + if let Some(preferences) = backup.get("preferences") { + if let Some(theme) = preferences.get("theme").and_then(|value| value.as_str()) + && matches!(theme, "system" | "carbon" | "snow" | "dark" | "light") + { + settings.theme = theme.to_owned(); + } + if let Some(enabled) = preferences + .get("monochromeSidebarIcons") + .and_then(|value| value.as_bool()) + { + settings.monochrome_sidebar_icons = enabled; + } + if let Some(enabled) = preferences + .get("showAvatars") + .and_then(|value| value.as_bool()) + { + settings.show_avatars = enabled; + } + if let Some(frame) = preferences + .get("windowFrame") + .and_then(|value| value.as_str()) + && matches!(frame, "system" | "integrated") + { + settings.window_frame = frame.to_owned(); + } + if let Some(layout) = preferences + .get("workspaceLayout") + .or_else(|| preferences.get("mailLayout")) + .and_then(|value| value.as_str()) + && matches!(layout, "default" | "minimal") + { + settings.workspace_layout = layout.to_owned(); + } + if let Some(preference) = preferences + .get("messageAppearance") + .and_then(|value| value.as_str()) + .and_then(flectar_mail_core::models::MessageAppearance::parse) + { + settings.message_appearance = preference; + } + if let Some(width) = preferences + .get("workspaceListPaneWidth") + .and_then(|value| value.as_i64()) + { + settings.workspace_list_pane_width = normalized_workspace_list_pane_width(width); + } + if let Some(language) = preferences.get("language").and_then(|value| value.as_str()) + && language.len() <= 32 + { + settings.language = language.to_owned(); + } + if let Some(week_start) = preferences + .get("calendarWeekStart") + .and_then(|value| value.as_str()) + && matches!(week_start, "sunday" | "monday") + { + settings.calendar_week_start = week_start.to_owned(); + } + if let Some(enabled) = preferences + .get("loadRemoteImages") + .and_then(|value| value.as_bool()) + { + settings.load_remote_images = enabled; + } + if let Some(enabled) = preferences + .get("markReadOnOpen") + .and_then(|value| value.as_bool()) + { + settings.mark_read_on_open = enabled; + } + if let Some(enabled) = preferences + .get("collectOutgoingContacts") + .and_then(|value| value.as_bool()) + { + settings.collect_outgoing_contacts = enabled; + } + if let Some(enabled) = preferences + .get("collectIncomingContacts") + .and_then(|value| value.as_bool()) + { + settings.collect_incoming_contacts = enabled; + } + if let Some(enabled) = preferences + .get("contactSuggestAllAccounts") + .and_then(|value| value.as_bool()) + { + settings.contact_suggest_all_accounts = enabled; + } + if let Some(enabled) = preferences + .get("suggestLearnedContacts") + .and_then(|value| value.as_bool()) + { + settings.suggest_learned_contacts = enabled; + } + if let Some(enabled) = preferences + .get("notificationsEnabled") + .and_then(|value| value.as_bool()) + { + settings.notifications_enabled = enabled; + } + if let Some(scope) = preferences + .get("notificationScope") + .and_then(|value| value.as_str()) + && matches!(scope, "important" | "all") + { + settings.notification_scope = scope.to_owned(); + } + if let Some(enabled) = preferences + .get("soundEnabled") + .and_then(|value| value.as_bool()) + { + settings.sound_enabled = enabled; + } + if let Some(minutes) = preferences + .get("syncIntervalMinutes") + .and_then(|value| value.as_i64()) + && matches!(minutes, 1 | 5 | 15) + { + settings.sync_interval_minutes = minutes; + } + if let Some(enabled) = preferences + .get("closeToTray") + .and_then(|value| value.as_bool()) + { + settings.close_to_tray = enabled; + } + if let Some(enabled) = preferences + .get("showAccountMarkers") + .and_then(|value| value.as_bool()) + { + settings.show_account_badges = enabled; + } + } + + let imported = runtime.block_on(core.import_account_configs(configs))?; + runtime.block_on(core.save_settings(settings.clone()))?; + let accounts = runtime.block_on(core.load_accounts())?; + let configs = runtime.block_on(core.load_account_configs())?; + let account_ids = accounts + .iter() + .map(|account| (account.email.trim().to_ascii_lowercase(), account.id)) + .collect::>(); + if has_portable_colors { + for email in &imported_emails { + if let Some(account_id) = account_ids.get(email) { + runtime.block_on(core.set_account_color(*account_id, None))?; + } + } + for (email, color) in portable_colors { + if let Some(account_id) = account_ids.get(&email) { + runtime.block_on(core.set_account_color(*account_id, Some(color)))?; + } + } + } + if has_portable_profiles { + for email in &imported_emails { + if let Some(account_id) = account_ids.get(email) { + runtime.block_on(core.assign_account_profile(*account_id, None))?; + } + } + } + for profile in portable_profiles { + let current = runtime.block_on(core.load_settings())?; + let existing_id = current + .mail_profiles + .iter() + .find(|candidate| candidate.name.eq_ignore_ascii_case(profile.name.trim())) + .map(|candidate| candidate.id.clone()); + let (_, saved) = runtime.block_on(core.save_mail_profile( + existing_id, + profile.name, + profile.color, + ))?; + for email in profile.account_emails { + if let Some(account_id) = account_ids.get(&email.trim().to_ascii_lowercase()) { + runtime.block_on( + core.assign_account_profile(*account_id, Some(saved.id.clone())), + )?; + } + } + } + settings = runtime.block_on(core.load_settings())?; + Ok((imported, settings, accounts, configs)) + })(); + + match result { + Ok((imported, settings, accounts, configs)) => { + let total = accounts.len(); + apply_language(app, &settings.language); + state.borrow_mut().account_presentation = + AccountPresentationSettings::from_settings(&settings); + update_connected_accounts(app, state, runtime, accounts, configs); + refresh_rows_only(app, state, runtime); + app.set_theme_mode( + match settings.theme.as_str() { + "carbon" | "dark" => "dark", + "snow" | "light" => "light", + _ => "system", + } + .into(), + ); + app.set_monochrome_sidebar_icons(settings.monochrome_sidebar_icons); + app.global::() + .set_preference(settings.message_appearance.as_str().into()); + app.set_show_avatars(settings.show_avatars); + app.set_show_account_markers(settings.show_account_badges); + crate::window_chrome::apply_preference( + app, + crate::window_chrome::WindowFrame::parse(&settings.window_frame), + ); + app.set_workspace_layout(settings.workspace_layout.clone().into()); + app.set_workspace_list_pane_width(normalized_workspace_list_pane_width( + settings.workspace_list_pane_width, + ) as f32); + app.set_notifications_enabled(settings.notifications_enabled); + app.set_notification_sound_enabled(settings.sound_enabled); + app.set_notification_scope(settings.notification_scope.clone().into()); + app.set_sync_interval_minutes(settings.sync_interval_minutes as i32); + app.set_mark_read_on_open(settings.mark_read_on_open); + app.set_collect_outgoing_contacts(settings.collect_outgoing_contacts); + app.set_collect_incoming_contacts(settings.collect_incoming_contacts); + app.set_contact_suggest_all_accounts(settings.contact_suggest_all_accounts); + app.set_suggest_learned_contacts(settings.suggest_learned_contacts); + state.borrow_mut().mark_read_on_open = settings.mark_read_on_open; + app.set_remote_images_enabled( + settings.load_remote_images && cfg!(feature = "remote-content"), + ); + #[cfg(not(any(mobile, feature = "flatpak")))] + app.set_close_to_tray(settings.close_to_tray); + #[cfg(any(mobile, feature = "flatpak"))] + app.set_close_to_tray(false); + app.set_sync_status(if imported == 0 { + UiMessage::detail( + "Backup imported. All {} account configurations already existed.", + total, + ) + } else { + UiMessage::detail( + "Imported {} account configurations. Open Accounts and sign in to each one.", + imported, + ) + }); + } + Err(error) => app.set_sync_status(UiMessage::detail("Could not import backup: {}", error)), + } } -#[cfg(desktop)] -fn pick_backup_import_path(title: String, filter: String) -> Option { - rfd::FileDialog::new() - .set_title(title) - .add_filter(filter, &["json"]) - .pick_file() +/// Hands generated bytes to the document provider through a private staging +/// file; the host copies it to the destination its picker offers. +fn export_document_bytes( + documents: std::sync::Arc, + staging_root: PathBuf, + bytes: Vec, + name: String, + dialog: documents::DocumentDialog, +) -> documents::DocumentFuture { + Box::pin(async move { + tokio::fs::create_dir_all(&staging_root) + .await + .map_err(|error| format!("creating {}: {error}", staging_root.display()))?; + let staging = tempfile::tempdir_in(&staging_root) + .map_err(|error| format!("creating staging in {}: {error}", staging_root.display()))?; + let path = staging.path().join(&name); + tokio::fs::write(&path, bytes) + .await + .map_err(|error| format!("writing {}: {error}", path.display()))?; + documents.export_with(path, name, dialog).await + }) } -#[cfg(mobile)] -fn pick_backup_import_path(_title: String, _filter: String) -> Option { - None +fn backup_dialog(app: &AppWindow, title: UiMessage) -> documents::DocumentDialog { + documents::DocumentDialog { + title: translated(app, &title).to_string(), + filter: Some(( + translated(app, &UiMessage::plain("Flectar Mail backup")).to_string(), + &["json"], + )), + } } #[cfg(desktop)] @@ -384,18 +684,187 @@ fn pick_database_snapshot_path(title: String) -> Option { }) } +/// Mobile hosts export single files from their app-private storage, so the +/// verified snapshot is staged beside the data directory (Core refuses +/// destinations inside the data and cache roots) and archived as one zip. #[cfg(mobile)] -fn pick_database_snapshot_path(_title: String) -> Option { - None +fn snapshot_staging_root(paths: &Paths) -> Result { + Ok(paths + .data_dir + .parent() + .ok_or("the data directory has no parent for snapshot staging")? + .join("snapshot-staging")) +} + +/// A zipped snapshot waiting in staging for the export picker. +#[cfg(mobile)] +struct StagedSnapshot { + root: PathBuf, + archive: PathBuf, + name: String, + /// Keeps the startup cleanup out of the staging root until it is removed. + _staging: tokio::sync::OwnedMutexGuard<()>, +} + +#[cfg(mobile)] +impl StagedSnapshot { + async fn remove(self) { + let _ = tokio::fs::remove_dir_all(&self.root).await; + } +} + +#[cfg(mobile)] +async fn stage_database_snapshot( + core: CoreMailSource, + paths: Paths, + staging: Arc>, +) -> Result { + let staging = staging.lock_owned().await; + let root = snapshot_staging_root(&paths)?; + // The root only ever holds this export, so clearing it also removes what + // an interrupted export left behind. + let _ = tokio::fs::remove_dir_all(&root).await; + let staged = async { + tokio::fs::create_dir_all(&root) + .await + .map_err(|error| format!("creating {}: {error}", root.display()))?; + let name = format!( + "flectar-mail-snapshot-{}", + Local::now().format("%Y-%m-%d-%H%M%S") + ); + let directory = root.join(&name); + core.create_database_snapshot(directory.clone()) + .await + .map_err(|error| error.to_string())?; + let name = format!("{name}.zip"); + let archive = root.join(&name); + let archive_for_zip = archive.clone(); + tokio::task::spawn_blocking(move || { + archive_snapshot_directory( + &directory, + &archive_for_zip, + flectar_mail_core::files::MAX_TRANSFER as u64, + ) + }) + .await + .map_err(|error| error.to_string())??; + Ok((archive, name)) + } + .await; + match staged { + Ok((archive, name)) => Ok(StagedSnapshot { + root, + archive, + name, + _staging: staging, + }), + Err(error) => { + let _ = tokio::fs::remove_dir_all(&root).await; + Err(error) + } + } +} + +/// Archives a snapshot directory without compression: the stores are dense +/// SQLite files plus already-compressed attachments. Symlinks are skipped so +/// nothing outside the snapshot enters the archive. Hosts transfer at most +/// `limit` bytes, so a larger snapshot fails before the archive is written. +#[cfg(any(mobile, test))] +fn archive_snapshot_directory( + directory: &std::path::Path, + archive: &std::path::Path, + limit: u64, +) -> Result<(), String> { + /// Archive name, source path and, for files, the size. + type Entry = (String, PathBuf, Option); + fn collect( + root: &std::path::Path, + directory: &std::path::Path, + entries: &mut Vec, + ) -> Result<(), String> { + let mut children = std::fs::read_dir(directory) + .map_err(|error| format!("reading {}: {error}", directory.display()))? + .collect::, _>>() + .map_err(|error| error.to_string())?; + children.sort_by_key(|entry| entry.file_name()); + for entry in children { + let path = entry.path(); + let kind = entry.file_type().map_err(|error| error.to_string())?; + let name = path + .strip_prefix(root) + .map_err(|error| error.to_string())? + .to_string_lossy() + .replace('\\', "/"); + if kind.is_dir() { + entries.push((format!("{name}/"), path.clone(), None)); + collect(root, &path, entries)?; + } else if kind.is_file() { + let size = entry.metadata().map_err(|error| error.to_string())?.len(); + entries.push((name, path, Some(size))); + } + } + Ok(()) + } + let too_large = |size| { + format!( + "the snapshot is {}, more than the {} this device can export", + format_storage_size(size), + format_storage_size(limit) + ) + }; + let mut entries = Vec::new(); + collect(directory, directory, &mut entries)?; + let size = entries.iter().filter_map(|(_, _, size)| *size).sum::(); + if size > limit { + return Err(too_large(size)); + } + let file = std::fs::File::create(archive) + .map_err(|error| format!("creating {}: {error}", archive.display()))?; + let mut zip = zip::ZipWriter::new(file); + let options = + zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored); + for (name, path, size) in entries { + let Some(size) = size else { + zip.add_directory(name, options) + .map_err(|error| error.to_string())?; + continue; + }; + let mut source = std::fs::File::open(&path) + .map_err(|error| format!("reading {}: {error}", path.display()))?; + // Stores past 4 GiB need ZIP64 entries. + zip.start_file(name, options.large_file(size >= u64::from(u32::MAX))) + .map_err(|error| error.to_string())?; + std::io::copy(&mut source, &mut zip).map_err(|error| error.to_string())?; + } + let size = zip + .finish() + .and_then(|file| Ok(file.metadata()?.len())) + .map_err(|error| error.to_string())?; + // Entry headers can still push the archive past the limit. + if size > limit { + return Err(too_large(size)); + } + Ok(()) +} + +/// Clears the in-progress flag when the snapshot task ends, even by panic. +struct SnapshotRunning(Arc); + +impl Drop for SnapshotRunning { + fn drop(&mut self) { + self.0.store(false, Ordering::Release); + } } #[allow(clippy::too_many_arguments)] pub(super) fn register_data_management_callbacks( app: &AppWindow, paths: &Paths, + documents: &std::sync::Arc, state: &Rc>, runtime: &Rc, - ui_task_tx: &UiSender, + // Only the desktop snapshot export reports through the task channel. + #[cfg_attr(mobile, allow(unused_variables))] ui_task_tx: &UiSender, contact_state: &Rc>, contacts_loaded: &Rc>, contacts_loading: &Rc>, @@ -466,10 +935,18 @@ pub(super) fn register_data_management_callbacks( let app_weak = app.as_weak(); let state_for_backup = Rc::clone(state); let runtime_for_backup = Rc::clone(runtime); + let documents_for_backup = documents.clone(); + let paths_for_backup = paths.clone(); + let exporting_backup = Rc::new(Cell::new(false)); app.on_export_account_backup(move || { let Some(app) = app_weak.upgrade() else { return; }; + // The dialog does not block the window; a second click while it is + // open would start a second export. + if exporting_backup.get() { + return; + } let (core, configs) = { let state = state_for_backup.borrow(); (state.core.clone(), state.account_configs.clone()) @@ -480,21 +957,6 @@ pub(super) fn register_data_management_callbacks( )); return; } - let Some(path) = pick_backup_export_path( - translated( - &app, - &UiMessage::plain("Export Flectar Mail account backup"), - ) - .to_string(), - translated(&app, &UiMessage::plain("Flectar Mail backup")).to_string(), - ) else { - #[cfg(mobile)] - app.set_sync_status(UiMessage::plain( - "The platform host does not provide document export.", - )); - return; - }; - let preferences = core .as_ref() .and_then(|core| runtime_for_backup.block_on(core.load_settings()).ok()) @@ -575,23 +1037,67 @@ pub(super) fn register_data_management_callbacks( "accounts": accounts, "preferences": preferences }); - match serde_json::to_vec_pretty(&backup) - .map_err(|error| error.to_string()) - .and_then(|bytes| std::fs::write(&path, bytes).map_err(|error| error.to_string())) - { - Ok(()) => app.set_sync_status(UiMessage::plain( - "Backup exported. Passwords and OAuth tokens were not included.", - )), + let bytes = match serde_json::to_vec_pretty(&backup) { + Ok(bytes) => bytes, Err(error) => { - app.set_sync_status(UiMessage::detail("Could not export backup: {}", error)) + app.set_sync_status(UiMessage::detail("Could not export backup: {}", error)); + return; } - } + }; + let name = format!( + "flectar-mail-backup-{}.json", + Local::now().format("%Y-%m-%d") + ); + let dialog = backup_dialog(&app, UiMessage::plain("Export Flectar Mail account backup")); + let request = export_document_bytes( + documents_for_backup.clone(), + paths_for_backup.temp_dir(), + bytes, + name, + dialog, + ); + let app_weak = app.as_weak(); + let exporting = Rc::clone(&exporting_backup); + exporting.set(true); + documents::run_request(&runtime_for_backup, request, move |result| { + exporting.set(false); + let Some(app) = app_weak.upgrade() else { + return; + }; + match result { + Ok(true) => app.set_sync_status(UiMessage::plain( + "Backup exported. Passwords and OAuth tokens were not included.", + )), + Ok(false) => {} + Err(error) => { + app.set_sync_status(UiMessage::detail("Could not export backup: {}", error)) + } + } + }); }); let app_weak = app.as_weak(); let state_for_database_snapshot = Rc::clone(state); let runtime_for_database_snapshot = Rc::clone(runtime); + #[cfg(desktop)] let updates_for_database_snapshot = ui_task_tx.clone(); + #[cfg(mobile)] + let documents_for_database_snapshot = documents.clone(); + #[cfg(mobile)] + let paths_for_database_snapshot = paths.clone(); + #[cfg(mobile)] + let snapshot_staging = Arc::new(tokio::sync::Mutex::new(())); + // A killed export leaves a full copy of the stores in staging; reclaim it + // now instead of at the next export. + #[cfg(mobile)] + if let Ok(root) = snapshot_staging_root(paths) { + let staging = Arc::clone(&snapshot_staging); + runtime.spawn(async move { + let _staging = staging.lock().await; + let _ = tokio::fs::remove_dir_all(root).await; + }); + } + let database_snapshot_running = Arc::new(AtomicBool::new(false)); app.on_export_database_snapshot(move || { let Some(app) = app_weak.upgrade() else { return; @@ -602,6 +1108,11 @@ pub(super) fn register_data_management_callbacks( )); return; }; + if database_snapshot_running.load(Ordering::Acquire) { + app.set_sync_status(UiMessage::plain("Creating verified database snapshot…")); + return; + } + #[cfg(desktop)] let Some(path) = pick_database_snapshot_path( translated( &app, @@ -609,370 +1120,139 @@ pub(super) fn register_data_management_callbacks( ) .to_string(), ) else { - #[cfg(mobile)] - app.set_sync_status(UiMessage::plain( - "The platform host does not provide document export.", - )); return; }; + database_snapshot_running.store(true, Ordering::Release); app.set_sync_status(UiMessage::plain("Creating verified database snapshot…")); - let updates = updates_for_database_snapshot.clone(); - runtime_for_database_snapshot.spawn(async move { - let display_path = path.display().to_string(); - let message = match core.create_database_snapshot(path).await { - Ok(_) => UiMessage::detail("Database snapshot exported to {}.", display_path), - Err(error) => UiMessage::detail("Could not export database snapshot: {}", error), - }; - let _ = updates - .send(UiTaskUpdate { - message, - accounts: None, - calendar_connections: None, - carddav_connections: None, - calendar_error: None, - account_removal: None, - clear_account_form: false, - finishes_account_setup: false, - finishes_oauth: false, - close_to_tray: None, - }) - .await; - }); + let running = SnapshotRunning(Arc::clone(&database_snapshot_running)); + #[cfg(desktop)] + { + let updates = updates_for_database_snapshot.clone(); + runtime_for_database_snapshot.spawn(async move { + let display_path = path.display().to_string(); + let message = match core.create_database_snapshot(path).await { + Ok(_) => UiMessage::detail("Database snapshot exported to {}.", display_path), + Err(error) => { + UiMessage::detail("Could not export database snapshot: {}", error) + } + }; + drop(running); + let _ = updates + .send(UiTaskUpdate { + message, + accounts: None, + calendar_connections: None, + carddav_connections: None, + calendar_error: None, + account_removal: None, + clear_account_form: false, + finishes_account_setup: false, + finishes_oauth: false, + close_to_tray: None, + }) + .await; + }); + } + // Staging runs on the runtime; only the export picker goes through + // the shared document request path. + #[cfg(mobile)] + { + let staged = runtime_for_database_snapshot.spawn(stage_database_snapshot( + core, + paths_for_database_snapshot.clone(), + Arc::clone(&snapshot_staging), + )); + let abort_staging = staged.abort_handle(); + let app_weak = app.as_weak(); + let documents = documents_for_database_snapshot.clone(); + let runtime = Rc::clone(&runtime_for_database_snapshot); + let spawned = slint::spawn_local(async move { + let snapshot = match staged.await.unwrap_or_else(|error| Err(error.to_string())) { + Ok(snapshot) => snapshot, + Err(error) => { + drop(running); + if let Some(app) = app_weak.upgrade() { + app.set_sync_status(UiMessage::detail( + "Could not export database snapshot: {}", + error, + )); + } + return; + } + }; + let request = documents.export(snapshot.archive.clone(), snapshot.name.clone()); + let cleanup = runtime.handle().clone(); + documents::run_request(&runtime, request, move |exported| { + let name = snapshot.name.clone(); + cleanup.spawn(snapshot.remove()); + drop(running); + let Some(app) = app_weak.upgrade() else { + return; + }; + app.set_sync_status(match exported { + Ok(true) => UiMessage::detail("Database snapshot exported to {}.", name), + // Cancelled at the picker: return the status line to idle. + Ok(false) => UiMessage::plain("Local data ready."), + Err(error) => { + UiMessage::detail("Could not export database snapshot: {}", error) + } + }); + }); + }); + if let Err(error) = spawned { + abort_staging.abort(); + app.set_sync_status(UiMessage::detail( + "Could not export database snapshot: {}", + error, + )); + } + } }); let app_weak = app.as_weak(); let state_for_backup_import = Rc::clone(state); let runtime_for_backup_import = Rc::clone(runtime); + let documents_for_backup_import = documents.clone(); + let importing_backup = Rc::new(Cell::new(false)); app.on_import_account_backup(move || { let Some(app) = app_weak.upgrade() else { return; }; + // A second click while the picker is open would apply two backups. + if importing_backup.get() { + return; + } let Some(core) = state_for_backup_import.borrow().core.clone() else { app.set_sync_status(UiMessage::plain( "Local data storage is unavailable. Retry startup.", )); return; }; - let Some(path) = pick_backup_import_path( - translated( - &app, - &UiMessage::plain("Import Flectar Mail account backup"), - ) - .to_string(), - translated(&app, &UiMessage::plain("Flectar Mail backup")).to_string(), - ) else { - #[cfg(mobile)] - app.set_sync_status(UiMessage::plain( - "The platform host does not provide document import.", - )); - return; - }; - - let result = (|| -> Result<(usize, Settings, Vec, Vec), String> { - let bytes = read_account_backup(&path)?; - let backup: serde_json::Value = - serde_json::from_slice(&bytes).map_err(|error| format!("invalid JSON: {error}"))?; - if backup.get("format").and_then(|value| value.as_str()) - != Some("flectar-mail-account-backup") - { - return Err("this is not a Flectar Mail account backup".into()); - } - if backup.get("version").and_then(|value| value.as_u64()) != Some(1) { - return Err("this backup version is not supported".into()); - } - if backup - .get("includesCredentials") - .and_then(|value| value.as_bool()) - == Some(true) - { - return Err("backups containing credentials are not accepted".into()); - } - let configs = serde_json::from_value::>( - backup - .get("accounts") - .cloned() - .ok_or_else(|| "backup has no accounts list".to_owned())?, - ) - .map_err(|error| format!("invalid account data: {error}"))?; - if configs.len() > 100 { - return Err("backup contains more than 100 accounts".into()); - } - let imported_emails = configs - .iter() - .map(|config| config.email.trim().to_ascii_lowercase()) - .collect::>(); - let portable_profiles = portable_mail_profiles(&backup, &imported_emails)?; - let has_portable_profiles = portable_profiles.is_some(); - let portable_profiles = portable_profiles.unwrap_or_default(); - let portable_colors = portable_account_colors(&backup, &imported_emails)?; - let has_portable_colors = portable_colors.is_some(); - let portable_colors = portable_colors.unwrap_or_default(); - - let mut settings = runtime_for_backup_import.block_on(core.load_settings())?; - if let Some(preferences) = backup.get("preferences") { - if let Some(theme) = preferences.get("theme").and_then(|value| value.as_str()) - && matches!(theme, "system" | "carbon" | "snow" | "dark" | "light") - { - settings.theme = theme.to_owned(); - } - if let Some(enabled) = preferences - .get("monochromeSidebarIcons") - .and_then(|value| value.as_bool()) - { - settings.monochrome_sidebar_icons = enabled; - } - if let Some(enabled) = preferences - .get("showAvatars") - .and_then(|value| value.as_bool()) - { - settings.show_avatars = enabled; - } - if let Some(frame) = preferences - .get("windowFrame") - .and_then(|value| value.as_str()) - && matches!(frame, "system" | "integrated") - { - settings.window_frame = frame.to_owned(); - } - if let Some(layout) = preferences - .get("workspaceLayout") - .or_else(|| preferences.get("mailLayout")) - .and_then(|value| value.as_str()) - && matches!(layout, "default" | "minimal") - { - settings.workspace_layout = layout.to_owned(); - } - if let Some(preference) = preferences - .get("messageAppearance") - .and_then(|value| value.as_str()) - .and_then(flectar_mail_core::models::MessageAppearance::parse) - { - settings.message_appearance = preference; - } - if let Some(width) = preferences - .get("workspaceListPaneWidth") - .and_then(|value| value.as_i64()) - { - settings.workspace_list_pane_width = - normalized_workspace_list_pane_width(width); - } - if let Some(language) = preferences - .get("language") - .and_then(|value| value.as_str()) - && language.len() <= 32 - { - settings.language = language.to_owned(); - } - if let Some(week_start) = preferences - .get("calendarWeekStart") - .and_then(|value| value.as_str()) - && matches!(week_start, "sunday" | "monday") - { - settings.calendar_week_start = week_start.to_owned(); - } - if let Some(enabled) = preferences - .get("loadRemoteImages") - .and_then(|value| value.as_bool()) - { - settings.load_remote_images = enabled; - } - if let Some(enabled) = preferences - .get("markReadOnOpen") - .and_then(|value| value.as_bool()) - { - settings.mark_read_on_open = enabled; - } - if let Some(enabled) = preferences - .get("collectOutgoingContacts") - .and_then(|value| value.as_bool()) - { - settings.collect_outgoing_contacts = enabled; - } - if let Some(enabled) = preferences - .get("collectIncomingContacts") - .and_then(|value| value.as_bool()) - { - settings.collect_incoming_contacts = enabled; - } - if let Some(enabled) = preferences - .get("contactSuggestAllAccounts") - .and_then(|value| value.as_bool()) - { - settings.contact_suggest_all_accounts = enabled; - } - if let Some(enabled) = preferences - .get("suggestLearnedContacts") - .and_then(|value| value.as_bool()) - { - settings.suggest_learned_contacts = enabled; - } - if let Some(enabled) = preferences - .get("notificationsEnabled") - .and_then(|value| value.as_bool()) - { - settings.notifications_enabled = enabled; - } - if let Some(scope) = preferences - .get("notificationScope") - .and_then(|value| value.as_str()) - && matches!(scope, "important" | "all") - { - settings.notification_scope = scope.to_owned(); - } - if let Some(enabled) = preferences - .get("soundEnabled") - .and_then(|value| value.as_bool()) - { - settings.sound_enabled = enabled; - } - if let Some(minutes) = preferences - .get("syncIntervalMinutes") - .and_then(|value| value.as_i64()) - && matches!(minutes, 1 | 5 | 15) - { - settings.sync_interval_minutes = minutes; - } - if let Some(enabled) = preferences - .get("closeToTray") - .and_then(|value| value.as_bool()) - { - settings.close_to_tray = enabled; - } - if let Some(enabled) = preferences - .get("showAccountMarkers") - .and_then(|value| value.as_bool()) - { - settings.show_account_badges = enabled; - } - } - - let imported = runtime_for_backup_import - .block_on(core.import_account_configs(configs))?; - runtime_for_backup_import.block_on(core.save_settings(settings.clone()))?; - let accounts = runtime_for_backup_import.block_on(core.load_accounts())?; - let configs = runtime_for_backup_import.block_on(core.load_account_configs())?; - let account_ids = accounts - .iter() - .map(|account| (account.email.trim().to_ascii_lowercase(), account.id)) - .collect::>(); - if has_portable_colors { - for email in &imported_emails { - if let Some(account_id) = account_ids.get(email) { - runtime_for_backup_import - .block_on(core.set_account_color(*account_id, None))?; - } - } - for (email, color) in portable_colors { - if let Some(account_id) = account_ids.get(&email) { - runtime_for_backup_import - .block_on(core.set_account_color(*account_id, Some(color)))?; - } - } - } - if has_portable_profiles { - for email in &imported_emails { - if let Some(account_id) = account_ids.get(email) { - runtime_for_backup_import - .block_on(core.assign_account_profile(*account_id, None))?; - } - } - } - for profile in portable_profiles { - let current = runtime_for_backup_import.block_on(core.load_settings())?; - let existing_id = current - .mail_profiles - .iter() - .find(|candidate| candidate.name.eq_ignore_ascii_case(profile.name.trim())) - .map(|candidate| candidate.id.clone()); - let (_, saved) = runtime_for_backup_import.block_on(core.save_mail_profile( - existing_id, - profile.name, - profile.color, - ))?; - for email in profile.account_emails { - if let Some(account_id) = account_ids.get(&email.trim().to_ascii_lowercase()) { - runtime_for_backup_import.block_on(core.assign_account_profile( - *account_id, - Some(saved.id.clone()), - ))?; - } + // The import keeps its synchronous shape on the UI thread so the + // existing state updates stay on the main loop. + let dialog = backup_dialog(&app, UiMessage::plain("Import Flectar Mail account backup")); + let request = documents_for_backup_import.import_with(dialog); + let app_weak = app.as_weak(); + let state = Rc::clone(&state_for_backup_import); + let runtime = Rc::clone(&runtime_for_backup_import); + let importing = Rc::clone(&importing_backup); + importing.set(true); + documents::run_request(&runtime_for_backup_import, request, move |imported| { + importing.set(false); + let Some(app) = app_weak.upgrade() else { + return; + }; + let document = match imported { + Ok(Some(document)) => document, + Ok(None) => return, + Err(error) => { + app.set_sync_status(UiMessage::detail("Could not import backup: {}", error)); + return; } - } - settings = runtime_for_backup_import.block_on(core.load_settings())?; - Ok((imported, settings, accounts, configs)) - })(); - - match result { - Ok((imported, settings, accounts, configs)) => { - let total = accounts.len(); - apply_language(&app, &settings.language); - state_for_backup_import.borrow_mut().account_presentation = - AccountPresentationSettings::from_settings(&settings); - update_connected_accounts( - &app, - &state_for_backup_import, - &runtime_for_backup_import, - accounts, - configs, - ); - refresh_rows_only(&app, &state_for_backup_import, &runtime_for_backup_import); - app.set_theme_mode( - match settings.theme.as_str() { - "carbon" | "dark" => "dark", - "snow" | "light" => "light", - _ => "system", - } - .into(), - ); - app.set_monochrome_sidebar_icons(settings.monochrome_sidebar_icons); - app.global::() - .set_preference(settings.message_appearance.as_str().into()); - app.set_show_avatars(settings.show_avatars); - app.set_show_account_markers(settings.show_account_badges); - crate::window_chrome::apply_preference( - &app, - crate::window_chrome::WindowFrame::parse(&settings.window_frame), - ); - app.set_workspace_layout(settings.workspace_layout.clone().into()); - app.set_workspace_list_pane_width( - normalized_workspace_list_pane_width(settings.workspace_list_pane_width) - as f32, - ); - app.set_notifications_enabled(settings.notifications_enabled); - app.set_notification_sound_enabled(settings.sound_enabled); - app.set_notification_scope(settings.notification_scope.clone().into()); - app.set_sync_interval_minutes(settings.sync_interval_minutes as i32); - app.set_mark_read_on_open(settings.mark_read_on_open); - app.set_collect_outgoing_contacts(settings.collect_outgoing_contacts); - app.set_collect_incoming_contacts(settings.collect_incoming_contacts); - app.set_contact_suggest_all_accounts(settings.contact_suggest_all_accounts); - app.set_suggest_learned_contacts(settings.suggest_learned_contacts); - state_for_backup_import.borrow_mut().mark_read_on_open = - settings.mark_read_on_open; - app.set_remote_images_enabled( - settings.load_remote_images && cfg!(feature = "remote-content"), - ); - #[cfg(not(any(mobile, feature = "flatpak")))] - app.set_close_to_tray(settings.close_to_tray); - #[cfg(any(mobile, feature = "flatpak"))] - app.set_close_to_tray(false); - app.set_sync_status(if imported == 0 { - UiMessage::detail( - "Backup imported. All {} account configurations already existed.", - total, - ) - } else { - UiMessage::detail( - "Imported {} account configurations. Open Accounts and sign in to each one.", - imported, - ) - }); - } - Err(error) => { - app.set_sync_status(UiMessage::detail("Could not import backup: {}", error)) - } - } + }; + import_account_backup_document(&app, document.path(), &core, &state, &runtime); + }); }); let (data_reset_raw_tx, data_reset_rx) = bounded_ui_channel::>(); @@ -1322,4 +1602,52 @@ mod tests { }); assert!(portable_account_colors(&outside, &emails).is_err()); } + + #[test] + fn snapshot_archive_keeps_the_tree_and_skips_symlinks() { + let staging = tempfile::tempdir().unwrap(); + let snapshot = staging.path().join("snapshot"); + std::fs::create_dir_all(snapshot.join("file_transfers")).unwrap(); + std::fs::write(snapshot.join("mail.sqlite3"), b"mail").unwrap(); + std::fs::write(snapshot.join("file_transfers/part"), b"part").unwrap(); + let outside = staging.path().join("outside"); + std::fs::write(&outside, b"private").unwrap(); + #[cfg(unix)] + std::os::unix::fs::symlink(&outside, snapshot.join("link")).unwrap(); + let archive = staging.path().join("snapshot.zip"); + + archive_snapshot_directory(&snapshot, &archive, u64::MAX).unwrap(); + + let mut zip = zip::ZipArchive::new(std::fs::File::open(&archive).unwrap()).unwrap(); + let mut names = zip + .file_names() + .map(|name| name.unwrap().into_owned()) + .collect::>(); + names.sort(); + assert_eq!( + names, + ["file_transfers/", "file_transfers/part", "mail.sqlite3"] + ); + let mut contents = String::new(); + std::io::Read::read_to_string(&mut zip.by_name("mail.sqlite3").unwrap(), &mut contents) + .unwrap(); + assert_eq!(contents, "mail"); + } + + #[test] + fn snapshot_archive_refuses_stores_over_the_transfer_limit() { + let staging = tempfile::tempdir().unwrap(); + let snapshot = staging.path().join("snapshot"); + std::fs::create_dir_all(&snapshot).unwrap(); + std::fs::write(snapshot.join("mail.sqlite3"), [0; 64]).unwrap(); + let archive = staging.path().join("snapshot.zip"); + + let error = archive_snapshot_directory(&snapshot, &archive, 63).unwrap_err(); + + assert!(error.contains("64 B"), "{error}"); + assert!(!archive.exists()); + // Headers alone push an archive of a store at the limit past it. + assert!(archive_snapshot_directory(&snapshot, &archive, 64).is_err()); + archive_snapshot_directory(&snapshot, &archive, 4096).unwrap(); + } } diff --git a/src/documents.rs b/src/documents.rs index 884839a8..e5ca3d54 100644 --- a/src/documents.rs +++ b/src/documents.rs @@ -39,9 +39,76 @@ impl Drop for DocumentRequestGuard { (std::mem::replace(&mut self.0, Box::new(|| {})))(); } } +/// How a desktop file dialog presents one request. Hosts whose system picker +/// has no title or file-type filter ignore it. +#[derive(Clone, Default)] +pub struct DocumentDialog { + pub title: String, + /// Filter name and the extensions it offers. + pub filter: Option<(String, &'static [&'static str])>, +} +impl DocumentDialog { + pub fn titled(title: impl Into) -> Self { + Self { + title: title.into(), + filter: None, + } + } +} pub trait DocumentProvider: Send + Sync { fn import(&self) -> DocumentFuture>; fn export(&self, path: PathBuf, name: String) -> DocumentFuture; + fn import_with(&self, _dialog: DocumentDialog) -> DocumentFuture> { + self.import() + } + fn export_with( + &self, + path: PathBuf, + name: String, + _dialog: DocumentDialog, + ) -> DocumentFuture { + self.export(path, name) + } + /// Imports the documents for one user action (compose attachments). The + /// default opens a single-document picker once; hosts with a multi-select + /// picker override it. `limit` bounds how many copies a host makes; + /// hosts that return source paths need not apply it. + fn import_many( + &self, + _limit: usize, + dialog: DocumentDialog, + ) -> DocumentFuture> { + let import = self.import_with(dialog); + Box::pin(async move { Ok(import.await?.into_iter().collect()) }) + } +} +/// Runs one document request and hands its result to `on_ready` on the UI +/// thread. Providers rely on Tokio timers and file I/O, so the request runs on +/// the runtime while the event loop keeps going, which desktop dialogs need. +/// Every picker and export goes through here, so a host that must keep the +/// presentation thread idle while its picker covers the window changes only +/// this function. +pub(crate) fn run_request( + runtime: &tokio::runtime::Runtime, + request: DocumentFuture, + on_ready: impl FnOnce(Result) + 'static, +) { + let request = runtime.spawn(request); + let abort = request.abort_handle(); + let on_ready = std::rc::Rc::new(std::cell::Cell::new(Some(on_ready))); + let pending = std::rc::Rc::clone(&on_ready); + let spawned = slint::spawn_local(async move { + let result = request.await.unwrap_or_else(|error| Err(error.to_string())); + if let Some(on_ready) = pending.take() { + on_ready(result); + } + }); + if let Err(error) = spawned { + abort.abort(); + if let Some(on_ready) = on_ready.take() { + on_ready(Err(error.to_string())); + } + } } /// Desktop file dialogs. Mobile hosts inject their own provider through /// `PlatformContext::app_private`. @@ -60,24 +127,56 @@ pub(crate) fn safe_name(name: &str) -> String { #[cfg(desktop)] struct DesktopDocuments; #[cfg(desktop)] +impl DocumentDialog { + fn file_dialog(&self) -> rfd::AsyncFileDialog { + let dialog = rfd::AsyncFileDialog::new().set_title(&self.title); + match &self.filter { + Some((name, extensions)) => dialog.add_filter(name, extensions), + None => dialog, + } + } +} +#[cfg(desktop)] impl DocumentProvider for DesktopDocuments { fn import(&self) -> DocumentFuture> { - Box::pin(async { - Ok(rfd::AsyncFileDialog::new() - .set_title("Upload file") + self.import_with(DocumentDialog::titled("Upload file")) + } + fn export(&self, path: PathBuf, name: String) -> DocumentFuture { + self.export_with(path, name, DocumentDialog::titled("Download file")) + } + fn import_with(&self, dialog: DocumentDialog) -> DocumentFuture> { + Box::pin(async move { + Ok(dialog + .file_dialog() .pick_file() .await .map(|f| ImportedDocument::source(f.path().to_owned()))) }) } - fn export(&self, path: PathBuf, name: String) -> DocumentFuture { + fn import_many( + &self, + _limit: usize, + dialog: DocumentDialog, + ) -> DocumentFuture> { Box::pin(async move { - let Some(file) = rfd::AsyncFileDialog::new() - .set_title("Download file") - .set_file_name(name) - .save_file() + Ok(dialog + .file_dialog() + .pick_files() .await - else { + .unwrap_or_default() + .into_iter() + .map(|file| ImportedDocument::source(file.path().to_owned())) + .collect()) + }) + } + fn export_with( + &self, + path: PathBuf, + name: String, + dialog: DocumentDialog, + ) -> DocumentFuture { + Box::pin(async move { + let Some(file) = dialog.file_dialog().set_file_name(name).save_file().await else { return Ok(false); }; flectar_mail_core::files::save_cached_file(&path, file.path()) @@ -156,6 +255,60 @@ mod tests { assert_eq!(cancelled.load(Ordering::SeqCst), 1); } + struct FakeDocuments { + directory: PathBuf, + imports: AtomicUsize, + cancel_after: usize, + } + + impl DocumentProvider for FakeDocuments { + fn import(&self) -> DocumentFuture> { + let seen = self.imports.fetch_add(1, Ordering::SeqCst); + let cancel = seen >= self.cancel_after; + let path = self.directory.join(format!("import-{seen}")); + Box::pin(async move { + if cancel { + return Ok(None); + } + std::fs::write(&path, b"attachment").unwrap(); + Ok(Some(ImportedDocument::temporary(path, |path| { + let _ = std::fs::remove_file(path); + }))) + }) + } + fn export(&self, _path: PathBuf, _name: String) -> DocumentFuture { + Box::pin(async { Ok(false) }) + } + } + + #[tokio::test] + async fn import_many_uses_one_picker_request() { + let directory = tempfile::tempdir().unwrap(); + let provider = Arc::new(FakeDocuments { + directory: directory.path().to_owned(), + imports: AtomicUsize::new(0), + cancel_after: usize::MAX, + }); + let documents = provider.import_many(20, DocumentDialog::default()).await.unwrap(); + assert_eq!(documents.len(), 1); + assert_eq!(provider.imports.load(Ordering::SeqCst), 1); + drop(documents); + assert_eq!(std::fs::read_dir(directory.path()).unwrap().count(), 0); + } + + #[tokio::test] + async fn import_many_returns_empty_when_the_picker_is_cancelled() { + let directory = tempfile::tempdir().unwrap(); + let provider = Arc::new(FakeDocuments { + directory: directory.path().to_owned(), + imports: AtomicUsize::new(0), + cancel_after: 0, + }); + let documents = provider.import_many(20, DocumentDialog::default()).await.unwrap(); + assert!(documents.is_empty()); + assert_eq!(provider.imports.load(Ordering::SeqCst), 1); + } + #[test] fn desktop_source_survives_document_drop() { let dir = tempfile::tempdir().unwrap(); diff --git a/src/main.rs b/src/main.rs index 541a902e..49e4e327 100644 --- a/src/main.rs +++ b/src/main.rs @@ -132,6 +132,7 @@ const SENDER_AVATAR_REGULAR_SIDE: f32 = 38.0; const ACCOUNT_AVATAR_SMALL_SIDE: f32 = 22.0; const ACCOUNT_AVATAR_REGULAR_SIDE: f32 = 38.0; const MAX_COMPOSE_ATTACHMENT_BYTES: u64 = 25 * 1024 * 1024; +const MAX_COMPOSE_ATTACHMENT_FILES: usize = 20; fn favicon_retry_delay(attempts: u8) -> Duration { let exponent = attempts.saturating_sub(1).min(6); @@ -326,21 +327,15 @@ fn reconcile_model_rows_by( } } -#[cfg(desktop)] -fn pick_compose_attachment_paths(title: String) -> Option> { - rfd::FileDialog::new().set_title(title).pick_files() -} - -#[cfg(mobile)] -fn pick_compose_attachment_paths(_title: String) -> Option> { - None -} - -#[derive(Clone, Debug)] +#[derive(Clone)] struct ComposeFile { path: PathBuf, filename: String, size: u64, + /// Host-owned import copy behind `path`. Removing the attachment or + /// clearing the composer releases it; drafts and sends stage the file in + /// Core first. + _import: Option>, } struct FaviconUpdate { @@ -1402,7 +1397,10 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> }); theme::register_theme_utilities(&app); app.set_print_supported(!cfg!(mobile)); - app.set_document_apis_supported(!cfg!(mobile)); + // Every host provides a document provider now: desktop dialogs on desktop, + // the system picker on mobile. The document entry points (compose + // attachments, account backups, database snapshots) stay available. + app.set_document_apis_supported(true); renderer_preferences::initialize_step(use_wgpu, || app.show())?; startup_metrics.emit( "window_shown", @@ -5206,6 +5204,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> register_settings_preference_callbacks(&app, &state, &runtime); let compose_files = Rc::new(RefCell::new(Vec::::new())); + let compose_session = Rc::new(Cell::new(0u64)); let compose_document = Rc::new(RefCell::new(RichComposeDocument::default())); let compose_editor = Rc::new(RefCell::new(LazyComposeEditor::default())); let compose_contacts = Rc::new(RefCell::new( @@ -5459,6 +5458,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> let state_for_message_compose = Rc::clone(&state); let runtime_for_message_compose = Rc::clone(&runtime); let files_for_message_compose = Rc::clone(&compose_files); + let session_for_message_compose = Rc::clone(&compose_session); let document_for_message_compose = Rc::clone(&compose_document); let editor_for_message_compose = Rc::clone(&compose_editor); let contacts_for_message_compose = Rc::clone(&compose_contacts); @@ -5544,6 +5544,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> clear_compose( &app, &files_for_message_compose, + &session_for_message_compose, &document_for_message_compose, &editor_for_message_compose, &contacts_for_message_compose, @@ -5567,6 +5568,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> path, filename: attachment.filename.clone(), size, + _import: None, } }) .collect::>(); @@ -5644,6 +5646,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> clear_compose( &app, &files_for_message_compose, + &session_for_message_compose, &document_for_message_compose, &editor_for_message_compose, &contacts_for_message_compose, @@ -5683,6 +5686,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> let app_weak = app.as_weak(); let files_for_close = Rc::clone(&compose_files); + let session_for_close = Rc::clone(&compose_session); let document_for_close = Rc::clone(&compose_document); let editor_for_close = Rc::clone(&compose_editor); let contacts_for_close = Rc::clone(&compose_contacts); @@ -5696,6 +5700,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> clear_compose( &app, &files_for_close, + &session_for_close, &document_for_close, &editor_for_close, &contacts_for_close, @@ -5706,72 +5711,104 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> let app_weak = app.as_weak(); let files_for_picker = Rc::clone(&compose_files); + let session_for_picker = Rc::clone(&compose_session); + let documents_for_picker = platform.documents.clone(); + let runtime_for_picker = Rc::clone(&runtime); + let picking_for_picker = Rc::new(Cell::new(false)); app.on_pick_compose_attachments(move || { let Some(app) = app_weak.upgrade() else { return; }; - let Some(paths) = pick_compose_attachment_paths( - translated(&app, &UiMessage::plain("Attach files to this email")).to_string(), - ) else { - #[cfg(mobile)] - { - app.set_compose_notice(UiMessage::plain( - "The platform host does not provide a document picker.", - )); - app.set_compose_notice_is_error(true); - } + if picking_for_picker.replace(true) { return; - }; - - let mut compose_files = files_for_picker.borrow_mut(); - let mut total_size = compose_files.iter().map(|file| file.size).sum::(); - let mut added = 0usize; - let mut skipped = 0usize; - for path in paths { - let path = std::fs::canonicalize(&path).unwrap_or(path); - if compose_files.iter().any(|file| file.path == path) { - skipped += 1; - continue; - } - let Ok(metadata) = std::fs::metadata(&path) else { - skipped += 1; - continue; + } + let dialog = documents::DocumentDialog::titled(translated( + &app, + &UiMessage::plain("Attach files to this email"), + )); + let request = documents_for_picker.import_many(MAX_COMPOSE_ATTACHMENT_FILES, dialog); + let app_weak = app.as_weak(); + let files = Rc::clone(&files_for_picker); + let session = Rc::clone(&session_for_picker); + let picked_in = session.get(); + let picking = Rc::clone(&picking_for_picker); + documents::run_request(&runtime_for_picker, request, move |imported| { + picking.set(false); + let Some(app) = app_weak.upgrade() else { + return; }; - if !metadata.is_file() - || total_size.saturating_add(metadata.len()) > MAX_COMPOSE_ATTACHMENT_BYTES - { - skipped += 1; - continue; + // The composer was sent, discarded or replaced while the picker + // was open; dropping the documents releases their copies. + if session.get() != picked_in { + return; } - let Some(filename) = path.file_name().map(|name| name.to_string_lossy().into_owned()) - else { - skipped += 1; - continue; + let documents = match imported { + Ok(documents) => documents, + Err(error) => { + tracing::warn!(%error, "compose attachment picker failed"); + app.set_compose_notice(UiMessage::detail( + "Could not add attachments: {}", + error, + )); + app.set_compose_notice_is_error(true); + return; + } }; - total_size += metadata.len(); - compose_files.push(ComposeFile { - path, - filename, - size: metadata.len(), - }); - added += 1; - } - apply_compose_files(&app, &compose_files); - if skipped == 0 { - app.set_compose_notice(if added == 1 { - UiMessage::plain("Added 1 attachment.") + if documents.is_empty() { + return; + } + let mut compose_files = files.borrow_mut(); + let mut total_size = compose_files.iter().map(|file| file.size).sum::(); + let mut added = 0usize; + let mut skipped = 0usize; + for document in documents { + let path = std::fs::canonicalize(document.path()) + .unwrap_or_else(|_| document.path().to_path_buf()); + if compose_files.iter().any(|file| file.path == path) { + skipped += 1; + continue; + } + let Ok(metadata) = std::fs::metadata(&path) else { + skipped += 1; + continue; + }; + if !metadata.is_file() + || total_size.saturating_add(metadata.len()) > MAX_COMPOSE_ATTACHMENT_BYTES + { + skipped += 1; + continue; + } + let Some(filename) = path.file_name().map(|name| name.to_string_lossy().into_owned()) + else { + skipped += 1; + continue; + }; + total_size += metadata.len(); + compose_files.push(ComposeFile { + path, + filename, + size: metadata.len(), + _import: Some(Rc::new(document)), + }); + added += 1; + } + apply_compose_files(&app, &compose_files); + if skipped == 0 { + app.set_compose_notice(if added == 1 { + UiMessage::plain("Added 1 attachment.") + } else { + UiMessage::detail("Added {} attachments.", added) + }); + app.set_compose_notice_is_error(false); } else { - UiMessage::detail("Added {} attachments.", added) - }); - app.set_compose_notice_is_error(false); - } else { - app.set_compose_notice(UiMessage::arguments( - "Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB total limit).", - added, - skipped, - )); - app.set_compose_notice_is_error(added == 0); - } + app.set_compose_notice(UiMessage::arguments( + "Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB total limit).", + added, + skipped, + )); + app.set_compose_notice_is_error(added == 0); + } + }); }); let app_weak = app.as_weak(); @@ -6040,6 +6077,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> let state_for_compose = Rc::clone(&state); let runtime_for_compose = Rc::clone(&runtime); let files_for_save = Rc::clone(&compose_files); + let session_for_save = Rc::clone(&compose_session); let document_for_save = Rc::clone(&compose_document); let editor_for_save = Rc::clone(&compose_editor); let contacts_for_save = Rc::clone(&compose_contacts); @@ -6164,6 +6202,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> clear_compose( &app, &files_for_save, + &session_for_save, &document_for_save, &editor_for_save, &contacts_for_save, @@ -6187,6 +6226,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> let state_for_send_result = Rc::clone(&state); let runtime_for_send_result = Rc::clone(&runtime); let files_for_send_result = Rc::clone(&compose_files); + let session_for_send_result = Rc::clone(&compose_session); let document_for_send_result = Rc::clone(&compose_document); let editor_for_send_result = Rc::clone(&compose_editor); let contacts_for_send_result = Rc::clone(&compose_contacts); @@ -6207,6 +6247,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> clear_compose( &app, &files_for_send_result, + &session_for_send_result, &document_for_send_result, &editor_for_send_result, &contacts_for_send_result, @@ -6888,6 +6929,7 @@ pub fn run(platform: PlatformContext) -> Result<(), Box> register_data_management_callbacks( &app, &platform.paths, + &platform.documents, &state, &runtime, &ui_task_tx, diff --git a/ui/app.slint b/ui/app.slint index ee9d58db..b4239122 100644 --- a/ui/app.slint +++ b/ui/app.slint @@ -286,8 +286,6 @@ export global I18n { if (key == "Import Flectar Mail account backup") { return @tr("Import Flectar Mail account backup"); } if (key == "Flectar Mail backup") { return @tr("Flectar Mail backup"); } if (key == "Choose where to export the database snapshot") { return @tr("Choose where to export the database snapshot"); } - if (key == "The platform host does not provide document export.") { return @tr("The platform host does not provide document export."); } - if (key == "The platform host does not provide document import.") { return @tr("The platform host does not provide document import."); } if (key == "Backup exported. Passwords and OAuth tokens were not included.") { return @tr("Backup exported. Passwords and OAuth tokens were not included."); } if (key == "Could not export backup: {}") { return @tr("Could not export backup: {}", argument); } if (key == "Local data storage is unavailable. Retry startup.") { return @tr("Local data storage is unavailable. Retry startup."); } @@ -429,7 +427,7 @@ export global I18n { ui-message-compose(key, argument, second-argument, third-argument) => { if (key == "Attach files to this email") { return @tr("Attach files to this email"); } - if (key == "The platform host does not provide a document picker.") { return @tr("The platform host does not provide a document picker."); } + if (key == "Could not add attachments: {}") { return @tr("Could not add attachments: {}", argument); } if (key == "Added 1 attachment.") { return @tr("Added 1 attachment."); } if (key == "Added {} attachments.") { return @tr("Added {} attachments.", argument); } if (key == "Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB total limit).") { return @tr("Attachments added: {}. Skipped: {} (duplicate, unreadable, or over the 25 MB total limit).", argument, second-argument); }