diff --git a/src/modules/context/executors.rs b/src/modules/context/executors.rs index a615c5b..c2ccb8f 100644 --- a/src/modules/context/executors.rs +++ b/src/modules/context/executors.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - +use crate::modules::account::migration::AccountType; use crate::modules::context::Initialize; use crate::modules::error::code::ErrorCode; use crate::raise_error; @@ -33,8 +33,7 @@ use dashmap::DashMap; use std::sync::{Arc, LazyLock}; use tracing::info; -pub static MAIL_CONTEXT: LazyLock = - LazyLock::new(EmailClientExecutors::new); +pub static MAIL_CONTEXT: LazyLock = LazyLock::new(EmailClientExecutors::new); pub struct EmailClientExecutors { start_at: i64, @@ -88,15 +87,17 @@ impl EmailClientExecutors { pub async fn start_account_syncers(&self) -> BichonResult<()> { let accounts = AccountModel::list_all().await?; - let active_accounts: Vec = - accounts.into_iter().filter(|a| a.enabled).collect(); + let active_accounts: Vec = accounts + .into_iter() + .filter(|a| a.enabled && matches!(a.account_type, AccountType::IMAP)) + .collect(); if active_accounts.is_empty() { info!("No active accounts found for account initialization."); return Ok(()); } info!( - "System has {} active accounts to initialize.", + "System has {} active IMAP accounts to initialize.", active_accounts.len() ); for account in active_accounts { diff --git a/src/modules/envelope/extractor.rs b/src/modules/envelope/extractor.rs index ed6134a..7a17ba4 100644 --- a/src/modules/envelope/extractor.rs +++ b/src/modules/envelope/extractor.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use crate::modules::common::AddrVec; use crate::modules::error::code::ErrorCode; use crate::modules::error::BichonResult; @@ -33,12 +32,12 @@ pub fn extract_envelope(fetch: &Fetch, account_id: u64, mailbox_id: u64) -> Bich .map(|d| d.timestamp_millis()) .unwrap_or(0); let uid = fetch.uid.unwrap_or(0); - let size = fetch.size.unwrap_or(0); let body = fetch .body() .ok_or_else(|| raise_error!("No body available".into(), ErrorCode::InternalError))?; + let size = fetch.size.unwrap_or(body.len() as u32); let message = MessageParser::new().parse(body).ok_or_else(|| { raise_error!( "Email header parse result is not available".into(), @@ -118,6 +117,92 @@ pub fn extract_envelope(fetch: &Fetch, account_id: u64, mailbox_id: u64) -> Bich Ok(envelope) } +pub fn extract_envelope_from_eml( + body: &[u8], + account_id: u64, + mailbox_id: u64, +) -> BichonResult { + let uid = 0; + let size = body.len() as u32; + let message = MessageParser::new().parse(body).ok_or_else(|| { + raise_error!( + "Email header parse result is not available".into(), + ErrorCode::InternalError + ) + })?; + + let text = if let Some(text) = message.body_text(0).map(|cow| cow.into_owned()) { + text + } else if let Some(html) = message.body_html(0).map(|cow| cow.into_owned()) { + from_read(html.as_bytes(), 0) + .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? + } else { + String::new() + }; + + let message_id = message + .message_id() + .map(String::from) + .unwrap_or(generate_message_id()); + let in_reply_to = message.in_reply_to().as_text().map(String::from); + let references = extract_references(&message); + let thread_id = compute_thread_id(in_reply_to, references, &message_id); + let subject = message.subject().map(String::from).unwrap_or("".into()); + let date = message.date().map(|d| d.to_timestamp() * 1000).unwrap_or(0); + let bcc: Option> = message.bcc().map(|addr| { + AddrVec::from(addr) + .0 + .into_iter() + .filter_map(|a| a.address) + .collect() + }); + let cc: Option> = message.cc().map(|addr| { + AddrVec::from(addr) + .0 + .into_iter() + .filter_map(|a| a.address) + .collect() + }); + let to: Option> = message.to().map(|addr| { + AddrVec::from(addr) + .0 + .into_iter() + .filter_map(|a| a.address) + .collect() + }); + let from = message + .from() + .and_then(|addr| AddrVec::from(addr).0.into_iter().next()) + .and_then(|add| add.address) + .unwrap_or_else(|| "unknown".to_string()); + + let attachments: Vec = message + .attachments() + .filter_map(|att| att.attachment_name()) + .map(|name| name.to_string()) + .collect(); + let envelope = Envelope { + id: create_hash(account_id, &message_id), + message_id, + account_id, + mailbox_id, + uid, + subject, + text, + from, + to: to.unwrap_or_default(), + cc: cc.unwrap_or_default(), + bcc: bcc.unwrap_or_default(), + date, + internal_date: date, + size, + thread_id, + attachments, + tags: None, + }; + Ok(envelope) +} + pub fn compute_thread_id( in_reply_to: Option, references: Option>, diff --git a/src/modules/import/mod.rs b/src/modules/import/mod.rs new file mode 100644 index 0000000..e0ba03f --- /dev/null +++ b/src/modules/import/mod.rs @@ -0,0 +1,162 @@ +use poem_openapi::Object; +use serde::{Deserialize, Serialize}; +use tantivy::doc; + +use crate::{ + base64_decode_url_safe, + modules::{ + account::migration::{AccountModel, AccountType}, + cache::imap::mailbox::{Attribute, AttributeEnum, MailBox}, + envelope::extractor::extract_envelope_from_eml, + error::{code::ErrorCode, BichonResult}, + indexer::{ + manager::{EML_INDEX_MANAGER, ENVELOPE_INDEX_MANAGER}, + schema::SchemaTools, + }, + utils::create_hash, + }, + raise_error, +}; + +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +pub struct BatchEmlRequest { + pub account_id: u64, + pub mail_folder: String, + /// A list of emails in base64-encoded format. Each element represents one .eml file. + pub emls: Vec, +} + +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +pub struct FailedEmlDetail { + /// The 0-based index of the failed EML in the request list + pub index: usize, + /// The error message that caused the import to fail + pub error_message: String, +} + +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] +pub struct BatchEmlResult { + /// Total number of emails processed + pub total: usize, + /// Number of emails successfully imported + pub success: usize, + /// Number of emails failed to import + pub failed: usize, + /// A list of details for failed imports + pub failed_details: Vec, +} + +pub struct ImportEmls; + +impl ImportEmls { + pub async fn do_import(request: BatchEmlRequest) -> BichonResult { + let account = AccountModel::check_account_exists(request.account_id).await?; + + if !account.enabled { + return Err(raise_error!("The account is disabled and cannot be used for this operation.".into(), ErrorCode::InvalidParameter)); + } + + let mailbox_id = match account.account_type { + AccountType::IMAP => { + let all_mailboxes = MailBox::list_all(account.id).await?; + let mailbox = all_mailboxes.into_iter().find(|m| m.name == request.mail_folder); + + match mailbox { + Some(mailbox) => mailbox.id, + None => return Err(raise_error!( + format!("Mail folder '{}' not found for account ID {}. The target folder must exist before importing.", + request.mail_folder, + request.account_id).into(), + ErrorCode::ResourceNotFound + )), + } + }, + AccountType::NoSync => { + let mailbox = MailBox { + id: create_hash(request.account_id, &request.mail_folder), + account_id: request.account_id, + name: request.mail_folder.clone(), + delimiter: Some("/".to_string()), + attributes: vec![Attribute { + attr: AttributeEnum::Extension, + extension: Some("CreatedByBichon".into()), + }], + exists: 0, + unseen: None, + uid_next: None, + uid_validity: None, + }; + let mailbox_id = mailbox.id; + // Upsert the mailbox, creating it if it doesn't exist + MailBox::batch_upsert(&[mailbox]).await?; + mailbox_id + }, + }; + + let fields = SchemaTools::eml_fields(); + let account_id = account.id; + let mut success_count = 0; + let mut failed_details: Vec = Vec::new(); // Store failure details + + let total = request.emls.len(); + for (index, eml_base64) in request.emls.into_iter().enumerate() { + // 1. Decode Base64 + let decoded = match base64_decode_url_safe!(eml_base64.as_bytes()) { + Ok(bytes) => bytes, + Err(e) => { + let error_msg = + format!("Failed to decode base64 EML at index {}: {:?}", index, e); + tracing::error!("{}", error_msg); + failed_details.push(FailedEmlDetail { + index, + error_message: error_msg, + }); + continue; + } + }; + + let envelope = match extract_envelope_from_eml(&decoded, account_id, mailbox_id) { + Ok(env) => env, + Err(e) => { + let error_msg = format!( + "Failed to extract envelope from EML at index {}: {:?}", + index, e + ); + tracing::error!("{}", error_msg); + failed_details.push(FailedEmlDetail { + index, + error_message: error_msg, + }); + continue; + } + }; + + ENVELOPE_INDEX_MANAGER + .add_document(envelope.id, envelope.to_document(mailbox_id).unwrap()) + .await; + + EML_INDEX_MANAGER + .add_document( + envelope.id, + doc!( + fields.f_id => envelope.id, + fields.f_account_id => account_id, + fields.f_mailbox_id => mailbox_id, + fields.f_eml => decoded + ), + ) + .await; + + success_count += 1; + } + + let failed_count = failed_details.len(); + + Ok(BatchEmlResult { + total, + success: success_count, + failed: failed_count, + failed_details, // Return the list of failure details + }) + } +} diff --git a/src/modules/mod.rs b/src/modules/mod.rs index f3fbf0c..1264760 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - pub mod account; pub mod autoconfig; pub mod cache; @@ -27,6 +26,7 @@ pub mod database; pub mod envelope; pub mod error; pub mod imap; +pub mod import; pub mod indexer; pub mod logger; pub mod mailbox; diff --git a/src/modules/rest/api/import.rs b/src/modules/rest/api/import.rs new file mode 100644 index 0000000..ede9650 --- /dev/null +++ b/src/modules/rest/api/import.rs @@ -0,0 +1,49 @@ +// +// Copyright (c) 2025 rustmailer.com (https://rustmailer.com) +// +// This file is part of the Bichon Email Archiving Project +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::modules::common::auth::ClientContext; +use crate::modules::import::BatchEmlResult; +use crate::modules::import::{BatchEmlRequest, ImportEmls}; +use crate::modules::rest::api::ApiTags; +use crate::modules::rest::ApiResult; +use poem_openapi::payload::Json; +use poem_openapi::OpenApi; + +pub struct ImportApi; + +#[OpenApi(prefix_path = "/api/v1", tag = "ApiTags::Import")] +impl ImportApi { + /// Batch import one or more EML files into a specified account and mail folder. + /// + /// This endpoint accepts a JSON payload containing: + /// - `account_id`: the target account to import emails into + /// - `mail_folder`: the mailbox/folder name + /// - `emls`: a list of base64-encoded .eml files + /// + /// Returns a summary of the import result, including total processed, successful, and failed emails. + #[oai(path = "/import", method = "post", operation_id = "do_batch_import")] + async fn do_batch_import( + &self, + /// JSON payload with account info and EML files to import + payload: Json, + context: ClientContext, + ) -> ApiResult> { + context.require_root()?; + Ok(Json(ImportEmls::do_import(payload.0).await?)) + } +} diff --git a/src/modules/rest/api/mod.rs b/src/modules/rest/api/mod.rs index e609425..5248db4 100644 --- a/src/modules/rest/api/mod.rs +++ b/src/modules/rest/api/mod.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use access_token::AccessTokenApi; use account::AccountApi; use auto_config::AutoConfigApi; @@ -26,11 +25,12 @@ use oauth2::OAuth2Api; use poem_openapi::{OpenApiService, Tags}; use system::SystemApi; -use crate::bichon_version; +use crate::{bichon_version, modules::rest::api::import::ImportApi}; pub mod access_token; pub mod account; pub mod auto_config; +pub mod import; pub mod mailbox; pub mod message; pub mod oauth2; @@ -45,6 +45,7 @@ pub enum ApiTags { OAuth2, Message, System, + Import, } type RustMailOpenApi = ( @@ -55,6 +56,7 @@ type RustMailOpenApi = ( MailBoxApi, OAuth2Api, MessageApi, + ImportApi, ); pub fn create_openapi_service() -> OpenApiService { @@ -67,6 +69,7 @@ pub fn create_openapi_service() -> OpenApiService { MailBoxApi, OAuth2Api, MessageApi, + ImportApi, ), "BichonApi", bichon_version!(), diff --git a/web/src/features/accounts/components/nosync-dialog.tsx b/web/src/features/accounts/components/nosync-dialog.tsx index 751165e..c2c2231 100644 --- a/web/src/features/accounts/components/nosync-dialog.tsx +++ b/web/src/features/accounts/components/nosync-dialog.tsx @@ -37,10 +37,10 @@ import { AccountModel } from '../data/schema'; import { useTranslation } from 'react-i18next'; -const accountSchema = () => +const accountSchema = (t: (key: string) => string) => z.object({ name: z.string().optional(), - email: z.string({ required_error: 'Email is required' }).email({ message: 'Invalid email address' }), + email: z.string({ required_error: t('validation.emailRequired') }).email({ message: t('validation.invalidEmail') }), enabled: z.boolean() }); @@ -85,7 +85,7 @@ export function NoSyncAccountDialog({ currentRow, open, onOpenChange }: Props) { const form = useForm({ mode: "all", defaultValues: isEdit ? mapCurrentRowToFormValues(currentRow) : defaultValues, - resolver: zodResolver(accountSchema()), + resolver: zodResolver(accountSchema(t)), }); const queryClient = useQueryClient(); @@ -118,7 +118,7 @@ export function NoSyncAccountDialog({ currentRow, open, onOpenChange }: Props) { const errorMessage = (error.response?.data as { message?: string })?.message || error.message || - `${isEdit ? 'Update' : 'Creation'} failed, please try again later`; + (isEdit ? t('accounts.updateFailed') : t('accounts.creationFailed')); toast({ variant: "destructive", @@ -134,7 +134,8 @@ export function NoSyncAccountDialog({ currentRow, open, onOpenChange }: Props) { const commonData = { email: data.email, name: data.name, - enabled: data.enabled + enabled: data.enabled, + use_dangerous: false }; if (isEdit) { updateMutation.mutate(commonData); diff --git a/web/src/features/accounts/components/oauth2-action.tsx b/web/src/features/accounts/components/oauth2-action.tsx index c353a5b..f31194d 100644 --- a/web/src/features/accounts/components/oauth2-action.tsx +++ b/web/src/features/accounts/components/oauth2-action.tsx @@ -33,7 +33,7 @@ export function OAuth2Action({ row }: DataTableRowActionsProps) { const account_type = mailer.account_type; if (account_type === "NoSync") { - return n/a + return } const isOAuth2 = mailer.imap?.auth.auth_type === "OAuth2" diff --git a/web/src/locales/ar.json b/web/src/locales/ar.json index 0b7961b..058e537 100644 --- a/web/src/locales/ar.json +++ b/web/src/locales/ar.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "تحديث حساب البريد الإلكتروني هنا. ", "addNewEmailAccountHere": "إضافة حساب بريد إلكتروني جديد هنا. ", "thisAccountIsUsedForIdentificationPurposesOnly": "يستخدم هذا الحساب لأغراض التعريف فقط ولا يتطلب المزامنة مع خادم بريد إلكتروني. إنه يساعد في استيراد بيانات البريد الإلكتروني.", - "determinesWhetherThisAccountIsActiveNoSync": "يحدد ما إذا كان هذا الحساب نشطًا. إذا تم تعطيله، فلن يتمكن الحساب من استيراد البيانات أو إجراء استعلامات.", + "determinesWhetherThisAccountIsActiveNoSync": "يحدد ما إذا كان هذا الحساب نشطًا. إذا تم تعطيله، فلن يكون قادرًا على استيراد البيانات.", "folderSync": { "noData": "لا توجد بيانات", "noFolders": "لا توجد مجلدات للمزامنة", diff --git a/web/src/locales/da.json b/web/src/locales/da.json index b30b0c1..b3a3caf 100644 --- a/web/src/locales/da.json +++ b/web/src/locales/da.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Opdater e-mailkontoen her. ", "addNewEmailAccountHere": "Tilføj ny e-mailkonto her. ", "thisAccountIsUsedForIdentificationPurposesOnly": "Denne konto bruges kun til identifikationsformål og kræver ikke synkronisering med en e-mailserver. Den hjælper med import af e-mail-data.", - "determinesWhetherThisAccountIsActiveNoSync": "Bestemmer, om denne konto er aktiv. Hvis deaktiveret, vil kontoen ikke kunne importere data eller udføre søgninger.", + "determinesWhetherThisAccountIsActiveNoSync": "Bestemmer, om denne konto er aktiv. Hvis den er deaktiveret, kan data ikke importeres.", "folderSync": { "noData": "Ingen data", "noFolders": "Ingen mapper at synkronisere", diff --git a/web/src/locales/de.json b/web/src/locales/de.json index 7c0cd7b..85b89c4 100644 --- a/web/src/locales/de.json +++ b/web/src/locales/de.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Aktualisieren Sie das E-Mail-Konto hier. ", "addNewEmailAccountHere": "Fügen Sie hier ein neues E-Mail-Konto hinzu. ", "thisAccountIsUsedForIdentificationPurposesOnly": "Dieses Konto dient nur zur Identifizierung und erfordert keine Synchronisierung mit dem Mailserver. Es hilft beim Import von E-Mail-Daten.", - "determinesWhetherThisAccountIsActiveNoSync": "Bestimmt, ob dieses Konto aktiv ist. Wenn es deaktiviert ist, kann das Konto keine Daten importieren oder Abfragen ausführen.", + "determinesWhetherThisAccountIsActiveNoSync": "Legt fest, ob dieses Konto aktiv ist. Wenn es deaktiviert ist, können keine Daten importiert werden.", "folderSync": { "noData": "Keine Daten", "noFolders": "Keine Ordner zum Synchronisieren", diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 5f26891..7fb81f7 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Update the email account here. ", "addNewEmailAccountHere": "Add new email account here. ", "thisAccountIsUsedForIdentificationPurposesOnly": "This account is used for identification purposes only and does not require syncing with an email server. It helps with importing email data.", - "determinesWhetherThisAccountIsActiveNoSync": "Determines whether this account is active. If disabled, the account will not be able to import data or perform queries.", + "determinesWhetherThisAccountIsActiveNoSync": "Determines whether this account is active. If disabled, the account will not be able to import data.", "folderSync": { "noData": "No Data", "noFolders": "No folders to sync", diff --git a/web/src/locales/es.json b/web/src/locales/es.json index 7dd0bba..40b4e67 100644 --- a/web/src/locales/es.json +++ b/web/src/locales/es.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Actualiza la cuenta de correo electrónico aquí. ", "addNewEmailAccountHere": "Añade una nueva cuenta de correo electrónico aquí. ", "thisAccountIsUsedForIdentificationPurposesOnly": "Esta cuenta se utiliza solo para fines de identificación y no requiere sincronización con el servidor de correo. Ayuda a importar datos de correo electrónico.", - "determinesWhetherThisAccountIsActiveNoSync": "Determina si esta cuenta está activa. Si está deshabilitada, la cuenta no podrá importar datos ni ejecutar consultas.", + "determinesWhetherThisAccountIsActiveNoSync": "Determina si esta cuenta está activa. Si está desactivada, no podrá importar datos.", "folderSync": { "noData": "Sin datos", "noFolders": "No hay carpetas para sincronizar", diff --git a/web/src/locales/fi.json b/web/src/locales/fi.json index d39d86d..fce722a 100644 --- a/web/src/locales/fi.json +++ b/web/src/locales/fi.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Päivitä sähköpostitili täällä. ", "addNewEmailAccountHere": "Lisää uusi sähköpostitili täällä. ", "thisAccountIsUsedForIdentificationPurposesOnly": "Tätä tiliä käytetään vain tunnistamistarkoituksiin, eikä se vaadi synkronointia sähköpostipalvelimen kanssa. Auttaa sähköpostidatan tuomisessa.", - "determinesWhetherThisAccountIsActiveNoSync": "Määrittää, onko tämä tili aktiivinen. Jos poistettu käytöstä, tili ei voi tuoda tietoja tai suorittaa kyselyjä.", + "determinesWhetherThisAccountIsActiveNoSync": "Määrittää, onko tämä tili aktiivinen. Jos se on poistettu käytöstä, tietoja ei voi tuoda.", "folderSync": { "noData": "Ei tietoja", "noFolders": "Ei synkronoitavia kansioita", diff --git a/web/src/locales/fr.json b/web/src/locales/fr.json index d91d62d..d8a3928 100644 --- a/web/src/locales/fr.json +++ b/web/src/locales/fr.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Mettez à jour le compte e-mail ici. ", "addNewEmailAccountHere": "Ajoutez un nouveau compte e-mail ici. ", "thisAccountIsUsedForIdentificationPurposesOnly": "Ce compte est utilisé uniquement à des fins d'identification et ne nécessite pas de synchronisation avec un serveur de messagerie. Il est utile pour l'importation de données d'e-mails.", - "determinesWhetherThisAccountIsActiveNoSync": "Détermine si ce compte est actif. S'il est désactivé, l'importation de données ou l'exécution de requêtes ne sera pas possible pour ce compte.", + "determinesWhetherThisAccountIsActiveNoSync": "Détermine si ce compte est actif. S’il est désactivé, il ne pourra pas importer de données.", "folderSync": { "noData": "Aucune Donnée", "noFolders": "Aucun dossier à synchroniser", diff --git a/web/src/locales/it.json b/web/src/locales/it.json index 18183db..3099f35 100644 --- a/web/src/locales/it.json +++ b/web/src/locales/it.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Aggiorna l'account email qui. ", "addNewEmailAccountHere": "Aggiungi un nuovo account email qui. ", "thisAccountIsUsedForIdentificationPurposesOnly": "Questo account viene utilizzato solo a scopo di identificazione e non richiede la sincronizzazione con un server email. Aiuta nell'importazione dei dati email.", - "determinesWhetherThisAccountIsActiveNoSync": "Determina se questo account è attivo. Se disabilitato, l'account non potrà importare dati o eseguire query.", + "determinesWhetherThisAccountIsActiveNoSync": "Determina se questo account è attivo. Se disattivato, non sarà possibile importare dati.", "folderSync": { "noData": "Nessun Dato", "noFolders": "Nessuna cartella da sincronizzare", diff --git a/web/src/locales/jp.json b/web/src/locales/jp.json index 3a022a3..381c39e 100644 --- a/web/src/locales/jp.json +++ b/web/src/locales/jp.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "こちらでメールアカウントを更新してください。", "addNewEmailAccountHere": "こちらで新しいメールアカウントを追加してください。", "thisAccountIsUsedForIdentificationPurposesOnly": "このアカウントは識別目的でのみ使用され、メールサーバーとの同期は必要ありません。メールデータのインポートに役立ちます。", - "determinesWhetherThisAccountIsActiveNoSync": "このアカウントが有効かどうかを決定します。無効の場合、データのインポートやクエリの実行はできません。", + "determinesWhetherThisAccountIsActiveNoSync": "このアカウントが有効かどうかを判定します。無効の場合、データをインポートできません。", "folderSync": { "noData": "データなし", "noFolders": "同期するフォルダーがありません", diff --git a/web/src/locales/ko.json b/web/src/locales/ko.json index 5a4697f..2682922 100644 --- a/web/src/locales/ko.json +++ b/web/src/locales/ko.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "여기에서 이메일 계정을 업데이트하십시오.", "addNewEmailAccountHere": "여기에서 새 이메일 계정을 추가하십시오.", "thisAccountIsUsedForIdentificationPurposesOnly": "이 계정은 식별 목적으로만 사용되며 이메일 서버와의 동기화가 필요하지 않습니다. 이메일 데이터 가져오기에 도움이 됩니다.", - "determinesWhetherThisAccountIsActiveNoSync": "이 계정이 활성화되었는지 여부를 결정합니다. 비활성화된 경우 데이터 가져오기 또는 쿼리 실행이 불가능합니다.", + "determinesWhetherThisAccountIsActiveNoSync": "이 계정이 활성화되어 있는지 결정합니다. 비활성화되면 데이터를 가져올 수 없습니다.", "folderSync": { "noData": "데이터 없음", "noFolders": "동기화할 폴더가 없습니다", diff --git a/web/src/locales/nl.json b/web/src/locales/nl.json index e94e8af..cb81967 100644 --- a/web/src/locales/nl.json +++ b/web/src/locales/nl.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Werk het e-mailaccount hier bij. ", "addNewEmailAccountHere": "Voeg hier een nieuw e-mailaccount toe. ", "thisAccountIsUsedForIdentificationPurposesOnly": "Dit account wordt alleen gebruikt voor identificatiedoeleinden en vereist geen synchronisatie met een e-mailserver. Het helpt bij het importeren van e-mailgegevens.", - "determinesWhetherThisAccountIsActiveNoSync": "Bepaalt of dit account actief is. Indien uitgeschakeld, kan het account geen gegevens importeren of zoekopdrachten uitvoeren.", + "determinesWhetherThisAccountIsActiveNoSync": "Bepaalt of dit account actief is. Als het is uitgeschakeld, kan er geen data worden geïmporteerd.", "folderSync": { "noData": "Geen Gegevens", "noFolders": "Geen mappen om te synchroniseren", diff --git a/web/src/locales/no.json b/web/src/locales/no.json index 4613f13..3ff4f02 100644 --- a/web/src/locales/no.json +++ b/web/src/locales/no.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Oppdater e-postkontoen her. ", "addNewEmailAccountHere": "Legg til ny e-postkonto her. ", "thisAccountIsUsedForIdentificationPurposesOnly": "Denne kontoen brukes kun for identifikasjonsformål og krever ikke synkronisering med en e-postserver. Den hjelper med import av e-postdata.", - "determinesWhetherThisAccountIsActiveNoSync": "Bestemmer om denne kontoen er aktiv. Hvis deaktivert, vil ikke kontoen kunne importere data eller utføre spørringer.", + "determinesWhetherThisAccountIsActiveNoSync": "Bestemmer om denne kontoen er aktiv. Hvis den er deaktivert, kan data ikke importeres.", "folderSync": { "noData": "Ingen data", "noFolders": "Ingen mapper å synkronisere", diff --git a/web/src/locales/pt.json b/web/src/locales/pt.json index 232e9f0..bec77b5 100644 --- a/web/src/locales/pt.json +++ b/web/src/locales/pt.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Atualize a conta de email aqui.", "addNewEmailAccountHere": "Adicione uma nova conta de email aqui.", "thisAccountIsUsedForIdentificationPurposesOnly": "Esta conta é usada apenas para fins de identificação e não requer sincronização com um servidor de email. Ajuda na importação de dados de email.", - "determinesWhetherThisAccountIsActiveNoSync": "Determina se esta conta está ativa. Se desativada, nenhuma importação de dados ou execução de consulta será possível.", + "determinesWhetherThisAccountIsActiveNoSync": "Determina se esta conta está ativa. Se estiver desativada, não será possível importar dados.", "folderSync": { "noData": "Sem Dados", "noFolders": "Nenhuma pasta para sincronizar", diff --git a/web/src/locales/ru.json b/web/src/locales/ru.json index b81f4d2..3f44b5a 100644 --- a/web/src/locales/ru.json +++ b/web/src/locales/ru.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Обновите почтовый аккаунт здесь. ", "addNewEmailAccountHere": "Добавьте новый почтовый аккаунт здесь. ", "thisAccountIsUsedForIdentificationPurposesOnly": "Этот аккаунт используется только для идентификации и не требует синхронизации с почтовым сервером. Он помогает при импорте почтовых данных.", - "determinesWhetherThisAccountIsActiveNoSync": "Определяет, активен ли этот аккаунт. Если отключено, аккаунт не сможет импортировать данные или выполнять запросы.", + "determinesWhetherThisAccountIsActiveNoSync": "Определяет, активна ли учетная запись. Если она отключена, импорт данных будет невозможен.", "folderSync": { "noData": "Нет данных", "noFolders": "Нет папок для синхронизации", diff --git a/web/src/locales/sv.json b/web/src/locales/sv.json index 3667cce..25b1e7c 100644 --- a/web/src/locales/sv.json +++ b/web/src/locales/sv.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "Uppdatera e-postkontot här. ", "addNewEmailAccountHere": "Lägg till nytt e-postkonto här. ", "thisAccountIsUsedForIdentificationPurposesOnly": "Detta konto används endast för identifieringssyften och kräver inte synkronisering med en e-postserver. Det hjälper till med import av e-postdata.", - "determinesWhetherThisAccountIsActiveNoSync": "Avgör om detta konto är aktivt. Om inaktiverat kommer kontot inte kunna importera data eller utföra sökningar.", + "determinesWhetherThisAccountIsActiveNoSync": "Avgör om kontot är aktivt. Om det är inaktiverat kan ingen data importeras.", "folderSync": { "noData": "Inga data", "noFolders": "Inga mappar att synkronisera", diff --git a/web/src/locales/zh-tw.json b/web/src/locales/zh-tw.json index c10edcf..743b17d 100644 --- a/web/src/locales/zh-tw.json +++ b/web/src/locales/zh-tw.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "在此更新電子郵件帳號。", "addNewEmailAccountHere": "在此新增電子郵件帳號。", "thisAccountIsUsedForIdentificationPurposesOnly": "此帳號僅用於識別目的,不需要與郵件伺服器同步。有助於匯入郵件資料。", - "determinesWhetherThisAccountIsActiveNoSync": "決定此帳號是否為啟用狀態。如果停用,則無法匯入資料或執行查詢。", + "determinesWhetherThisAccountIsActiveNoSync": "用來判斷帳戶是否啟用。若停用,將無法匯入資料。", "folderSync": { "noData": "無資料", "noFolders": "沒有可同步的資料夾", diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index 528bc8b..12d136a 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -251,7 +251,7 @@ "updateTheEmailAccountHere": "在此更新邮件账户。", "addNewEmailAccountHere": "在此添加新邮件账户。", "thisAccountIsUsedForIdentificationPurposesOnly": "此账户仅用于识别目的,不需要与邮件服务器同步。它有助于导入邮件数据。", - "determinesWhetherThisAccountIsActiveNoSync": "确定此账户是否处于活动状态。如果禁用,账户将无法导入数据或执行查询。", + "determinesWhetherThisAccountIsActiveNoSync": "用于判断账户是否处于启用状态。如果被禁用,将无法导入数据。", "folderSync": { "noData": "无数据", "noFolders": "没有需要同步的文件夹",