From 174d56e7b453d44a4c48e5d3c8a82e146094fc5f Mon Sep 17 00:00:00 2001 From: rustmailer Date: Fri, 8 May 2026 01:00:46 +0800 Subject: [PATCH] feat: add manual download and cancel download for email accounts --- Cargo.lock | 1 + crates/core/Cargo.toml | 1 + crates/core/src/account/migration.rs | 2 +- .../src/cache/imap/download/download_type.rs | 44 +++--- crates/core/src/cache/imap/download/mod.rs | 15 +- crates/core/src/cache/imap/task.rs | 129 ++++++++++++++++-- crates/core/src/context/controller.rs | 13 +- crates/core/src/context/executors.rs | 6 +- crates/server/src/rest/api/account.rs | 67 ++++++++- web/src/api/account/api.ts | 11 ++ .../components/data-table-row-actions.tsx | 54 +++++++- .../components/running-state-dialog.tsx | 2 +- web/src/locales/ar.json | 6 + web/src/locales/da.json | 6 + web/src/locales/de.json | 6 + web/src/locales/en.json | 6 + web/src/locales/es.json | 6 + web/src/locales/fi.json | 6 + web/src/locales/fr.json | 6 + web/src/locales/it.json | 6 + web/src/locales/jp.json | 6 + web/src/locales/ko.json | 6 + web/src/locales/nl.json | 6 + web/src/locales/no.json | 6 + web/src/locales/pl.json | 6 + web/src/locales/pt.json | 6 + web/src/locales/ru.json | 6 + web/src/locales/sv.json | 6 + web/src/locales/zh-tw.json | 6 + web/src/locales/zh.json | 6 + 30 files changed, 404 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fb36f1..d1031f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -529,6 +529,7 @@ dependencies = [ "ring", "rustls", "rustls-pki-types", + "scopeguard", "serde", "serde_json", "snafu", diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index fd891a5..65cbcab 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -70,3 +70,4 @@ tracing-log.workspace = true tokio-util.workspace = true whichlang = "0.1.1" deunicode = "1.6.2" +scopeguard = "1.2.0" diff --git a/crates/core/src/account/migration.rs b/crates/core/src/account/migration.rs index b276ea3..7c1d66e 100644 --- a/crates/core/src/account/migration.rs +++ b/crates/core/src/account/migration.rs @@ -318,7 +318,7 @@ impl AccountV4 { if matches!(cloned.account_type, AccountType::IMAP) { DOWNLOAD_CONTROLLER - .trigger_start(cloned.id, cloned.email.clone()) + .trigger_schedule(cloned.id, cloned.email.clone()) .await; } Ok(cloned) diff --git a/crates/core/src/cache/imap/download/download_type.rs b/crates/core/src/cache/imap/download/download_type.rs index 346413e..b0fcc13 100644 --- a/crates/core/src/cache/imap/download/download_type.rs +++ b/crates/core/src/cache/imap/download/download_type.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use crate::{ + utc_now, { account::{ migration::AccountModel, @@ -24,7 +25,6 @@ use crate::{ }, error::BichonResult, }, - utc_now, }; #[derive(Clone, Debug, Eq, PartialEq)] @@ -34,27 +34,33 @@ pub enum DownloadTask { Idle, } -pub async fn decide_next_download_task(account: &AccountModel) -> BichonResult { - Ok(match DownloadState::get(account.id).await? { - Some(state) => { - let should_trigger = should_trigger_next_download( - state.last_trigger_at, - state.last_finished_at.unwrap_or(0), - account.download_interval_min.unwrap(), - ); - - if should_trigger { - DownloadState::start_new_session(account.id, TriggerType::Scheduled).await?; - DownloadTask::TraceFetch - } else { - DownloadTask::Idle - } - } +pub async fn decide_next_download_task( + account: &AccountModel, + trigger_type: TriggerType, +) -> BichonResult { + let state = match DownloadState::get(account.id).await? { None => { DownloadState::init(account.id).await?; - DownloadTask::FullFetch + return Ok(DownloadTask::FullFetch); } - }) + Some(s) => s, + }; + + let should_start = match trigger_type { + TriggerType::Manual => true, + TriggerType::Scheduled => should_trigger_next_download( + state.last_trigger_at, + state.last_finished_at.unwrap_or(0), + account.download_interval_min.unwrap_or(60), + ), + }; + + if should_start { + DownloadState::start_new_session(account.id, trigger_type).await?; + Ok(DownloadTask::TraceFetch) + } else { + Ok(DownloadTask::Idle) + } } fn should_trigger_next_download( diff --git a/crates/core/src/cache/imap/download/mod.rs b/crates/core/src/cache/imap/download/mod.rs index 0cfe354..d68ef39 100644 --- a/crates/core/src/cache/imap/download/mod.rs +++ b/crates/core/src/cache/imap/download/mod.rs @@ -19,33 +19,34 @@ use crate::{ account::{ migration::{AccountModel, AccountType}, - state::{DownloadState, DownloadStatus}, + state::{DownloadState, DownloadStatus, TriggerType}, }, - cache::imap::{mailbox::MailBox, download::flow::FetchDirection}, + cache::imap::{download::flow::FetchDirection, mailbox::MailBox}, error::BichonResult, imap::executor::ImapExecutor, }; +use download_folders::get_download_folders; +use download_type::{decide_next_download_task, DownloadTask}; use flow::reconcile_mailboxes; use rebuild::{rebuild_cache, rebuild_cache_by_date}; use std::time::Instant; -use download_folders::get_download_folders; -use download_type::{decide_next_download_task, DownloadTask}; use tokio_util::sync::CancellationToken; use tracing::{debug, warn}; -pub mod flow; -pub mod rebuild; pub mod download_folders; pub mod download_type; +pub mod flow; +pub mod rebuild; pub async fn process_imap_download( account: &AccountModel, token: CancellationToken, + trigger_type: TriggerType, ) -> BichonResult<()> { assert_eq!(account.account_type, AccountType::IMAP); let start_time = Instant::now(); let account_id = account.id; - let download_task = decide_next_download_task(account).await?; + let download_task = decide_next_download_task(account, trigger_type).await?; if matches!(download_task, DownloadTask::Idle) { return Ok(()); } diff --git a/crates/core/src/cache/imap/task.rs b/crates/core/src/cache/imap/task.rs index 975a1db..e9544e8 100644 --- a/crates/core/src/cache/imap/task.rs +++ b/crates/core/src/cache/imap/task.rs @@ -17,37 +17,56 @@ // along with this program. If not, see . use crate::account::entity::AuthType; -use crate::account::state::DownloadState; +use crate::account::state::{DownloadState, TriggerType}; use crate::cache::imap::download::process_imap_download; use crate::common::periodic::{PeriodicTask, TaskHandle}; +use crate::error::code::ErrorCode; use crate::oauth2::token::OAuth2AccessToken; -use crate::utc_now; use crate::{account::migration::AccountModel, error::BichonResult}; -use std::collections::HashMap; +use crate::{raise_error, utc_now}; +use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicI64, Ordering}; use std::{sync::LazyLock, time::Duration}; use tokio::sync::Mutex; +use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; use tracing::{error, info, warn}; static _DESCRIPTION: &str = "This task periodically synchronizes mailbox data for a specified account, ensuring that all local data is up-to-date."; const TASK_INTERVAL: Duration = Duration::from_secs(10); -pub static SYNC_TASKS: LazyLock = LazyLock::new(AccountSyncTask::new); +pub static SYNC_TASKS: LazyLock = LazyLock::new(AccountDownTask::new); static LAST_WARN_TIME: AtomicI64 = AtomicI64::new(0); const WARN_INTERVAL_MS: i64 = 600_000; -pub struct AccountSyncTask { +pub struct AccountDownTask { tasks: Mutex>>, + manual_tasks: Mutex, CancellationToken)>>, + busy_accounts: Mutex>, } -impl AccountSyncTask { +impl AccountDownTask { pub fn new() -> Self { Self { tasks: Mutex::new(Some(HashMap::new())), + manual_tasks: Mutex::new(HashMap::new()), + busy_accounts: Mutex::new(HashSet::new()), } } - pub async fn start_account_download_task(&self, account_id: u64, email: String) { + async fn set_busy(&self, account_id: u64, is_busy: bool) { + let mut guard = self.busy_accounts.lock().await; + if is_busy { + guard.insert(account_id); + } else { + guard.remove(&account_id); + } + } + + async fn is_busy(&self, account_id: u64) -> bool { + self.busy_accounts.lock().await.contains(&account_id) + } + + pub async fn start_download_task(&self, account_id: u64, email: String) { let task_name = format!("account-download-task-{}-{}", account_id, &email); let periodic_task = PeriodicTask::new(&task_name); @@ -58,6 +77,28 @@ impl AccountSyncTask { let account_id = param.unwrap(); let internal_token = task_token.clone(); Box::pin(async move { + if SYNC_TASKS.is_manual_running(account_id).await { + info!( + "Account {}: Scheduled task skipped (Manual task is running).", + account_id + ); + return Ok(()); + } + + if SYNC_TASKS.is_busy(account_id).await { + warn!( + "Account {}: Scheduled task skipped (Previous sync still active).", + account_id + ); + return Ok(()); + } + + SYNC_TASKS.set_busy(account_id, true).await; + let _busy_guard = scopeguard::guard(account_id, |id| { + tokio::spawn(async move { + SYNC_TASKS.set_busy(id, false).await; + }); + }); let account = AccountModel::async_get(account_id).await.ok(); match account { Some(account) => { @@ -82,7 +123,13 @@ impl AccountSyncTask { } } } - if let Err(e) = process_imap_download(&account, internal_token).await { + if let Err(e) = process_imap_download( + &account, + internal_token, + TriggerType::Scheduled, + ) + .await + { DownloadState::append_session_error( account.id, format!("error in account download task: {:#?}", e), @@ -150,4 +197,70 @@ impl AccountSyncTask { info!("Shutdown: All download tasks processed."); } } + + pub async fn start_manual_task(&self, account_id: u64) -> BichonResult<()> { + { + if self.is_manual_running(account_id).await { + return Err(raise_error!( + "Manual task already running.".into(), + ErrorCode::Forbidden + )); + } + if self.is_busy(account_id).await { + return Err(raise_error!( + "The background synchronization is currently active. Please try again in a few seconds.".into(), + ErrorCode::Forbidden + )); + } + } + + let cancel_token = CancellationToken::new(); + let token_clone = cancel_token.clone(); + let handle = tokio::spawn(async move { + SYNC_TASKS.set_busy(account_id, true).await; + let _cleanup = scopeguard::guard(account_id, |id| { + tokio::spawn(async move { + SYNC_TASKS.set_busy(id, false).await; + let mut guard = SYNC_TASKS.manual_tasks.lock().await; + guard.remove(&id); + }); + }); + if token_clone.is_cancelled() { + return; + } + let account = match AccountModel::async_get(account_id).await { + Ok(acc) => acc, + Err(e) => { + error!("Failed to fetch account {}: {:?}", account_id, e); + return; + } + }; + + if let Err(e) = process_imap_download(&account, token_clone, TriggerType::Manual).await + { + error!("Manual download failed for {}: {:?}", account_id, e); + let error_msg = format!("error in account download task: {:#?}", e); + let _ = DownloadState::append_session_error(account.id, error_msg).await; + } + }); + { + let mut guard = self.manual_tasks.lock().await; + guard.insert(account_id, (handle, cancel_token)); + } + + Ok(()) + } + + pub async fn cancel_manual_task(&self, account_id: u64) { + let mut guard = self.manual_tasks.lock().await; + if let Some((handle, token)) = guard.remove(&account_id) { + token.cancel(); + let _ = handle.await; + } + } + + pub async fn is_manual_running(&self, account_id: u64) -> bool { + let guard = self.manual_tasks.lock().await; + guard.contains_key(&account_id) + } } diff --git a/crates/core/src/context/controller.rs b/crates/core/src/context/controller.rs index f63e531..eb2a012 100644 --- a/crates/core/src/context/controller.rs +++ b/crates/core/src/context/controller.rs @@ -35,11 +35,10 @@ impl DownloadController { tokio::spawn(async move { while let Some((account_id, email)) = rx.recv().await { match Self::start_download(account_id, email.clone()).await { - Ok(Some(_)) => {} - Ok(None) => {} + Ok(_) => {} Err(err) => { error!( - "Failed to prepare and start download of account {{{}-{}}}, error: {:#?}", + "Failed to prepare and start scheduled download of account {{{}-{}}}, error: {:#?}", &account_id, &email, err ); } @@ -51,7 +50,7 @@ impl DownloadController { } /// Trigger synchronization for a specific account - pub async fn trigger_start(&self, account_id: u64, email: String) { + pub async fn trigger_schedule(&self, account_id: u64, email: String) { if let Err(e) = self.channel.send((account_id, email)).await { error!( "Failed to trigger download for account={{{}}}, error: {:?}", @@ -60,13 +59,13 @@ impl DownloadController { } } - async fn start_download(account_id: u64, email: String) -> BichonResult> { + async fn start_download(account_id: u64, email: String) -> BichonResult<()> { info!( "Account download starting for account: {}-{}.", account_id, email ); - SYNC_TASKS.start_account_download_task(account_id, email).await; + SYNC_TASKS.start_download_task(account_id, email).await; tokio::time::sleep(Duration::from_millis(100)).await; - Ok(Some(())) + Ok(()) } } diff --git a/crates/core/src/context/executors.rs b/crates/core/src/context/executors.rs index aad48e2..b9c64fc 100644 --- a/crates/core/src/context/executors.rs +++ b/crates/core/src/context/executors.rs @@ -35,7 +35,7 @@ pub struct BichonContext { impl Initialize for BichonContext { async fn initialize() -> BichonResult<()> { - BICHON_CONTEXT.start_account_syncers().await + BICHON_CONTEXT.start_account_downloader().await } } @@ -49,7 +49,7 @@ impl BichonContext { utc_now!() - self.start_at } - pub async fn start_account_syncers(&self) -> BichonResult<()> { + pub async fn start_account_downloader(&self) -> BichonResult<()> { let accounts = AccountModel::list_all().await?; let active_accounts: Vec = accounts .into_iter() @@ -66,7 +66,7 @@ impl BichonContext { ); for account in active_accounts { DOWNLOAD_CONTROLLER - .trigger_start(account.id, account.email) + .trigger_schedule(account.id, account.email) .await } diff --git a/crates/server/src/rest/api/account.rs b/crates/server/src/rest/api/account.rs index 87ef359..c9b1960 100644 --- a/crates/server/src/rest/api/account.rs +++ b/crates/server/src/rest/api/account.rs @@ -20,14 +20,17 @@ use crate::common::auth::WrappedContext; use crate::rest::api::ApiTags; use crate::rest::ApiResult; use bichon_core::account::grant::BatchAccountRoleRequest; -use bichon_core::account::migration::AccountModel; +use bichon_core::account::migration::{AccountModel, AccountType}; use bichon_core::account::payload::{ filter_accessible_accounts, AccountCreateRequest, AccountUpdateRequest, MinimalAccount, }; use bichon_core::account::state::DownloadState; use bichon_core::account::stats::AccountStats; use bichon_core::account::view::AccountResp; +use bichon_core::cache::imap::task::SYNC_TASKS; use bichon_core::common::paginated::{paginate_vec, DataPage}; +use bichon_core::error::code::ErrorCode; +use bichon_core::raise_error; use bichon_core::store::tantivy::envelope::ENVELOPE_MANAGER; use bichon_core::users::permissions::Permission; use bichon_core::users::UserModel; @@ -204,6 +207,68 @@ impl AccountApi { Ok(Json(state)) } + /// Start a manual download task for an account + #[oai( + path = "/accounts/:account_id/start-download", + method = "post", + operation_id = "accounts_start_download" + )] + async fn accounts_start_download( + &self, + /// The account ID to start download for + account_id: Path, + context: WrappedContext, + ) -> ApiResult<()> { + let account_id = account_id.0; + let account = AccountModel::check_account_exists(account_id).await?; + if !matches!(account.account_type, AccountType::IMAP) { + return Err(raise_error!( + format!("Manual download is not supported for '{:#?}' accounts. Only IMAP accounts are supported.", account.account_type), + ErrorCode::InvalidParameter + ))?; + } + context + .require_permission(Some(account_id), Permission::ACCOUNT_MANAGE) + .await?; + SYNC_TASKS.start_manual_task(account_id).await?; + Ok(()) + } + + /// Cancel a running manual download task + #[oai( + path = "/accounts/:account_id/cancel-download", + method = "post", + operation_id = "accounts_cancel_download" + )] + async fn accounts_cancel_download( + &self, + /// The account ID to cancel download for + account_id: Path, + context: WrappedContext, + ) -> ApiResult<()> { + let account_id = account_id.0; + let account = AccountModel::check_account_exists(account_id).await?; + + if !matches!(account.account_type, AccountType::IMAP) { + return Err(raise_error!( + "This operation is only supported for IMAP accounts.".into(), + ErrorCode::InvalidParameter + ))?; + } + context + .require_permission(Some(account_id), Permission::ACCOUNT_MANAGE) + .await?; + + if !SYNC_TASKS.is_manual_running(account_id).await { + return Err(raise_error!( + "No running manual task found for this account.".into(), + ErrorCode::ResourceNotFound + ))?; + } + SYNC_TASKS.cancel_manual_task(account_id).await; + Ok(()) + } + /// Get the stats of an account #[oai( path = "/accounts/:account_id/stats", diff --git a/web/src/api/account/api.ts b/web/src/api/account/api.ts index d6e3fd9..3130162 100644 --- a/web/src/api/account/api.ts +++ b/web/src/api/account/api.ts @@ -170,6 +170,17 @@ export const remove_account = async (account_id: number) => { return response.data; }; + +export const start_account_download = async (account_id: number) => { + const response = await axiosInstance.post(`api/v1/accounts/${account_id}/start-download`); + return response.data; +}; + +export const cancel_account_download = async (account_id: number) => { + const response = await axiosInstance.post(`api/v1/accounts/${account_id}/cancel-download`); + return response.data; +}; + export interface AutoConfigResult { imap: ServerConfig; oauth2?: OAuth2Config; diff --git a/web/src/features/accounts/components/data-table-row-actions.tsx b/web/src/features/accounts/components/data-table-row-actions.tsx index 9c55bfe..8607517 100644 --- a/web/src/features/accounts/components/data-table-row-actions.tsx +++ b/web/src/features/accounts/components/data-table-row-actions.tsx @@ -19,7 +19,7 @@ import { DotsHorizontalIcon } from '@radix-ui/react-icons' import { Row } from '@tanstack/react-table' -import { IconEdit, IconShieldLock, IconTrash } from '@tabler/icons-react' +import { IconEdit, IconPlayerPlay, IconPlayerStop, IconShieldLock, IconTrash } from '@tabler/icons-react' import { Button } from '@/components/ui/button' import { DropdownMenu, @@ -33,7 +33,8 @@ import { useAccountContext } from '../context' import { Mailbox, MessageSquareMore } from 'lucide-react' import { useTranslation } from 'react-i18next' import { useCurrentUser } from '@/hooks/use-current-user' -import { AccountModel } from '@/api/account/api' +import { AccountModel, cancel_account_download, start_account_download } from '@/api/account/api' +import { toast } from '@/hooks/use-toast' interface DataTableRowActionsProps { row: Row @@ -55,6 +56,35 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) { (account_type === 'IMAP' && hasPermission) || (account_type === 'IMAP' && hasReadPermission); + const showDownload = account_type === 'IMAP' && hasPermission; + + const handleStartDownload = async () => { + try { + await start_account_download(row.original.id); + toast({ title: t('accounts.downloadStarted') }); + } catch (error: any) { + toast({ + variant: "destructive", + title: t('accounts.downloadFailed'), + description: error.response?.data?.message || error.message + }); + } + } + + + const handleCancelDownload = async () => { + try { + await cancel_account_download(row.original.id); + toast({ title: t('accounts.downloadCancelled') }); + } catch (error: any) { + toast({ + variant: "destructive", + title: t('accounts.cancelFailed'), + description: error.response?.data?.message || error.message + }); + } + } + return ( <> @@ -68,6 +98,26 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) { + + {showDownload && ( + + {t('accounts.startDownload')} + + + + + )} + + + {showDownload && ( + + {t('accounts.cancelDownload')} + + + + + )} + {showDownload && } {hasPermission && { setCurrentRow(row.original) diff --git a/web/src/features/accounts/components/running-state-dialog.tsx b/web/src/features/accounts/components/running-state-dialog.tsx index 2125d60..9b1beb3 100644 --- a/web/src/features/accounts/components/running-state-dialog.tsx +++ b/web/src/features/accounts/components/running-state-dialog.tsx @@ -298,7 +298,7 @@ export function RunningStateDialog({ currentRow, open, onOpenChange }: Props) { {format(new Date(h.start_time), 'yyyy-MM-dd HH:mm:ss')} -
+
{Object.keys(h.folder_details).length} {t('accounts.runningState.folders')} diff --git a/web/src/locales/ar.json b/web/src/locales/ar.json index fa4a543..7e5ad65 100644 --- a/web/src/locales/ar.json +++ b/web/src/locales/ar.json @@ -104,6 +104,8 @@ "autoConfiguring": "جارٍ التكوين التلقائي...", "beforeRelative": "تنزيل رسائل البريد الإلكتروني القديمة فقط", "beforeRelativeValue": "تنزيل رسائل البريد الإلكتروني قبل {{value}} {{unit}} مضت", + "cancelDownload": "إلغاء التنزيل", + "cancelFailed": "فشل إلغاء مهمة التنزيل", "capabilities": "الإمكانيات", "chooseAuthMethod": "اختر طريقة المصادقة لـ IMAP.", "chooseEncryptionMethod": "اختر طريقة التشفير لـ IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "تنزيل جميع رسائل البريد الإلكتروني", "downloadBatchSize": "حجم دفعة التنزيل", "downloadBatchSizeDescription": "عدد الرسائل التي يتم جلبها لكل طلب IMAP", + "downloadCancelled": "تم إلغاء المهمة", + "downloadFailed": "فشل بدء مهمة التنزيل", "downloadInterval": "دورة التنزيل (بالدقائق)", "downloadIntervalPlaceholder": "أدخل الدقائق", "downloadScope": "استراتيجية التنزيل", "downloadScopeDescription": "اختر رسائل البريد الإلكتروني التي يجب فهرستها وتنزيلها.", + "downloadStarted": "بدأت مهمة التنزيل", "duration": "المدة", "edit": "تعديل", "email": "البريد الإلكتروني", @@ -255,6 +260,7 @@ "sinceFixed": "منذ تاريخ محدد", "sinceRelative": "تنزيل رسائل البريد الإلكتروني الأخيرة فقط", "sinceRelativeValue": "تنزيل رسائل البريد الإلكتروني من آخر", + "startDownload": "بدء التنزيل", "state": "الحالة", "status": "الحالة", "step": "الخطوة {{index}}", diff --git a/web/src/locales/da.json b/web/src/locales/da.json index 1cf31d0..d8c30e1 100644 --- a/web/src/locales/da.json +++ b/web/src/locales/da.json @@ -104,6 +104,8 @@ "autoConfiguring": "Konfigurerer automatisk...", "beforeRelative": "Download kun gamle e-mails", "beforeRelativeValue": "Download e-mails før {{value}} {{unit}} siden", + "cancelDownload": "Annuller download", + "cancelFailed": "Kunne ikke annullere download-opgave", "capabilities": "Funktioner", "chooseAuthMethod": "Vælg godkendelsesmetode til IMAP.", "chooseEncryptionMethod": "Vælg krypteringsmetode til IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Download alle e-mails", "downloadBatchSize": "Download batchstørrelse", "downloadBatchSizeDescription": "Antal beskeder hentet pr. IMAP-anmodning", + "downloadCancelled": "Opgave annulleret", + "downloadFailed": "Kunne ikke starte download-opgave", "downloadInterval": "Downloadinterval (minutter)", "downloadIntervalPlaceholder": "Indtast minutter", "downloadScope": "Downloadstrategi", "downloadScopeDescription": "Vælg hvilke e-mails der skal indekseres og downloades.", + "downloadStarted": "Download-opgave startet", "duration": "Varighed", "edit": "Rediger", "email": "E-mail", @@ -255,6 +260,7 @@ "sinceFixed": "Siden specifik dato", "sinceRelative": "Download kun seneste e-mails", "sinceRelativeValue": "Download e-mails fra de sidste", + "startDownload": "Start download", "state": "Tilstand", "status": "Status", "step": "Trin {{index}}", diff --git a/web/src/locales/de.json b/web/src/locales/de.json index 90aa8b9..6b32fb9 100644 --- a/web/src/locales/de.json +++ b/web/src/locales/de.json @@ -104,6 +104,8 @@ "autoConfiguring": "Automatische Konfiguration läuft...", "beforeRelative": "Nur alte E-Mails herunterladen", "beforeRelativeValue": "E-Mails von vor {{value}} {{unit}} herunterladen", + "cancelDownload": "Download abbrechen", + "cancelFailed": "Download-Aufgabe konnte nicht abgebrochen werden", "capabilities": "Funktionen", "chooseAuthMethod": "Wählen Sie die Authentifizierungsmethode für IMAP.", "chooseEncryptionMethod": "Wählen Sie die Verschlüsselungsmethode für IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Alle E-Mails herunterladen", "downloadBatchSize": "Download-Batch-Größe", "downloadBatchSizeDescription": "Anzahl der pro IMAP-Anfrage abgerufenen Nachrichten", + "downloadCancelled": "Aufgabe abgebrochen", + "downloadFailed": "Download-Aufgabe konnte nicht gestartet werden", "downloadInterval": "Download-Intervall (Minuten)", "downloadIntervalPlaceholder": "Minuten eingeben", "downloadScope": "Download-Strategie", "downloadScopeDescription": "Wählen Sie aus, welche E-Mails indiziert und heruntergeladen werden sollen.", + "downloadStarted": "Download-Aufgabe gestartet", "duration": "Dauer", "edit": "Bearbeiten", "email": "E-Mail", @@ -255,6 +260,7 @@ "sinceFixed": "Seit einem bestimmten Datum", "sinceRelative": "Nur aktuelle E-Mails herunterladen", "sinceRelativeValue": "E-Mails der letzten Zeit herunterladen", + "startDownload": "Download starten", "state": "Zustand", "status": "Status", "step": "Schritt {{index}}", diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 135c2ad..ae2d206 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -104,6 +104,8 @@ "autoConfiguring": "Auto-configuring...", "beforeRelative": "Download Old Emails Only", "beforeRelativeValue": "Download emails before {{value}} {{unit}} ago", + "cancelDownload": "Cancel download", + "cancelFailed": "Failed to cancel download task", "capabilities": "Capabilities", "chooseAuthMethod": "Choose the authentication method for IMAP.", "chooseEncryptionMethod": "Choose the encryption method for IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Download All Emails", "downloadBatchSize": "Download batch size", "downloadBatchSizeDescription": "Number of messages fetched per IMAP request", + "downloadCancelled": "Task cancelled", + "downloadFailed": "Failed to start download task", "downloadInterval": "Download Interval (minutes)", "downloadIntervalPlaceholder": "Enter minutes", "downloadScope": "Download Strategy", "downloadScopeDescription": "Choose which emails should be indexed and downloaded.", + "downloadStarted": "Download task started", "duration": "Duration", "edit": "Edit", "email": "Email", @@ -255,6 +260,7 @@ "sinceFixed": "Since Specific Date", "sinceRelative": "Download Recent Emails Only", "sinceRelativeValue": "Download emails from the last", + "startDownload": "Start download", "state": "State", "status": "Status", "step": "Step {{index}}", diff --git a/web/src/locales/es.json b/web/src/locales/es.json index c49683b..1145596 100644 --- a/web/src/locales/es.json +++ b/web/src/locales/es.json @@ -104,6 +104,8 @@ "autoConfiguring": "Autoconfigurando...", "beforeRelative": "Descargar solo correos antiguos", "beforeRelativeValue": "Descargar correos de hace {{value}} {{unit}}", + "cancelDownload": "Cancelar descarga", + "cancelFailed": "Error al cancelar la tarea de descarga", "capabilities": "Capacidades", "chooseAuthMethod": "Elige el método de autenticación a utilizar para IMAP.", "chooseEncryptionMethod": "Elige el método de cifrado a utilizar para IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Descargar todos los correos", "downloadBatchSize": "Tamaño del lote de descarga", "downloadBatchSizeDescription": "Número de mensajes obtenidos por solicitud IMAP", + "downloadCancelled": "Tarea cancelada", + "downloadFailed": "Error al iniciar la tarea de descarga", "downloadInterval": "Intervalo de descarga (minutos)", "downloadIntervalPlaceholder": "Ingresa los minutos", "downloadScope": "Estrategia de descarga", "downloadScopeDescription": "Elija qué correos electrónicos deben indexarse y descargarse.", + "downloadStarted": "Tarea de descarga iniciada", "duration": "Duración", "edit": "Editar", "email": "Correo electrónico", @@ -255,6 +260,7 @@ "sinceFixed": "Desde una fecha específica", "sinceRelative": "Descargar solo correos recientes", "sinceRelativeValue": "Descargar correos de los últimos", + "startDownload": "Iniciar descarga", "state": "Estado", "status": "Estado", "step": "Paso {{index}}", diff --git a/web/src/locales/fi.json b/web/src/locales/fi.json index 4f15e1c..948bbfc 100644 --- a/web/src/locales/fi.json +++ b/web/src/locales/fi.json @@ -104,6 +104,8 @@ "autoConfiguring": "Automaattinen määritys...", "beforeRelative": "Lataa vain vanhat sähköpostit", "beforeRelativeValue": "Lataa sähköpostit {{value}} {{unit}} sitten", + "cancelDownload": "Peruuta lataus", + "cancelFailed": "Lataustehtävän peruuttaminen epäonnistui", "capabilities": "Ominaisuudet", "chooseAuthMethod": "Valitse IMAP:lle käytettävä todennusmenetelmä.", "chooseEncryptionMethod": "Valitse IMAP:lle käytettävä salausmenetelmä.", @@ -127,10 +129,13 @@ "downloadAll": "Lataa kaikki sähköpostit", "downloadBatchSize": "Latauserän koko", "downloadBatchSizeDescription": "IMAP-pyyntöä kohden noudettujen viestien määrä", + "downloadCancelled": "Tehtävä peruutettu", + "downloadFailed": "Lataustehtävän aloittaminen epäonnistui", "downloadInterval": "Latausväli (minuuttia)", "downloadIntervalPlaceholder": "Syötä minuutit", "downloadScope": "Latausstrategia", "downloadScopeDescription": "Valitse, mitkä sähköpostit indeksoidaan ja ladataan.", + "downloadStarted": "Lataustehtävä aloitettu", "duration": "Kesto", "edit": "Muokkaa", "email": "Sähköposti", @@ -255,6 +260,7 @@ "sinceFixed": "Tietystä päivämäärästä lähtien", "sinceRelative": "Lataa vain viimeisimmät sähköpostit", "sinceRelativeValue": "Lataa sähköpostit viimeisimmiltä", + "startDownload": "Aloita lataus", "state": "Tila", "status": "Tila", "step": "Vaihe {{index}}", diff --git a/web/src/locales/fr.json b/web/src/locales/fr.json index 85c15fa..b9aea65 100644 --- a/web/src/locales/fr.json +++ b/web/src/locales/fr.json @@ -104,6 +104,8 @@ "autoConfiguring": "Configuration automatique...", "beforeRelative": "Télécharger uniquement les anciens e-mails", "beforeRelativeValue": "Télécharger les e-mails d'il y a {{value}} {{unit}}", + "cancelDownload": "Annuler le téléchargement", + "cancelFailed": "Échec de l'annulation de la tâche de téléchargement", "capabilities": "Capacités", "chooseAuthMethod": "Choisissez la méthode d'authentification à utiliser pour IMAP.", "chooseEncryptionMethod": "Choisissez la méthode de chiffrement à utiliser pour IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Télécharger tous les e-mails", "downloadBatchSize": "Taille du lot de téléchargement", "downloadBatchSizeDescription": "Nombre de messages récupérés par requête IMAP", + "downloadCancelled": "Tâche annulée", + "downloadFailed": "Échec du lancement de la tâche de téléchargement", "downloadInterval": "Intervalle de téléchargement (minutes)", "downloadIntervalPlaceholder": "Entrer les minutes", "downloadScope": "Stratégie de téléchargement", "downloadScopeDescription": "Choisissez les e-mails à indexer et à télécharger.", + "downloadStarted": "Tâche de téléchargement lancée", "duration": "Durée", "edit": "Modifier", "email": "E-mail", @@ -255,6 +260,7 @@ "sinceFixed": "Depuis une date spécifique", "sinceRelative": "Télécharger uniquement les e-mails récents", "sinceRelativeValue": "Télécharger les e-mails des derniers", + "startDownload": "Lancer le téléchargement", "state": "État", "status": "Statut", "step": "Étape {{index}}", diff --git a/web/src/locales/it.json b/web/src/locales/it.json index d3e53ed..5f3388f 100644 --- a/web/src/locales/it.json +++ b/web/src/locales/it.json @@ -104,6 +104,8 @@ "autoConfiguring": "Configurazione automatica...", "beforeRelative": "Scarica solo le vecchie email", "beforeRelativeValue": "Scarica email da {{value}} {{unit}} fa", + "cancelDownload": "Annulla download", + "cancelFailed": "Annullamento attività di download non riuscito", "capabilities": "Capacità", "chooseAuthMethod": "Scegli il metodo di autenticazione per IMAP.", "chooseEncryptionMethod": "Scegli il metodo di crittografia per IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Scarica tutte le email", "downloadBatchSize": "Dimensione del lotto di download", "downloadBatchSizeDescription": "Numero di messaggi recuperati per richiesta IMAP", + "downloadCancelled": "Attività annullata", + "downloadFailed": "Avvio attività di download non riuscito", "downloadInterval": "Intervallo di download (minuti)", "downloadIntervalPlaceholder": "Inserisci i minuti", "downloadScope": "Strategia di download", "downloadScopeDescription": "Scegli quali email indicizzare e scaricare.", + "downloadStarted": "Attività di download avviata", "duration": "Durata", "edit": "Modifica", "email": "Email", @@ -255,6 +260,7 @@ "sinceFixed": "Da una data specifica", "sinceRelative": "Scarica solo le email recenti", "sinceRelativeValue": "Scarica email degli ultimi", + "startDownload": "Avvia download", "state": "Stato", "status": "Stato", "step": "Passo {{index}}", diff --git a/web/src/locales/jp.json b/web/src/locales/jp.json index 38de665..92a933c 100644 --- a/web/src/locales/jp.json +++ b/web/src/locales/jp.json @@ -104,6 +104,8 @@ "autoConfiguring": "自動設定中...", "beforeRelative": "古いメールのみダウンロード", "beforeRelativeValue": "{{value}} {{unit}}前より古いメールをダウンロード", + "cancelDownload": "ダウンロードをキャンセル", + "cancelFailed": "ダウンロードタスクのキャンセルに失敗しました", "capabilities": "機能", "chooseAuthMethod": "IMAPの認証方式を選択してください。", "chooseEncryptionMethod": "IMAPの暗号化方式を選択してください。", @@ -127,10 +129,13 @@ "downloadAll": "すべてのメールをダウンロード", "downloadBatchSize": "ダウンロードバッチサイズ", "downloadBatchSizeDescription": "IMAPリクエストごとに取得されるメッセージ数", + "downloadCancelled": "タスクをキャンセルしました", + "downloadFailed": "ダウンロードタスクの起動に失敗しました", "downloadInterval": "ダウンロード間隔 (分)", "downloadIntervalPlaceholder": "分を入力してください", "downloadScope": "ダウンロード戦略", "downloadScopeDescription": "インデックスとダウンロードの対象となるメールを選択してください。", + "downloadStarted": "ダウンロードタスクを開始しました", "duration": "期間", "edit": "編集", "email": "メールアドレス", @@ -255,6 +260,7 @@ "sinceFixed": "指定した日付以降", "sinceRelative": "最近のメールのみダウンロード", "sinceRelativeValue": "直近の期間のメールをダウンロード", + "startDownload": "ダウンロードを開始", "state": "状態", "status": "ステータス", "step": "ステップ {{index}}", diff --git a/web/src/locales/ko.json b/web/src/locales/ko.json index 32b925f..313d5c4 100644 --- a/web/src/locales/ko.json +++ b/web/src/locales/ko.json @@ -104,6 +104,8 @@ "autoConfiguring": "자동 구성 중...", "beforeRelative": "이전 이메일만 다운로드", "beforeRelativeValue": "{{value}} {{unit}} 전의 이메일 다운로드", + "cancelDownload": "다운로드 취소", + "cancelFailed": "다운로드 작업 취소 실패", "capabilities": "기능", "chooseAuthMethod": "IMAP에 사용할 인증 방법을 선택하십시오.", "chooseEncryptionMethod": "IMAP에 사용할 암호화 방법을 선택하십시오.", @@ -127,10 +129,13 @@ "downloadAll": "모든 이메일 다운로드", "downloadBatchSize": "다운로드 일괄 처리 크기", "downloadBatchSizeDescription": "IMAP 요청당 가져온 메시지 수", + "downloadCancelled": "작업 취소됨", + "downloadFailed": "다운로드 작업 시작 실패", "downloadInterval": "다운로드 주기 (분)", "downloadIntervalPlaceholder": "분 단위 입력", "downloadScope": "다운로드 전략", "downloadScopeDescription": "색인화 및 다운로드할 이메일을 선택하십시오.", + "downloadStarted": "다운로드 작업 시작됨", "duration": "기간", "edit": "편집", "email": "이메일", @@ -255,6 +260,7 @@ "sinceFixed": "특정 날짜 이후", "sinceRelative": "최근 이메일만 다운로드", "sinceRelativeValue": "최근 기간의 이메일 다운로드", + "startDownload": "다운로드 시작", "state": "상태", "status": "상태", "step": "단계 {{index}}", diff --git a/web/src/locales/nl.json b/web/src/locales/nl.json index 4d36590..2d750c1 100644 --- a/web/src/locales/nl.json +++ b/web/src/locales/nl.json @@ -104,6 +104,8 @@ "autoConfiguring": "Automatisch configureren...", "beforeRelative": "Download alleen oude e-mails", "beforeRelativeValue": "Download e-mails van {{value}} {{unit}} geleden", + "cancelDownload": "Download annuleren", + "cancelFailed": "Downloadtaak annuleren mislukt", "capabilities": "Mogelijkheden", "chooseAuthMethod": "Kies de authenticatiemethode voor IMAP.", "chooseEncryptionMethod": "Kies de versleutelingsmethode voor IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Download alle e-mails", "downloadBatchSize": "Download batchgrootte", "downloadBatchSizeDescription": "Aantal berichten opgehaald per IMAP-verzoek", + "downloadCancelled": "Taak geannuleerd", + "downloadFailed": "Downloadtaak starten mislukt", "downloadInterval": "Download-interval (minuten)", "downloadIntervalPlaceholder": "Voer minuten in", "downloadScope": "Downloadstrategie", "downloadScopeDescription": "Kies welke e-mails moeten worden geïndexeerd en gedownload.", + "downloadStarted": "Downloadtaak gestart", "duration": "Duur", "edit": "Bewerken", "email": "E-mail", @@ -255,6 +260,7 @@ "sinceFixed": "Sinds een specifieke datum", "sinceRelative": "Download alleen recente e-mails", "sinceRelativeValue": "Download e-mails van de laatste", + "startDownload": "Download starten", "state": "Status", "status": "Status", "step": "Stap {{index}}", diff --git a/web/src/locales/no.json b/web/src/locales/no.json index 0339a6d..e6715bb 100644 --- a/web/src/locales/no.json +++ b/web/src/locales/no.json @@ -104,6 +104,8 @@ "autoConfiguring": "Konfigurerer automatisk...", "beforeRelative": "Last ned kun gamle e-poster", "beforeRelativeValue": "Last ned e-poster fra {{value}} {{unit}} siden", + "cancelDownload": "Avbryt nedlasting", + "cancelFailed": "Kunne ikke avbryte nedlastingsoppgave", "capabilities": "Funksjoner", "chooseAuthMethod": "Velg autentiseringsmetoden for IMAP.", "chooseEncryptionMethod": "Velg krypteringsmetoden for IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Last ned alle e-poster", "downloadBatchSize": "Nedlastingsbatchstørrelse", "downloadBatchSizeDescription": "Antall meldinger hentet per IMAP-forespørsel", + "downloadCancelled": "Oppgave avbrutt", + "downloadFailed": "Kunne ikke starte nedlastingsoppgave", "downloadInterval": "Nedlastingsintervall (minutter)", "downloadIntervalPlaceholder": "Skriv inn minutter", "downloadScope": "Nedlastingsstrategi", "downloadScopeDescription": "Velg hvilke e-poster som skal indekseres og lastes ned.", + "downloadStarted": "Nedlastingsoppgave startet", "duration": "Varighet", "edit": "Rediger", "email": "E-post", @@ -255,6 +260,7 @@ "sinceFixed": "Siden spesifikk dato", "sinceRelative": "Last ned kun nylige e-poster", "sinceRelativeValue": "Last ned e-poster fra de siste", + "startDownload": "Start nedlasting", "state": "Tilstand", "status": "Status", "step": "Trinn {{index}}", diff --git a/web/src/locales/pl.json b/web/src/locales/pl.json index 075a90a..eaeb183 100644 --- a/web/src/locales/pl.json +++ b/web/src/locales/pl.json @@ -104,6 +104,8 @@ "autoConfiguring": "Auto konfiguracja...", "beforeRelative": "Pobierz tylko stare e-maile", "beforeRelativeValue": "Pobierz e-maile sprzed {{value}} {{unit}}", + "cancelDownload": "Anuluj pobieranie", + "cancelFailed": "Nie udało się anulować zadania pobierania", "capabilities": "Możliwości", "chooseAuthMethod": "Wybierz metodę uwierzytelniania IMAP.", "chooseEncryptionMethod": "Wybierz metodę szyfrowania dla IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Pobierz wszystkie e-maile", "downloadBatchSize": "Rozmiar partii pobierania", "downloadBatchSizeDescription": "Liczba wiadomości pobieranych na żądanie IMAP", + "downloadCancelled": "Zadanie anulowane", + "downloadFailed": "Nie udało się uruchomić zadania pobierania", "downloadInterval": "Cykl pobierania (minuty)", "downloadIntervalPlaceholder": "Wprowadź minuty", "downloadScope": "Strategia pobierania", "downloadScopeDescription": "Wybierz, które wiadomości e-mail mają być indeksowane i pobierane.", + "downloadStarted": "Uruchomiono zadanie pobierania", "duration": "Czas trwania", "edit": "Edytuj", "email": "Email", @@ -255,6 +260,7 @@ "sinceFixed": "Od konkretnej daty", "sinceRelative": "Pobierz tylko ostatnie e-maile", "sinceRelativeValue": "Pobierz e-maile z ostatnich", + "startDownload": "Uruchom pobieranie", "state": "Status", "status": "Status", "step": "Krok {{index}}", diff --git a/web/src/locales/pt.json b/web/src/locales/pt.json index 327a719..88bc5b5 100644 --- a/web/src/locales/pt.json +++ b/web/src/locales/pt.json @@ -104,6 +104,8 @@ "autoConfiguring": "Configurando Automaticamente...", "beforeRelative": "Baixar apenas e-mails antigos", "beforeRelativeValue": "Baixar e-mails de {{value}} {{unit}} atrás", + "cancelDownload": "Cancelar download", + "cancelFailed": "Falha ao cancelar tarefa de download", "capabilities": "Capacidades", "chooseAuthMethod": "Por favor, escolha o método de autenticação para IMAP.", "chooseEncryptionMethod": "Por favor, escolha o método de criptografia para IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Baixar todos os e-mails", "downloadBatchSize": "Tamanho do lote de download", "downloadBatchSizeDescription": "Número de mensagens recuperadas por solicitação IMAP", + "downloadCancelled": "Tarefa cancelada", + "downloadFailed": "Falha ao iniciar tarefa de download", "downloadInterval": "Intervalo de download (minutos)", "downloadIntervalPlaceholder": "Insira os minutos", "downloadScope": "Estratégia de download", "downloadScopeDescription": "Escolha quais e-mails devem ser indexados e baixados.", + "downloadStarted": "Tarefa de download iniciada", "duration": "Duração", "edit": "Editar", "email": "Email", @@ -255,6 +260,7 @@ "sinceFixed": "Desde uma data específica", "sinceRelative": "Baixar apenas e-mails recentes", "sinceRelativeValue": "Baixar e-mails dos últimos", + "startDownload": "Iniciar download", "state": "Estado", "status": "Status", "step": "Passo {{index}}", diff --git a/web/src/locales/ru.json b/web/src/locales/ru.json index a74a31c..af12855 100644 --- a/web/src/locales/ru.json +++ b/web/src/locales/ru.json @@ -104,6 +104,8 @@ "autoConfiguring": "Автонастройка...", "beforeRelative": "Скачать только старые письма", "beforeRelativeValue": "Скачать письма за {{value}} {{unit}} назад", + "cancelDownload": "Отменить загрузку", + "cancelFailed": "Не удалось отменить задачу загрузки", "capabilities": "Возможности", "chooseAuthMethod": "Выберите метод авторизации для IMAP.", "chooseEncryptionMethod": "Выберите метод шифрования для IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Скачать все письма", "downloadBatchSize": "Размер пакета загрузки", "downloadBatchSizeDescription": "Количество сообщений, получаемых за один IMAP-запрос", + "downloadCancelled": "Задача отменена", + "downloadFailed": "Не удалось запустить задачу загрузки", "downloadInterval": "Интервал загрузки (мин.)", "downloadIntervalPlaceholder": "Введите минуты", "downloadScope": "Стратегия загрузки", "downloadScopeDescription": "Выберите, какие электронные письма должны быть проиндексированы и скачаны.", + "downloadStarted": "Задача загрузки запущена", "duration": "Продолжительность", "edit": "Ред.", "email": "Email", @@ -255,6 +260,7 @@ "sinceFixed": "С определенной даты", "sinceRelative": "Скачать только недавние письма", "sinceRelativeValue": "Скачать письма за последние", + "startDownload": "Запустить загрузку", "state": "Состояние", "status": "Статус", "step": "Шаг {{index}}", diff --git a/web/src/locales/sv.json b/web/src/locales/sv.json index 6383a75..0bd1182 100644 --- a/web/src/locales/sv.json +++ b/web/src/locales/sv.json @@ -104,6 +104,8 @@ "autoConfiguring": "Konfigurerar automatiskt...", "beforeRelative": "Ladda ner endast gamla e-postmeddelanden", "beforeRelativeValue": "Ladda ner e-post från {{value}} {{unit}} sedan", + "cancelDownload": "Avbryt hämtning", + "cancelFailed": "Misslyckades med att avbryta hämtningsuppgift", "capabilities": "Funktioner", "chooseAuthMethod": "Välj autentiseringsmetod för IMAP.", "chooseEncryptionMethod": "Välj krypteringsmetod för IMAP.", @@ -127,10 +129,13 @@ "downloadAll": "Ladda ner alla e-postmeddelanden", "downloadBatchSize": "Batchstorlek för nedladdning", "downloadBatchSizeDescription": "Antal meddelanden som hämtas per IMAP-begäran", + "downloadCancelled": "Uppgiften avbruten", + "downloadFailed": "Misslyckades med att starta hämtningsuppgift", "downloadInterval": "Nedladdningsintervall (minuter)", "downloadIntervalPlaceholder": "Ange minuter", "downloadScope": "Nedladdningsstrategi", "downloadScopeDescription": "Välj vilka e-postmeddelanden som ska indexeras och laddas ner.", + "downloadStarted": "Hämtningsuppgift startad", "duration": "Varaktighet", "edit": "Redigera", "email": "E-post", @@ -255,6 +260,7 @@ "sinceFixed": "Sedan specifikt datum", "sinceRelative": "Ladda ner endast senaste e-postmeddelanden", "sinceRelativeValue": "Ladda ner e-post från de senaste", + "startDownload": "Starta hämtning", "state": "Tillstånd", "status": "Status", "step": "Steg {{index}}", diff --git a/web/src/locales/zh-tw.json b/web/src/locales/zh-tw.json index 6bf9f03..da0b76d 100644 --- a/web/src/locales/zh-tw.json +++ b/web/src/locales/zh-tw.json @@ -104,6 +104,8 @@ "autoConfiguring": "正在自動設定...", "beforeRelative": "僅下載舊郵件", "beforeRelativeValue": "下載 {{value}} {{unit}} 之前的郵件", + "cancelDownload": "取消下載", + "cancelFailed": "取消下載任務失敗", "capabilities": "功能", "chooseAuthMethod": "請選擇 IMAP 的驗證方法。", "chooseEncryptionMethod": "請選擇 IMAP 的加密方法。", @@ -127,10 +129,13 @@ "downloadAll": "下載所有郵件", "downloadBatchSize": "下載批量大小", "downloadBatchSizeDescription": "每個 IMAP 請求獲取的郵件數量", + "downloadCancelled": "下載任務已取消", + "downloadFailed": "啟動下載任務失敗", "downloadInterval": "下載週期 (分鐘)", "downloadIntervalPlaceholder": "請輸入分鐘數", "downloadScope": "下載策略", "downloadScopeDescription": "選擇哪些郵件應被索引和下載。", + "downloadStarted": "下載任務已啟動", "duration": "時長", "edit": "編輯", "email": "電子郵件", @@ -255,6 +260,7 @@ "sinceFixed": "自特定日期起", "sinceRelative": "僅下載最近郵件", "sinceRelativeValue": "下載最近一段時期的郵件", + "startDownload": "啟動下載", "state": "狀態", "status": "狀態", "step": "步驟 {{index}}", diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index 20245e6..468a39a 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -104,6 +104,8 @@ "autoConfiguring": "自动配置中...", "beforeRelative": "仅下载旧邮件", "beforeRelativeValue": "下载 {{value}} {{unit}} 之前的邮件", + "cancelDownload": "取消下载", + "cancelFailed": "取消下载任务失败", "capabilities": "功能", "chooseAuthMethod": "选择 IMAP 的认证方法。", "chooseEncryptionMethod": "选择 IMAP 的加密方法。", @@ -127,10 +129,13 @@ "downloadAll": "下载所有邮件", "downloadBatchSize": "下载批量大小", "downloadBatchSizeDescription": "每个 IMAP 请求获取的邮件数量", + "downloadCancelled": "下载任务已取消", + "downloadFailed": "启动下载任务失败", "downloadInterval": "下载周期 (分钟)", "downloadIntervalPlaceholder": "请输入分钟数", "downloadScope": "下载策略", "downloadScopeDescription": "选择哪些邮件应被索引和下载。", + "downloadStarted": "下载任务已启动", "duration": "时长", "edit": "编辑", "email": "邮箱", @@ -255,6 +260,7 @@ "sinceFixed": "自特定日期起", "sinceRelative": "仅下载最近邮件", "sinceRelativeValue": "下载最近一段时期的邮件", + "startDownload": "启动下载", "state": "状态", "status": "状态", "step": "步骤 {{index}}",