mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
feat: restructure IMAP mail download state and ui
This commit is contained in:
+49
-18
@@ -31,28 +31,58 @@ export const minimal_account_list = async () => {
|
||||
};
|
||||
|
||||
|
||||
export interface ErrorMessage {
|
||||
export enum DownloadStatus {
|
||||
Running = "Running",
|
||||
Success = "Success",
|
||||
Failed = "Failed",
|
||||
Cancelled = "Cancelled",
|
||||
}
|
||||
|
||||
export enum TriggerType {
|
||||
Manual = "Manual",
|
||||
Scheduled = "Scheduled",
|
||||
}
|
||||
|
||||
export enum FolderStatus {
|
||||
Pending = "Pending",
|
||||
Downloading = "Downloading",
|
||||
Success = "Success",
|
||||
Failed = "Failed",
|
||||
Cancelled = "Cancelled",
|
||||
}
|
||||
|
||||
export interface FolderProgress {
|
||||
folder_name: string;
|
||||
planned: number;
|
||||
current: number;
|
||||
status: FolderStatus;
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
|
||||
export interface AccountError {
|
||||
at: number;
|
||||
error: string;
|
||||
at: number; // milliseconds timestamp
|
||||
}
|
||||
|
||||
export type ProgressMap = Record<string, MailboxBatchProgress>;
|
||||
export interface DownloadSession {
|
||||
start_time: number;
|
||||
end_time: number | null;
|
||||
status: DownloadStatus;
|
||||
message: string | null;
|
||||
trigger: TriggerType;
|
||||
folder_details: Record<string, FolderProgress>;
|
||||
current_folder: string | null;
|
||||
errors: AccountError[];
|
||||
}
|
||||
|
||||
export interface AccountRunningState {
|
||||
export interface DownloadState {
|
||||
account_id: number;
|
||||
last_incremental_sync_start: number;
|
||||
last_incremental_sync_end?: number,
|
||||
errors: ErrorMessage[];
|
||||
is_initial_sync_completed: boolean;
|
||||
progress?: ProgressMap;
|
||||
initial_sync_start_time?: number;
|
||||
initial_sync_end_time?: number;
|
||||
}
|
||||
|
||||
|
||||
export interface MailboxBatchProgress {
|
||||
total_batches: number;
|
||||
current_batch: number;
|
||||
active_session: DownloadSession | null;
|
||||
history: DownloadSession[];
|
||||
last_trigger_at: number;
|
||||
last_finished_at: number | null;
|
||||
global_errors: AccountError[];
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +91,7 @@ type Encryption = 'Ssl' | 'StartTls' | 'None';
|
||||
type AuthType = 'Password' | 'OAuth2';
|
||||
type Unit = 'Days' | 'Months' | 'Years';
|
||||
type AccountType = 'IMAP' | 'NoSync';
|
||||
|
||||
// Interface definitions
|
||||
interface AuthConfig {
|
||||
auth_type: AuthType;
|
||||
@@ -109,7 +140,7 @@ export interface AccountModel {
|
||||
}
|
||||
|
||||
export const account_state = async (account_id: number) => {
|
||||
const response = await axiosInstance.get<AccountRunningState>(`api/v1/account-state/${account_id}`);
|
||||
const response = await axiosInstance.get<DownloadState>(`api/v1/account-state/${account_id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('accounts.syncFoldersTitle')}</CardTitle>
|
||||
<CardTitle>{t('accounts.selectedMailboxes')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{currentRow.sync_folders?.length ? (
|
||||
|
||||
@@ -42,6 +42,7 @@ export function useColumns(): ColumnDef<AccountModel>[] {
|
||||
},
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: { className: 'max-w-[120px]' },
|
||||
},
|
||||
{
|
||||
accessorKey: "email",
|
||||
@@ -52,6 +53,7 @@ export function useColumns(): ColumnDef<AccountModel>[] {
|
||||
return <LongText>{row.original.email}</LongText>
|
||||
},
|
||||
enableHiding: false,
|
||||
meta: { className: 'max-w-[220px]' },
|
||||
},
|
||||
{
|
||||
accessorKey: "enabled",
|
||||
@@ -59,7 +61,7 @@ export function useColumns(): ColumnDef<AccountModel>[] {
|
||||
<DataTableColumnHeader className="justify-center" column={column} title={t('accounts.enabled')} />
|
||||
),
|
||||
cell: EnableAction,
|
||||
meta: { className: 'w-18 text-center' },
|
||||
meta: { className: 'max-w-[86px] text-center' },
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
@@ -68,7 +70,7 @@ export function useColumns(): ColumnDef<AccountModel>[] {
|
||||
<DataTableColumnHeader column={column} title={t('accounts.auth')} />
|
||||
),
|
||||
cell: OAuth2Action,
|
||||
meta: { className: 'text-center' },
|
||||
meta: { className: 'text-center max-w-[86px]' },
|
||||
enableHiding: false,
|
||||
enableSorting: false
|
||||
},
|
||||
@@ -80,7 +82,7 @@ export function useColumns(): ColumnDef<AccountModel>[] {
|
||||
cell: ({ row }) => {
|
||||
return <LongText>{row.original.account_type}</LongText>
|
||||
},
|
||||
meta: { className: 'w-18' },
|
||||
meta: { className: 'text-center max-w-[60px]' },
|
||||
enableHiding: false,
|
||||
enableSorting: false
|
||||
},
|
||||
@@ -96,7 +98,7 @@ export function useColumns(): ColumnDef<AccountModel>[] {
|
||||
}
|
||||
return <LongText className="text-center">{row.original.sync_interval_min} min</LongText>
|
||||
},
|
||||
//meta: { className: 'w-18 text-center' },
|
||||
meta: { className: 'text-center max-w-[120px]' },
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import LongText from '@/components/long-text'
|
||||
|
||||
interface DataTableColumnHeaderProps<TData, TValue>
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
@@ -59,7 +60,7 @@ export function DataTableColumnHeader<TData, TValue>({
|
||||
size='sm'
|
||||
className=' h-8 data-[state=open]:bg-accent'
|
||||
>
|
||||
<span>{title}</span>
|
||||
<LongText className='max-w-[80px]'>{title}</LongText>
|
||||
{column.getIsSorted() === 'desc' ? (
|
||||
<ArrowDownIcon className='ml-2 h-4 w-4' />
|
||||
) : column.getIsSorted() === 'asc' ? (
|
||||
|
||||
@@ -67,7 +67,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
<span className='sr-only'>{t('accounts.openMenu')}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='end' className='w-[160px]'>
|
||||
<DropdownMenuContent align='end' className='w-[220px]'>
|
||||
{hasPermission && <DropdownMenuItem
|
||||
onClick={() => {
|
||||
setCurrentRow(row.original)
|
||||
@@ -90,7 +90,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
setOpen('sync-folders')
|
||||
}}
|
||||
>
|
||||
{t('accounts.syncFolders')}
|
||||
{t('accounts.selectMailboxes')}
|
||||
<DropdownMenuShortcut>
|
||||
<Mailbox size={16} />
|
||||
</DropdownMenuShortcut>
|
||||
|
||||
+3
-3
@@ -145,7 +145,7 @@ interface Props {
|
||||
currentRow: AccountModel
|
||||
}
|
||||
|
||||
export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
export function DownloadFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
const [selectedItems, setSelectedItems] = React.useState<string[]>([]);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
@@ -382,9 +382,9 @@ export function SyncFoldersDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-3xl max-h-[90vh] flex flex-col">
|
||||
<DialogHeader className="flex-shrink-0">
|
||||
<DialogTitle>{t('accounts.selectSyncFolders')}</DialogTitle>
|
||||
<DialogTitle>{t('accounts.selectMailboxes')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t('accounts.chooseFoldersToSync', { "email": currentRow.email })}
|
||||
{t('accounts.chooseMailboxesToDownload', { "email": currentRow.email })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2025-2026 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { ProgressMap } from "@/api/account/api";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
progressMap: ProgressMap | undefined | null;
|
||||
}
|
||||
|
||||
export function FolderSyncProgress({ progressMap }: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!progressMap || Object.keys(progressMap).length === 0) {
|
||||
return <div className="text-muted-foreground text-sm">{t('accounts.folderSync.noData')}</div>;
|
||||
}
|
||||
|
||||
const folderNames = Object.keys(progressMap);
|
||||
|
||||
if (folderNames.length === 0) {
|
||||
return <div className="text-muted-foreground text-sm">{t('accounts.folderSync.noFolders')}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{folderNames.map((folder) => {
|
||||
const progress = progressMap[folder];
|
||||
const percentage =
|
||||
progress.total_batches > 0
|
||||
? Math.min((progress.current_batch / progress.total_batches) * 100, 100)
|
||||
: 0;
|
||||
const isComplete = progress.current_batch >= progress.total_batches;
|
||||
const textColor = isComplete ? "text-green-800" : "text-blue-800";
|
||||
const progressColor = isComplete
|
||||
? "bg-gray-200 [&>div]:bg-green-800 [&>div]:rounded-full h-1.5"
|
||||
: "bg-gray-200 [&>div]:bg-blue-800 [&>div]:rounded-full h-1.5";
|
||||
|
||||
return (
|
||||
<div key={folder} className="space-y-1">
|
||||
<div className={`flex justify-between text-sm ${textColor}`}>
|
||||
<span className="truncate max-w-[70%]">{folder}</span>
|
||||
<span className="text-sm font-medium">
|
||||
{progress.current_batch}/{progress.total_batches} {t('accounts.folderSync.batches')}
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={percentage} className={progressColor} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -26,15 +26,27 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { account_state, AccountModel } from '@/api/account/api'
|
||||
import { formatDistanceToNow, formatDuration, intervalToDuration } from 'date-fns'
|
||||
import { account_state, AccountModel, FolderProgress } from '@/api/account/api'
|
||||
import { format } from 'date-fns'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { CheckCircle, Clock, Loader2, PlayCircle, FolderSync, FolderCheck } from 'lucide-react'
|
||||
import { FolderSyncProgress } from './folder-sync-progress'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion"
|
||||
import {
|
||||
CheckCircle,
|
||||
Clock,
|
||||
Loader2,
|
||||
Activity,
|
||||
AlertTriangle,
|
||||
Info,
|
||||
} from 'lucide-react'
|
||||
import LongText from '@/components/long-text'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { dateFnsLocaleMap } from '@/lib/utils'
|
||||
import { enUS } from 'date-fns/locale'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
@@ -42,241 +54,433 @@ interface Props {
|
||||
currentRow: AccountModel
|
||||
}
|
||||
|
||||
function StatusBadge({ status }: { status: string }) {
|
||||
const map: Record<string, string> = {
|
||||
Running: 'bg-blue-100 text-blue-700',
|
||||
Downloading: 'bg-blue-100 text-blue-700',
|
||||
Success: 'bg-green-100 text-green-700',
|
||||
Failed: 'bg-red-100 text-red-700',
|
||||
Cancelled: 'bg-gray-100 text-gray-700',
|
||||
Pending: 'bg-amber-100 text-amber-700',
|
||||
}
|
||||
return (
|
||||
<Badge variant="outline" className={`${map[status] || ''} border-none font-medium text-[11px] px-1.5 h-5 shrink-0`}>
|
||||
{status}
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
function TriggerBadge({ trigger }: { trigger: string }) {
|
||||
const isScheduled = trigger === 'Scheduled'
|
||||
return (
|
||||
<Badge variant="secondary" className={`${isScheduled ? 'bg-purple-50 text-purple-700 border-purple-100' : 'bg-orange-50 text-orange-700 border-orange-100'} font-normal text-xs shrink-0`}>
|
||||
{trigger}
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function FolderDetailItem({ f, t }: { f: FolderProgress, t: (key: string) => string }) {
|
||||
const percentage = Math.min(Math.round((f.current / f.planned) * 100), 100) || 0;
|
||||
|
||||
return (
|
||||
<div className="border rounded-xl bg-card overflow-hidden mb-3 shadow-sm border-slate-200">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center p-4 gap-3 sm:gap-4 bg-white">
|
||||
<div className="flex-1 min-w-0">
|
||||
<LongText className="text-xs font-bold text-slate-900 tracking-tight">
|
||||
{f.folder_name}
|
||||
</LongText>
|
||||
</div>
|
||||
<div className="flex items-center justify-between sm:justify-end gap-4">
|
||||
<div className="flex items-center gap-2 flex-1 sm:flex-initial">
|
||||
<div className="flex-1 sm:w-40 lg:w-48 bg-slate-100 rounded-full h-1.5 overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-500 bg-gradient-to-r from-blue-400 to-indigo-500"
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] font-medium text-slate-400 w-7 shrink-0 text-right">{percentage}%</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 shrink-0">
|
||||
<div className="w-20 sm:w-24 text-right font-mono text-xs sm:text-sm text-slate-500">
|
||||
<span className="font-bold text-slate-900">{f.current}</span>
|
||||
<span className="mx-0.5 opacity-50">/</span>
|
||||
{f.planned}
|
||||
</div>
|
||||
<div className="w-16 sm:w-20 flex justify-end">
|
||||
<StatusBadge status={f.status} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{f.message && (
|
||||
<div className="px-4 pb-4">
|
||||
<div className="bg-slate-50 border border-slate-100 rounded-lg p-3 flex gap-3 items-start">
|
||||
<Info className="w-4 h-4 text-slate-400 mt-0.5 shrink-0" />
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-[10px] font-bold text-slate-900">{t('accounts.runningState.message')}:</p>
|
||||
<p className="text-[10px] font-medium text-slate-600 leading-relaxed">{f.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function RunningStateDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
const { t, i18n } = useTranslation()
|
||||
const locale = dateFnsLocaleMap[i18n.language.toLowerCase()] ?? enUS;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data: state, isLoading } = useQuery({
|
||||
queryKey: ['running-state', currentRow.id],
|
||||
queryFn: () => account_state(currentRow.id),
|
||||
retry: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchInterval: 5000,
|
||||
enabled: open && !!currentRow.id && currentRow.account_type != "NoSync",
|
||||
enabled: open && !!currentRow.id,
|
||||
})
|
||||
|
||||
const calculateDuration = (start?: number, end?: number) => {
|
||||
if (!start) {
|
||||
return (
|
||||
<span className="text-yellow-600 flex items-center gap-1">
|
||||
<Clock className="w-4 h-4" /> {t('accounts.runningState.notStarted')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
if (!end) {
|
||||
return (
|
||||
<span className="text-blue-600 flex items-center gap-1">
|
||||
<PlayCircle className="w-4 h-4" /> {t('accounts.runningState.inProgress')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
const duration = intervalToDuration({ start: new Date(start), end: new Date(end) })
|
||||
return (
|
||||
<span className="text-green-600 flex items-center gap-1">
|
||||
<CheckCircle className="w-4 h-4" /> {formatDuration(duration, { format: ['hours', 'minutes', 'seconds'] })}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
const session = state?.active_session
|
||||
const history = state?.history || []
|
||||
const globalErrors = state?.global_errors || []
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="w-full max-w-7xl sm:rounded-xl p-0 overflow-hidden max-h-[90vh]">
|
||||
<DialogHeader className="text-left space-y-2 px-4 sm:px-6 pt-4 sm:pt-6">
|
||||
<DialogTitle className="flex flex-wrap items-center gap-2 text-base sm:text-lg">
|
||||
<span className="text-blue-500 font-medium truncate">{currentRow.email}</span>
|
||||
</DialogTitle>
|
||||
<DialogContent className="max-w-5xl w-[95vw] sm:w-full p-0 flex flex-col h-[90vh] sm:h-[85vh] overflow-hidden gap-0 rounded-t-2xl sm:rounded-xl">
|
||||
<DialogHeader className="px-4 py-3 sm:px-6 sm:py-4 border-b bg-muted/20 shrink-0">
|
||||
<div className="space-y-0.5">
|
||||
<DialogTitle className="text-base sm:text-xl font-bold flex items-center gap-2 text-blue-600 truncate">
|
||||
<Activity className="w-4 h-4 sm:w-5 sm:h-5 shrink-0" />
|
||||
<span className="truncate">{currentRow.email}</span>
|
||||
</DialogTitle>
|
||||
<p className="text-[9px] sm:text-[11px] text-muted-foreground font-mono uppercase tracking-widest opacity-70">
|
||||
{t('accounts.runningState.account.id')}: {currentRow.id}
|
||||
</p>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="max-h-[calc(90vh-8rem)] px-4 sm:px-6 pb-6">
|
||||
{isLoading && (
|
||||
<div className="space-y-4 py-6">
|
||||
<Skeleton className="h-6 w-1/2" />
|
||||
<Skeleton className="h-6 w-1/3" />
|
||||
<div className="flex justify-center items-center py-4">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && state && (
|
||||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-4 sm:gap-6 mt-4">
|
||||
<div className="xl:col-span-2 space-y-1 sm:space-y-1">
|
||||
{/* Initial Sync */}
|
||||
<div className="p-4 border rounded-lg bg-card">
|
||||
<div className="flex flex-wrap items-center justify-between mb-3 gap-2">
|
||||
<h3 className="text-base sm:text-lg font-semibold flex items-center gap-2">
|
||||
{state.is_initial_sync_completed ? (
|
||||
<FolderCheck className="w-5 h-5 text-green-500" />
|
||||
<Tabs defaultValue="active" className="flex-1 flex flex-col min-h-0">
|
||||
<div className="px-4 sm:px-6 border-b bg-white shrink-0 overflow-x-auto no-scrollbar">
|
||||
<TabsList className="h-12 w-full justify-start bg-transparent p-0 gap-6 sm:gap-8 flex-nowrap">
|
||||
<TabsTrigger value="active" className="whitespace-nowrap data-[state=active]:border-b-2 data-[state=active]:border-blue-600 rounded-none h-full bg-transparent shadow-none px-0 text-xs sm:text-sm font-bold">
|
||||
{t('accounts.runningState.tabs.active_session')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="history" className="whitespace-nowrap data-[state=active]:border-b-2 data-[state=active]:border-blue-600 rounded-none h-full bg-transparent shadow-none px-0 text-xs sm:text-sm font-bold">
|
||||
{t('accounts.runningState.tabs.history')}
|
||||
<Badge variant="secondary" className="ml-2 h-4 px-1 text-[10px] font-bold">{history.length}</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="errors" className="whitespace-nowrap data-[state=active]:border-b-2 data-[state=active]:border-blue-600 rounded-none h-full bg-transparent shadow-none px-0 text-xs sm:text-sm font-bold">
|
||||
{t('accounts.runningState.tabs.global_errors')}
|
||||
{globalErrors.length > 0 && <Badge variant="destructive" className="ml-2 h-4 px-1 text-[10px] font-bold">{globalErrors.length}</Badge>}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 bg-slate-50/50 min-h-0 overflow-hidden relative">
|
||||
{isLoading ? (
|
||||
<div className="h-full flex flex-col items-center justify-center">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-blue-500 mb-2" />
|
||||
<p className="text-sm text-slate-500 font-medium italic">{t('accounts.runningState.loading.fetching_account_state')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<TabsContent value="active" className="h-full m-0 data-[state=active]:flex flex-col">
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="p-4 sm:p-6">
|
||||
{!session ? (
|
||||
<div className="flex flex-col items-center justify-center py-24 text-muted-foreground">
|
||||
<Clock className="w-12 h-12 opacity-10 mb-4" />
|
||||
<p className="italic text-sm">{t('accounts.runningState.empty.no_active_download')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<FolderSync className="w-5 h-5 text-blue-500" />
|
||||
)}
|
||||
{t('accounts.runningState.initialSync')}
|
||||
</h3>
|
||||
{state.is_initial_sync_completed ? (
|
||||
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded-full">
|
||||
{t('accounts.runningState.completed')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full">
|
||||
{t('accounts.runningState.inProgress')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-6 w-full">
|
||||
{/* Start Time */}
|
||||
<div className="flex flex-col text-sm">
|
||||
<span className="text-muted-foreground">{t('accounts.runningState.startTime')}</span>
|
||||
<span className="font-medium">
|
||||
{state.initial_sync_start_time ? (
|
||||
<span className="text-green-600">
|
||||
{formatDistanceToNow(new Date(state.initial_sync_start_time), { addSuffix: true, locale })}
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-1 text-yellow-600">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-yellow-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-yellow-500"></span>
|
||||
</span>
|
||||
{t('accounts.runningState.notStarted')}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{/* End Time */}
|
||||
<div className="flex flex-col text-sm">
|
||||
<span className="text-muted-foreground">{t('accounts.runningState.endTime')}</span>
|
||||
<span className="font-medium">
|
||||
{state.initial_sync_end_time ? (
|
||||
<span className="text-green-600">
|
||||
{formatDistanceToNow(new Date(state.initial_sync_end_time), { addSuffix: true, locale })}
|
||||
</span>
|
||||
) : state.initial_sync_start_time ? (
|
||||
<span className="flex items-center gap-1 text-blue-600">
|
||||
{t('accounts.runningState.inProgress')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-yellow-600">{t('accounts.runningState.notStarted')}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{/* Duration */}
|
||||
<div className="flex flex-col text-sm">
|
||||
<span className="text-muted-foreground">{t('accounts.runningState.duration')}</span>
|
||||
<span className="font-medium">
|
||||
{(() => {
|
||||
if (!state.initial_sync_start_time) return <span className="text-yellow-600">{t('accounts.runningState.notStarted')}</span>
|
||||
if (!state.initial_sync_end_time) return <span className="text-blue-600 animate-pulse">{t('accounts.runningState.calculating')}</span>
|
||||
const diff = new Date(state.initial_sync_end_time).getTime() - new Date(state.initial_sync_start_time).getTime()
|
||||
const h = Math.floor(diff / 3600000)
|
||||
const m = Math.floor((diff % 3600000) / 60000)
|
||||
const s = Math.floor((diff % 60000) / 1000)
|
||||
return <span className="text-foreground">{h}h {m}m {s}s</span>
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 border rounded-lg bg-card">
|
||||
{state && (
|
||||
<div>
|
||||
<h3 className="text-base sm:text-lg font-semibold mb-3">{t('accounts.runningState.syncProgress')}</h3>
|
||||
<ScrollArea className="h-70 sm:h-70 border rounded-md p-2">
|
||||
<FolderSyncProgress progressMap={state.progress} />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Incremental Sync */}
|
||||
<div className="p-4 border rounded-lg bg-card">
|
||||
<h3 className="text-base sm:text-lg font-semibold mb-3">{t('accounts.runningState.incrementalSync')}</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('accounts.runningState.startTime')}</span>
|
||||
<span className="font-medium">
|
||||
{state.last_incremental_sync_start ? (
|
||||
formatDistanceToNow(new Date(state.last_incremental_sync_start), { addSuffix: true, locale })
|
||||
) : (
|
||||
<span className="text-yellow-600">{t('accounts.runningState.notStarted')}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('accounts.runningState.endTime')}</span>
|
||||
<span className="font-medium">
|
||||
{state.last_incremental_sync_end ? (
|
||||
formatDistanceToNow(new Date(state.last_incremental_sync_end), { addSuffix: true, locale })
|
||||
) : state.last_incremental_sync_start ? (
|
||||
<span className="text-blue-600">{t('accounts.runningState.inProgress')}</span>
|
||||
) : (
|
||||
<span className="text-yellow-600">{t('accounts.runningState.notStarted')}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('accounts.runningState.duration')}</span>
|
||||
<span className="font-medium">
|
||||
{calculateDuration(state.last_incremental_sync_start, state.last_incremental_sync_end)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">{t('accounts.runningState.syncInterval')}</span>
|
||||
<span className="font-medium">
|
||||
{t('accounts.runningState.everyMinutes', { minutes: currentRow.sync_interval_min })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 border rounded-lg bg-card">
|
||||
<h3 className="text-base sm:text-lg font-semibold mb-3">{t('accounts.runningState.errorLogs')}</h3>
|
||||
<ScrollArea className="h-[20rem] sm:h-[32rem]">
|
||||
<div className="space-y-3">
|
||||
{state.errors.length ? (
|
||||
state.errors
|
||||
.sort((a, b) => b.at - a.at)
|
||||
.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-xs sm:text-sm transition-all hover:bg-accent"
|
||||
>
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
<div className="text-xs font-medium text-muted-foreground">
|
||||
{formatDistanceToNow(new Date(item.at), { addSuffix: true, locale })}
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-2">
|
||||
<div className="p-3 sm:p-4 rounded-xl border bg-white shadow-sm flex items-center justify-between sm:block">
|
||||
<p className="text-[10px] font-bold text-muted-foreground uppercase mb-1">{t('accounts.runningState.session.status')}</p>
|
||||
<StatusBadge status={session.status} />
|
||||
</div>
|
||||
<div className="p-3 sm:p-4 rounded-xl border bg-white shadow-sm flex items-center justify-between sm:block">
|
||||
<p className="text-[10px] font-bold text-muted-foreground uppercase mb-1">{t('accounts.runningState.session.trigger')}</p>
|
||||
<TriggerBadge trigger={session.trigger} />
|
||||
</div>
|
||||
<div className="p-3 sm:p-4 rounded-xl border bg-white shadow-sm flex items-center justify-between sm:block">
|
||||
<p className="text-[10px] font-bold text-muted-foreground uppercase mb-1">{t('accounts.runningState.session.started_at')}</p>
|
||||
<div className="text-sm font-bold font-mono">
|
||||
{format(new Date(session.start_time), 'yyyy-MM-dd HH:mm:ss')}
|
||||
</div>
|
||||
<div className="font-medium break-words">{item.error}</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="h-full flex justify-center items-center py-8">
|
||||
<p className="text-sm text-muted-foreground">{t('accounts.runningState.noErrorLogs')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Tabs defaultValue="folders" className="w-full">
|
||||
<TabsList className="bg-slate-100 mb-3 h-8">
|
||||
<TabsTrigger value="folders" className="text-[11px] font-bold">
|
||||
{t('accounts.runningState.tabs.folders')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="errors"
|
||||
className="text-[11px] font-bold text-red-600"
|
||||
>
|
||||
{t('accounts.runningState.tabs.errors')} ({session.errors?.length || 0})
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="folders" className="space-y-1">
|
||||
{Object.values(session.folder_details).map((f: FolderProgress) => (
|
||||
<FolderDetailItem key={f.folder_name} f={f} t={t} />
|
||||
))}
|
||||
</TabsContent>
|
||||
<TabsContent value="errors">
|
||||
{(!session.errors || session.errors.length === 0) ? (
|
||||
<div className="text-center py-10 text-slate-400 italic text-xs">
|
||||
{t('accounts.runningState.empty.no_errors_current')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative">
|
||||
<div className="absolute left-[14px] top-1 bottom-1 w-0.5 bg-red-100" />
|
||||
<div className="space-y-4">
|
||||
{[...session.errors]
|
||||
.sort((a, b) => new Date(b.at).getTime() - new Date(a.at).getTime())
|
||||
.map((err, ei) => (
|
||||
<div key={ei} className="relative pl-8 min-w-0">
|
||||
<div className="absolute left-0 top-2 w-[28px] flex justify-center">
|
||||
{ei === 0 ? (
|
||||
<span className="relative flex h-2.5 w-2.5">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75" />
|
||||
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-red-600" />
|
||||
</span>
|
||||
) : (
|
||||
<div className="w-2 h-2 rounded-full bg-red-200 border border-white" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
p-3 border rounded-lg flex flex-col gap-2 min-w-0
|
||||
${ei === 0
|
||||
? 'bg-red-50 border-red-200 ring-1 ring-red-100'
|
||||
: 'bg-white border-red-100'}
|
||||
`}
|
||||
>
|
||||
<div className="flex justify-between items-start gap-2 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap min-w-0">
|
||||
{ei === 0 && (
|
||||
<Badge className="bg-red-600 text-[9px] h-4 px-1">
|
||||
{t('accounts.runningState.latest')}
|
||||
</Badge>
|
||||
)}
|
||||
<span className="text-[10px] font-mono text-red-400 font-bold break-all">
|
||||
{format(new Date(err.at), 'yyyy-MM-dd HH:mm:ss')}
|
||||
</span>
|
||||
</div>
|
||||
<AlertTriangle
|
||||
className={`w-4 h-4 shrink-0 ${ei === 0 ? 'text-red-600' : 'text-red-300'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs font-bold text-red-950 whitespace-pre-wrap break-all">
|
||||
{err.error}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
<TabsContent value="history" className="h-full m-0 data-[state=active]:flex flex-col">
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="p-4 sm:p-6">
|
||||
{history.length === 0 ? (
|
||||
<div className="text-center py-20 text-slate-400 italic text-sm">{t('accounts.runningState.empty.no_history')}</div>
|
||||
) : (
|
||||
<Accordion type="single" collapsible className="space-y-3">
|
||||
{[...history].reverse().map((h, i) => (
|
||||
<AccordionItem key={i} value={`history-${i}`} className="border rounded-xl bg-white shadow-sm px-4 border-slate-200 overflow-hidden">
|
||||
<AccordionTrigger className="hover:no-underline py-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between w-full pr-4 gap-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-xs sm:text-xs font-bold font-mono text-slate-700">
|
||||
{format(new Date(h.start_time), 'yyyy-MM-dd HH:mm:ss')}
|
||||
</div>
|
||||
<StatusBadge status={h.status} />
|
||||
<div className="hidden xs:block"><TriggerBadge trigger={h.trigger} /></div>
|
||||
</div>
|
||||
<span className="text-[10px] font-bold text-slate-400 bg-slate-50 px-2 py-0.5 rounded-full self-start sm:self-auto">
|
||||
{Object.keys(h.folder_details).length} {t('accounts.runningState.folders')}
|
||||
<span className="mx-1 opacity-30">·</span>
|
||||
{Object.values(h.folder_details).reduce((sum, f) => sum + (f.current || 0), 0)}
|
||||
<span className="mx-0.5 opacity-50">/</span>
|
||||
{Object.values(h.folder_details).reduce((sum, f) => sum + (f.planned || 0), 0)}
|
||||
</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="pb-4 border-t pt-4 mt-1">
|
||||
<Tabs defaultValue="h-folders" className="w-full">
|
||||
<TabsList className="bg-slate-100 mb-4 h-8">
|
||||
<TabsTrigger value="h-folders" className="text-[11px] font-bold">{t('accounts.runningState.tabs.folders')}</TabsTrigger>
|
||||
<TabsTrigger value="h-errors" className="text-[11px] font-bold text-red-600">
|
||||
{t('accounts.runningState.tabs.errors')} ({h.errors?.length || 0})
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="h-folders" className="space-y-1">
|
||||
{Object.values(h.folder_details).map((f: any, idx) => (
|
||||
<FolderDetailItem key={idx} f={f} t={t} />
|
||||
))}
|
||||
</TabsContent>
|
||||
<TabsContent value="h-errors">
|
||||
{(!h.errors || h.errors.length === 0) ? (
|
||||
<div className="text-center py-10 text-slate-400 italic text-xs">
|
||||
{t('accounts.runningState.empty.no_errors_session')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative">
|
||||
<div className="absolute left-[14px] top-1 bottom-1 w-0.5 bg-red-100" />
|
||||
<div className="space-y-4">
|
||||
{[...h.errors]
|
||||
.sort((a, b) => new Date(b.at).getTime() - new Date(a.at).getTime())
|
||||
.map((err, ei) => (
|
||||
<div key={ei} className="relative pl-8 min-w-0">
|
||||
<div className="absolute left-0 top-2 w-[28px] flex justify-center">
|
||||
{ei === 0 ? (
|
||||
<span className="relative flex h-2.5 w-2.5">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-red-600"></span>
|
||||
</span>
|
||||
) : (
|
||||
<div className="w-2 h-2 rounded-full bg-red-200 border border-white" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
p-3 border rounded-lg flex flex-col gap-2 min-w-0
|
||||
${ei === 0
|
||||
? 'bg-red-50 border-red-200 ring-1 ring-red-100'
|
||||
: 'bg-white border-red-100'}
|
||||
`}
|
||||
>
|
||||
<div className="flex justify-between items-start gap-2 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap min-w-0">
|
||||
{ei === 0 && (
|
||||
<Badge className="bg-red-600 text-[9px] h-4 px-1">
|
||||
{t('accounts.runningState.latest')}
|
||||
</Badge>
|
||||
)}
|
||||
<span className="text-[10px] font-mono text-red-400 font-bold break-all">
|
||||
{format(new Date(err.at), 'yyyy-MM-dd HH:mm:ss')}
|
||||
</span>
|
||||
</div>
|
||||
<AlertTriangle
|
||||
className={`w-4 h-4 shrink-0 ${ei === 0 ? 'text-red-600' : 'text-red-300'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs font-bold whitespace-pre-wrap break-all text-red-950">
|
||||
{err.error}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
<TabsContent value="errors" className="h-full m-0 data-[state=active]:flex flex-col">
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="p-4 sm:p-6">
|
||||
{globalErrors.length === 0 ? (
|
||||
<div className="py-32 text-center text-slate-300">
|
||||
<CheckCircle className="w-12 h-12 mx-auto mb-2 opacity-20" />
|
||||
<p className="font-medium italic text-sm">{t('accounts.runningState.empty.no_global_errors')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative">
|
||||
<div className="absolute left-[19px] top-2 bottom-2 w-0.5 bg-red-100" />
|
||||
<div className="space-y-6">
|
||||
{[...globalErrors]
|
||||
.sort((a, b) => new Date(b.at).getTime() - new Date(a.at).getTime())
|
||||
.map((e, i) => (
|
||||
<div key={i} className="relative pl-10 min-w-0">
|
||||
<div className="absolute left-0 top-1.5 w-[40px] flex justify-center">
|
||||
{i === 0 ? (
|
||||
<span className="relative flex h-3 w-3">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-3 w-3 bg-red-600"></span>
|
||||
</span>
|
||||
) : (
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-red-200 border-2 border-white mt-0.5" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
p-4 border rounded-2xl shadow-sm transition-all min-w-0
|
||||
${i === 0
|
||||
? 'border-red-200 bg-red-50/80 ring-1 ring-red-100'
|
||||
: 'border-slate-100 bg-white opacity-80'}
|
||||
`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3 mb-2 min-w-0">
|
||||
<div className="flex items-center gap-1.5 flex-wrap min-w-0">
|
||||
{i === 0 && (
|
||||
<Badge className="bg-red-600 hover:bg-red-600 text-[9px] h-4 px-1">
|
||||
{t('accounts.runningState.latest')}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{!isLoading && !state && (
|
||||
<div className="h-full flex flex-col justify-center items-center py-8 text-center space-y-2">
|
||||
<p className="text-sm text-muted-foreground">{t('accounts.runningState.noActiveState')}</p>
|
||||
<p className="text-xs text-muted-foreground">{t('accounts.runningState.accountNotStarted')}</p>
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
<div className="text-[10px] font-mono font-bold text-red-500 bg-red-50 px-1.5 py-0.5 rounded break-all">
|
||||
<span className="sm:hidden">
|
||||
{format(new Date(e.at), 'HH:mm')}
|
||||
</span>
|
||||
<span className="hidden sm:inline">
|
||||
{format(new Date(e.at), 'yyyy-MM-dd HH:mm:ss')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="pt-4 px-4 sm:px-6 pb-4 border-t">
|
||||
<AlertTriangle
|
||||
className={`w-4 h-4 shrink-0 ${i === 0 ? 'text-red-600' : 'text-red-300'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p
|
||||
className={`
|
||||
text-xs font-bold leading-relaxed whitespace-pre-wrap break-all min-w-0
|
||||
${i === 0 ? 'text-red-950' : 'text-slate-600'}
|
||||
`}
|
||||
>
|
||||
{e.error}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Tabs>
|
||||
<DialogFooter className="px-4 py-3 sm:px-6 sm:py-4 border-t bg-card shrink-0">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline" className="w-full sm:w-auto">{t('accounts.runningState.close')}</Button>
|
||||
<Button variant="outline" className="w-full sm:w-24 font-bold border-2">{t('common.close')}</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { OAuth2TokensDialog } from './components/oauth2-tokens'
|
||||
import { RunningStateDialog } from './components/running-state-dialog'
|
||||
import { FixedHeader } from '@/components/layout/fixed-header'
|
||||
import { SyncFoldersDialog } from './components/sync-folders'
|
||||
import { DownloadFoldersDialog } from './components/download-folders'
|
||||
import { NoSyncAccountDialog } from './components/nosync-dialog'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -192,7 +192,7 @@ export default function Accounts() {
|
||||
}}
|
||||
currentRow={currentRow}
|
||||
/>
|
||||
<SyncFoldersDialog
|
||||
<DownloadFoldersDialog
|
||||
key={`sync-folders-${currentRow.id}`}
|
||||
open={open === 'sync-folders'}
|
||||
onOpenChange={() => {
|
||||
|
||||
@@ -8,21 +8,13 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, BarChart, Bar } from 'recharts';
|
||||
import { Mail, HardDrive, Database, Users, Inbox, Info } from 'lucide-react';
|
||||
import { Mail, Users, Inbox, Zap } from 'lucide-react';
|
||||
import { formatBytes, formatNumber } from '@/lib/utils';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { get_dashboard_stats, INITIAL_DASHBOARD_STATS, TimeBucket } from '@/api/system/api';
|
||||
@@ -32,12 +24,32 @@ import { useTranslation } from 'react-i18next';
|
||||
import { getToken } from '@/stores/authStore';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import useMinimalAccountList from '@/hooks/use-minimal-account-list';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
interface DailyActivity {
|
||||
date: string;
|
||||
count: number;
|
||||
timestamp_ms: number;
|
||||
}
|
||||
|
||||
const GithubIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
function convertRecentActivity(timeBuckets: TimeBucket[], locale: string): DailyActivity[] {
|
||||
const dateFormatter = new Intl.DateTimeFormat(locale, {
|
||||
month: 'short',
|
||||
@@ -65,7 +77,6 @@ const formatTooltipDate = (timestamp_ms: number, locale: string): string => {
|
||||
|
||||
const COLORS = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444'];
|
||||
|
||||
// Skeleton Components
|
||||
const MetricCardSkeleton = () => (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
@@ -79,17 +90,6 @@ const MetricCardSkeleton = () => (
|
||||
</Card>
|
||||
);
|
||||
|
||||
const ChartSkeleton = () => (
|
||||
<div className="h-80 w-full flex items-center justify-center">
|
||||
<div className="space-y-2 w-full px-4">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<Skeleton key={i} className="h-12 w-full" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Empty State Components
|
||||
const EmptyChart = ({ title }: { title: string }) => (
|
||||
<div className="h-80 flex flex-col items-center justify-center text-muted-foreground">
|
||||
<Inbox className="h-12 w-12 mb-3 opacity-40" />
|
||||
@@ -113,12 +113,22 @@ export default function MailArchiveDashboard() {
|
||||
placeholderData: INITIAL_DASHBOARD_STATS,
|
||||
});
|
||||
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const currentLocale = i18n.resolvedLanguage || i18n.language || navigator.language;
|
||||
|
||||
const logicalSize = stats?.total_size_bytes ?? 0;
|
||||
const blobSize = stats?.storage_usage_bytes ?? 0;
|
||||
const indexSize = stats?.index_usage_bytes ?? 0;
|
||||
const physicalTotal = blobSize + indexSize;
|
||||
|
||||
const savingsPercent = logicalSize > 0
|
||||
? Math.max(0, ((1 - physicalTotal / logicalSize) * 100)).toFixed(1)
|
||||
: "0.0";
|
||||
|
||||
const blobWidth = logicalSize > 0 ? (blobSize / logicalSize) * 100 : 0;
|
||||
const indexWidth = logicalSize > 0 ? (indexSize / logicalSize) * 100 : 0;
|
||||
|
||||
const totalAttachments = (stats?.with_attachment_count ?? 0) + (stats?.without_attachment_count ?? 0);
|
||||
const attachmentRatio = totalAttachments > 0 ? (stats?.with_attachment_count ?? 0) / totalAttachments : 0;
|
||||
|
||||
@@ -134,8 +144,6 @@ export default function MailArchiveDashboard() {
|
||||
return account ? account.id : null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleQuickSearch = (filter: Record<string, any>) => {
|
||||
navigate({
|
||||
to: '/search',
|
||||
@@ -161,32 +169,10 @@ export default function MailArchiveDashboard() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex-1 space-y-6 p-6 md:p-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Skeleton className="h-9 w-48 mb-2" />
|
||||
<Skeleton className="h-4 w-64" />
|
||||
</div>
|
||||
<Skeleton className="h-7 w-28 rounded-full" />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-6">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<MetricCardSkeleton key={i} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Skeleton className="h-10 w-full max-w-md" />
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Skeleton className="h-6 w-48 mb-2" />
|
||||
<Skeleton className="h-4 w-64" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartSkeleton />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{[...Array(4)].map((_, i) => <MetricCardSkeleton key={i} />)}
|
||||
</div>
|
||||
<Skeleton className="h-80 w-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -196,82 +182,92 @@ export default function MailArchiveDashboard() {
|
||||
<FixedHeader />
|
||||
<Main higher>
|
||||
<div className="flex-1 space-y-6 p-6 md:p-8">
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-6">
|
||||
<Card>
|
||||
<div className="grid gap-4 grid-cols-1 md:grid-cols-12">
|
||||
<Card className="md:col-span-2 lg:col-span-2">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">{t('dashboard.mailAccounts')}</CardTitle>
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{formatNumber(stats!.account_count)}</div>
|
||||
<div className="text-4xl font-bold">{formatNumber(stats!.account_count)}</div>
|
||||
<p className="text-xs text-muted-foreground">{t('dashboard.connected')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card className="md:col-span-3 lg:col-span-3">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">{t('dashboard.totalEmails')}</CardTitle>
|
||||
<Mail className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{formatNumber(stats!.email_count)}</div>
|
||||
<div className="text-4xl font-bold">{formatNumber(stats!.email_count)}</div>
|
||||
<p className="text-xs text-muted-foreground">{t('dashboard.syncedLocally')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card className="border-primary/20 bg-primary/[0.01] md:col-span-4 lg:col-span-4">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">{t('dashboard.totalEmailSize')}</CardTitle>
|
||||
<HardDrive className="h-4 w-4 text-muted-foreground" />
|
||||
<CardTitle className="text-sm font-bold text-primary flex items-center gap-1 uppercase">
|
||||
<Zap className="h-3.5 w-3.5 fill-primary" />
|
||||
{t('dashboard.efficiency', 'Efficiency')}
|
||||
</CardTitle>
|
||||
<div className="flex flex-col items-end leading-none">
|
||||
<span className="text-xl font-black text-primary">{savingsPercent}%</span>
|
||||
<span className="text-[9px] font-bold text-primary uppercase">{t('dashboard.saved', 'Saved')}</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{formatBytes(stats!.total_size_bytes)}</div>
|
||||
<p className="text-xs text-muted-foreground">{t('dashboard.logicalVolume')}</p>
|
||||
<CardContent className="py-2.5 space-y-2">
|
||||
<div className="h-2 w-full bg-muted rounded-full overflow-hidden flex">
|
||||
<div className="h-full bg-primary transition-all" style={{ width: `${blobWidth}%` }} />
|
||||
<div className="h-full bg-orange-400 transition-all" style={{ width: `${indexWidth}%` }} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex justify-between items-center text-[11px]">
|
||||
<div className="flex items-center gap-1.5 overflow-hidden">
|
||||
<span className="text-muted-foreground uppercase text-[9px] shrink-0">{t('dashboard.logicalVolume')}:</span>
|
||||
<span className="font-bold tracking-tight truncate">{formatBytes(logicalSize)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 overflow-hidden ml-2">
|
||||
<span className="text-muted-foreground uppercase text-[9px] shrink-0">{t('dashboard.actualDiskUsage')}:</span>
|
||||
<span className="font-bold tracking-tight truncate">{formatBytes(physicalTotal)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-[10px]">
|
||||
<div className="flex items-center gap-1.5 overflow-hidden">
|
||||
<div className="h-1.5 w-1.5 shrink-0 rounded-full bg-primary" />
|
||||
<span className="text-muted-foreground truncate">
|
||||
{t('dashboard.dataStorage')}: <span className="text-foreground font-medium">{formatBytes(blobSize)}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 overflow-hidden ml-2">
|
||||
<div className="h-1.5 w-1.5 shrink-0 rounded-full bg-orange-400" />
|
||||
<span className="text-muted-foreground truncate text-right">
|
||||
{t('dashboard.indexSize')}: <span className="text-foreground font-medium">{formatBytes(indexSize)}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">{t('dashboard.localDataFiles')}</CardTitle>
|
||||
<Database className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{formatBytes(stats!.storage_usage_bytes)}</div>
|
||||
<p className="text-xs text-muted-foreground">{t('dashboard.actualDiskUsage')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">{t('dashboard.metadataSize')}</CardTitle>
|
||||
<Database className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{formatBytes(stats!.index_usage_bytes)}</div>
|
||||
<p className="text-xs text-muted-foreground">{t('dashboard.duckdbDatabase')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<Card className="md:col-span-3 lg:col-span-3">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">{t('dashboard.systemVersion')}</CardTitle>
|
||||
<Info className="h-4 w-4 text-muted-foreground" />
|
||||
<Badge variant="secondary" className="text-[9px] h-4.5 font-bold px-1.5 uppercase tracking-wider">Community</Badge>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className='text-2xl font-bold'>
|
||||
<div className='text-4xl font-bold truncate text-primary tracking-tighter'>
|
||||
{stats!.system_version ? (
|
||||
<a
|
||||
href={`https://github.com/rustmailer/bichon/releases/tag/${stats!.system_version}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:underline"
|
||||
>
|
||||
<a href={`https://github.com/rustmailer/bichon/releases/tag/${stats!.system_version}`} target="_blank" rel="noopener noreferrer" className="hover:underline">
|
||||
{stats!.system_version}
|
||||
</a>
|
||||
) : 'N/A'}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{stats!.commit_hash ?? 'N/A'}
|
||||
</p>
|
||||
<div className="flex items-center gap-1.5 mt-1">
|
||||
<GithubIcon className="h-5 w-5 text-muted-foreground" />
|
||||
<p className="text-[10px] text-muted-foreground font-mono truncate">{stats!.commit_hash?.substring(0, 7) ?? 'N/A'}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -293,7 +289,7 @@ export default function MailArchiveDashboard() {
|
||||
{hasRecentActivity ? (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={convertRecentActivity(stats!.recent_activity, currentLocale)} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={false} opacity={0.3} />
|
||||
<XAxis dataKey="date" tick={{ fontSize: 12 }} interval="preserveStart" tickCount={10} />
|
||||
<YAxis tick={{ fontSize: 12 }} />
|
||||
<Tooltip
|
||||
@@ -311,13 +307,8 @@ export default function MailArchiveDashboard() {
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
contentStyle={{
|
||||
backgroundColor: 'hsl(var(--background))',
|
||||
border: '1px solid hsl(var(--border))',
|
||||
borderRadius: '8px'
|
||||
}}
|
||||
/>
|
||||
<Bar dataKey="count" fill="#3b82f6" radius={[8, 8, 0, 0]} barSize={36} />
|
||||
<Bar dataKey="count" fill="currentColor" className="text-primary" radius={[4, 4, 0, 0]} barSize={28} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
@@ -348,6 +339,7 @@ export default function MailArchiveDashboard() {
|
||||
outerRadius={100}
|
||||
paddingAngle={5}
|
||||
dataKey="value"
|
||||
stroke="none"
|
||||
>
|
||||
{attachmentData.map((entry, i) => (
|
||||
<Cell key={`cell-${i}`} fill={entry.fill} />
|
||||
@@ -356,67 +348,30 @@ export default function MailArchiveDashboard() {
|
||||
<Tooltip formatter={(v) => totalAttachments > 0 ? `${((v as number) * 100).toFixed(1)}%` : '0%'} />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
<div className="ml-8 space-y-2">
|
||||
{totalAttachments > 0 ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-3 w-3 rounded-full bg-green-500"></div>
|
||||
<span className="text-sm">
|
||||
{t('dashboard.withAttachments')} ({(attachmentRatio * 100).toFixed(1)}%)
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-3 w-3 rounded-full bg-gray-200"></div>
|
||||
<span className="text-sm">{t('dashboard.noAttachments')}</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-3 w-3 rounded-full bg-gray-200"></div>
|
||||
<span className="text-sm">{t('common.noData')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="top" className="space-y-6">
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card className="md:col-span-1">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('dashboard.top10Senders')}</CardTitle>
|
||||
<CardDescription>{t('dashboard.byMessageCount')}</CardDescription>
|
||||
<CardTitle className="text-sm font-bold uppercase">{t('dashboard.top10Senders')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent className="p-0">
|
||||
{hasTopSenders ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t('dashboard.sender')}</TableHead>
|
||||
<TableHead className="text-right">{t('dashboard.count')}</TableHead>
|
||||
<TableHead className="text-xs">{t('dashboard.sender')}</TableHead>
|
||||
<TableHead className="text-right text-xs">{t('dashboard.count')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{stats!.top_senders.map((s) => (
|
||||
<TableRow
|
||||
key={s.key}
|
||||
className="cursor-pointer hover:bg-accent/50 group relative"
|
||||
onClick={() => handleQuickSearch({ from: s.key })}
|
||||
>
|
||||
<TableCell className="font-medium max-w-[380px] truncate relative px-4 h-9">
|
||||
<div className="absolute left-0 top-2 bottom-2 w-0.5 bg-primary opacity-0 group-hover:opacity-100 transition-opacity duration-150" />
|
||||
<div className="absolute inset-x-4 bottom-1 h-[1px] bg-primary scale-x-0 group-hover:scale-x-100 transition-transform duration-200 origin-left" />
|
||||
<span
|
||||
title={s.key}
|
||||
className="group-hover:text-primary transition-colors duration-200"
|
||||
>
|
||||
{s.key}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatNumber(s.count)}
|
||||
</TableCell>
|
||||
<TableRow key={s.key} className="cursor-pointer hover:bg-accent/50 group" onClick={() => handleQuickSearch({ from: s.key })}>
|
||||
<TableCell className="max-w-[200px] truncate text-sm group-hover:text-primary transition-colors">{s.key}</TableCell>
|
||||
<TableCell className="text-right font-mono text-sm">{formatNumber(s.count)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
@@ -426,42 +381,25 @@ export default function MailArchiveDashboard() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="md:col-span-1">
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('dashboard.top10LargestEmails')}</CardTitle>
|
||||
<CardDescription>{t('dashboard.byMessageSize')}</CardDescription>
|
||||
<CardTitle className="text-sm font-bold uppercase">{t('dashboard.top10LargestEmails')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent className="p-0">
|
||||
{hasTopEmails ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t('dashboard.subject')}</TableHead>
|
||||
<TableHead className="text-right">{t('dashboard.size')}</TableHead>
|
||||
<TableHead className="text-xs">{t('dashboard.subject')}</TableHead>
|
||||
<TableHead className="text-right text-xs">{t('dashboard.size')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{stats!.top_largest_emails.map((m, index) => (
|
||||
<TableRow
|
||||
key={index}
|
||||
className="cursor-pointer hover:bg-accent/50 group relative"
|
||||
onClick={() => handleQuickSearch({ subject: `"${m.subject}"` })}
|
||||
>
|
||||
<TableCell
|
||||
className="max-w-[350px] truncate font-medium relative px-4"
|
||||
title={m.subject}
|
||||
>
|
||||
<div className="absolute left-0 top-2 bottom-2 w-0.5 bg-primary opacity-0 group-hover:opacity-100 transition-opacity duration-150" />
|
||||
<div className="absolute inset-x-4 bottom-1 h-[1px] bg-primary scale-x-0 group-hover:scale-x-100 transition-transform duration-200 origin-left" />
|
||||
|
||||
<span className="group-hover:text-primary transition-colors duration-200">
|
||||
{m.subject || t('dashboard.noSubject')}
|
||||
</span>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-right font-mono text-orange-600">
|
||||
{formatBytes(m.size_bytes)}
|
||||
</TableCell>
|
||||
{stats!.top_largest_emails.map((m, i) => (
|
||||
<TableRow key={i} className="cursor-pointer hover:bg-accent/50 group" onClick={() => handleQuickSearch({ subject: m.subject })}>
|
||||
<TableCell className="max-w-[200px] truncate text-sm group-hover:text-primary transition-colors">{m.subject || t('dashboard.noSubject')}</TableCell>
|
||||
<TableCell className="text-right font-mono text-sm text-orange-600">{formatBytes(m.size_bytes)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
@@ -471,51 +409,27 @@ export default function MailArchiveDashboard() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="md:col-span-1">
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('dashboard.top10Accounts')}</CardTitle>
|
||||
<CardDescription>{t('dashboard.byMessageCount')}</CardDescription>
|
||||
<CardTitle className="text-sm font-bold uppercase">{t('dashboard.top10Accounts')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent className="p-0">
|
||||
{hasTopAccounts ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t('dashboard.account')}</TableHead>
|
||||
<TableHead className="text-right">{t('dashboard.emails')}</TableHead>
|
||||
<TableHead className="text-xs">{t('dashboard.account')}</TableHead>
|
||||
<TableHead className="text-right text-xs">{t('dashboard.emails')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{stats!.top_accounts.map((acc) => {
|
||||
const accountId = getAccountIdByEmail(acc.key);
|
||||
return (
|
||||
<TableRow
|
||||
key={acc.key}
|
||||
className="group cursor-pointer hover:bg-accent/50 transition-colors relative"
|
||||
onClick={() => {
|
||||
if (accountId) {
|
||||
handleQuickSearch({ account_ids: [accountId] });
|
||||
} else {
|
||||
handleQuickSearch({ to: acc.key });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TableCell className="font-medium max-w-[300px] truncate relative px-4 h-9">
|
||||
<div className="absolute left-0 top-2 bottom-2 w-0.5 bg-primary opacity-0 group-hover:opacity-100 transition-opacity duration-150" />
|
||||
<div className="absolute inset-x-4 bottom-1 h-[1px] bg-primary scale-x-0 group-hover:scale-x-100 transition-transform duration-200 origin-left" />
|
||||
<span
|
||||
title={acc.key}
|
||||
className="group-hover:text-primary transition-colors duration-200"
|
||||
>
|
||||
{acc.key}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatNumber(acc.count)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{stats!.top_accounts.map((acc) => (
|
||||
<TableRow key={acc.key} className="cursor-pointer hover:bg-accent/50 group" onClick={() => handleQuickSearch({ account_ids: [getAccountIdByEmail(acc.key) || 0] })}>
|
||||
<TableCell className="max-w-[200px] truncate text-sm group-hover:text-primary transition-colors">{acc.key}</TableCell>
|
||||
<TableCell className="text-right font-mono text-sm">{formatNumber(acc.count)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
@@ -528,7 +442,7 @@ export default function MailArchiveDashboard() {
|
||||
</Tabs>
|
||||
</div>
|
||||
</Main>
|
||||
<div className="mt-auto p-6 md:p-8 pt-0 text-center text-xs text-muted-foreground">
|
||||
<div className="mt-auto p-6 text-center text-xs text-muted-foreground border-t">
|
||||
© 2025-2026 <a href="https://rustmailer.com" target="_blank" rel="noopener noreferrer" className="hover:underline">rustmailer.com</a> - Bichon Email Archiving Project
|
||||
</div>
|
||||
</>
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "إجمالي رسائل البريد الإلكتروني",
|
||||
"syncedLocally": "تمت مزامنته محليًا",
|
||||
"totalEmailSize": "الحجم الإجمالي لرسائل البريد الإلكتروني",
|
||||
"logicalVolume": "الحجم المنطقي",
|
||||
"logicalVolume": "الحجم الأصلي",
|
||||
"localDataFiles": "ملفات البيانات المحلية",
|
||||
"actualDiskUsage": "الاستخدام الفعلي للقرص",
|
||||
"efficiency": "كفاءة التخزين",
|
||||
"saved": "الموفرة",
|
||||
"dataStorage": "تخزين البيانات",
|
||||
"indexSize": "حجم الفهرس",
|
||||
"metadataSize": "حجم البيانات التعريفية",
|
||||
"duckdbDatabase": "استناداً إلى تخزين DuckDB",
|
||||
"tantivyDatabase": "تعتمد على محرك البحث Tantivy",
|
||||
"dayTrend": "اتجاه 30 يومًا",
|
||||
"attachments": "المرفقات",
|
||||
"topLists": "القوائم الأعلى",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "تعديل",
|
||||
"delete": "حذف",
|
||||
"detail": "التفاصيل",
|
||||
"syncFolders": "مجلدات المزامنة",
|
||||
"selectMailboxes": "اختيار صناديق البريد",
|
||||
"oauth2Tokens": "رموز OAuth2",
|
||||
"id": "المعرّف",
|
||||
"enabled": "مفعل",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "فشل إنشاء الحساب",
|
||||
"updateFailed": "فشل التحديث، يرجى المحاولة مرة أخرى لاحقًا",
|
||||
"creationFailed": "فشل الإنشاء، يرجى المحاولة مرة أخرى لاحقًا",
|
||||
"selectSyncFolders": "تحديد مجلدات المزامنة",
|
||||
"allMailFolderSelected": "تنبيه: تم تحديد مجلد \"جميع رسائل البريد\"",
|
||||
"accountSyncFoldersUpdated": "تم تحديث مجلدات مزامنة الحساب",
|
||||
"accountSyncFoldersUpdateFailed": "فشل تحديث مجلدات مزامنة الحساب",
|
||||
"serverConfiguration": "تكوين الخادم (IMAP)",
|
||||
"syncFoldersTitle": "مجلدات المزامنة",
|
||||
"selectedMailboxes": "صناديق البريد المحددة",
|
||||
"allMailFolderSelectedDesc": "من المحتمل أن يؤدي تحديد المجلدات التي تحمل سمة \"جميع رسائل البريد\" إلى تكرار الرسائل التي تمت مزامنتها بالفعل من مجلدات مثل البريد الوارد والمرسل. قد يستهلك هذا مساحة تخزين أكبر بكثير.",
|
||||
"allMailSkipped": "تم تحديد المجلدات القياسية. تم تخطي 'جميع رسائل البريد' لتجنب التكرارات.",
|
||||
"selectAtLeastOneFolder": "يرجى تحديد مجلد واحد على الأقل",
|
||||
"chooseFoldersToSync": "اختر المجلدات المراد مزامنتها لـ {{email}}، ستبدأ المجلدات المضافة حديثًا في التنزيل خلال دورة المزامنة التالية.",
|
||||
"chooseMailboxesToDownload": "اختر صناديق البريد لتنزيلها لـ {{email}}. سيتم تنزيل الصناديق المضافة حديثًا في الدورة التالية.",
|
||||
"accountDetails": "تفاصيل الحساب",
|
||||
"capabilities": "الإمكانيات",
|
||||
"folderLimit": "حد المجلد",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "عرض التفاصيل",
|
||||
"runningState": {
|
||||
"notStarted": "لم تبدأ",
|
||||
"inProgress": "قيد التقدم",
|
||||
"calculating": "جارٍ الحساب...",
|
||||
"initialSync": "المزامنة الأولية",
|
||||
"completed": "مكتمل",
|
||||
"duration": "المدة",
|
||||
"startTime": "وقت البدء:",
|
||||
"endTime": "وقت الانتهاء:",
|
||||
"syncProgress": "تقدم المزامنة",
|
||||
"incrementalSync": "المزامنة التزايدية",
|
||||
"syncInterval": "فاصل المزامنة:",
|
||||
"everyMinutes": "كل {{minutes}} دقيقة",
|
||||
"errorLogs": "سجلات الأخطاء",
|
||||
"noErrorLogs": "لا تتوفر سجلات أخطاء.",
|
||||
"noActiveState": "لا توجد حالة نشطة متاحة",
|
||||
"accountNotStarted": "قد لا تكون مزامنة الحساب قد بدأت بعد",
|
||||
"close": "إغلاق"
|
||||
"message": "رسالة",
|
||||
"account": {
|
||||
"id": "معرّف الحساب"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "الجلسة النشطة",
|
||||
"history": "السجل",
|
||||
"global_errors": "الأخطاء العامة",
|
||||
"folders": "صناديق البريد",
|
||||
"errors": "أخطاء"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "جارٍ تحميل حالة الحساب..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "لا يوجد تنزيل نشط",
|
||||
"no_errors_current": "لا توجد أخطاء في الجلسة الحالية",
|
||||
"no_errors_session": "لا توجد أخطاء في هذه الجلسة",
|
||||
"no_global_errors": "لا توجد أخطاء عامة",
|
||||
"no_history": "لا يوجد سجل"
|
||||
},
|
||||
"session": {
|
||||
"status": "الحالة",
|
||||
"trigger": "المشغّل",
|
||||
"started_at": "وقت البدء"
|
||||
},
|
||||
"common": {
|
||||
"folders": "صناديق البريد",
|
||||
"close": "إغلاق"
|
||||
},
|
||||
"latest": "الأحدث",
|
||||
"folders": "صناديق البريد"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "مكتمل",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Samlet antal e-mails",
|
||||
"syncedLocally": "Synkroniseret lokalt",
|
||||
"totalEmailSize": "Samlet e-mailstørrelse",
|
||||
"logicalVolume": "Logisk volumen",
|
||||
"logicalVolume": "Oprindelig størrelse",
|
||||
"localDataFiles": "Lokale datafiler",
|
||||
"actualDiskUsage": "Faktisk diskforbrug",
|
||||
"efficiency": "Lagringseffektivitet",
|
||||
"saved": "Sparet",
|
||||
"dataStorage": "Datalagring",
|
||||
"indexSize": "Indeksstørrelse",
|
||||
"metadataSize": "Metadatastørrelse",
|
||||
"duckdbDatabase": "Baseret på DuckDB-lager",
|
||||
"tantivyDatabase": "Baseret på Tantivy-søgemaskine",
|
||||
"dayTrend": "30-dages trend",
|
||||
"attachments": "Vedhæftninger",
|
||||
"topLists": "Toplister",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Rediger",
|
||||
"delete": "Slet",
|
||||
"detail": "Detaljer",
|
||||
"syncFolders": "Synkroniser mapper",
|
||||
"selectMailboxes": "Vælg postkasser",
|
||||
"oauth2Tokens": "OAuth2-tokens",
|
||||
"id": "ID",
|
||||
"enabled": "Aktiveret",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Kontooprettelse mislykkedes",
|
||||
"updateFailed": "Opdatering mislykkedes, prøv venligst igen senere",
|
||||
"creationFailed": "Oprettelse mislykkedes, prøv venligst igen senere",
|
||||
"selectSyncFolders": "Vælg mapper til synkronisering",
|
||||
"allMailFolderSelected": "Bemærk: Mappen \"Al mail\" er valgt",
|
||||
"accountSyncFoldersUpdated": "Synkroniseringsmapper opdateret",
|
||||
"accountSyncFoldersUpdateFailed": "Opdatering af synkroniseringsmapper mislykkedes",
|
||||
"serverConfiguration": "Serverkonfiguration (IMAP)",
|
||||
"syncFoldersTitle": "Synkroniser mapper",
|
||||
"selectedMailboxes": "Valgte postkasser",
|
||||
"allMailFolderSelectedDesc": "Valg af mapper med attributtet \"Al mail\" vil sandsynligvis resultere i duplikerede meddelelser, der allerede er synkroniseret fra mapper som Indbakke og Sendt. Dette kan optage betydeligt mere lagerplads.",
|
||||
"allMailSkipped": "Valgte standardmapper. \"Al mail\" blev sprunget over for at undgå dubletter.",
|
||||
"selectAtLeastOneFolder": "Vælg venligst mindst én mappe",
|
||||
"chooseFoldersToSync": "Vælg mapper til synkronisering for {{email}}. Nytilføjede mapper begynder at downloade ved næste synkroniseringscyklus.",
|
||||
"chooseMailboxesToDownload": "Vælg postkasser til download for {{email}}. Nye postkasser hentes i næste cyklus.",
|
||||
"accountDetails": "Kontodetaljer",
|
||||
"capabilities": "Funktioner",
|
||||
"folderLimit": "Mappegrænse",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "vis detaljer",
|
||||
"runningState": {
|
||||
"notStarted": "Ikke startet",
|
||||
"inProgress": "I gang",
|
||||
"calculating": "Beregner...",
|
||||
"initialSync": "Initial synkronisering",
|
||||
"completed": "Fuldført",
|
||||
"duration": "Varighed",
|
||||
"startTime": "Starttid:",
|
||||
"endTime": "Sluttid:",
|
||||
"syncProgress": "Synkroniseringsforløb",
|
||||
"incrementalSync": "Inkrementel synkronisering",
|
||||
"syncInterval": "Synkroniseringsinterval:",
|
||||
"everyMinutes": "Hvert {{minutes}} minut",
|
||||
"errorLogs": "Fejllogs",
|
||||
"noErrorLogs": "Ingen fejllogs tilgængelige.",
|
||||
"noActiveState": "Ingen aktiv tilstand tilgængelig",
|
||||
"accountNotStarted": "Kontosynkronisering er muligvis ikke startet endnu",
|
||||
"close": "Luk"
|
||||
"message": "Besked",
|
||||
"account": {
|
||||
"id": "Konto-ID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Aktiv session",
|
||||
"history": "Historik",
|
||||
"global_errors": "Globale fejl",
|
||||
"folders": "Postkasser",
|
||||
"errors": "Fejl"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Henter kontostatus..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Ingen aktiv download",
|
||||
"no_errors_current": "Ingen fejl i nuværende session",
|
||||
"no_errors_session": "Ingen fejl i denne session",
|
||||
"no_global_errors": "Ingen globale fejl",
|
||||
"no_history": "Ingen historik"
|
||||
},
|
||||
"session": {
|
||||
"status": "Status",
|
||||
"trigger": "Trigger",
|
||||
"started_at": "Starttid"
|
||||
},
|
||||
"common": {
|
||||
"folders": "postkasser",
|
||||
"close": "Luk"
|
||||
},
|
||||
"latest": "SENESTE",
|
||||
"folders": "postkasser"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Fuldfør",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Gesamt-E-Mails",
|
||||
"syncedLocally": "Lokal synchronisiert",
|
||||
"totalEmailSize": "Gesamtgröße der E-Mails",
|
||||
"logicalVolume": "Logisches Volumen",
|
||||
"logicalVolume": "Originalgröße",
|
||||
"localDataFiles": "Lokale Datendateien",
|
||||
"actualDiskUsage": "Tatsächliche Festplattennutzung",
|
||||
"efficiency": "Speichereffizienz",
|
||||
"saved": "Eingespart",
|
||||
"dataStorage": "Datenspeicher",
|
||||
"indexSize": "Indexgröße",
|
||||
"metadataSize": "Metadatengröße",
|
||||
"duckdbDatabase": "Basierend auf DuckDB-Speicher",
|
||||
"tantivyDatabase": "Basiert auf der Tantivy-Suchmaschine",
|
||||
"dayTrend": "30-Tage-Trend",
|
||||
"attachments": "Anhänge",
|
||||
"topLists": "Top-Listen",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"detail": "Detail",
|
||||
"syncFolders": "Zu synchronisierende Ordner",
|
||||
"selectMailboxes": "Postfächer wählen",
|
||||
"oauth2Tokens": "OAuth2-Token",
|
||||
"id": "ID",
|
||||
"enabled": "Aktiviert",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Kontoerstellung fehlgeschlagen",
|
||||
"updateFailed": "Aktualisierung fehlgeschlagen, bitte versuchen Sie es später erneut",
|
||||
"creationFailed": "Erstellung fehlgeschlagen, bitte versuchen Sie es später erneut",
|
||||
"selectSyncFolders": "Zu synchronisierende Ordner auswählen",
|
||||
"allMailFolderSelected": "Warnung: 'Alle E-Mails'-Ordner ausgewählt",
|
||||
"accountSyncFoldersUpdated": "Synchronisierungsordner des Kontos aktualisiert",
|
||||
"accountSyncFoldersUpdateFailed": "Aktualisierung der Synchronisierungsordner des Kontos fehlgeschlagen",
|
||||
"serverConfiguration": "Serverkonfiguration (IMAP)",
|
||||
"syncFoldersTitle": "Zu synchronisierende Ordner",
|
||||
"selectedMailboxes": "Ausgewählte Postfächer",
|
||||
"allMailFolderSelectedDesc": "Die Auswahl von Ordnern mit dem Attribut 'Alle E-Mails' führt wahrscheinlich zur Duplizierung von Nachrichten, die bereits aus Ordnern wie Posteingang und Gesendet synchronisiert wurden. Dies könnte erheblich mehr Speicherplatz verbrauchen.",
|
||||
"allMailSkipped": "Standardordner ausgewählt. 'Alle E-Mails' wurde übersprungen, um Duplikate zu vermeiden.",
|
||||
"selectAtLeastOneFolder": "Wählen Sie mindestens einen Ordner aus",
|
||||
"chooseFoldersToSync": "Wählen Sie die zu synchronisierenden Ordner für {{email}}. Neu hinzugefügte Ordner beginnen mit dem Download während des nächsten Synchronisierungszyklus.",
|
||||
"chooseMailboxesToDownload": "Wählen Sie Postfächer zum Herunterladen für {{email}}. Neue Postfächer werden im nächsten Zyklus heruntergeladen.",
|
||||
"accountDetails": "Kontodetails",
|
||||
"capabilities": "Funktionen",
|
||||
"folderLimit": "Ordnerlimit",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "Details anzeigen",
|
||||
"runningState": {
|
||||
"notStarted": "Nicht gestartet",
|
||||
"inProgress": "In Bearbeitung",
|
||||
"calculating": "Wird berechnet...",
|
||||
"initialSync": "Erste Synchronisierung",
|
||||
"completed": "Abgeschlossen",
|
||||
"duration": "Dauer",
|
||||
"startTime": "Startzeit:",
|
||||
"endTime": "Endzeit:",
|
||||
"syncProgress": "Synchronisierungsfortschritt",
|
||||
"incrementalSync": "Inkrementelle Synchronisierung",
|
||||
"syncInterval": "Synchronisierungsintervall:",
|
||||
"everyMinutes": "Alle {{minutes}} Minuten",
|
||||
"errorLogs": "Fehlerprotokolle",
|
||||
"noErrorLogs": "Keine Fehlerprotokolle verfügbar.",
|
||||
"noActiveState": "Kein aktiver Zustand verfügbar",
|
||||
"accountNotStarted": "Die Kontosynchronisierung hat möglicherweise noch nicht begonnen",
|
||||
"close": "Schließen"
|
||||
"message": "Nachricht",
|
||||
"account": {
|
||||
"id": "Konto-ID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Aktive Sitzung",
|
||||
"history": "Verlauf",
|
||||
"global_errors": "Globale Fehler",
|
||||
"folders": "Postfächer",
|
||||
"errors": "Fehler"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Kontostatus wird geladen..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Kein aktiver Download",
|
||||
"no_errors_current": "Keine Fehler in der aktuellen Sitzung",
|
||||
"no_errors_session": "Keine Fehler in dieser Sitzung",
|
||||
"no_global_errors": "Keine globalen Fehler",
|
||||
"no_history": "Kein Verlauf vorhanden"
|
||||
},
|
||||
"session": {
|
||||
"status": "Status",
|
||||
"trigger": "Auslöser",
|
||||
"started_at": "Startzeit"
|
||||
},
|
||||
"common": {
|
||||
"folders": "Postfächer",
|
||||
"close": "Schließen"
|
||||
},
|
||||
"latest": "NEU",
|
||||
"folders": "Postfächer"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Abgeschlossen",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Total Emails",
|
||||
"syncedLocally": "Synced locally",
|
||||
"totalEmailSize": "Total Email Size",
|
||||
"logicalVolume": "Logical volume",
|
||||
"logicalVolume": "Original Size",
|
||||
"localDataFiles": "Local Data Files",
|
||||
"actualDiskUsage": "Actual disk usage",
|
||||
"efficiency": "Efficiency",
|
||||
"saved": "Saved",
|
||||
"dataStorage": "Storage",
|
||||
"indexSize": "Index",
|
||||
"metadataSize": "Metadata Size",
|
||||
"duckdbDatabase": "Based on DuckDB Storage",
|
||||
"tantivyDatabase": "Based on Tantivy Search Engine",
|
||||
"dayTrend": "30-Day Trend",
|
||||
"attachments": "Attachments",
|
||||
"topLists": "Top Lists",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"detail": "Detail",
|
||||
"syncFolders": "Sync Folders",
|
||||
"selectMailboxes": "Choose Mailboxes",
|
||||
"oauth2Tokens": "OAuth2 Tokens",
|
||||
"id": "ID",
|
||||
"enabled": "Enabled",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Account Creation Failed",
|
||||
"updateFailed": "Update failed, please try again later",
|
||||
"creationFailed": "Creation failed, please try again later",
|
||||
"selectSyncFolders": "Select Sync Folders",
|
||||
"allMailFolderSelected": "Heads Up: \"All Mail\" Folder Selected",
|
||||
"accountSyncFoldersUpdated": "Account Sync Folders Updated",
|
||||
"accountSyncFoldersUpdateFailed": "Account Sync Folders Update Failed",
|
||||
"serverConfiguration": "Server Configuration (IMAP)",
|
||||
"syncFoldersTitle": "Sync Folders",
|
||||
"selectedMailboxes": "Selected Mailboxes",
|
||||
"allMailFolderSelectedDesc": "Selecting folders with the \"All Mail\" attribute will likely lead to duplicating messages already synced from folders like Inbox and Sent. This may consume significantly more storage space.",
|
||||
"allMailSkipped": "Selected standard folders. 'All Mail' was skipped to avoid duplicates.",
|
||||
"selectAtLeastOneFolder": "Please select at least one folder",
|
||||
"chooseFoldersToSync": "Choose folders to sync for {{email}}, Newly added folders will begin downloading during the next sync cycle.",
|
||||
"chooseMailboxesToDownload": "Choose mailboxes to download for {{email}}. Newly added mailboxes will be downloaded in the next cycle.",
|
||||
"accountDetails": "Account Details",
|
||||
"capabilities": "Capabilities",
|
||||
"folderLimit": "Folder Limit",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "view details",
|
||||
"runningState": {
|
||||
"notStarted": "Not Started",
|
||||
"inProgress": "In Progress",
|
||||
"calculating": "Calculating...",
|
||||
"initialSync": "Initial Sync",
|
||||
"completed": "Completed",
|
||||
"duration": "Duration",
|
||||
"startTime": "Start Time:",
|
||||
"endTime": "End Time:",
|
||||
"syncProgress": "Sync Progress",
|
||||
"incrementalSync": "Incremental Sync",
|
||||
"syncInterval": "Sync Interval:",
|
||||
"everyMinutes": "Every {{minutes}} minutes",
|
||||
"errorLogs": "Error Logs",
|
||||
"noErrorLogs": "No error logs available.",
|
||||
"noActiveState": "No active state available",
|
||||
"accountNotStarted": "Account synchronization may not have started yet",
|
||||
"close": "Close"
|
||||
"message": "Message",
|
||||
"account": {
|
||||
"id": "Account ID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Active Session",
|
||||
"history": "History",
|
||||
"global_errors": "Global Errors",
|
||||
"folders": "Mailboxes",
|
||||
"errors": "Errors"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Fetching account state..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "No download in progress",
|
||||
"no_errors_current": "No errors in current session",
|
||||
"no_errors_session": "No errors in this session",
|
||||
"no_global_errors": "No global errors",
|
||||
"no_history": "No historical records found"
|
||||
},
|
||||
"session": {
|
||||
"status": "Status",
|
||||
"trigger": "Trigger",
|
||||
"started_at": "Started At"
|
||||
},
|
||||
"common": {
|
||||
"folders": "mailboxes",
|
||||
"close": "Close"
|
||||
},
|
||||
"latest": "LATEST",
|
||||
"folders": "mailboxes"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Complete",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Total de correos",
|
||||
"syncedLocally": "Sincronizado localmente",
|
||||
"totalEmailSize": "Tamaño total de correos",
|
||||
"logicalVolume": "Volumen lógico",
|
||||
"logicalVolume": "Tamaño original",
|
||||
"localDataFiles": "Archivos de datos locales",
|
||||
"actualDiskUsage": "Uso real de disco",
|
||||
"efficiency": "Eficiencia",
|
||||
"saved": "Ahorrado",
|
||||
"dataStorage": "Almacenamiento",
|
||||
"indexSize": "Tamaño índice",
|
||||
"metadataSize": "Tamaño de metadatos",
|
||||
"duckdbDatabase": "Basado en el almacenamiento DuckDB",
|
||||
"tantivyDatabase": "Basado en el motor de búsqueda Tantivy",
|
||||
"dayTrend": "Tendencia de 30 días",
|
||||
"attachments": "Archivos adjuntos",
|
||||
"topLists": "Listas principales",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"detail": "Detalle",
|
||||
"syncFolders": "Carpetas a sincronizar",
|
||||
"selectMailboxes": "Elegir buzones",
|
||||
"oauth2Tokens": "Tokens OAuth2",
|
||||
"id": "ID",
|
||||
"enabled": "Habilitado",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Error al crear la cuenta",
|
||||
"updateFailed": "Error al actualizar, por favor, inténtalo de nuevo más tarde",
|
||||
"creationFailed": "Error al crear, por favor, inténtalo de nuevo más tarde",
|
||||
"selectSyncFolders": "Selecciona carpetas a sincronizar",
|
||||
"allMailFolderSelected": "Advertencia: Carpeta 'Todo el correo' seleccionada",
|
||||
"accountSyncFoldersUpdated": "Carpetas de sincronización de cuenta actualizadas",
|
||||
"accountSyncFoldersUpdateFailed": "Error al actualizar las carpetas de sincronización de cuenta",
|
||||
"serverConfiguration": "Configuración del servidor (IMAP)",
|
||||
"syncFoldersTitle": "Carpetas a sincronizar",
|
||||
"selectedMailboxes": "Buzones seleccionados",
|
||||
"allMailFolderSelectedDesc": "Seleccionar carpetas con el atributo 'Todo el correo' probablemente resultará en la duplicación de mensajes ya sincronizados de carpetas como Bandeja de entrada y Enviados. Esto podría consumir significativamente más espacio de almacenamiento.",
|
||||
"allMailSkipped": "Carpetas predeterminadas seleccionadas. 'Todo el correo' omitido para evitar duplicados.",
|
||||
"selectAtLeastOneFolder": "Selecciona al menos una carpeta",
|
||||
"chooseFoldersToSync": "Elige las carpetas a sincronizar para {{email}}. Las carpetas añadidas recientemente comenzarán a descargarse durante el próximo ciclo de sincronización.",
|
||||
"chooseMailboxesToDownload": "Elige los buzones a descargar para {{email}}. Los nuevos buzones se descargarán en el próximo ciclo.",
|
||||
"accountDetails": "Detalles de la cuenta",
|
||||
"capabilities": "Capacidades",
|
||||
"folderLimit": "Límite de carpetas",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "ver detalles",
|
||||
"runningState": {
|
||||
"notStarted": "No iniciado",
|
||||
"inProgress": "En curso",
|
||||
"calculating": "Calculando...",
|
||||
"initialSync": "Sincronización inicial",
|
||||
"completed": "Completado",
|
||||
"duration": "Duración",
|
||||
"startTime": "Hora de inicio:",
|
||||
"endTime": "Hora de finalización:",
|
||||
"syncProgress": "Progreso de sincronización",
|
||||
"incrementalSync": "Sincronización incremental",
|
||||
"syncInterval": "Intervalo de sincronización:",
|
||||
"everyMinutes": "Cada {{minutes}} minutos",
|
||||
"errorLogs": "Registros de errores",
|
||||
"noErrorLogs": "No hay registros de errores disponibles.",
|
||||
"noActiveState": "No hay estado activo disponible",
|
||||
"accountNotStarted": "La sincronización de la cuenta podría no haber comenzado todavía",
|
||||
"close": "Cerrar"
|
||||
"message": "Mensaje",
|
||||
"account": {
|
||||
"id": "ID de cuenta"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Sesión activa",
|
||||
"history": "Historial",
|
||||
"global_errors": "Errores globales",
|
||||
"folders": "Buzones",
|
||||
"errors": "Errores"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Obteniendo estado de la cuenta..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "No hay descargas en curso",
|
||||
"no_errors_current": "Sin errores en la sesión actual",
|
||||
"no_errors_session": "Sin errores en esta sesión",
|
||||
"no_global_errors": "Sin errores globales",
|
||||
"no_history": "Sin historial"
|
||||
},
|
||||
"session": {
|
||||
"status": "Estado",
|
||||
"trigger": "Disparador",
|
||||
"started_at": "Hora de inicio"
|
||||
},
|
||||
"common": {
|
||||
"folders": "buzones",
|
||||
"close": "Cerrar"
|
||||
},
|
||||
"latest": "RECIENTE",
|
||||
"folders": "buzones"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Completado",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Sähköpostien kokonaismäärä",
|
||||
"syncedLocally": "Synkronoitu paikallisesti",
|
||||
"totalEmailSize": "Sähköpostien kokonaiskoko",
|
||||
"logicalVolume": "Looginen tilavuus",
|
||||
"logicalVolume": "Alkuperäinen koko",
|
||||
"localDataFiles": "Paikalliset datatiedostot",
|
||||
"actualDiskUsage": "Todellinen levynkäyttö",
|
||||
"efficiency": "Tallennustehokkuus",
|
||||
"saved": "Säästetty",
|
||||
"dataStorage": "Tallennustila",
|
||||
"indexSize": "Indeksikoko",
|
||||
"metadataSize": "Metatietojen koko",
|
||||
"duckdbDatabase": "Perustuu DuckDB-tallennustilaan",
|
||||
"tantivyDatabase": "Perustuu Tantivy-hakukoneeseen",
|
||||
"dayTrend": "30 päivän trendi",
|
||||
"attachments": "Liitteet",
|
||||
"topLists": "Parhaat listat",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Muokkaa",
|
||||
"delete": "Poista",
|
||||
"detail": "Tiedot",
|
||||
"syncFolders": "Synkronoi kansiot",
|
||||
"selectMailboxes": "Valitse postilaatikot",
|
||||
"oauth2Tokens": "OAuth2-tunnukset",
|
||||
"id": "ID",
|
||||
"enabled": "Käytössä",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Tilin luominen epäonnistui",
|
||||
"updateFailed": "Päivitys epäonnistui, yritä myöhemmin uudelleen",
|
||||
"creationFailed": "Luominen epäonnistui, yritä myöhemmin uudelleen",
|
||||
"selectSyncFolders": "Valitse synkronoitavat kansiot",
|
||||
"allMailFolderSelected": "Varoitus: 'Kaikki sähköpostit' -kansio valittu",
|
||||
"accountSyncFoldersUpdated": "Tilin synkronointikansiot päivitetty",
|
||||
"accountSyncFoldersUpdateFailed": "Tilin synkronointikansioiden päivitys epäonnistui",
|
||||
"serverConfiguration": "Palvelinmääritys (IMAP)",
|
||||
"syncFoldersTitle": "Synkronoi kansiot",
|
||||
"selectedMailboxes": "Valitut postilaatikot",
|
||||
"allMailFolderSelectedDesc": "Kansioiden valitseminen, joilla on 'Kaikki sähköpostit' -attribuutti, johtaa todennäköisesti jo synkronoitujen viestien kahdentumiseen kansioista, kuten Saapuneet ja Lähetetyt. Tämä voi kuluttaa huomattavasti enemmän tallennustilaa.",
|
||||
"allMailSkipped": "Oletuskansiot valittu. 'Kaikki sähköpostit' ohitettiin päällekkäisyyksien välttämiseksi.",
|
||||
"selectAtLeastOneFolder": "Valitse vähintään yksi kansio",
|
||||
"chooseFoldersToSync": "Valitse synkronoitavat kansiot {{email}}. Äskettäin lisätyt kansiot alkavat latautua seuraavan synkronointisyklin aikana.",
|
||||
"chooseMailboxesToDownload": "Valitse ladattavat postilaatikot käyttäjälle {{email}}. Uudet postilaatikot ladataan seuraavassa kierrossa.",
|
||||
"accountDetails": "Tilin tiedot",
|
||||
"capabilities": "Ominaisuudet",
|
||||
"folderLimit": "Kansioraja",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "katso tiedot",
|
||||
"runningState": {
|
||||
"notStarted": "Ei aloitettu",
|
||||
"inProgress": "Käynnissä",
|
||||
"calculating": "Lasketaan...",
|
||||
"initialSync": "Alkusynkronointi",
|
||||
"completed": "Valmis",
|
||||
"duration": "Kesto",
|
||||
"startTime": "Aloitusaika:",
|
||||
"endTime": "Päättymisaika:",
|
||||
"syncProgress": "Synkronoinnin edistyminen",
|
||||
"incrementalSync": "Lisäävä synkronointi",
|
||||
"syncInterval": "Synkronointiväli:",
|
||||
"everyMinutes": "Joka {{minutes}} minuutti",
|
||||
"errorLogs": "Virhelokit",
|
||||
"noErrorLogs": "Virhelokeja ei saatavilla.",
|
||||
"noActiveState": "Ei aktiivista tilaa saatavilla",
|
||||
"accountNotStarted": "Tilin synkronointi ei ehkä ole vielä alkanut",
|
||||
"close": "Sulje"
|
||||
"message": "Viesti",
|
||||
"account": {
|
||||
"id": "Tilin ID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Aktiivinen istunto",
|
||||
"history": "Historia",
|
||||
"global_errors": "Yleiset virheet",
|
||||
"folders": "Postilaatikot",
|
||||
"errors": "Virheet"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Haetaan tilan tietoja..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Ei aktiivista latausta",
|
||||
"no_errors_current": "Ei virheitä nykyisessä istunnossa",
|
||||
"no_errors_session": "Ei virheitä tässä istunnossa",
|
||||
"no_global_errors": "Ei yleisiä virheitä",
|
||||
"no_history": "Ei historiaa"
|
||||
},
|
||||
"session": {
|
||||
"status": "Tila",
|
||||
"trigger": "Laukaisija",
|
||||
"started_at": "Aloitusaika"
|
||||
},
|
||||
"common": {
|
||||
"folders": "postilaatikot",
|
||||
"close": "Sulje"
|
||||
},
|
||||
"latest": "UUSIN",
|
||||
"folders": "postilaatikot"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Valmis",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Total des e-mails",
|
||||
"syncedLocally": "Synchronisés localement",
|
||||
"totalEmailSize": "Taille totale des e-mails",
|
||||
"logicalVolume": "Volume logique",
|
||||
"logicalVolume": "Taille originale",
|
||||
"localDataFiles": "Fichiers de données locaux",
|
||||
"actualDiskUsage": "Utilisation réelle du disque",
|
||||
"efficiency": "Efficacité",
|
||||
"saved": "Économisé",
|
||||
"dataStorage": "Stockage",
|
||||
"indexSize": "Taille d'index",
|
||||
"metadataSize": "Taille des métadonnées",
|
||||
"duckdbDatabase": "Basé sur le stockage DuckDB",
|
||||
"tantivyDatabase": "Basé sur le moteur de recherche Tantivy",
|
||||
"dayTrend": "Tendance sur 30 jours",
|
||||
"attachments": "Pièces jointes",
|
||||
"topLists": "Top Listes",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Modifier",
|
||||
"delete": "Supprimer",
|
||||
"detail": "Détail",
|
||||
"syncFolders": "Synchroniser les Dossiers",
|
||||
"selectMailboxes": "Choisir les boîtes mail",
|
||||
"oauth2Tokens": "Jetons OAuth2",
|
||||
"id": "ID",
|
||||
"enabled": "Activé",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Échec de la Création du Compte",
|
||||
"updateFailed": "La mise à jour a échoué, veuillez réessayer plus tard",
|
||||
"creationFailed": "La création a échoué, veuillez réessayer plus tard",
|
||||
"selectSyncFolders": "Sélectionner les Dossiers de Synchronisation",
|
||||
"allMailFolderSelected": "Attention : Dossier 'Tous les messages' sélectionné",
|
||||
"accountSyncFoldersUpdated": "Dossiers de Synchronisation de Compte Mis à Jour",
|
||||
"accountSyncFoldersUpdateFailed": "Échec de la Mise à Jour des Dossiers de Synchronisation de Compte",
|
||||
"serverConfiguration": "Configuration du Serveur (IMAP)",
|
||||
"syncFoldersTitle": "Synchroniser les Dossiers",
|
||||
"selectedMailboxes": "Boîtes mail sélectionnées",
|
||||
"allMailFolderSelectedDesc": "La sélection de dossiers avec l'attribut 'Tous les messages' entraînera probablement la duplication des messages déjà synchronisés à partir de dossiers tels que Boîte de réception et Éléments envoyés. Cela peut consommer beaucoup plus d'espace de stockage.",
|
||||
"allMailSkipped": "Dossiers par défaut sélectionnés. 'Tous les messages' a été ignoré pour éviter les doublons.",
|
||||
"selectAtLeastOneFolder": "Veuillez sélectionner au moins un dossier",
|
||||
"chooseFoldersToSync": "Choisissez les dossiers à synchroniser pour {{email}}. Les dossiers nouvellement ajoutés commenceront à être téléchargés lors du prochain cycle de synchronisation.",
|
||||
"chooseMailboxesToDownload": "Choisissez les boîtes mail à télécharger pour {{email}}. Les nouvelles boîtes seront téléchargées au prochain cycle.",
|
||||
"accountDetails": "Détails du Compte",
|
||||
"capabilities": "Capacités",
|
||||
"folderLimit": "Limite de Dossiers",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "voir les détails",
|
||||
"runningState": {
|
||||
"notStarted": "Non Démarré",
|
||||
"inProgress": "En Cours",
|
||||
"calculating": "Calcul en cours...",
|
||||
"initialSync": "Synchronisation Initiale",
|
||||
"completed": "Terminé",
|
||||
"duration": "Durée",
|
||||
"startTime": "Heure de Début :",
|
||||
"endTime": "Heure de Fin :",
|
||||
"syncProgress": "Progression de la Synchronisation",
|
||||
"incrementalSync": "Synchronisation Incrémentielle",
|
||||
"syncInterval": "Intervalle de Synchronisation :",
|
||||
"everyMinutes": "Toutes les {{minutes}} minutes",
|
||||
"errorLogs": "Journaux d'Erreurs",
|
||||
"noErrorLogs": "Aucun journal d'erreurs disponible.",
|
||||
"noActiveState": "Aucun état actif disponible",
|
||||
"accountNotStarted": "La synchronisation du compte n'a peut-être pas encore démarré",
|
||||
"close": "Fermer"
|
||||
"message": "Message",
|
||||
"account": {
|
||||
"id": "ID du compte"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Session active",
|
||||
"history": "Historique",
|
||||
"global_errors": "Erreurs globales",
|
||||
"folders": "Boîtes mail",
|
||||
"errors": "Erreurs"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Chargement de l'état du compte..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Aucun téléchargement en cours",
|
||||
"no_errors_current": "Aucune erreur dans la session actuelle",
|
||||
"no_errors_session": "Aucune erreur dans cette session",
|
||||
"no_global_errors": "Aucune erreur globale",
|
||||
"no_history": "Aucun historique disponible"
|
||||
},
|
||||
"session": {
|
||||
"status": "Statut",
|
||||
"trigger": "Déclencheur",
|
||||
"started_at": "Heure de début"
|
||||
},
|
||||
"common": {
|
||||
"folders": "boîtes mail",
|
||||
"close": "Fermer"
|
||||
},
|
||||
"latest": "RÉCENT",
|
||||
"folders": "boîtes mail"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Terminé",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Email totali",
|
||||
"syncedLocally": "Sincronizzate localmente",
|
||||
"totalEmailSize": "Dimensione totale email",
|
||||
"logicalVolume": "Volume logico",
|
||||
"logicalVolume": "Dimensioni originali",
|
||||
"localDataFiles": "File di dati locali",
|
||||
"actualDiskUsage": "Uso effettivo del disco",
|
||||
"efficiency": "Efficienza",
|
||||
"saved": "Risparmiato",
|
||||
"dataStorage": "Archiviazione",
|
||||
"indexSize": "Dimens. indice",
|
||||
"metadataSize": "Dimensione metadati",
|
||||
"duckdbDatabase": "Basato sull'archiviazione DuckDB",
|
||||
"tantivyDatabase": "Basato sul motore di ricerca Tantivy",
|
||||
"dayTrend": "Tendenza a 30 giorni",
|
||||
"attachments": "Allegati",
|
||||
"topLists": "Liste Top",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Modifica",
|
||||
"delete": "Elimina",
|
||||
"detail": "Dettaglio",
|
||||
"syncFolders": "Sincronizza Cartelle",
|
||||
"selectMailboxes": "Scegli caselle",
|
||||
"oauth2Tokens": "Token OAuth2",
|
||||
"id": "ID",
|
||||
"enabled": "Abilitato",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Creazione Account Fallita",
|
||||
"updateFailed": "Aggiornamento fallito, riprova più tardi",
|
||||
"creationFailed": "Creazione fallita, riprova più tardi",
|
||||
"selectSyncFolders": "Seleziona Cartelle di Sincronizzazione",
|
||||
"allMailFolderSelected": "Attenzione: Cartella 'Tutta la Posta' Selezionata",
|
||||
"accountSyncFoldersUpdated": "Cartelle di Sincronizzazione Account Aggiornate",
|
||||
"accountSyncFoldersUpdateFailed": "Aggiornamento Cartelle di Sincronizzazione Account Fallito",
|
||||
"serverConfiguration": "Configurazione Server (IMAP)",
|
||||
"syncFoldersTitle": "Sincronizza Cartelle",
|
||||
"selectedMailboxes": "Caselle selezionate",
|
||||
"allMailFolderSelectedDesc": "La selezione di cartelle con l'attributo 'Tutta la Posta' probabilmente causerà la duplicazione dei messaggi già sincronizzati da cartelle come Posta in arrivo e Posta inviata. Questo può consumare significativamente più spazio di archiviazione.",
|
||||
"allMailSkipped": "Cartelle predefinite selezionate. 'Tutta la Posta' è stata saltata per evitare duplicati.",
|
||||
"selectAtLeastOneFolder": "Seleziona almeno una cartella",
|
||||
"chooseFoldersToSync": "Scegli le cartelle da sincronizzare per {{email}}. Le cartelle appena aggiunte inizieranno a essere scaricate durante il prossimo ciclo di sincronizzazione.",
|
||||
"chooseMailboxesToDownload": "Scegli le caselle da scaricare per {{email}}. Le nuove caselle verranno scaricate nel prossimo ciclo.",
|
||||
"accountDetails": "Dettagli Account",
|
||||
"capabilities": "Capacità",
|
||||
"folderLimit": "Limite Cartelle",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "visualizza dettagli",
|
||||
"runningState": {
|
||||
"notStarted": "Non Avviato",
|
||||
"inProgress": "In Corso",
|
||||
"calculating": "Calcolo in corso...",
|
||||
"initialSync": "Sincronizzazione Iniziale",
|
||||
"completed": "Completato",
|
||||
"duration": "Durata",
|
||||
"startTime": "Ora di Inizio:",
|
||||
"endTime": "Ora di Fine:",
|
||||
"syncProgress": "Avanzamento Sincronizzazione",
|
||||
"incrementalSync": "Sincronizzazione Incrementale",
|
||||
"syncInterval": "Intervallo di Sincronizzazione:",
|
||||
"everyMinutes": "Ogni {{minutes}} minuti",
|
||||
"errorLogs": "Log Errori",
|
||||
"noErrorLogs": "Nessun log errori disponibile.",
|
||||
"noActiveState": "Nessuno stato attivo disponibile",
|
||||
"accountNotStarted": "La sincronizzazione dell'account potrebbe non essere ancora iniziata",
|
||||
"close": "Chiudi"
|
||||
"message": "Messaggio",
|
||||
"account": {
|
||||
"id": "ID account"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Sessione attiva",
|
||||
"history": "Cronologia",
|
||||
"global_errors": "Errori globali",
|
||||
"folders": "Caselle di posta",
|
||||
"errors": "Errori"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Caricamento stato account..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Nessun download in corso",
|
||||
"no_errors_current": "Nessun errore nella sessione corrente",
|
||||
"no_errors_session": "Nessun errore in questa sessione",
|
||||
"no_global_errors": "Nessun errore globale",
|
||||
"no_history": "Nessuna cronologia disponibile"
|
||||
},
|
||||
"session": {
|
||||
"status": "Stato",
|
||||
"trigger": "Trigger",
|
||||
"started_at": "Ora di inizio"
|
||||
},
|
||||
"common": {
|
||||
"folders": "caselle di posta",
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"latest": "RECENTE",
|
||||
"folders": "caselle di posta"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Completato",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "合計メール数",
|
||||
"syncedLocally": "ローカル同期済み",
|
||||
"totalEmailSize": "メール総容量",
|
||||
"logicalVolume": "論理ボリューム",
|
||||
"logicalVolume": "元のサイズ",
|
||||
"localDataFiles": "ローカルデータファイル",
|
||||
"actualDiskUsage": "実際のディスク使用量",
|
||||
"efficiency": "圧縮効率",
|
||||
"saved": "節約済み",
|
||||
"dataStorage": "データストレージ",
|
||||
"indexSize": "インデックスサイズ",
|
||||
"metadataSize": "メタデータサイズ",
|
||||
"duckdbDatabase": "DuckDBストレージベース",
|
||||
"tantivyDatabase": "Tantivy 検索エンジンを搭載",
|
||||
"dayTrend": "30日間トレンド",
|
||||
"attachments": "添付ファイル",
|
||||
"topLists": "トップリスト",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "編集",
|
||||
"delete": "削除",
|
||||
"detail": "詳細",
|
||||
"syncFolders": "同期フォルダー",
|
||||
"selectMailboxes": "メールボックスを選択",
|
||||
"oauth2Tokens": "OAuth2トークン",
|
||||
"id": "ID",
|
||||
"enabled": "有効",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "アカウントの作成に失敗しました",
|
||||
"updateFailed": "更新に失敗しました。しばらくしてからもう一度お試しください。",
|
||||
"creationFailed": "作成に失敗しました。しばらくしてからもう一度お試しください。",
|
||||
"selectSyncFolders": "同期フォルダーを選択",
|
||||
"allMailFolderSelected": "ご注意: 「すべてのメール」フォルダーが選択されています",
|
||||
"accountSyncFoldersUpdated": "アカウント同期フォルダーが更新されました",
|
||||
"accountSyncFoldersUpdateFailed": "アカウント同期フォルダーの更新に失敗しました",
|
||||
"serverConfiguration": "サーバー設定 (IMAP)",
|
||||
"syncFoldersTitle": "同期フォルダー",
|
||||
"selectedMailboxes": "選択されたメールボックス",
|
||||
"allMailFolderSelectedDesc": "「すべてのメール」属性を持つフォルダーを選択すると、受信トレイや送信済みなどのフォルダーからすでに同期されているメッセージが重複する可能性があります。これにより、ストレージ容量が大幅に消費される可能性があります。",
|
||||
"allMailSkipped": "標準フォルダーが選択されました。「すべてのメール」は重複を避けるためにスキップされました。",
|
||||
"selectAtLeastOneFolder": "少なくとも1つのフォルダーを選択してください",
|
||||
"chooseFoldersToSync": "{{email}}の同期フォルダーを選択してください。新たに追加されたフォルダーは、次回の同期サイクルでダウンロードが開始されます。",
|
||||
"chooseMailboxesToDownload": "{{email}} のダウンロード対象メールボックスを選択してください。新しく追加されたメールボックスは次回のサイクルでダウンロードされます。",
|
||||
"accountDetails": "アカウント詳細",
|
||||
"capabilities": "機能",
|
||||
"folderLimit": "フォルダーの制限",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "詳細を見る",
|
||||
"runningState": {
|
||||
"notStarted": "未開始",
|
||||
"inProgress": "進行中",
|
||||
"calculating": "計算中...",
|
||||
"initialSync": "初期同期",
|
||||
"completed": "完了",
|
||||
"duration": "期間",
|
||||
"startTime": "開始時刻:",
|
||||
"endTime": "終了時刻:",
|
||||
"syncProgress": "同期進捗",
|
||||
"incrementalSync": "増分同期",
|
||||
"syncInterval": "同期間隔:",
|
||||
"everyMinutes": "{{minutes}}分ごと",
|
||||
"errorLogs": "エラーログ",
|
||||
"noErrorLogs": "エラーログはありません。",
|
||||
"noActiveState": "アクティブな状態はありません",
|
||||
"accountNotStarted": "アカウント同期がまだ開始されていない可能性があります",
|
||||
"close": "閉じる"
|
||||
"message": "メッセージ",
|
||||
"account": {
|
||||
"id": "アカウントID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "実行中タスク",
|
||||
"history": "履歴",
|
||||
"global_errors": "全体エラー",
|
||||
"folders": "メールボックス",
|
||||
"errors": "エラー"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "アカウント状態を取得中..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "現在ダウンロード中のタスクはありません",
|
||||
"no_errors_current": "現在のタスクにエラーはありません",
|
||||
"no_errors_session": "このタスクにエラーはありません",
|
||||
"no_global_errors": "全体エラーはありません",
|
||||
"no_history": "履歴がありません"
|
||||
},
|
||||
"session": {
|
||||
"status": "状態",
|
||||
"trigger": "トリガー",
|
||||
"started_at": "開始時刻"
|
||||
},
|
||||
"common": {
|
||||
"folders": "メールボックス",
|
||||
"close": "閉じる"
|
||||
},
|
||||
"latest": "最新",
|
||||
"folders": "メールボックス"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "完了",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "총 이메일 수",
|
||||
"syncedLocally": "로컬 동기화됨",
|
||||
"totalEmailSize": "총 이메일 크기",
|
||||
"logicalVolume": "논리 볼륨",
|
||||
"logicalVolume": "원래 크기",
|
||||
"localDataFiles": "로컬 데이터 파일",
|
||||
"actualDiskUsage": "실제 디스크 사용량",
|
||||
"efficiency": "압축 효율",
|
||||
"saved": "절약됨",
|
||||
"dataStorage": "데이터 스토리지",
|
||||
"indexSize": "인덱스 크기",
|
||||
"metadataSize": "메타데이터 크기",
|
||||
"duckdbDatabase": "DuckDB 스토리지 기반",
|
||||
"tantivyDatabase": "Tantivy 검색 엔진 기반",
|
||||
"dayTrend": "30일 추세",
|
||||
"attachments": "첨부 파일",
|
||||
"topLists": "상위 목록",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "편집",
|
||||
"delete": "삭제",
|
||||
"detail": "세부 정보",
|
||||
"syncFolders": "동기화 폴더",
|
||||
"selectMailboxes": "메일함 선택",
|
||||
"oauth2Tokens": "OAuth2 토큰",
|
||||
"id": "ID",
|
||||
"enabled": "활성화됨",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "계정 생성 실패",
|
||||
"updateFailed": "업데이트에 실패했습니다. 나중에 다시 시도하십시오.",
|
||||
"creationFailed": "생성에 실패했습니다. 나중에 다시 시도하십시오.",
|
||||
"selectSyncFolders": "동기화 폴더 선택",
|
||||
"allMailFolderSelected": "주의: '모든 메일' 폴더가 선택되었습니다",
|
||||
"accountSyncFoldersUpdated": "계정 동기화 폴더 업데이트됨",
|
||||
"accountSyncFoldersUpdateFailed": "계정 동기화 폴더 업데이트 실패",
|
||||
"serverConfiguration": "서버 구성 (IMAP)",
|
||||
"syncFoldersTitle": "동기화 폴더",
|
||||
"selectedMailboxes": "선택된 메일함",
|
||||
"allMailFolderSelectedDesc": "'모든 메일' 속성을 가진 폴더를 선택하면 받은 편지함이나 보낸 항목과 같은 폴더에서 이미 동기화된 메시지가 중복될 수 있습니다. 이로 인해 저장 공간이 상당히 많이 사용될 수 있습니다.",
|
||||
"allMailSkipped": "기본 폴더가 선택되었습니다. 중복을 방지하기 위해 '모든 메일'은 건너뛰었습니다.",
|
||||
"selectAtLeastOneFolder": "하나 이상의 폴더를 선택하십시오",
|
||||
"chooseFoldersToSync": "{{email}}에 대해 동기화할 폴더를 선택하십시오. 새로 추가된 폴더는 다음 동기화 주기부터 다운로드되기 시작합니다.",
|
||||
"chooseMailboxesToDownload": "{{email}}에 대해 다운로드할 메일함을 선택하세요. 새로 추가된 메일함은 다음 주기에서 다운로드됩니다.",
|
||||
"accountDetails": "계정 세부 정보",
|
||||
"capabilities": "기능",
|
||||
"folderLimit": "폴더 제한",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "세부 정보 보기",
|
||||
"runningState": {
|
||||
"notStarted": "시작되지 않음",
|
||||
"inProgress": "진행 중",
|
||||
"calculating": "계산 중...",
|
||||
"initialSync": "초기 동기화",
|
||||
"completed": "완료됨",
|
||||
"duration": "기간",
|
||||
"startTime": "시작 시간:",
|
||||
"endTime": "종료 시간:",
|
||||
"syncProgress": "동기화 진행률",
|
||||
"incrementalSync": "증분 동기화",
|
||||
"syncInterval": "동기화 간격:",
|
||||
"everyMinutes": "매 {{minutes}}분",
|
||||
"errorLogs": "오류 로그",
|
||||
"noErrorLogs": "오류 로그가 없습니다.",
|
||||
"noActiveState": "활성 상태 없음",
|
||||
"accountNotStarted": "계정 동기화가 아직 시작되지 않았을 수 있습니다",
|
||||
"close": "닫기"
|
||||
"message": "메시지",
|
||||
"account": {
|
||||
"id": "계정 ID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "현재 작업",
|
||||
"history": "기록",
|
||||
"global_errors": "전체 오류",
|
||||
"folders": "메일함",
|
||||
"errors": "오류"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "계정 상태를 불러오는 중..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "진행 중인 다운로드가 없습니다",
|
||||
"no_errors_current": "현재 작업에 오류가 없습니다",
|
||||
"no_errors_session": "이 작업에 오류가 없습니다",
|
||||
"no_global_errors": "전체 오류가 없습니다",
|
||||
"no_history": "기록이 없습니다"
|
||||
},
|
||||
"session": {
|
||||
"status": "상태",
|
||||
"trigger": "트리거",
|
||||
"started_at": "시작 시간"
|
||||
},
|
||||
"common": {
|
||||
"folders": "메일함",
|
||||
"close": "닫기"
|
||||
},
|
||||
"latest": "최신",
|
||||
"folders": "메일함"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "완료",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Totaal aantal e-mails",
|
||||
"syncedLocally": "Lokaal gesynchroniseerd",
|
||||
"totalEmailSize": "Totale e-mailgrootte",
|
||||
"logicalVolume": "Logisch volume",
|
||||
"logicalVolume": "Oorspronkelijke grootte",
|
||||
"localDataFiles": "Lokale Gegevensbestanden",
|
||||
"actualDiskUsage": "Feitelijk schijfgebruik",
|
||||
"efficiency": "Opslagefficiëntie",
|
||||
"saved": "Bespaard",
|
||||
"dataStorage": "Gegevensopslag",
|
||||
"indexSize": "Indexgrootte",
|
||||
"metadataSize": "Metadata grootte",
|
||||
"duckdbDatabase": "Gebaseerd op DuckDB-opslag",
|
||||
"tantivyDatabase": "Gebaseerd op de zoekmachine Tantivy",
|
||||
"dayTrend": "30-Dagen Trend",
|
||||
"attachments": "Bijlagen",
|
||||
"topLists": "Toplijsten",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Bewerken",
|
||||
"delete": "Verwijderen",
|
||||
"detail": "Detail",
|
||||
"syncFolders": "Sync Mappen",
|
||||
"selectMailboxes": "Mailboxen kiezen",
|
||||
"oauth2Tokens": "OAuth2 Tokens",
|
||||
"id": "ID",
|
||||
"enabled": "Ingeschakeld",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Account Aanmaken Mislukt",
|
||||
"updateFailed": "Bijwerken mislukt, probeer het later opnieuw",
|
||||
"creationFailed": "Aanmaken mislukt, probeer het later opnieuw",
|
||||
"selectSyncFolders": "Selecteer Sync Mappen",
|
||||
"allMailFolderSelected": "Let Op: 'Alle Mail' Map Geselecteerd",
|
||||
"accountSyncFoldersUpdated": "Account Sync Mappen Bijgewerkt",
|
||||
"accountSyncFoldersUpdateFailed": "Account Sync Mappen Bijwerken Mislukt",
|
||||
"serverConfiguration": "Serverconfiguratie (IMAP)",
|
||||
"syncFoldersTitle": "Sync Mappen",
|
||||
"selectedMailboxes": "Geselecteerde mailboxen",
|
||||
"allMailFolderSelectedDesc": "Het selecteren van mappen met het 'Alle Mail'-kenmerk zal waarschijnlijk leiden tot dubbele berichten die al zijn gesynchroniseerd vanuit mappen zoals Postvak In en Verzonden. Dit kan aanzienlijk meer opslagruimte in beslag nemen.",
|
||||
"allMailSkipped": "Standaardmappen geselecteerd. 'Alle Mail' is overgeslagen om duplicaten te voorkomen.",
|
||||
"selectAtLeastOneFolder": "Selecteer alstublieft ten minste één map",
|
||||
"chooseFoldersToSync": "Kies mappen om te synchroniseren voor {{email}}. Nieuw toegevoegde mappen beginnen met downloaden tijdens de volgende synchronisatiecyclus.",
|
||||
"chooseMailboxesToDownload": "Kies mailboxen om te downloaden voor {{email}}. Nieuw toegevoegde mailboxen worden in de volgende cyclus gedownload.",
|
||||
"accountDetails": "Accountdetails",
|
||||
"capabilities": "Mogelijkheden",
|
||||
"folderLimit": "Mappenlimiet",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "details bekijken",
|
||||
"runningState": {
|
||||
"notStarted": "Niet Gestart",
|
||||
"inProgress": "Bezig",
|
||||
"calculating": "Bezig met berekenen...",
|
||||
"initialSync": "Initiële Sync",
|
||||
"completed": "Voltooid",
|
||||
"duration": "Duur",
|
||||
"startTime": "Starttijd:",
|
||||
"endTime": "Eindtijd:",
|
||||
"syncProgress": "Sync Voortgang",
|
||||
"incrementalSync": "Incrementale Sync",
|
||||
"syncInterval": "Sync Interval:",
|
||||
"everyMinutes": "Elke {{minutes}} minuten",
|
||||
"errorLogs": "Foutlogboeken",
|
||||
"noErrorLogs": "Geen foutlogboeken beschikbaar.",
|
||||
"noActiveState": "Geen actieve status beschikbaar",
|
||||
"accountNotStarted": "Accountsynchronisatie is mogelijk nog niet gestart",
|
||||
"close": "Sluiten"
|
||||
"message": "Bericht",
|
||||
"account": {
|
||||
"id": "Account-ID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Actieve sessie",
|
||||
"history": "Geschiedenis",
|
||||
"global_errors": "Globale fouten",
|
||||
"folders": "Mailboxen",
|
||||
"errors": "Fouten"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Accountstatus ophalen..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Geen actieve download",
|
||||
"no_errors_current": "Geen fouten in huidige sessie",
|
||||
"no_errors_session": "Geen fouten in deze sessie",
|
||||
"no_global_errors": "Geen globale fouten",
|
||||
"no_history": "Geen geschiedenis beschikbaar"
|
||||
},
|
||||
"session": {
|
||||
"status": "Status",
|
||||
"trigger": "Trigger",
|
||||
"started_at": "Starttijd"
|
||||
},
|
||||
"common": {
|
||||
"folders": "mailboxen",
|
||||
"close": "Sluiten"
|
||||
},
|
||||
"latest": "RECENT",
|
||||
"folders": "mailboxen"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Voltooid",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Totalt antall e-poster",
|
||||
"syncedLocally": "Synkronisert lokalt",
|
||||
"totalEmailSize": "Total e-poststørrelse",
|
||||
"logicalVolume": "Logisk volum",
|
||||
"logicalVolume": "Opprinnelig størrelse",
|
||||
"localDataFiles": "Lokale datafiler",
|
||||
"actualDiskUsage": "Faktisk diskbruk",
|
||||
"efficiency": "Lagringseffektivitet",
|
||||
"saved": "Spart",
|
||||
"dataStorage": "Datalagring",
|
||||
"indexSize": "Indeksstørrelse",
|
||||
"metadataSize": "Metadatastørrelse",
|
||||
"duckdbDatabase": "Basert på DuckDB-lagring",
|
||||
"tantivyDatabase": "Basert på søkemotoren Tantivy",
|
||||
"dayTrend": "30-dagers trend",
|
||||
"attachments": "Vedlegg",
|
||||
"topLists": "Topplister",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Rediger",
|
||||
"delete": "Slett",
|
||||
"detail": "Detaljer",
|
||||
"syncFolders": "Synkroniser mapper",
|
||||
"selectMailboxes": "Velg postbokser",
|
||||
"oauth2Tokens": "OAuth2-tokener",
|
||||
"id": "ID",
|
||||
"enabled": "Aktivert",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Kontoopprettelse mislyktes",
|
||||
"updateFailed": "Oppdatering mislyktes, prøv igjen senere",
|
||||
"creationFailed": "Opprettelse mislyktes, prøv igjen senere",
|
||||
"selectSyncFolders": "Velg mapper for synkronisering",
|
||||
"allMailFolderSelected": "OBS: Mappen \"All e-post\" er valgt",
|
||||
"accountSyncFoldersUpdated": "Synkroniseringsmapper oppdatert",
|
||||
"accountSyncFoldersUpdateFailed": "Oppdatering av synkroniseringsmapper mislyktes",
|
||||
"serverConfiguration": "Serverkonfigurasjon (IMAP)",
|
||||
"syncFoldersTitle": "Synkroniser mapper",
|
||||
"selectedMailboxes": "Valgte postbokser",
|
||||
"allMailFolderSelectedDesc": "Å velge mapper med attributtet \"All e-post\" (All Mail) vil sannsynligvis føre til duplisering av meldinger som allerede er synkronisert fra mapper som Innboks og Sendt. Dette kan forbruke betydelig mer lagringsplass.",
|
||||
"allMailSkipped": "Valgte standardmapper. 'All e-post' ble hoppet over for å unngå duplikater.",
|
||||
"selectAtLeastOneFolder": "Vennligst velg minst én mappe",
|
||||
"chooseFoldersToSync": "Velg mapper som skal synkroniseres for {{email}}. Nytilførte mapper vil begynne å lastes ned ved neste synkroniseringssyklus.",
|
||||
"chooseMailboxesToDownload": "Velg postbokser som skal lastes ned for {{email}}. Nye postbokser lastes ned i neste syklus.",
|
||||
"accountDetails": "Kontodetaljer",
|
||||
"capabilities": "Funksjoner",
|
||||
"folderLimit": "Mappegrense",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "se detaljer",
|
||||
"runningState": {
|
||||
"notStarted": "Ikke startet",
|
||||
"inProgress": "Pågår",
|
||||
"calculating": "Beregner...",
|
||||
"initialSync": "Første synkronisering",
|
||||
"completed": "Fullført",
|
||||
"duration": "Varighet",
|
||||
"startTime": "Starttid:",
|
||||
"endTime": "Sluttid:",
|
||||
"syncProgress": "Synkroniseringsfremdrift",
|
||||
"incrementalSync": "Inkrementell synkronisering",
|
||||
"syncInterval": "Synkroniseringsintervall:",
|
||||
"everyMinutes": "Hvert {{minutes}} minutt",
|
||||
"errorLogs": "Feillogger",
|
||||
"noErrorLogs": "Ingen feillogger tilgjengelig.",
|
||||
"noActiveState": "Ingen aktiv tilstand tilgjengelig",
|
||||
"accountNotStarted": "Kontosynkronisering har kanskje ikke startet ennå",
|
||||
"close": "Lukk"
|
||||
"message": "Melding",
|
||||
"account": {
|
||||
"id": "Konto-ID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Aktiv økt",
|
||||
"history": "Historikk",
|
||||
"global_errors": "Globale feil",
|
||||
"folders": "Postbokser",
|
||||
"errors": "Feil"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Henter kontostatus..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Ingen aktiv nedlasting",
|
||||
"no_errors_current": "Ingen feil i gjeldende økt",
|
||||
"no_errors_session": "Ingen feil i denne økten",
|
||||
"no_global_errors": "Ingen globale feil",
|
||||
"no_history": "Ingen historikk"
|
||||
},
|
||||
"session": {
|
||||
"status": "Status",
|
||||
"trigger": "Trigger",
|
||||
"started_at": "Starttid"
|
||||
},
|
||||
"common": {
|
||||
"folders": "postbokser",
|
||||
"close": "Lukk"
|
||||
},
|
||||
"latest": "NYEST",
|
||||
"folders": "postbokser"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Fullfør",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Wszystkie wiadomości",
|
||||
"syncedLocally": "Synchronizowane lokalnie",
|
||||
"totalEmailSize": "Wielkość wszystkich wiadomości",
|
||||
"logicalVolume": "Wielkość wolumenu",
|
||||
"logicalVolume": "Oryginalny rozmiar",
|
||||
"localDataFiles": "Lokalne pliki danych",
|
||||
"actualDiskUsage": "Zajętość na dysku",
|
||||
"efficiency": "Wydajność",
|
||||
"saved": "Zaoszczędzone",
|
||||
"dataStorage": "Magazyn danych",
|
||||
"indexSize": "Rozmiar indeksu",
|
||||
"metadataSize": "Rozmiar metadanych",
|
||||
"duckdbDatabase": "Oparte na pamięci DuckDB",
|
||||
"tantivyDatabase": "Oparte na silniku wyszukiwania Tantivy",
|
||||
"dayTrend": "Trend z 30 dni",
|
||||
"attachments": "Załączniki",
|
||||
"topLists": "Lista TOP",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Edytuj",
|
||||
"delete": "Usuń",
|
||||
"detail": "Szczegóły",
|
||||
"syncFolders": "Synchronizuj foldery",
|
||||
"selectMailboxes": "Wybierz skrzynki",
|
||||
"oauth2Tokens": "Tokeny OAuth2",
|
||||
"id": "ID",
|
||||
"enabled": "Aktywne",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Bład podczas tworzenia konta",
|
||||
"updateFailed": "Bład aktulizacji, spróbuj później",
|
||||
"creationFailed": "Bład tworzenia, spróbuj później",
|
||||
"selectSyncFolders": "Wybierz foldery do synchronizacji",
|
||||
"allMailFolderSelected": "Uwaga: folder \"Wszystkie wiadomości\" izostał zaznaczony",
|
||||
"accountSyncFoldersUpdated": "Zaktualizowano foldery synchronizacji kont",
|
||||
"accountSyncFoldersUpdateFailed": "Nie udało się zaktualizować folderiów synchronizacji kont",
|
||||
"serverConfiguration": "Konfiguracja serwera (IMAP)",
|
||||
"syncFoldersTitle": "Synchronizcja folderów",
|
||||
"selectedMailboxes": "Wybrane skrzynki",
|
||||
"allMailFolderSelectedDesc": "Wybierając foldery z atrybutem \"Wszystkie wiadomości\" prawdopodobnie spowoduje to duplikowanie się wiadomości już zsynchronizowanych z folderów takich jak Skrzynka odbiorcza i Wysłane. Może to zająć znacznie więcej miejsca",
|
||||
"allMailSkipped": "Wybrane foldery standardowe. Pominięto 'Wszystkie wiadomości', aby uniknąć duplikatów.",
|
||||
"selectAtLeastOneFolder": "Wybierz co najmniej jeden folder",
|
||||
"chooseFoldersToSync": "Wybierz foldery do synchronizacji {{email}} a nowo dodane foldery zaczną być pobierane podczas kolejnej synchronizacji.",
|
||||
"chooseMailboxesToDownload": "Wybierz skrzynki do pobrania dla {{email}}. Nowe skrzynki zostaną pobrane w następnym cyklu.",
|
||||
"accountDetails": "Szczegóły konta",
|
||||
"capabilities": "Możliwości",
|
||||
"folderLimit": "Limit folderu",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "pokaż szczegóły",
|
||||
"runningState": {
|
||||
"notStarted": "Nie uruchomione",
|
||||
"inProgress": "W trakcie",
|
||||
"calculating": "Obliczanie...",
|
||||
"initialSync": "Synchronizacja początkowa",
|
||||
"completed": "Wykonano",
|
||||
"duration": "Czas trwania",
|
||||
"startTime": "Czas rozpoczęcia:",
|
||||
"endTime": "Czas zakończenia:",
|
||||
"syncProgress": "Postęp synchronizacji",
|
||||
"incrementalSync": "Synchronizacja przyrostowa",
|
||||
"syncInterval": "Cykliczność synchronizacji:",
|
||||
"everyMinutes": "Co {{minutes}} minut",
|
||||
"errorLogs": "Dzienniki błędów",
|
||||
"noErrorLogs": "Brak błędów w dziennikach logów.",
|
||||
"noActiveState": "Brak wpisów",
|
||||
"accountNotStarted": "Synchronizacja konta mogła się jeszcze nie rozpocząć",
|
||||
"close": "Zamknij"
|
||||
"message": "Wiadomość",
|
||||
"account": {
|
||||
"id": "ID konta"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Aktywna sesja",
|
||||
"history": "Historia",
|
||||
"global_errors": "Błędy globalne",
|
||||
"folders": "Skrzynki",
|
||||
"errors": "Błędy"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Pobieranie stanu konta..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Brak aktywnego pobierania",
|
||||
"no_errors_current": "Brak błędów w bieżącej sesji",
|
||||
"no_errors_session": "Brak błędów w tej sesji",
|
||||
"no_global_errors": "Brak błędów globalnych",
|
||||
"no_history": "Brak historii"
|
||||
},
|
||||
"session": {
|
||||
"status": "Status",
|
||||
"trigger": "Wyzwalacz",
|
||||
"started_at": "Czas rozpoczęcia"
|
||||
},
|
||||
"common": {
|
||||
"folders": "skrzynki",
|
||||
"close": "Zamknij"
|
||||
},
|
||||
"latest": "NAJNOWSZE",
|
||||
"folders": "skrzynki"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Zakończono",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Total de Emails",
|
||||
"syncedLocally": "Sincronizado Localmente",
|
||||
"totalEmailSize": "Tamanho Total do Email",
|
||||
"logicalVolume": "Volume Lógico",
|
||||
"logicalVolume": "Tamanho original",
|
||||
"localDataFiles": "Arquivos de Dados Locais",
|
||||
"actualDiskUsage": "Uso Real do Disco",
|
||||
"efficiency": "Eficiência",
|
||||
"saved": "Economizado",
|
||||
"dataStorage": "Armazenamento",
|
||||
"indexSize": "Tamanho índice",
|
||||
"metadataSize": "Tamanho dos metadatos",
|
||||
"duckdbDatabase": "Baseado em armazenamento DuckDB",
|
||||
"tantivyDatabase": "Baseado no motor de busca Tantivy",
|
||||
"dayTrend": "Tendência de 30 Dias",
|
||||
"attachments": "Anexos",
|
||||
"topLists": "Principais Listas",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Editar",
|
||||
"delete": "Excluir",
|
||||
"detail": "Detalhe",
|
||||
"syncFolders": "Pastas de Sincronização",
|
||||
"selectMailboxes": "Escolher caixas de correio",
|
||||
"oauth2Tokens": "Tokens OAuth2",
|
||||
"id": "ID",
|
||||
"enabled": "Ativo",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Falha ao Criar Conta",
|
||||
"updateFailed": "Falha na atualização, por favor, tente novamente mais tarde.",
|
||||
"creationFailed": "Falha na criação, por favor, tente novamente mais tarde.",
|
||||
"selectSyncFolders": "Selecionar Pastas de Sincronização",
|
||||
"allMailFolderSelected": "Atenção: A pasta 'Todos os Emails' está selecionada",
|
||||
"accountSyncFoldersUpdated": "Pastas de Sincronização da Conta Atualizadas",
|
||||
"accountSyncFoldersUpdateFailed": "Falha ao Atualizar Pastas de Sincronização da Conta",
|
||||
"serverConfiguration": "Configuração do Servidor (IMAP)",
|
||||
"syncFoldersTitle": "Pastas de Sincronização",
|
||||
"selectedMailboxes": "Caixas de correio selecionadas",
|
||||
"allMailFolderSelectedDesc": "Selecionar uma pasta com o atributo 'Todos os Emails' pode duplicar mensagens já sincronizadas de pastas como Caixa de Entrada ou Itens Enviados. Isso pode consumir significativamente o seu armazenamento.",
|
||||
"allMailSkipped": "Pastas padrão selecionadas. 'Todos os Emails' foi ignorado para evitar duplicação.",
|
||||
"selectAtLeastOneFolder": "Por favor, selecione pelo menos uma pasta",
|
||||
"chooseFoldersToSync": "Escolha as pastas para sincronizar para {{email}}. As pastas recém-adicionadas começarão a ser baixadas no próximo ciclo de sincronização.",
|
||||
"chooseMailboxesToDownload": "Escolha as caixas de correio para baixar para {{email}}. Novas caixas serão baixadas no próximo ciclo.",
|
||||
"accountDetails": "Detalhes da Conta",
|
||||
"capabilities": "Capacidades",
|
||||
"folderLimit": "Limite de Pasta",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "Ver Detalhes",
|
||||
"runningState": {
|
||||
"notStarted": "Não Iniciado",
|
||||
"inProgress": "Em Progresso",
|
||||
"calculating": "Calculando...",
|
||||
"initialSync": "Sincronização Inicial",
|
||||
"completed": "Concluído",
|
||||
"duration": "Duração",
|
||||
"startTime": "Hora de Início:",
|
||||
"endTime": "Hora de Término:",
|
||||
"syncProgress": "Progresso da Sincronização",
|
||||
"incrementalSync": "Sincronização Incremental",
|
||||
"syncInterval": "Intervalo de Sincronização:",
|
||||
"everyMinutes": "A cada {{minutes}} minutos",
|
||||
"errorLogs": "Logs de Erro",
|
||||
"noErrorLogs": "Nenhum log de erro.",
|
||||
"noActiveState": "Nenhum estado ativo",
|
||||
"accountNotStarted": "A sincronização da conta pode não ter começado ainda",
|
||||
"close": "Fechar"
|
||||
"message": "Mensagem",
|
||||
"account": {
|
||||
"id": "ID da conta"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Sessão ativa",
|
||||
"history": "Histórico",
|
||||
"global_errors": "Erros globais",
|
||||
"folders": "Caixas de correio",
|
||||
"errors": "Erros"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Obtendo estado da conta..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Nenhum download em andamento",
|
||||
"no_errors_current": "Sem erros na sessão atual",
|
||||
"no_errors_session": "Sem erros nesta sessão",
|
||||
"no_global_errors": "Sem erros globais",
|
||||
"no_history": "Sem histórico"
|
||||
},
|
||||
"session": {
|
||||
"status": "Status",
|
||||
"trigger": "Gatilho",
|
||||
"started_at": "Hora de início"
|
||||
},
|
||||
"common": {
|
||||
"folders": "caixas de correio",
|
||||
"close": "Fechar"
|
||||
},
|
||||
"latest": "RECENTE",
|
||||
"folders": "caixas de correio"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Concluído",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Всего писем",
|
||||
"syncedLocally": "Синхронизировано локально",
|
||||
"totalEmailSize": "Общий размер писем",
|
||||
"logicalVolume": "Логический объем",
|
||||
"logicalVolume": "Оригинальный размер",
|
||||
"localDataFiles": "Локальные файлы данных",
|
||||
"actualDiskUsage": "Фактическое использование диска",
|
||||
"efficiency": "Эффективность",
|
||||
"saved": "Сэкономлено",
|
||||
"dataStorage": "Хранилище",
|
||||
"indexSize": "Размер индекса",
|
||||
"metadataSize": "Размер метаданных",
|
||||
"duckdbDatabase": "На базе хранилища DuckDB",
|
||||
"tantivyDatabase": "На базе поискового движка Tantivy",
|
||||
"dayTrend": "Тренд за 30 дней",
|
||||
"attachments": "Вложения",
|
||||
"topLists": "Топ списки",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Ред.",
|
||||
"delete": "Удалить",
|
||||
"detail": "Подробно",
|
||||
"syncFolders": "Папки синхр.",
|
||||
"selectMailboxes": "Выбрать почтовые ящики",
|
||||
"oauth2Tokens": "Токены OAuth2",
|
||||
"id": "ID",
|
||||
"enabled": "Включено",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Ошибка создания аккаунта",
|
||||
"updateFailed": "Ошибка обновления, попробуйте позже",
|
||||
"creationFailed": "Ошибка создания, попробуйте позже",
|
||||
"selectSyncFolders": "Выберите папки для синхронизации",
|
||||
"allMailFolderSelected": "Внимание: Выбрана папка \"Вся почта\"",
|
||||
"accountSyncFoldersUpdated": "Папки синхронизации обновлены",
|
||||
"accountSyncFoldersUpdateFailed": "Не удалось обновить папки синхронизации",
|
||||
"serverConfiguration": "Конфигурация сервера (IMAP)",
|
||||
"syncFoldersTitle": "Папки синхронизации",
|
||||
"selectedMailboxes": "Выбранные почтовые ящики",
|
||||
"allMailFolderSelectedDesc": "Выбор папок с атрибутом \"Вся почта\" скорее всего приведет к дублированию сообщений, уже синхронизированных из папок \"Входящие\" и \"Отправленные\". Это может занять значительно больше места.",
|
||||
"allMailSkipped": "Выбраны стандартные папки. Папка 'Вся почта' пропущена во избежание дубликатов.",
|
||||
"selectAtLeastOneFolder": "Пожалуйста, выберите хотя бы одну папку",
|
||||
"chooseFoldersToSync": "Выберите папки для синхронизации для {{email}}. Новые добавленные папки начнут загружаться во время следующего цикла синхронизации.",
|
||||
"chooseMailboxesToDownload": "Выберите почтовые ящики для загрузки для {{email}}. Новые ящики будут загружены в следующем цикле.",
|
||||
"accountDetails": "Детали аккаунта",
|
||||
"capabilities": "Возможности",
|
||||
"folderLimit": "Лимит папки",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "смотреть детали",
|
||||
"runningState": {
|
||||
"notStarted": "Не начато",
|
||||
"inProgress": "В процессе",
|
||||
"calculating": "Вычисление...",
|
||||
"initialSync": "Начальная синхронизация",
|
||||
"completed": "Завершено",
|
||||
"duration": "Длительность",
|
||||
"startTime": "Время начала:",
|
||||
"endTime": "Время окончания:",
|
||||
"syncProgress": "Прогресс синхронизации",
|
||||
"incrementalSync": "Инкрементальная синхронизация",
|
||||
"syncInterval": "Интервал синхронизации:",
|
||||
"everyMinutes": "Каждые {{minutes}} минут",
|
||||
"errorLogs": "Журнал ошибок",
|
||||
"noErrorLogs": "Журналы ошибок отсутствуют.",
|
||||
"noActiveState": "Нет активного состояния",
|
||||
"accountNotStarted": "Синхронизация аккаунта, возможно, еще не началась",
|
||||
"close": "Закрыть"
|
||||
"message": "Сообщение",
|
||||
"account": {
|
||||
"id": "ID аккаунта"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Активная сессия",
|
||||
"history": "История",
|
||||
"global_errors": "Глобальные ошибки",
|
||||
"folders": "Почтовые ящики",
|
||||
"errors": "Ошибки"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Загрузка состояния аккаунта..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Нет активных загрузок",
|
||||
"no_errors_current": "Нет ошибок в текущей сессии",
|
||||
"no_errors_session": "Нет ошибок в этой сессии",
|
||||
"no_global_errors": "Нет глобальных ошибок",
|
||||
"no_history": "Нет истории"
|
||||
},
|
||||
"session": {
|
||||
"status": "Статус",
|
||||
"trigger": "Триггер",
|
||||
"started_at": "Время начала"
|
||||
},
|
||||
"common": {
|
||||
"folders": "почтовые ящики",
|
||||
"close": "Закрыть"
|
||||
},
|
||||
"latest": "ПОСЛЕДНЕЕ",
|
||||
"folders": "почтовые ящики"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Завершено",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "Totalt antal e-postmeddelanden",
|
||||
"syncedLocally": "Synkroniserat lokalt",
|
||||
"totalEmailSize": "Total e-poststorlek",
|
||||
"logicalVolume": "Logisk volym",
|
||||
"logicalVolume": "Ursprunglig storlek",
|
||||
"localDataFiles": "Lokala datafiler",
|
||||
"actualDiskUsage": "Faktisk diskanvändning",
|
||||
"efficiency": "Lagringseffektivitet",
|
||||
"saved": "Sparat",
|
||||
"dataStorage": "Datalagring",
|
||||
"indexSize": "Indexstorlek",
|
||||
"metadataSize": "Metadatastorlek",
|
||||
"duckdbDatabase": "Baserat på DuckDB-lagring",
|
||||
"tantivyDatabase": "Baserat på sökmotorn Tantivy",
|
||||
"dayTrend": "30-dagarstrend",
|
||||
"attachments": "Bilagor",
|
||||
"topLists": "Topplistor",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "Redigera",
|
||||
"delete": "Ta bort",
|
||||
"detail": "Detaljer",
|
||||
"syncFolders": "Synkronisera mappar",
|
||||
"selectMailboxes": "Välj brevlådor",
|
||||
"oauth2Tokens": "OAuth2-tokens",
|
||||
"id": "ID",
|
||||
"enabled": "Aktiverad",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "Kontoskapande misslyckades",
|
||||
"updateFailed": "Uppdatering misslyckades, försök igen senare",
|
||||
"creationFailed": "Skapande misslyckades, försök igen senare",
|
||||
"selectSyncFolders": "Välj mappar att synkronisera",
|
||||
"allMailFolderSelected": "Observera: Mappen \"All e-post\" vald",
|
||||
"accountSyncFoldersUpdated": "Synkroniseringsmappar uppdaterade",
|
||||
"accountSyncFoldersUpdateFailed": "Uppdatering av synkroniseringsmappar misslyckades",
|
||||
"serverConfiguration": "Serverkonfiguration (IMAP)",
|
||||
"syncFoldersTitle": "Synkronisera mappar",
|
||||
"selectedMailboxes": "Valda brevlådor",
|
||||
"allMailFolderSelectedDesc": "Att välja mappar med attributet \"All e-post\" (All Mail) leder sannolikt till dubbletter av meddelanden som redan synkroniserats från mappar som Inkorg och Skickat. Detta kan ta upp betydligt mer lagringsutrymme.",
|
||||
"allMailSkipped": "Valde standardmappar. \"All e-post\" hoppades över för att undvika dubbletter.",
|
||||
"selectAtLeastOneFolder": "Vänligen välj minst en mapp",
|
||||
"chooseFoldersToSync": "Välj mappar att synkronisera för {{email}}. Nyligen tillagda mappar börjar laddas ner vid nästa synkroniseringscykel.",
|
||||
"chooseMailboxesToDownload": "Välj brevlådor att ladda ner för {{email}}. Nya brevlådor laddas ner i nästa cykel.",
|
||||
"accountDetails": "Kontodetaljer",
|
||||
"capabilities": "Funktioner",
|
||||
"folderLimit": "Mappgräns",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "visa detaljer",
|
||||
"runningState": {
|
||||
"notStarted": "Ej startad",
|
||||
"inProgress": "Pågår",
|
||||
"calculating": "Beräknar...",
|
||||
"initialSync": "Initial synkronisering",
|
||||
"completed": "Slutförd",
|
||||
"duration": "Varaktighet",
|
||||
"startTime": "Starttid:",
|
||||
"endTime": "Sluttid:",
|
||||
"syncProgress": "Synkroniseringsförlopp",
|
||||
"incrementalSync": "Inkrementell synkronisering",
|
||||
"syncInterval": "Synkroniseringsintervall:",
|
||||
"everyMinutes": "Varje {{minutes}} minut",
|
||||
"errorLogs": "Felloggar",
|
||||
"noErrorLogs": "Inga felloggar tillgängliga.",
|
||||
"noActiveState": "Inget aktivt tillstånd tillgängligt",
|
||||
"accountNotStarted": "Kontosynkronisering har kanske inte startat ännu",
|
||||
"close": "Stäng"
|
||||
"message": "Meddelande",
|
||||
"account": {
|
||||
"id": "Kontots ID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "Aktiv session",
|
||||
"history": "Historik",
|
||||
"global_errors": "Globala fel",
|
||||
"folders": "Postlådor",
|
||||
"errors": "Fel"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "Hämtar kontostatus..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "Ingen aktiv nedladdning",
|
||||
"no_errors_current": "Inga fel i aktuell session",
|
||||
"no_errors_session": "Inga fel i denna session",
|
||||
"no_global_errors": "Inga globala fel",
|
||||
"no_history": "Ingen historik"
|
||||
},
|
||||
"session": {
|
||||
"status": "Status",
|
||||
"trigger": "Trigger",
|
||||
"started_at": "Starttid"
|
||||
},
|
||||
"common": {
|
||||
"folders": "postlådor",
|
||||
"close": "Stäng"
|
||||
},
|
||||
"latest": "SENASTE",
|
||||
"folders": "postlådor"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "Slutför",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "郵件總數",
|
||||
"syncedLocally": "本地同步數量",
|
||||
"totalEmailSize": "郵件總容量",
|
||||
"logicalVolume": "邏輯容量",
|
||||
"logicalVolume": "原始大小",
|
||||
"localDataFiles": "本地資料檔案",
|
||||
"actualDiskUsage": "實際磁碟用量",
|
||||
"efficiency": "壓縮效率",
|
||||
"saved": "節省空間",
|
||||
"dataStorage": "資料儲存",
|
||||
"indexSize": "索引體積",
|
||||
"metadataSize": "元數據大小",
|
||||
"duckdbDatabase": "基於 DuckDB 存儲",
|
||||
"tantivyDatabase": "基于 Tantivy 搜索引擎",
|
||||
"dayTrend": "30 天趨勢",
|
||||
"attachments": "附件",
|
||||
"topLists": "排行榜",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "編輯",
|
||||
"delete": "刪除",
|
||||
"detail": "詳細資訊",
|
||||
"syncFolders": "同步資料夾",
|
||||
"selectMailboxes": "選擇郵件夾",
|
||||
"oauth2Tokens": "OAuth2 權杖",
|
||||
"id": "ID",
|
||||
"enabled": "啟用",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "帳號建立失敗",
|
||||
"updateFailed": "更新失敗,請稍後再試。",
|
||||
"creationFailed": "建立失敗,請稍後再試。",
|
||||
"selectSyncFolders": "選擇同步資料夾",
|
||||
"allMailFolderSelected": "注意:「所有郵件」資料夾已選擇",
|
||||
"accountSyncFoldersUpdated": "帳號同步資料夾已更新",
|
||||
"accountSyncFoldersUpdateFailed": "帳號同步資料夾更新失敗",
|
||||
"serverConfiguration": "伺服器設定 (IMAP)",
|
||||
"syncFoldersTitle": "同步資料夾",
|
||||
"selectedMailboxes": "已選擇的郵件夾",
|
||||
"allMailFolderSelectedDesc": "選擇具有「所有郵件」屬性的資料夾可能會導致已從收件匣或寄件備份等資料夾同步的郵件重複。這可能會大幅增加您的儲存用量。",
|
||||
"allMailSkipped": "標準資料夾已選擇,為避免重複已跳過「所有郵件」。",
|
||||
"selectAtLeastOneFolder": "請至少選擇一個資料夾",
|
||||
"chooseFoldersToSync": "請選擇要為 {{email}} 同步的資料夾。新新增的資料夾將在下一個同步週期開始下載。",
|
||||
"chooseMailboxesToDownload": "為 {{email}} 選擇要下載的郵件夾。新加入的郵件夾會在下個週期下載。",
|
||||
"accountDetails": "帳號詳細資訊",
|
||||
"capabilities": "功能",
|
||||
"folderLimit": "資料夾限制",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "檢視詳細資訊",
|
||||
"runningState": {
|
||||
"notStarted": "未啟動",
|
||||
"inProgress": "進行中",
|
||||
"calculating": "正在計算...",
|
||||
"initialSync": "初始同步",
|
||||
"completed": "已完成",
|
||||
"duration": "持續時間",
|
||||
"startTime": "開始時間:",
|
||||
"endTime": "結束時間:",
|
||||
"syncProgress": "同步進度",
|
||||
"incrementalSync": "增量同步",
|
||||
"syncInterval": "同步間隔:",
|
||||
"everyMinutes": "每 {{minutes}} 分鐘",
|
||||
"errorLogs": "錯誤日誌",
|
||||
"noErrorLogs": "無錯誤日誌。",
|
||||
"noActiveState": "無活動狀態",
|
||||
"accountNotStarted": "帳號同步可能尚未開始",
|
||||
"close": "關閉"
|
||||
"message": "訊息",
|
||||
"account": {
|
||||
"id": "帳戶 ID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "目前任務",
|
||||
"history": "歷史記錄",
|
||||
"global_errors": "全域錯誤",
|
||||
"folders": "郵件夾",
|
||||
"errors": "錯誤"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "正在取得帳戶狀態..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "目前沒有下載任務",
|
||||
"no_errors_current": "目前任務沒有錯誤",
|
||||
"no_errors_session": "此任務沒有錯誤",
|
||||
"no_global_errors": "沒有全域錯誤",
|
||||
"no_history": "沒有歷史記錄"
|
||||
},
|
||||
"session": {
|
||||
"status": "狀態",
|
||||
"trigger": "觸發方式",
|
||||
"started_at": "開始時間"
|
||||
},
|
||||
"common": {
|
||||
"folders": "個郵件夾",
|
||||
"close": "關閉"
|
||||
},
|
||||
"latest": "最新",
|
||||
"folders": "個郵件夾"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "完成",
|
||||
|
||||
+41
-23
@@ -104,11 +104,15 @@
|
||||
"totalEmails": "邮件总数",
|
||||
"syncedLocally": "本地已同步",
|
||||
"totalEmailSize": "邮件总大小",
|
||||
"logicalVolume": "逻辑卷",
|
||||
"logicalVolume": "原始大小",
|
||||
"localDataFiles": "本地数据文件",
|
||||
"actualDiskUsage": "实际磁盘使用",
|
||||
"efficiency": "压缩效率",
|
||||
"saved": "节省空间",
|
||||
"dataStorage": "数据存储",
|
||||
"indexSize": "索引体积",
|
||||
"metadataSize": "元数据大小",
|
||||
"duckdbDatabase": "基于 DuckDB 存储",
|
||||
"tantivyDatabase": "基於 Tantivy 搜尋引擎",
|
||||
"dayTrend": "30天趋势",
|
||||
"attachments": "附件",
|
||||
"topLists": "排行榜",
|
||||
@@ -195,7 +199,7 @@
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"detail": "详情",
|
||||
"syncFolders": "同步文件夹",
|
||||
"selectMailboxes": "选择邮件夹",
|
||||
"oauth2Tokens": "OAuth2 令牌",
|
||||
"id": "ID",
|
||||
"enabled": "已启用",
|
||||
@@ -223,16 +227,15 @@
|
||||
"accountCreationFailed": "账户创建失败",
|
||||
"updateFailed": "更新失败,请稍后重试",
|
||||
"creationFailed": "创建失败,请稍后重试",
|
||||
"selectSyncFolders": "选择同步文件夹",
|
||||
"allMailFolderSelected": "提示:已选择\"所有邮件\"文件夹",
|
||||
"accountSyncFoldersUpdated": "账户同步文件夹已更新",
|
||||
"accountSyncFoldersUpdateFailed": "账户同步文件夹更新失败",
|
||||
"serverConfiguration": "服务器配置 (IMAP)",
|
||||
"syncFoldersTitle": "同步文件夹",
|
||||
"selectedMailboxes": "已选择的邮件夹",
|
||||
"allMailFolderSelectedDesc": "选择具有\"所有邮件\"属性的文件夹可能会导致重复已从收件箱和已发送等文件夹同步的消息。这可能会消耗更多的存储空间。",
|
||||
"allMailSkipped": "已选择标准文件夹。已跳过'所有邮件'以避免重复。",
|
||||
"selectAtLeastOneFolder": "请至少选择一个文件夹",
|
||||
"chooseFoldersToSync": "选择要同步的文件夹({{email}}),新添加的文件夹将在下一个同步周期开始下载。",
|
||||
"chooseMailboxesToDownload": "为 {{email}} 选择要下载的邮件夹。新添加的邮件夹将在下一个周期下载。",
|
||||
"accountDetails": "账户详情",
|
||||
"capabilities": "功能",
|
||||
"folderLimit": "文件夹限制",
|
||||
@@ -313,23 +316,38 @@
|
||||
},
|
||||
"viewDetails": "查看详情",
|
||||
"runningState": {
|
||||
"notStarted": "未开始",
|
||||
"inProgress": "进行中",
|
||||
"calculating": "计算中...",
|
||||
"initialSync": "初始同步",
|
||||
"completed": "已完成",
|
||||
"duration": "耗时",
|
||||
"startTime": "开始时间:",
|
||||
"endTime": "结束时间:",
|
||||
"syncProgress": "同步进度",
|
||||
"incrementalSync": "增量同步",
|
||||
"syncInterval": "同步间隔:",
|
||||
"everyMinutes": "每 {{minutes}} 分钟",
|
||||
"errorLogs": "错误日志",
|
||||
"noErrorLogs": "暂无错误日志",
|
||||
"noActiveState": "暂无活动状态",
|
||||
"accountNotStarted": "账户同步可能尚未开始",
|
||||
"close": "关闭"
|
||||
"message": "消息",
|
||||
"account": {
|
||||
"id": "账户 ID"
|
||||
},
|
||||
"tabs": {
|
||||
"active_session": "当前任务",
|
||||
"history": "历史记录",
|
||||
"global_errors": "全局错误",
|
||||
"folders": "邮件夹",
|
||||
"errors": "错误"
|
||||
},
|
||||
"loading": {
|
||||
"fetching_account_state": "正在获取账户状态..."
|
||||
},
|
||||
"empty": {
|
||||
"no_active_download": "当前没有下载任务",
|
||||
"no_errors_current": "当前任务无错误",
|
||||
"no_errors_session": "该任务无错误",
|
||||
"no_global_errors": "暂无全局错误",
|
||||
"no_history": "暂无历史记录"
|
||||
},
|
||||
"session": {
|
||||
"status": "状态",
|
||||
"trigger": "触发方式",
|
||||
"started_at": "开始时间"
|
||||
},
|
||||
"common": {
|
||||
"folders": "个文件夹",
|
||||
"close": "关闭"
|
||||
},
|
||||
"latest": "最新",
|
||||
"folders": "个文件夹"
|
||||
},
|
||||
"steps": {
|
||||
"complete": "完成",
|
||||
|
||||
Reference in New Issue
Block a user