From d0e4cac229478455ac48e291f35f2e1b03969a75 Mon Sep 17 00:00:00 2001 From: rustmailer Date: Tue, 20 Jan 2026 00:53:09 +0800 Subject: [PATCH] i18n --- web/src/features/search/account-popover.tsx | 86 +-- web/src/features/search/contact-popover.tsx | 30 +- web/src/features/search/context/index.tsx | 2 +- web/src/features/search/filter-reset.tsx | 7 +- web/src/features/search/index.tsx | 32 -- web/src/features/search/mailbox-popover.tsx | 14 +- .../features/search/more-filters-popover.tsx | 31 +- web/src/features/search/search-form.tsx | 506 ------------------ web/src/features/search/table/table.tsx | 2 +- .../features/search/table/view-options.tsx | 5 +- web/src/features/search/tag-facet.tsx | 127 ----- .../features/search/tag-filter-popover.tsx | 10 +- web/src/features/search/text-search-input.tsx | 90 ++-- web/src/features/search/time-popover.tsx | 57 +- web/src/hooks/use-search-messages.ts | 9 +- web/src/locales/ar.json | 86 +++ web/src/locales/da.json | 86 +++ web/src/locales/de.json | 86 +++ web/src/locales/en.json | 86 +++ web/src/locales/es.json | 86 +++ web/src/locales/fi.json | 86 +++ web/src/locales/fr.json | 86 +++ web/src/locales/it.json | 86 +++ web/src/locales/jp.json | 86 +++ web/src/locales/ko.json | 86 +++ web/src/locales/nl.json | 86 +++ web/src/locales/no.json | 86 +++ web/src/locales/pl.json | 86 +++ web/src/locales/pt.json | 86 +++ web/src/locales/ru.json | 86 +++ web/src/locales/sv.json | 86 +++ web/src/locales/zh-tw.json | 86 +++ web/src/locales/zh.json | 86 +++ 33 files changed, 1742 insertions(+), 814 deletions(-) delete mode 100644 web/src/features/search/search-form.tsx delete mode 100644 web/src/features/search/tag-facet.tsx diff --git a/web/src/features/search/account-popover.tsx b/web/src/features/search/account-popover.tsx index 614b416..6daf83e 100644 --- a/web/src/features/search/account-popover.tsx +++ b/web/src/features/search/account-popover.tsx @@ -90,7 +90,7 @@ export function AccountPopover() { )} > - Account + {t('search_accounts.label')} {selectedIds.length > 0 && ( setSearch(e.target.value)} - placeholder={t('search.searchAccount')} + placeholder={t('search_accounts.search_placeholder')} className="h-8 text-sm" /> @@ -124,7 +124,7 @@ export function AccountPopover() { - {t('search.clearAccounts')} + {t('search_accounts.clear_accounts')} ({selectedIds.length}) @@ -134,46 +134,52 @@ export function AccountPopover() { )} - {filtered.map(account => { - const checked = selectedIds.includes(account.id) - const id = `account-${account.id}` + {filtered.length === 0 ? ( +

+ {t('search_accounts.no_accounts_found')} +

+ ) : ( + filtered.map(account => { + const checked = selectedIds.includes(account.id) + const id = `account-${account.id}` - return ( -
toggleAccount(account.id)} - className={cn( - 'flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer', - 'hover:bg-accent transition-colors' - )} - > - - toggleAccount(account.id) - } - onClick={e => e.stopPropagation()} - /> - - -
- ) - })} + + toggleAccount(account.id) + } + onClick={e => e.stopPropagation()} + /> + + + + ) + }) + )}
) -} +} \ No newline at end of file diff --git a/web/src/features/search/contact-popover.tsx b/web/src/features/search/contact-popover.tsx index 0c6de8f..1e7a36b 100644 --- a/web/src/features/search/contact-popover.tsx +++ b/web/src/features/search/contact-popover.tsx @@ -5,6 +5,7 @@ import { cn } from "@/lib/utils" import { Check, ChevronDown, Mail, X } from "lucide-react" import React from "react" import { useContacts } from "@/hooks/use-contacts" +import { useTranslation } from 'react-i18next' import { Command, CommandEmpty, @@ -15,6 +16,7 @@ import { } from "@/components/ui/command" export function MailFilterPopover() { + const { t } = useTranslation() const { filter, setFilter } = useSearchContext() const fields = ['from', 'to', 'cc', 'bcc'] as const @@ -47,7 +49,11 @@ export function MailFilterPopover() { )} > - {activeCount > 0 ? `Participants (${activeCount})` : 'Participants'} + + {activeCount > 0 + ? t('search_contacts.label_with_count', { count: activeCount }) + : t('search_contacts.label')} + @@ -69,14 +75,19 @@ export function MailFilterPopover() { {activeCount > 0 && ( -
+
)} @@ -96,6 +107,7 @@ function ContactSelectorField({ onSelect: (email: string | undefined) => void onReset: () => void }) { + const { t } = useTranslation() const [searchTerm, setSearchTerm] = React.useState("") const { contacts, isLoading } = useContacts(searchTerm) @@ -129,7 +141,7 @@ function ContactSelectorField({ : "text-xs text-muted-foreground/90" )} > - {value || 'Any'} + {value || t('search_contacts.any')}
@@ -161,16 +173,16 @@ function ContactSelectorField({ > {isLoading && ( -
Loading...
+
{t('search_contacts.loading')}
)} - No contact found. + {t('search_contacts.no_contact_found')} {contacts.slice(0, 100).map((email) => ( 100 && (
- Showing top 100 results • {contacts.length} total + {t('search_contacts.showing_limit', { total: contacts.length })}
)}
diff --git a/web/src/features/search/context/index.tsx b/web/src/features/search/context/index.tsx index 30bbfd8..87c2cad 100644 --- a/web/src/features/search/context/index.tsx +++ b/web/src/features/search/context/index.tsx @@ -21,7 +21,7 @@ import React from 'react' import { EmailEnvelope } from '@/api' import { SortingState } from '@tanstack/react-table' -export type SearchDialogType = 'mailbox' | 'display' | 'delete' | 'filters' | 'tags' | 'edit-tags' | 'search-form' | 'restore' +export type SearchDialogType = 'mailbox' | 'display' | 'delete' | 'filters' | 'tags' | 'edit-tags' | 'restore' interface SearchContextType { open: SearchDialogType | null diff --git a/web/src/features/search/filter-reset.tsx b/web/src/features/search/filter-reset.tsx index dbf1744..f3fac81 100644 --- a/web/src/features/search/filter-reset.tsx +++ b/web/src/features/search/filter-reset.tsx @@ -2,11 +2,13 @@ import { X } from "lucide-react" import { Button } from "@/components/ui/button" import { useSearchContext } from "./context" import { cn } from "@/lib/utils" +import { useTranslation } from "react-i18next"; export function FilterResetButton() { const { filter, setFilter } = useSearchContext(); - + const { t } = useTranslation() const { q, ...restFilters } = filter; + const activeFiltersCount = Object.keys(restFilters).filter(key => { const value = restFilters[key]; if (Array.isArray(value)) return value.length > 0; @@ -24,8 +26,9 @@ export function FilterResetButton() { "h-8 px-2 text-xs gap-1.5 font-normal", "text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors" )} + title={t('search_reset.tooltip')} > - Reset + {t('search_reset.label')}
{activeFiltersCount}
diff --git a/web/src/features/search/index.tsx b/web/src/features/search/index.tsx index f174376..a975a04 100644 --- a/web/src/features/search/index.tsx +++ b/web/src/features/search/index.tsx @@ -21,7 +21,6 @@ import { Card, CardContent } from '@/components/ui/card'; import { FixedHeader } from '@/components/layout/fixed-header'; import { Main } from '@/components/layout/main'; import { useSearchMessages } from '@/hooks/use-search-messages'; -import { SearchFormDialog } from './search-form'; import { EnvelopeListPagination } from '@/components/pagination'; import React from 'react'; import { EmailEnvelope } from '@/api'; @@ -49,15 +48,12 @@ export default function Search() { total, totalPages, isLoading, - isFetching, page, pageSize, setPage, setPageSize, setSortBy, setSortOrder, - onSubmit, - reset, filter, setFilter } = useSearchMessages(); @@ -67,12 +63,6 @@ export default function Search() { setPageSize(pageSize) } - - const handleReset = () => { - reset(); - setSelectedTags([]); - }; - const handleTagToggle = (tag: string) => { setSelectedTags(prev => prev.includes(tag) @@ -125,21 +115,6 @@ export default function Search() { )} - {/* {!isLoading && total === 0 &&
-
- Bichon Logo -

{t('search.noEmailsFound')}

-

- {Object.keys(filter).length === 0 - ? t('search.startSearching') - : t('search.adjustSearch')} -

-
-
} */} setOpen('edit-tags')} /> - setOpen('search-form')} - /> - - Mailbox + {t('search_mailbox.label')} {selectedMailboxIds.length > 0 && ( {selectedMailboxIds.length} @@ -138,7 +140,7 @@ export function MailboxPopover() { setSearch(e.target.value)} - placeholder="Search mailbox" + placeholder={t('search_mailbox.search_placeholder')} className="h-8 text-sm" /> @@ -151,14 +153,14 @@ export function MailboxPopover() { className="h-7 w-full justify-start text-xs text-muted-foreground hover:text-destructive transition-colors" > - Clear Mailboxes ({selectedMailboxIds.length}) + {t('search_mailbox.clear_mailboxes')} ({selectedMailboxIds.length}) )} {disabled ? (

- Please select account first + {t('search_mailbox.select_account_first')}

) : isLoading ? (
@@ -171,7 +173,7 @@ export function MailboxPopover() {
) : grouped.length === 0 ? (

- No mailbox found + {t('search_mailbox.no_mailbox_found')}

) : ( ) -} +} \ No newline at end of file diff --git a/web/src/features/search/more-filters-popover.tsx b/web/src/features/search/more-filters-popover.tsx index d553ba4..8569dea 100644 --- a/web/src/features/search/more-filters-popover.tsx +++ b/web/src/features/search/more-filters-popover.tsx @@ -94,7 +94,7 @@ export function MoreFiltersPopover() { )} > - Advanced + {t('search_more.trigger_label')} {activeCount > 0 && ( {activeCount} @@ -105,7 +105,7 @@ export function MoreFiltersPopover() {
-

Advanced Filters

+

{t('search_more.title')}

{activeCount > 0 && ( )}
@@ -140,22 +140,22 @@ export function MoreFiltersPopover() { htmlFor="has_attachment" className="text-xs font-normal cursor-pointer select-none" > - Has Attachments + {t('search_more.has_attachment')}
- + setLocalState(prev => ({ ...prev, attachment_name: e.target.value }))} - placeholder="e.g. invoice.pdf" + placeholder={t('search_more.attachment_name_placeholder')} />
- +
- + setLocalState(prev => ({ ...prev, message_id: e.target.value }))} />

- {t('search.originalMessageIdHeader')} + {t('search_more.message_id_description')}

diff --git a/web/src/features/search/search-form.tsx b/web/src/features/search/search-form.tsx deleted file mode 100644 index 78ad6c4..0000000 --- a/web/src/features/search/search-form.tsx +++ /dev/null @@ -1,506 +0,0 @@ -// -// Copyright (c) 2025 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - - -import { DatePicker } from "@/components/date-picker"; -import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; -import { Button } from "@/components/ui/button"; -import { Checkbox } from "@/components/ui/checkbox"; -import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { ChevronDown, ChevronUp, Filter, RotateCcw } from "lucide-react"; -import { z } from 'zod'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { VirtualizedSelect } from "@/components/virtualized-select"; -import useMinimalAccountList from "@/hooks/use-minimal-account-list"; -import { useNavigate } from "@tanstack/react-router"; -import { list_mailboxes, MailboxData } from "@/api/mailbox/api"; -import { useQueries } from "@tanstack/react-query"; -import { useSearchContext } from "./context"; -import { toast } from "@/hooks/use-toast"; -import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"; -import { useTranslation } from "react-i18next"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; - -const searchFilterSchema = z.object({ - text: z.string().optional().or(z.literal("")), - from: z - .string() - .optional() - .or(z.literal("")), - to: z - .string() - .optional() - .or(z.literal("")), - cc: z - .string() - .optional() - .or(z.literal("")), - bcc: z - .string() - .optional() - .or(z.literal("")), - has_attachment: z.boolean().optional(), - attachment_name: z.string().optional().or(z.literal("")), - since: z.date().optional(), - before: z.date().optional(), - account_ids: z.array(z.number()), - mailbox_ids: z.array(z.number()), - size_preset: z.enum(['any', 'tiny', 'small', 'medium', 'large', 'huge']).optional(), - message_id: z.string().optional().or(z.literal("")), -}); - -type SearchFilterForm = z.infer; - - -interface Props { - onSubmit: (values: Record) => void, - isLoading: boolean, - reset: () => void, - open: boolean, - onOpenChange: (open: boolean) => void; -} - -const isEmptyValue = (value: any): boolean => { - if (value === null || value === undefined) return true; - if (value === '') return true; - if (typeof value === 'string' && value.trim() === '') return true; - if (typeof value === 'number' && isNaN(value)) return true; - if (value === false) return true; - if (value === 0) return true; - if (Array.isArray(value) && value.length === 0) return true; - return false; -}; - -const cleanEmpty = >(obj: T): Partial => { - return Object.fromEntries( - Object.entries(obj).filter(([_, value]) => !isEmptyValue(value)) - ) as Partial; -}; - -function withSizePreset(values: Record) { - const { size_preset, ...rest } = values; - - switch (size_preset) { - case 'tiny': - return { ...rest, max_size: 15 * 1024 }; - case 'small': - return { ...rest, max_size: 2 * 1024 * 1024 }; - case 'medium': - return { ...rest, min_size: 2 * 1024 * 1024, max_size: 10 * 1024 * 1024 }; - case 'large': - return { ...rest, min_size: 10 * 1024 * 1024, max_size: 20 * 1024 * 1024 }; - case 'huge': - return { ...rest, min_size: 20 * 1024 * 1024 }; - default: - return rest; - } -} - -export function SearchFormDialog({ onSubmit, isLoading, reset, open, onOpenChange }: Props) { - const { t } = useTranslation() - const [showAdvanced, setShowAdvanced] = useState(false); - const [selectedAccountIds, setSelectedAccountIds] = useState([]); - const { accountsOptions, isLoading: accountsIsLoading } = useMinimalAccountList(); - const { selectedTags } = useSearchContext(); - - const form = useForm({ - resolver: zodResolver(searchFilterSchema), - defaultValues: { - text: "", - from: "", - to: "", - cc: "", - bcc: "", - attachment_name: "", - message_id: "", - size_preset: 'any', - has_attachment: false, - since: undefined, - before: undefined, - account_ids: [], - mailbox_ids: [], - }, - mode: "onChange", - }); - - const navigate = useNavigate(); - - const { mailboxes, isMailboxesLoading } = useQueries({ - queries: selectedAccountIds.map((id) => ({ - queryKey: ['search-account-mailboxes', id], - queryFn: () => list_mailboxes(id!, false), - })), - combine: (results) => ({ - mailboxes: results.flatMap((result) => result.data?.sort((a, b) => a.name.localeCompare(b.name))), - isMailboxesLoading: results.some((result) => result.isLoading), - }), - }) - - const mailboxesOptions = mailboxes?.filter((item) => !!item).map((mailbox: MailboxData) => ({ - value: mailbox.id.toString(), - label: mailbox.name, - description: accountsOptions.find((item) => Number(item.value) === mailbox.account_id)!.label - })) || []; - - - const handleSubmit = (values: Record) => { - let cleaned = cleanEmpty(values); - const payload = withSizePreset(cleaned); - - const finalPayload = - selectedTags.length > 0 - ? { ...payload, tags: selectedTags } - : payload; - - if (Object.keys(finalPayload).length > 0) { - onSubmit(finalPayload); - } else { - toast({ - title: t('search.pleaseSelectAtLeastOne'), - }); - } - } - - const handleClear = () => { - form.reset({ - text: "", - from: "", - to: "", - cc: "", - bcc: "", - has_attachment: false, - attachment_name: "", - since: undefined, - before: undefined, - account_ids: [], - mailbox_ids: [], - size_preset: 'any', - message_id: "", - }); - setSelectedAccountIds([]); - } - - return ( - - -
- - {t('search.searchArchivedEmails')} - -
-
- - {t('search.fullTextMultiAccount')} - -
- -
-
- ( - -
- {t('search.account')} - - { - const ids = values.map((id) => parseInt(id, 10)).sort() - setSelectedAccountIds(ids) - field.onChange(ids) - }} - value={field.value.map(String)} - placeholder={t('search.selectAccount')} - className="h-10 w-full" - noItemsComponent={ -
-

{t('search.noActiveEmailAccount')}

- -
- } - multiple - /> -
-
- -
- )} - /> - ( - -
- {t('search.mailbox')} - - { - field.onChange(values.map((id) => parseInt(id, 10))) - }} - value={field.value.map(String)} - placeholder={t('search.selectMailbox')} - className="h-10 w-full" - noItemsComponent={ -
-

- {t('search.noMailboxSelectAccount')} -

-
- } - multiple - /> -
-
- -
- )} - /> -
-
- ( - - - - - - - )} - /> - -
- - - -
-
-
- ( - - {t('search.since')} - - - - - )} - /> - - ( - - {t('search.before')} - - - - - )} - /> - - ( - - - - {t('search.hasAttachment')} - - - )} - /> -
-
- -
- {showAdvanced && - - - {t('search.sender')} / {t('search.recipient')} - - -
- {(['from', 'to', 'cc', 'bcc'] as const).map((key) => ( - ( - - - {key === 'from' ? t('search.from') : key === 'to' ? t('search.to') : key === 'cc' ? t('search.cc') : t('search.bcc')}: - - - - - - - )} - /> - ))} -
-
-
- - - {t('search.attachmentsSize')} - - -
- ( - - {t('search.attachmentName')}: - - - - - - )} - /> - ( - - - {t('search.size')} - - - - - {t('search.sizeDescription')} - - - )} - /> -
-
-
- - - {t('search.messageId')} - - -
- ( - - - - - - {t('search.originalMessageIdHeader')} - - - )} - /> -
-
-
-
} -
-
- -
-
); -} diff --git a/web/src/features/search/table/table.tsx b/web/src/features/search/table/table.tsx index d37894f..900674c 100755 --- a/web/src/features/search/table/table.tsx +++ b/web/src/features/search/table/table.tsx @@ -98,7 +98,7 @@ export function SearchTable({ columns, data, onRowClick, setSortBy, setSortOrder return (
{children && (<>{children(table)})} - + {table.getHeaderGroups().map((headerGroup) => ( diff --git a/web/src/features/search/table/view-options.tsx b/web/src/features/search/table/view-options.tsx index 6cca43e..2ffb6cf 100644 --- a/web/src/features/search/table/view-options.tsx +++ b/web/src/features/search/table/view-options.tsx @@ -22,7 +22,6 @@ const defaultColumns = (t: (key: string) => string) => [ { label: t('search.from'), value: "from" }, { label: t('search.to'), value: "to" }, { label: t('search.subject'), value: "subject" }, - { label: t('mail.attachments'), value: "attachments" }, { label: t('search.size'), value: "size" }, { label: t('search.date'), value: "date" }, ] @@ -54,11 +53,11 @@ export function DataTableViewOptions({ className='ms-auto hidden h-8 lg:flex rounded-none' > - View + {t('search_view.button_label')} - Toggle columns + {t('search_view.menu_title')} {table .getAllColumns() diff --git a/web/src/features/search/tag-facet.tsx b/web/src/features/search/tag-facet.tsx deleted file mode 100644 index e663959..0000000 --- a/web/src/features/search/tag-facet.tsx +++ /dev/null @@ -1,127 +0,0 @@ -// -// Copyright (c) 2025 rustmailer.com (https://rustmailer.com) -// -// This file is part of the Bichon Email Archiving Project -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - - -import { Badge } from '@/components/ui/badge'; -import { Checkbox } from '@/components/ui/checkbox'; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; -import { ChevronDown, ChevronUp, Tag } from 'lucide-react'; -import React from 'react'; -import { useAvailableTags } from '@/hooks/use-available-tags'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import { Label } from '@/components/ui/label'; -import { useTranslation } from 'react-i18next'; - -interface EnvelopeTagsProps { - selectedTags: string[]; - onTagToggle: (tag: string) => void; -} - -export function EnvelopeTags({ selectedTags, onTagToggle }: EnvelopeTagsProps) { - const { t } = useTranslation() - const [open, setOpen] = React.useState(true); - - const { - tagsCount: tagsCount = [], - isLoading: tagsIsLoading, - } = useAvailableTags(); - - const sortedTags = React.useMemo(() => { - return [...tagsCount].sort((a, b) => b.count - a.count); - }, [tagsCount]); - - if (tagsIsLoading) { - return ( -
-
-
-
-
- {[...Array(6)].map((_, i) => ( -
-
-
-
-
- ))} -
-
- ); - } - - return ( - - -
- - {t('mail.tags')} - {selectedTags.length > 0 && ( - - {selectedTags.length} - - )} -
- {open ? : } -
- - - {sortedTags.length === 0 ? ( -

{t('mail.noTagsYet')}

- ) : ( - - {sortedTags.map(({ tag: facet, count }) => { - const checked = selectedTags.includes(facet); - const id = `tag-${facet}`; - - return ( -
onTagToggle(facet)} - > - onTagToggle(facet)} - onClick={(e) => e.stopPropagation()} - className="h-4 w-4" - /> - -
- - {count} - -
-
- ); - })} -
- )} -
-
- ); -} \ No newline at end of file diff --git a/web/src/features/search/tag-filter-popover.tsx b/web/src/features/search/tag-filter-popover.tsx index 120da59..af12d08 100644 --- a/web/src/features/search/tag-filter-popover.tsx +++ b/web/src/features/search/tag-filter-popover.tsx @@ -86,7 +86,7 @@ export function TagFilterPopover() { )} > - {t('mail.tags')} + {t('tag.label')} {selectedTags.length > 0 && ( setSearch(e.target.value)} - placeholder={t('mail.searchTags')} + placeholder={t('tag.search_placeholder')} className="h-8 text-sm" autoFocus /> @@ -123,7 +123,7 @@ export function TagFilterPopover() {
- {t('common.clear_all_tags')} + {t('tag.clear_all')} ({selectedTags.length})
@@ -141,7 +141,7 @@ export function TagFilterPopover() {
) : filteredTags.length === 0 ? (

- {t('mail.noTagsFound')} + {t('tag.no_tags_found')}

) : ( filteredTags.map(({ tag, count }) => { @@ -190,4 +190,4 @@ export function TagFilterPopover() { ) -} +} \ No newline at end of file diff --git a/web/src/features/search/text-search-input.tsx b/web/src/features/search/text-search-input.tsx index 0a5d9e6..12f461d 100644 --- a/web/src/features/search/text-search-input.tsx +++ b/web/src/features/search/text-search-input.tsx @@ -1,14 +1,16 @@ import React, { useState, useEffect, useRef } from "react" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" -import { Search, X, Clock, Trash2 } from "lucide-react" +import { Search, X, Clock, Trash2, Info } from "lucide-react" import { cn } from "@/lib/utils" import { useSearchContext } from "./context" +import { useTranslation } from "react-i18next" const STORAGE_KEY = "mail_search_history" const MAX_HISTORY = 20 export function TextSearchInput() { + const { t } = useTranslation() const { filter, setFilter } = useSearchContext() const [value, setValue] = useState(filter.text || "") const [history, setHistory] = useState([]) @@ -31,6 +33,7 @@ export function TextSearchInput() { setValue(filter.text || "") }, [filter.text]) + const saveToHistory = (term: string) => { if (!term.trim()) return @@ -82,6 +85,7 @@ export function TextSearchInput() { const handleSelectHistory = (term: string) => { setValue(term) setShowHistory(false) + // 如果需要点击历史立即搜索,可以在这里调用 handleSearch() } const handleClearHistory = () => { @@ -108,49 +112,59 @@ export function TextSearchInput() { return (
-
-
- - setValue(e.target.value)} - onFocus={() => setShowHistory(true)} - onKeyDown={handleKeyDown} - placeholder='Search messages... (use "double quotes" for exact phrases)' - className={cn( - "h-9 pl-9 pr-9 text-sm", - isActive && "border-primary/50 focus-visible:ring-primary/30" +
+
+
+ + setValue(e.target.value)} + onFocus={() => setShowHistory(true)} + onKeyDown={handleKeyDown} + placeholder={t('search_input.placeholder')} + className={cn( + "h-9 pl-9 pr-9 text-sm", + isActive && "border-primary/50 focus-visible:ring-primary/30" + )} + /> + {value && ( + )} - /> - {value && ( - - )} +
+ +
- + {/* 搜索范围提示 */} +
+ + + {t('search_input.hint')} + +
{showHistory && ( -
-
+
+
- Recent searches + {t('search_input.recent_title')}
{history.length > 0 && ( )}
@@ -176,7 +190,7 @@ export function TextSearchInput() { )) ) : (
- No recent searches + {t('search_input.no_history')}
)}
diff --git a/web/src/features/search/time-popover.tsx b/web/src/features/search/time-popover.tsx index 85fe02a..1206bef 100644 --- a/web/src/features/search/time-popover.tsx +++ b/web/src/features/search/time-popover.tsx @@ -1,5 +1,7 @@ import * as React from 'react' import { CalendarRange, ChevronDown, X } from 'lucide-react' +import { useTranslation } from 'react-i18next' +import { format } from 'date-fns' import { Popover, PopoverContent, @@ -14,12 +16,24 @@ import { DatePicker } from '@/components/date-picker' const DAY = 86400000 export function TimePopover() { + const { t } = useTranslation() const { filter, setFilter } = useSearchContext() const [customDays, setCustomDays] = React.useState('') const since = filter.since const before = filter.before + const toDate = (ts: number) => { + return format(ts, t('time.format')) + } + + const label = (s?: number, b?: number) => { + if (!s && !b) return t('time.label') + if (s && b) return `${toDate(s)} → ${toDate(b)}` + if (s) return `${t('time.since')} ${toDate(s)}` + return `${t('time.before')} ${toDate(b!)}` + } + const setRange = (s?: number, b?: number) => { setFilter(prev => { const next = { ...prev } @@ -62,23 +76,23 @@ export function TimePopover() { -
+
{[1, 7, 30].map(d => ( setRange(Date.now() - d * DAY, undefined)}> - Last {d === 1 ? 'day' : `${d} days`} + {d === 1 ? t('time.last_day') : t('time.last_days', { count: d })} ))} {[3, 6].map(m => ( setRange(Date.now() - m * 30 * DAY, undefined)}> - Last {m} months + {t('time.last_months', { count: m })} ))}
- Recent: + {t('time.recent_prefix')} setCustomDays(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleApplyRecent()} /> - days ago to now + {t('time.days_ago_to_now')}
-
+
{[1, 2, 3, 5, 10].map(y => ( setRange(undefined, Date.now() - y * 365 * DAY)} className="border-orange-200 hover:border-orange-400 hover:text-orange-600" > - Over {y} {y === 1 ? 'year' : 'years'} ago + {t('time.over_years_ago', { + count: y, + unit: y === 1 ? t('time.year') : t('time.years') + })} ))}
-
+
- SINCE + {t('time.since').toUpperCase()} setSince(date?.getTime())} />
- BEFORE + {t('time.before').toUpperCase()} setBefore(date?.getTime())} /> @@ -143,7 +160,7 @@ export function TimePopover() { className="h-7 w-full justify-start text-xs text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors" > - Clear time filters + {t('time.clear_filters')}
)} @@ -152,18 +169,6 @@ export function TimePopover() { ) } -function toDate(ts: number) { - const d = new Date(ts) - return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` -} - -function label(s?: number, b?: number) { - if (!s && !b) return 'Time' - if (s && b) return `${toDate(s)} → ${toDate(b)}` - if (s) return `Since ${toDate(s)}` - return `Older than ${toDate(b!)}` -} - function Section({ title, children }: { title: string; children: React.ReactNode }) { return (
diff --git a/web/src/hooks/use-search-messages.ts b/web/src/hooks/use-search-messages.ts index 983e5bb..6dcc564 100644 --- a/web/src/hooks/use-search-messages.ts +++ b/web/src/hooks/use-search-messages.ts @@ -20,18 +20,24 @@ import { EmailEnvelope, PaginatedResponse } from '@/api'; import { search_messages } from '@/api/search/api'; import { useQuery } from '@tanstack/react-query'; +import React from 'react'; import { useState } from 'react'; export function useSearchMessages() { // const queryClient = useQueryClient(); - const [filter, setFilter] = useState>({}); + const [filter, _setFilter] = useState>({}); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(30); const [sortBy, setSortBy] = useState<"DATE" | "SIZE">("DATE"); const [sortOrder, setSortOrder] = useState<"desc" | "asc">("desc"); + const setFilter = React.useCallback((val: any) => { + _setFilter(val); + setPage(1); + }, []); + const onSubmit = (cleaned: Record) => { if ('has_attachment' in cleaned && cleaned.has_attachment === false) { delete cleaned.has_attachment; @@ -51,7 +57,6 @@ export function useSearchMessages() { const reset = () => { setFilter({}); - setPage(1); } const { diff --git a/web/src/locales/ar.json b/web/src/locales/ar.json index 7413f96..5f8bc2d 100644 --- a/web/src/locales/ar.json +++ b/web/src/locales/ar.json @@ -1461,5 +1461,91 @@ "successDesc": "تمت استعادة الرسائل المختارة إلى خادم IMAP بنجاح.", "failed": "فشلت استعادة الرسائل", "failedTitle": "فشل الاستعادة" + }, + "time": { + "label": "الوقت", + "since": "منذ", + "before": "قبل", + "recent_range": "النطاق الأخير (منذ...)", + "historical": "النطاق السابق (قبل...)", + "absolute_range": "نطاق تاريخ محدد", + "last_day": "آخر يوم واحد", + "last_days": "آخر {{count}} أيام", + "last_months": "آخر {{count}} أشهر", + "over_years_ago": "منذ {{count}} {{unit}}", + "year": "سنة", + "years": "سنوات", + "recent_prefix": "الأخيرة:", + "days_ago_to_now": "منذ أيام حتى الآن", + "apply": "تطبيق", + "start_date": "تاريخ البدء", + "end_date": "تاريخ الانتهاء", + "clear_filters": "مسح فلترة الوقت", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "الوسوم", + "search_placeholder": "بحث عن الوسوم...", + "clear_all": "مسح جميع الوسوم", + "no_tags_found": "لم يتم العثور على وسوم" + }, + "search_accounts": { + "label": "حسابات البريد", + "search_placeholder": "بحث عن حسابات البريد...", + "clear_accounts": "مسح الحسابات المحددة", + "no_accounts_found": "لم يتم العثور على حسابات" + }, + "search_mailbox": { + "label": "مجلدات البريد", + "search_placeholder": "بحث في المجلدات", + "clear_mailboxes": "مسح المجلدات المحددة", + "select_account_first": "يرجى اختيار حساب بريد أولاً", + "no_mailbox_found": "لم يتم العثور على مجلد" + }, + "search_contacts": { + "label": "جهات الاتصال", + "label_with_count": "جهات الاتصال ({{count}})", + "any": "الكل", + "search_placeholder": "بحث في {{field}}...", + "reset_all": "إعادة تعيين جميع جهات الاتصال", + "no_contact_found": "لم يتم العثور على جهات اتصال", + "loading": "جارٍ التحميل...", + "showing_limit": "عرض أول 100 نتيجة فقط • المجموع {{total}}" + }, + "search_more": { + "trigger_label": "متقدم", + "title": "فلاتر متقدمة", + "reset": "إعادة تعيين", + "has_attachment": "يحتوي على مرفق", + "attachment_name_label": "اسم المرفق", + "attachment_name_placeholder": "مثال: invoice.pdf", + "message_size_label": "حجم الرسالة", + "message_id_label": "معرّف Message-ID الأصلي", + "message_id_description": "البحث باستخدام Message-ID في ترويسة البريد", + "apply": "تطبيق الفلاتر", + "size_presets": { + "any": "أي حجم", + "tiny": "صغير جداً (< 15 كيلوبايت)", + "small": "صغير (< 2 ميغابايت)", + "medium": "متوسط (2 – 10 ميغابايت)", + "large": "كبير (10 – 20 ميغابايت)", + "huge": "ضخم (> 20 ميغابايت)" + } + }, + "search_view": { + "button_label": "العرض", + "menu_title": "إعدادات الأعمدة" + }, + "search_reset": { + "label": "إعادة تعيين", + "tooltip": "مسح جميع شروط التصفية" + }, + "search_input": { + "placeholder": "البحث في البريد... (استخدم \"علامات الاقتباس\" للمطابقة التامة)", + "button": "بحث", + "recent_title": "عمليات البحث الأخيرة", + "clear_history": "مسح السجل", + "no_history": "لا يوجد سجل بحث", + "hint": "نطاق البحث الافتراضي: العنوان، المحتوى، وأسماء المرفقات" } } \ No newline at end of file diff --git a/web/src/locales/da.json b/web/src/locales/da.json index 36799a2..31acde9 100644 --- a/web/src/locales/da.json +++ b/web/src/locales/da.json @@ -1461,5 +1461,91 @@ "successDesc": "De valgte meddelelser er blevet gendannet til IMAP-serveren.", "failed": "Kunne ikke gendanne meddelelser", "failedTitle": "Gendannelse mislykkedes" + }, + "time": { + "label": "Tid", + "since": "Siden", + "before": "Før", + "recent_range": "Seneste interval (siden...)", + "historical": "Historisk interval (før...)", + "absolute_range": "Absolut datointerval", + "last_day": "Seneste 1 dag", + "last_days": "Seneste {{count}} dage", + "last_months": "Seneste {{count}} måneder", + "over_years_ago": "For {{count}} {{unit}} siden", + "year": "år", + "years": "år", + "recent_prefix": "Seneste:", + "days_ago_to_now": "dage siden til nu", + "apply": "Anvend", + "start_date": "Startdato", + "end_date": "Slutdato", + "clear_filters": "Ryd tidsfiltre", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Tags", + "search_placeholder": "Søg tags...", + "clear_all": "Ryd alle tags", + "no_tags_found": "Ingen tags fundet" + }, + "search_accounts": { + "label": "Mailkonti", + "search_placeholder": "Søg mailkonti...", + "clear_accounts": "Ryd valgte konti", + "no_accounts_found": "Ingen konti fundet" + }, + "search_mailbox": { + "label": "Postkasser", + "search_placeholder": "Søg postkasser", + "clear_mailboxes": "Ryd valgte mapper", + "select_account_first": "Vælg venligst en mailkonto først", + "no_mailbox_found": "Ingen mappe fundet" + }, + "search_contacts": { + "label": "Kontakter", + "label_with_count": "Kontakter ({{count}})", + "any": "Alle", + "search_placeholder": "Søg i {{field}}...", + "reset_all": "Nulstil alle kontakter", + "no_contact_found": "Ingen kontakter fundet", + "loading": "Indlæser...", + "showing_limit": "Viser kun de første 100 resultater • I alt {{total}}" + }, + "search_more": { + "trigger_label": "Avanceret", + "title": "Avancerede filtre", + "reset": "Nulstil", + "has_attachment": "Har vedhæftning", + "attachment_name_label": "Navn på vedhæftning", + "attachment_name_placeholder": "F.eks.: invoice.pdf", + "message_size_label": "Mailstørrelse", + "message_id_label": "Original Message-ID", + "message_id_description": "Søg via Message-ID i mailheaderen", + "apply": "Anvend filtre", + "size_presets": { + "any": "Alle størrelser", + "tiny": "Meget lille (< 15 KB)", + "small": "Lille (< 2 MB)", + "medium": "Mellem (2 – 10 MB)", + "large": "Stor (10 – 20 MB)", + "huge": "Meget stor (> 20 MB)" + } + }, + "search_view": { + "button_label": "Visning", + "menu_title": "Indstillinger for kolonner" + }, + "search_reset": { + "label": "Nulstil", + "tooltip": "Ryd alle filtre" + }, + "search_input": { + "placeholder": "Søg i mails... (brug \"anførselstegn\" til nøjagtig match)", + "button": "Søg", + "recent_title": "Seneste søgninger", + "clear_history": "Ryd historik", + "no_history": "Ingen søgehistorik", + "hint": "Standard søgeområde: emne, indhold og vedhæftningsnavne" } } \ No newline at end of file diff --git a/web/src/locales/de.json b/web/src/locales/de.json index e7a21c7..b459074 100644 --- a/web/src/locales/de.json +++ b/web/src/locales/de.json @@ -1461,5 +1461,91 @@ "successDesc": "Die ausgewählten Nachrichten wurden erfolgreich auf dem IMAP-Server wiederhergestellt.", "failed": "Wiederherstellung fehlgeschlagen", "failedTitle": "Fehler bei der Wiederherstellung" + }, + "time": { + "label": "Zeit", + "since": "Seit", + "before": "Vor", + "recent_range": "Letzter Zeitraum (seit …)", + "historical": "Historischer Zeitraum (vor …)", + "absolute_range": "Absoluter Datumsbereich", + "last_day": "Letzter 1 Tag", + "last_days": "Letzte {{count}} Tage", + "last_months": "Letzte {{count}} Monate", + "over_years_ago": "Vor {{count}} {{unit}}", + "year": "Jahr", + "years": "Jahre", + "recent_prefix": "Kürzlich:", + "days_ago_to_now": "Tage bis heute", + "apply": "Anwenden", + "start_date": "Startdatum", + "end_date": "Enddatum", + "clear_filters": "Zeitfilter löschen", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Tags", + "search_placeholder": "Tags suchen...", + "clear_all": "Alle Tags löschen", + "no_tags_found": "Keine Tags gefunden" + }, + "search_accounts": { + "label": "E-Mail-Konten", + "search_placeholder": "E-Mail-Konten suchen...", + "clear_accounts": "Ausgewählte Konten löschen", + "no_accounts_found": "Keine Konten gefunden" + }, + "search_mailbox": { + "label": "Postfächer", + "search_placeholder": "Postfächer durchsuchen", + "clear_mailboxes": "Ausgewählte Ordner löschen", + "select_account_first": "Bitte zuerst ein E-Mail-Konto auswählen", + "no_mailbox_found": "Kein Ordner gefunden" + }, + "search_contacts": { + "label": "Kontakte", + "label_with_count": "Kontakte ({{count}})", + "any": "Alle", + "search_placeholder": "{{field}} suchen...", + "reset_all": "Alle Kontakte zurücksetzen", + "no_contact_found": "Keine Kontakte gefunden", + "loading": "Wird geladen...", + "showing_limit": "Nur die ersten 100 Ergebnisse angezeigt • Gesamt {{total}}" + }, + "search_more": { + "trigger_label": "Erweitert", + "title": "Erweiterte Filter", + "reset": "Zurücksetzen", + "has_attachment": "Mit Anhang", + "attachment_name_label": "Anhangname", + "attachment_name_placeholder": "z. B. invoice.pdf", + "message_size_label": "Nachrichtengröße", + "message_id_label": "Originale Message-ID", + "message_id_description": "Suche über die Message-ID im E-Mail-Header", + "apply": "Filter anwenden", + "size_presets": { + "any": "Beliebige Größe", + "tiny": "Sehr klein (< 15 KB)", + "small": "Klein (< 2 MB)", + "medium": "Mittel (2 – 10 MB)", + "large": "Groß (10 – 20 MB)", + "huge": "Sehr groß (> 20 MB)" + } + }, + "search_view": { + "button_label": "Ansicht", + "menu_title": "Spalteneinstellungen" + }, + "search_reset": { + "label": "Zurücksetzen", + "tooltip": "Alle Filter entfernen" + }, + "search_input": { + "placeholder": "E-Mails durchsuchen... (\"Anführungszeichen\" für exakte Übereinstimmung)", + "button": "Suchen", + "recent_title": "Letzte Suchanfragen", + "clear_history": "Verlauf löschen", + "no_history": "Kein Suchverlauf", + "hint": "Standard-Suchbereich: Betreff, Inhalt und Anhangnamen" } } \ No newline at end of file diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 8367b9f..7911c96 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -1461,5 +1461,91 @@ "successDesc": "The selected messages have been successfully restored to the IMAP server.", "failed": "Failed to restore messages", "failedTitle": "Restore Failed" + }, + "time": { + "label": "Time", + "since": "Since", + "before": "Before", + "recent_range": "Recent range (since...)", + "historical": "Historical range (before...)", + "absolute_range": "Absolute date range", + "last_day": "Last 1 day", + "last_days": "Last {{count}} days", + "last_months": "Last {{count}} months", + "over_years_ago": "{{count}} {{unit}} ago", + "year": "year", + "years": "years", + "recent_prefix": "Recent:", + "days_ago_to_now": "days ago to now", + "apply": "Apply", + "start_date": "Start date", + "end_date": "End date", + "clear_filters": "Clear time filters", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Tags", + "search_placeholder": "Search tags...", + "clear_all": "Clear all tags", + "no_tags_found": "No tags found" + }, + "search_accounts": { + "label": "Mail accounts", + "search_placeholder": "Search mail accounts...", + "clear_accounts": "Clear selected accounts", + "no_accounts_found": "No accounts found" + }, + "search_mailbox": { + "label": "Mailboxes", + "search_placeholder": "Search mailboxes", + "clear_mailboxes": "Clear selected mailboxes", + "select_account_first": "Please select an account first", + "no_mailbox_found": "No mailbox found" + }, + "search_contacts": { + "label": "Contacts", + "label_with_count": "Contacts ({{count}})", + "any": "Any", + "search_placeholder": "Search {{field}}...", + "reset_all": "Reset all contacts", + "no_contact_found": "No contacts found", + "loading": "Loading...", + "showing_limit": "Showing first 100 results • Total {{total}}" + }, + "search_more": { + "trigger_label": "Advanced", + "title": "Advanced filters", + "reset": "Reset", + "has_attachment": "Has attachment", + "attachment_name_label": "Attachment name", + "attachment_name_placeholder": "e.g. invoice.pdf", + "message_size_label": "Message size", + "message_id_label": "Original Message-ID", + "message_id_description": "Search by Message-ID header", + "apply": "Apply filters", + "size_presets": { + "any": "Any size", + "tiny": "Tiny (< 15 KB)", + "small": "Small (< 2 MB)", + "medium": "Medium (2 – 10 MB)", + "large": "Large (10 – 20 MB)", + "huge": "Huge (> 20 MB)" + } + }, + "search_view": { + "button_label": "View", + "menu_title": "Column settings" + }, + "search_reset": { + "label": "Reset", + "tooltip": "Clear all filters" + }, + "search_input": { + "placeholder": "Search emails... (use \"quotes\" for exact match)", + "button": "Search", + "recent_title": "Recent searches", + "clear_history": "Clear history", + "no_history": "No search history", + "hint": "Default scope: subject, body and attachment names" } } \ No newline at end of file diff --git a/web/src/locales/es.json b/web/src/locales/es.json index f04a992..8ee1764 100644 --- a/web/src/locales/es.json +++ b/web/src/locales/es.json @@ -1461,5 +1461,91 @@ "successDesc": "Los mensajes seleccionados se han restaurado correctamente en el servidor IMAP.", "failed": "Error al restaurar los mensajes", "failedTitle": "Error de restauración" + }, + "time": { + "label": "Tiempo", + "since": "Desde", + "before": "Antes de", + "recent_range": "Rango reciente (desde...)", + "historical": "Rango histórico (antes de...)", + "absolute_range": "Rango de fechas absoluto", + "last_day": "Último 1 día", + "last_days": "Últimos {{count}} días", + "last_months": "Últimos {{count}} meses", + "over_years_ago": "Hace {{count}} {{unit}}", + "year": "año", + "years": "años", + "recent_prefix": "Reciente:", + "days_ago_to_now": "días atrás hasta ahora", + "apply": "Aplicar", + "start_date": "Fecha de inicio", + "end_date": "Fecha de fin", + "clear_filters": "Borrar filtros de tiempo", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Etiquetas", + "search_placeholder": "Buscar etiquetas...", + "clear_all": "Borrar todas las etiquetas", + "no_tags_found": "No se encontraron etiquetas" + }, + "search_accounts": { + "label": "Cuentas de correo", + "search_placeholder": "Buscar cuentas de correo...", + "clear_accounts": "Borrar cuentas seleccionadas", + "no_accounts_found": "No se encontraron cuentas" + }, + "search_mailbox": { + "label": "Buzones", + "search_placeholder": "Buscar buzones", + "clear_mailboxes": "Borrar carpetas seleccionadas", + "select_account_first": "Seleccione primero una cuenta de correo", + "no_mailbox_found": "No se encontró ningún buzón" + }, + "search_contacts": { + "label": "Contactos", + "label_with_count": "Contactos ({{count}})", + "any": "Cualquiera", + "search_placeholder": "Buscar {{field}}...", + "reset_all": "Restablecer todos los contactos", + "no_contact_found": "No se encontraron contactos", + "loading": "Cargando...", + "showing_limit": "Mostrando solo los primeros 100 resultados • Total {{total}}" + }, + "search_more": { + "trigger_label": "Avanzado", + "title": "Filtros avanzados", + "reset": "Restablecer", + "has_attachment": "Con archivo adjunto", + "attachment_name_label": "Nombre del archivo adjunto", + "attachment_name_placeholder": "Ej.: invoice.pdf", + "message_size_label": "Tamaño del mensaje", + "message_id_label": "Message-ID original", + "message_id_description": "Buscar usando el encabezado Message-ID del correo", + "apply": "Aplicar filtros", + "size_presets": { + "any": "Cualquier tamaño", + "tiny": "Muy pequeño (< 15 KB)", + "small": "Pequeño (< 2 MB)", + "medium": "Mediano (2 – 10 MB)", + "large": "Grande (10 – 20 MB)", + "huge": "Muy grande (> 20 MB)" + } + }, + "search_view": { + "button_label": "Vista", + "menu_title": "Configuración de columnas" + }, + "search_reset": { + "label": "Restablecer", + "tooltip": "Eliminar todos los filtros" + }, + "search_input": { + "placeholder": "Buscar correos... (usa \"comillas\" para coincidencia exacta)", + "button": "Buscar", + "recent_title": "Búsquedas recientes", + "clear_history": "Borrar historial", + "no_history": "Sin historial de búsqueda", + "hint": "Ámbito de búsqueda predeterminado: asunto, contenido y nombres de archivos adjuntos" } } \ No newline at end of file diff --git a/web/src/locales/fi.json b/web/src/locales/fi.json index 793bf71..7f11ba0 100644 --- a/web/src/locales/fi.json +++ b/web/src/locales/fi.json @@ -1461,5 +1461,91 @@ "successDesc": "Valitut viestit on palautettu onnistuneesti IMAP-palvelimelle.", "failed": "Viestien palautus epäonnistui", "failedTitle": "Palautus epäonnistui" + }, + "time": { + "label": "Aika", + "since": "Alkaen", + "before": "Ennen", + "recent_range": "Viimeisin aikaväli (alkaen...)", + "historical": "Historiallinen aikaväli (ennen...)", + "absolute_range": "Kiinteä päivämääräväli", + "last_day": "Viimeiset 1 päivä", + "last_days": "Viimeiset {{count}} päivää", + "last_months": "Viimeiset {{count}} kuukautta", + "over_years_ago": "{{count}} {{unit}} sitten", + "year": "vuosi", + "years": "vuotta", + "recent_prefix": "Viimeisin:", + "days_ago_to_now": "päivää sitten – nyt", + "apply": "Käytä", + "start_date": "Aloituspäivä", + "end_date": "Päättymispäivä", + "clear_filters": "Tyhjennä aikasuodattimet", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Tunnisteet", + "search_placeholder": "Hae tunnisteita...", + "clear_all": "Tyhjennä kaikki tunnisteet", + "no_tags_found": "Tunnisteita ei löytynyt" + }, + "search_accounts": { + "label": "Sähköpostitilit", + "search_placeholder": "Hae sähköpostitilejä...", + "clear_accounts": "Poista valitut tilit", + "no_accounts_found": "Tilejä ei löytynyt" + }, + "search_mailbox": { + "label": "Postilaatikot", + "search_placeholder": "Hae postilaatikoita", + "clear_mailboxes": "Poista valitut kansiot", + "select_account_first": "Valitse ensin sähköpostitili", + "no_mailbox_found": "Kansiota ei löytynyt" + }, + "search_contacts": { + "label": "Yhteystiedot", + "label_with_count": "Yhteystiedot ({{count}})", + "any": "Kaikki", + "search_placeholder": "Hae {{field}}...", + "reset_all": "Palauta kaikki yhteystiedot", + "no_contact_found": "Yhteystietoja ei löytynyt", + "loading": "Ladataan...", + "showing_limit": "Näytetään vain ensimmäiset 100 tulosta • Yhteensä {{total}}" + }, + "search_more": { + "trigger_label": "Lisäasetukset", + "title": "Lisäsuodattimet", + "reset": "Palauta", + "has_attachment": "Sisältää liitteen", + "attachment_name_label": "Liitteen nimi", + "attachment_name_placeholder": "Esim.: invoice.pdf", + "message_size_label": "Viestin koko", + "message_id_label": "Alkuperäinen Message-ID", + "message_id_description": "Hae sähköpostin Message-ID-otsikon perusteella", + "apply": "Käytä suodattimia", + "size_presets": { + "any": "Mikä tahansa koko", + "tiny": "Erittäin pieni (< 15 KB)", + "small": "Pieni (< 2 MB)", + "medium": "Keskikokoinen (2 – 10 MB)", + "large": "Suuri (10 – 20 MB)", + "huge": "Erittäin suuri (> 20 MB)" + } + }, + "search_view": { + "button_label": "Näkymä", + "menu_title": "Sarakeasetukset" + }, + "search_reset": { + "label": "Palauta", + "tooltip": "Poista kaikki suodattimet" + }, + "search_input": { + "placeholder": "Hae sähköposteja... (käytä \"lainausmerkkejä\" tarkkaan hakuun)", + "button": "Hae", + "recent_title": "Viimeisimmät haut", + "clear_history": "Tyhjennä historia", + "no_history": "Ei hakuhistoriaa", + "hint": "Oletushakualue: otsikko, sisältö ja liitteiden nimet" } } \ No newline at end of file diff --git a/web/src/locales/fr.json b/web/src/locales/fr.json index cc3933d..41257ad 100644 --- a/web/src/locales/fr.json +++ b/web/src/locales/fr.json @@ -1461,5 +1461,91 @@ "successDesc": "Les messages sélectionnés ont été restaurés avec succès sur le serveur IMAP.", "failed": "Échec de la restauration des messages", "failedTitle": "Échec de la restauration" + }, + "time": { + "label": "Temps", + "since": "Depuis", + "before": "Avant", + "recent_range": "Période récente (depuis...)", + "historical": "Période historique (avant...)", + "absolute_range": "Plage de dates absolue", + "last_day": "Dernier jour", + "last_days": "Derniers {{count}} jours", + "last_months": "Derniers {{count}} mois", + "over_years_ago": "Il y a {{count}} {{unit}}", + "year": "an", + "years": "ans", + "recent_prefix": "Récent :", + "days_ago_to_now": "jours jusqu’à aujourd’hui", + "apply": "Appliquer", + "start_date": "Date de début", + "end_date": "Date de fin", + "clear_filters": "Effacer les filtres de temps", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Étiquettes", + "search_placeholder": "Rechercher des étiquettes...", + "clear_all": "Effacer toutes les étiquettes", + "no_tags_found": "Aucune étiquette trouvée" + }, + "search_accounts": { + "label": "Comptes e-mail", + "search_placeholder": "Rechercher des comptes e-mail...", + "clear_accounts": "Effacer les comptes sélectionnés", + "no_accounts_found": "Aucun compte trouvé" + }, + "search_mailbox": { + "label": "Boîtes mail", + "search_placeholder": "Rechercher des dossiers", + "clear_mailboxes": "Effacer les dossiers sélectionnés", + "select_account_first": "Veuillez d’abord sélectionner un compte e-mail", + "no_mailbox_found": "Aucun dossier trouvé" + }, + "search_contacts": { + "label": "Contacts", + "label_with_count": "Contacts ({{count}})", + "any": "Tous", + "search_placeholder": "Rechercher {{field}}...", + "reset_all": "Réinitialiser tous les contacts", + "no_contact_found": "Aucun contact trouvé", + "loading": "Chargement...", + "showing_limit": "Affichage des 100 premiers résultats uniquement • Total {{total}}" + }, + "search_more": { + "trigger_label": "Avancé", + "title": "Filtres avancés", + "reset": "Réinitialiser", + "has_attachment": "Contient une pièce jointe", + "attachment_name_label": "Nom de la pièce jointe", + "attachment_name_placeholder": "Ex. : invoice.pdf", + "message_size_label": "Taille du message", + "message_id_label": "Message-ID d’origine", + "message_id_description": "Recherche via l’en-tête Message-ID de l’e-mail", + "apply": "Appliquer les filtres", + "size_presets": { + "any": "Toutes tailles", + "tiny": "Très petite (< 15 Ko)", + "small": "Petite (< 2 Mo)", + "medium": "Moyenne (2 – 10 Mo)", + "large": "Grande (10 – 20 Mo)", + "huge": "Très grande (> 20 Mo)" + } + }, + "search_view": { + "button_label": "Affichage", + "menu_title": "Paramètres des colonnes" + }, + "search_reset": { + "label": "Réinitialiser", + "tooltip": "Effacer tous les filtres" + }, + "search_input": { + "placeholder": "Rechercher des e-mails... (utilisez les \"guillemets\" pour une correspondance exacte)", + "button": "Rechercher", + "recent_title": "Recherches récentes", + "clear_history": "Effacer l’historique", + "no_history": "Aucun historique de recherche", + "hint": "Portée par défaut : objet, contenu et noms des pièces jointes" } } \ No newline at end of file diff --git a/web/src/locales/it.json b/web/src/locales/it.json index 8b034ea..f4b2a06 100644 --- a/web/src/locales/it.json +++ b/web/src/locales/it.json @@ -1461,5 +1461,91 @@ "successDesc": "I messaggi selezionati sono stati ripristinati con successo sul server IMAP.", "failed": "Impossibile ripristinare i messaggi", "failedTitle": "Ripristino fallito" + }, + "time": { + "label": "Tempo", + "since": "Da", + "before": "Prima di", + "recent_range": "Intervallo recente (da...)", + "historical": "Intervallo storico (prima di...)", + "absolute_range": "Intervallo di date assoluto", + "last_day": "Ultimo 1 giorno", + "last_days": "Ultimi {{count}} giorni", + "last_months": "Ultimi {{count}} mesi", + "over_years_ago": "{{count}} {{unit}} fa", + "year": "anno", + "years": "anni", + "recent_prefix": "Recenti:", + "days_ago_to_now": "giorni fa fino ad oggi", + "apply": "Applica", + "start_date": "Data di inizio", + "end_date": "Data di fine", + "clear_filters": "Cancella filtri temporali", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Tag", + "search_placeholder": "Cerca tag...", + "clear_all": "Cancella tutti i tag", + "no_tags_found": "Nessun tag trovato" + }, + "search_accounts": { + "label": "Account email", + "search_placeholder": "Cerca account email...", + "clear_accounts": "Cancella account selezionati", + "no_accounts_found": "Nessun account trovato" + }, + "search_mailbox": { + "label": "Caselle di posta", + "search_placeholder": "Cerca cartelle", + "clear_mailboxes": "Cancella cartelle selezionate", + "select_account_first": "Seleziona prima un account email", + "no_mailbox_found": "Nessuna cartella trovata" + }, + "search_contacts": { + "label": "Contatti", + "label_with_count": "Contatti ({{count}})", + "any": "Qualsiasi", + "search_placeholder": "Cerca {{field}}...", + "reset_all": "Reimposta tutti i contatti", + "no_contact_found": "Nessun contatto trovato", + "loading": "Caricamento...", + "showing_limit": "Mostrati solo i primi 100 risultati • Totale {{total}}" + }, + "search_more": { + "trigger_label": "Avanzato", + "title": "Filtri avanzati", + "reset": "Reimposta", + "has_attachment": "Con allegato", + "attachment_name_label": "Nome allegato", + "attachment_name_placeholder": "Es.: invoice.pdf", + "message_size_label": "Dimensione email", + "message_id_label": "Message-ID originale", + "message_id_description": "Ricerca tramite Message-ID nell’intestazione email", + "apply": "Applica filtri", + "size_presets": { + "any": "Qualsiasi dimensione", + "tiny": "Molto piccola (< 15 KB)", + "small": "Piccola (< 2 MB)", + "medium": "Media (2 – 10 MB)", + "large": "Grande (10 – 20 MB)", + "huge": "Molto grande (> 20 MB)" + } + }, + "search_view": { + "button_label": "Vista", + "menu_title": "Impostazioni colonne" + }, + "search_reset": { + "label": "Reimposta", + "tooltip": "Cancella tutti i filtri" + }, + "search_input": { + "placeholder": "Cerca email... (usa le \"virgolette\" per corrispondenza esatta)", + "button": "Cerca", + "recent_title": "Ricerche recenti", + "clear_history": "Cancella cronologia", + "no_history": "Nessuna cronologia di ricerca", + "hint": "Ambito predefinito: oggetto, contenuto e nomi degli allegati" } } \ No newline at end of file diff --git a/web/src/locales/jp.json b/web/src/locales/jp.json index 0484396..4e30d33 100644 --- a/web/src/locales/jp.json +++ b/web/src/locales/jp.json @@ -1461,5 +1461,91 @@ "successDesc": "選択したメッセージがIMAPサーバーに正常に復元されました。", "failed": "メッセージの復元に失敗しました", "failedTitle": "復元失敗" + }, + "time": { + "label": "時間", + "since": "以降", + "before": "以前", + "recent_range": "最近の範囲(以降)", + "historical": "過去の範囲(以前)", + "absolute_range": "絶対日付範囲", + "last_day": "過去 1 日", + "last_days": "過去 {{count}} 日", + "last_months": "過去 {{count}} か月", + "over_years_ago": "{{count}}{{unit}}前", + "year": "年", + "years": "年", + "recent_prefix": "最近:", + "days_ago_to_now": "日前〜現在", + "apply": "適用", + "start_date": "開始日", + "end_date": "終了日", + "clear_filters": "時間フィルターをクリア", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "タグ", + "search_placeholder": "タグを検索...", + "clear_all": "すべてのタグをクリア", + "no_tags_found": "タグが見つかりません" + }, + "search_accounts": { + "label": "メールアカウント", + "search_placeholder": "アカウントを検索...", + "clear_accounts": "選択したアカウントをクリア", + "no_accounts_found": "アカウントが見つかりません" + }, + "search_mailbox": { + "label": "メールボックス", + "search_placeholder": "メールボックスを検索", + "clear_mailboxes": "選択したフォルダをクリア", + "select_account_first": "先にアカウントを選択してください", + "no_mailbox_found": "フォルダが見つかりません" + }, + "search_contacts": { + "label": "連絡先", + "label_with_count": "連絡先 ({{count}})", + "any": "指定なし", + "search_placeholder": "{{field}} を検索...", + "reset_all": "連絡先をリセット", + "no_contact_found": "連絡先が見つかりません", + "loading": "読み込み中...", + "showing_limit": "最初の100件のみ表示 • 合計 {{total}} 件" + }, + "search_more": { + "trigger_label": "詳細", + "title": "詳細フィルター", + "reset": "リセット", + "has_attachment": "添付ファイルあり", + "attachment_name_label": "添付ファイル名", + "attachment_name_placeholder": "例: invoice.pdf", + "message_size_label": "メールサイズ", + "message_id_label": "Message-ID", + "message_id_description": "メールヘッダーの Message-ID で検索", + "apply": "適用", + "size_presets": { + "any": "指定なし", + "tiny": "極小 (< 15 KB)", + "small": "小 (< 2 MB)", + "medium": "中 (2 – 10 MB)", + "large": "大 (10 – 20 MB)", + "huge": "特大 (> 20 MB)" + } + }, + "search_view": { + "button_label": "表示", + "menu_title": "列の設定" + }, + "search_reset": { + "label": "リセット", + "tooltip": "すべての条件をクリア" + }, + "search_input": { + "placeholder": "メールを検索…(\"引用符\"で完全一致)", + "button": "検索", + "recent_title": "最近の検索", + "clear_history": "履歴をクリア", + "no_history": "履歴なし", + "hint": "検索対象:件名・本文・添付ファイル名" } } \ No newline at end of file diff --git a/web/src/locales/ko.json b/web/src/locales/ko.json index dec1cb0..7355a4f 100644 --- a/web/src/locales/ko.json +++ b/web/src/locales/ko.json @@ -1461,5 +1461,91 @@ "successDesc": "선택한 메시지가 IMAP 서버로 성공적으로 복원되었습니다.", "failed": "메시지 복원 실패", "failedTitle": "복원 실패" + }, + "time": { + "label": "시간", + "since": "이후", + "before": "이전", + "recent_range": "최근 범위 (이후...)", + "historical": "과거 범위 (이전...)", + "absolute_range": "절대 날짜 범위", + "last_day": "최근 1일", + "last_days": "최근 {{count}}일", + "last_months": "최근 {{count}}개월", + "over_years_ago": "{{count}}{{unit}} 전", + "year": "년", + "years": "년", + "recent_prefix": "최근:", + "days_ago_to_now": "며칠 전부터 현재까지", + "apply": "적용", + "start_date": "시작 날짜", + "end_date": "종료 날짜", + "clear_filters": "시간 필터 초기화", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "태그", + "search_placeholder": "태그 검색...", + "clear_all": "모든 태그 지우기", + "no_tags_found": "태그를 찾을 수 없습니다" + }, + "search_accounts": { + "label": "메일 계정", + "search_placeholder": "메일 계정 검색...", + "clear_accounts": "선택한 계정 지우기", + "no_accounts_found": "계정을 찾을 수 없습니다" + }, + "search_mailbox": { + "label": "메일함", + "search_placeholder": "메일함 검색", + "clear_mailboxes": "선택한 폴더 지우기", + "select_account_first": "먼저 메일 계정을 선택하세요", + "no_mailbox_found": "폴더를 찾을 수 없습니다" + }, + "search_contacts": { + "label": "연락처", + "label_with_count": "연락처 ({{count}})", + "any": "제한 없음", + "search_placeholder": "{{field}} 검색...", + "reset_all": "모든 연락처 초기화", + "no_contact_found": "연락처를 찾을 수 없습니다", + "loading": "불러오는 중...", + "showing_limit": "상위 100개 결과만 표시 • 전체 {{total}}개" + }, + "search_more": { + "trigger_label": "고급", + "title": "고급 필터", + "reset": "초기화", + "has_attachment": "첨부파일 포함", + "attachment_name_label": "첨부파일 이름", + "attachment_name_placeholder": "예: invoice.pdf", + "message_size_label": "메일 크기", + "message_id_label": "원본 Message-ID", + "message_id_description": "메일 헤더의 Message-ID로 검색", + "apply": "필터 적용", + "size_presets": { + "any": "크기 제한 없음", + "tiny": "매우 작음 (< 15 KB)", + "small": "작음 (< 2 MB)", + "medium": "보통 (2 – 10 MB)", + "large": "큼 (10 – 20 MB)", + "huge": "매우 큼 (> 20 MB)" + } + }, + "search_view": { + "button_label": "보기", + "menu_title": "표시 열 설정" + }, + "search_reset": { + "label": "초기화", + "tooltip": "모든 필터 조건 지우기" + }, + "search_input": { + "placeholder": "메일 검색... (\"따옴표\"로 정확히 일치)", + "button": "검색", + "recent_title": "최근 검색", + "clear_history": "기록 지우기", + "no_history": "검색 기록 없음", + "hint": "기본 검색 범위: 제목, 본문 및 첨부파일 이름" } } \ No newline at end of file diff --git a/web/src/locales/nl.json b/web/src/locales/nl.json index e80a495..28d6dfa 100644 --- a/web/src/locales/nl.json +++ b/web/src/locales/nl.json @@ -1461,5 +1461,91 @@ "successDesc": "De geselecteerde berichten zijn succesvol hersteld op de IMAP-server.", "failed": "Herstellen van berichten mislukt", "failedTitle": "Herstel mislukt" + }, + "time": { + "label": "Tijd", + "since": "Sinds", + "before": "Voor", + "recent_range": "Recente periode (sinds...)", + "historical": "Historische periode (voor...)", + "absolute_range": "Absolute datumbereik", + "last_day": "Laatste 1 dag", + "last_days": "Laatste {{count}} dagen", + "last_months": "Laatste {{count}} maanden", + "over_years_ago": "{{count}} {{unit}} geleden", + "year": "jaar", + "years": "jaar", + "recent_prefix": "Recent:", + "days_ago_to_now": "dagen geleden tot nu", + "apply": "Toepassen", + "start_date": "Startdatum", + "end_date": "Einddatum", + "clear_filters": "Tijdfilters wissen", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Tags", + "search_placeholder": "Tags zoeken...", + "clear_all": "Alle tags wissen", + "no_tags_found": "Geen tags gevonden" + }, + "search_accounts": { + "label": "E-mailaccounts", + "search_placeholder": "E-mailaccounts zoeken...", + "clear_accounts": "Geselecteerde accounts wissen", + "no_accounts_found": "Geen accounts gevonden" + }, + "search_mailbox": { + "label": "Mailboxen", + "search_placeholder": "Mailboxen zoeken", + "clear_mailboxes": "Geselecteerde mappen wissen", + "select_account_first": "Selecteer eerst een e-mailaccount", + "no_mailbox_found": "Geen mailbox gevonden" + }, + "search_contacts": { + "label": "Contacten", + "label_with_count": "Contacten ({{count}})", + "any": "Alle", + "search_placeholder": "{{field}} zoeken...", + "reset_all": "Alle contacten resetten", + "no_contact_found": "Geen contacten gevonden", + "loading": "Bezig met laden...", + "showing_limit": "Alleen de eerste 100 resultaten • Totaal {{total}}" + }, + "search_more": { + "trigger_label": "Geavanceerd", + "title": "Geavanceerde filters", + "reset": "Resetten", + "has_attachment": "Bevat bijlage", + "attachment_name_label": "Bijlagenaam", + "attachment_name_placeholder": "Bijv.: invoice.pdf", + "message_size_label": "Berichtgrootte", + "message_id_label": "Originele Message-ID", + "message_id_description": "Zoeken via de Message-ID in de e-mailheader", + "apply": "Filters toepassen", + "size_presets": { + "any": "Elke grootte", + "tiny": "Zeer klein (< 15 KB)", + "small": "Klein (< 2 MB)", + "medium": "Gemiddeld (2 – 10 MB)", + "large": "Groot (10 – 20 MB)", + "huge": "Zeer groot (> 20 MB)" + } + }, + "search_view": { + "button_label": "Weergave", + "menu_title": "Kolominstellingen" + }, + "search_reset": { + "label": "Resetten", + "tooltip": "Alle filters wissen" + }, + "search_input": { + "placeholder": "E-mails zoeken... (gebruik \"aanhalingstekens\" voor exacte overeenkomst)", + "button": "Zoeken", + "recent_title": "Recente zoekopdrachten", + "clear_history": "Geschiedenis wissen", + "no_history": "Geen zoekgeschiedenis", + "hint": "Standaard zoekbereik: onderwerp, inhoud en bijlagenamen" } } \ No newline at end of file diff --git a/web/src/locales/no.json b/web/src/locales/no.json index 737a1e8..37a7a4b 100644 --- a/web/src/locales/no.json +++ b/web/src/locales/no.json @@ -1461,5 +1461,91 @@ "successDesc": "De valgte meldingene har blitt gjenopprettet til IMAP-serveren.", "failed": "Kunne ikke gjenopprette meldinger", "failedTitle": "Gjenoppretting mislyktes" + }, + "time": { + "label": "Tid", + "since": "Siden", + "before": "Før", + "recent_range": "Nylig periode (siden...)", + "historical": "Historisk periode (før...)", + "absolute_range": "Absolutt datointervall", + "last_day": "Siste 1 dag", + "last_days": "Siste {{count}} dager", + "last_months": "Siste {{count}} måneder", + "over_years_ago": "For {{count}} {{unit}} siden", + "year": "år", + "years": "år", + "recent_prefix": "Nylig:", + "days_ago_to_now": "dager siden til nå", + "apply": "Bruk", + "start_date": "Startdato", + "end_date": "Sluttdato", + "clear_filters": "Fjern tidsfiltre", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Etiketter", + "search_placeholder": "Søk etter etiketter...", + "clear_all": "Fjern alle etiketter", + "no_tags_found": "Ingen etiketter funnet" + }, + "search_accounts": { + "label": "E-postkontoer", + "search_placeholder": "Søk etter e-postkontoer...", + "clear_accounts": "Fjern valgte kontoer", + "no_accounts_found": "Ingen kontoer funnet" + }, + "search_mailbox": { + "label": "Postbokser", + "search_placeholder": "Søk i postbokser", + "clear_mailboxes": "Fjern valgte mapper", + "select_account_first": "Velg først en e-postkonto", + "no_mailbox_found": "Ingen mappe funnet" + }, + "search_contacts": { + "label": "Kontakter", + "label_with_count": "Kontakter ({{count}})", + "any": "Alle", + "search_placeholder": "Søk i {{field}}...", + "reset_all": "Tilbakestill alle kontakter", + "no_contact_found": "Ingen kontakter funnet", + "loading": "Laster...", + "showing_limit": "Viser kun de første 100 resultatene • Totalt {{total}}" + }, + "search_more": { + "trigger_label": "Avansert", + "title": "Avanserte filtre", + "reset": "Tilbakestill", + "has_attachment": "Har vedlegg", + "attachment_name_label": "Vedleggsnavn", + "attachment_name_placeholder": "F.eks.: invoice.pdf", + "message_size_label": "Meldingsstørrelse", + "message_id_label": "Opprinnelig Message-ID", + "message_id_description": "Søk via Message-ID i e-postheaderen", + "apply": "Bruk filtre", + "size_presets": { + "any": "Alle størrelser", + "tiny": "Svært liten (< 15 KB)", + "small": "Liten (< 2 MB)", + "medium": "Middels (2 – 10 MB)", + "large": "Stor (10 – 20 MB)", + "huge": "Svært stor (> 20 MB)" + } + }, + "search_view": { + "button_label": "Visning", + "menu_title": "Kolonneinnstillinger" + }, + "search_reset": { + "label": "Tilbakestill", + "tooltip": "Fjern alle filtre" + }, + "search_input": { + "placeholder": "Søk i e-poster... (bruk \"anførselstegn\" for eksakt treff)", + "button": "Søk", + "recent_title": "Nylige søk", + "clear_history": "Tøm historikk", + "no_history": "Ingen søkehistorikk", + "hint": "Standard søkeområde: emne, innhold og vedleggsnavn" } } \ No newline at end of file diff --git a/web/src/locales/pl.json b/web/src/locales/pl.json index 8ce206d..456527e 100644 --- a/web/src/locales/pl.json +++ b/web/src/locales/pl.json @@ -1461,5 +1461,91 @@ "successDesc": "Wybrane wiadomości zostały pomyślnie przywrócone na serwer IMAP.", "failed": "Nie udało się przywrócić wiadomości", "failedTitle": "Przywracanie nie powiodło się" + }, + "time": { + "label": "Czas", + "since": "Od", + "before": "Przed", + "recent_range": "Ostatni zakres (od...)", + "historical": "Zakres historyczny (przed...)", + "absolute_range": "Bezwzględny zakres dat", + "last_day": "Ostatni 1 dzień", + "last_days": "Ostatnie {{count}} dni", + "last_months": "Ostatnie {{count}} miesiące", + "over_years_ago": "{{count}} {{unit}} temu", + "year": "rok", + "years": "lata", + "recent_prefix": "Ostatnio:", + "days_ago_to_now": "dni temu do teraz", + "apply": "Zastosuj", + "start_date": "Data rozpoczęcia", + "end_date": "Data zakończenia", + "clear_filters": "Wyczyść filtry czasu", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Tagi", + "search_placeholder": "Szukaj tagów...", + "clear_all": "Wyczyść wszystkie tagi", + "no_tags_found": "Nie znaleziono tagów" + }, + "search_accounts": { + "label": "Konta e-mail", + "search_placeholder": "Szukaj kont e-mail...", + "clear_accounts": "Wyczyść wybrane konta", + "no_accounts_found": "Nie znaleziono kont" + }, + "search_mailbox": { + "label": "Skrzynki pocztowe", + "search_placeholder": "Szukaj skrzynek", + "clear_mailboxes": "Wyczyść wybrane foldery", + "select_account_first": "Najpierw wybierz konto e-mail", + "no_mailbox_found": "Nie znaleziono folderu" + }, + "search_contacts": { + "label": "Kontakty", + "label_with_count": "Kontakty ({{count}})", + "any": "Dowolne", + "search_placeholder": "Szukaj {{field}}...", + "reset_all": "Zresetuj wszystkie kontakty", + "no_contact_found": "Nie znaleziono kontaktów", + "loading": "Ładowanie...", + "showing_limit": "Wyświetlane tylko pierwsze 100 wyników • Łącznie {{total}}" + }, + "search_more": { + "trigger_label": "Zaawansowane", + "title": "Filtry zaawansowane", + "reset": "Resetuj", + "has_attachment": "Zawiera załącznik", + "attachment_name_label": "Nazwa załącznika", + "attachment_name_placeholder": "Np.: invoice.pdf", + "message_size_label": "Rozmiar wiadomości", + "message_id_label": "Oryginalny Message-ID", + "message_id_description": "Wyszukiwanie według nagłówka Message-ID", + "apply": "Zastosuj filtry", + "size_presets": { + "any": "Dowolny rozmiar", + "tiny": "Bardzo mały (< 15 KB)", + "small": "Mały (< 2 MB)", + "medium": "Średni (2 – 10 MB)", + "large": "Duży (10 – 20 MB)", + "huge": "Bardzo duży (> 20 MB)" + } + }, + "search_view": { + "button_label": "Widok", + "menu_title": "Ustawienia kolumn" + }, + "search_reset": { + "label": "Reset", + "tooltip": "Wyczyść wszystkie filtry" + }, + "search_input": { + "placeholder": "Szukaj e-maili... (użyj \"cudzysłowów\" dla dokładnego dopasowania)", + "button": "Szukaj", + "recent_title": "Ostatnie wyszukiwania", + "clear_history": "Wyczyść historię", + "no_history": "Brak historii wyszukiwania", + "hint": "Domyślny zakres wyszukiwania: temat, treść i nazwy załączników" } } \ No newline at end of file diff --git a/web/src/locales/pt.json b/web/src/locales/pt.json index 2ffe54b..3cc5706 100644 --- a/web/src/locales/pt.json +++ b/web/src/locales/pt.json @@ -1461,5 +1461,91 @@ "successDesc": "As mensagens selecionadas foram restauradas com sucesso para o servidor IMAP.", "failed": "Falha ao restaurar mensagens", "failedTitle": "Falha na restauração" + }, + "time": { + "label": "Tempo", + "since": "Desde", + "before": "Antes de", + "recent_range": "Intervalo recente (desde...)", + "historical": "Intervalo histórico (antes de...)", + "absolute_range": "Intervalo de datas absoluto", + "last_day": "Último 1 dia", + "last_days": "Últimos {{count}} dias", + "last_months": "Últimos {{count}} meses", + "over_years_ago": "Há {{count}} {{unit}}", + "year": "ano", + "years": "anos", + "recent_prefix": "Recente:", + "days_ago_to_now": "dias atrás até agora", + "apply": "Aplicar", + "start_date": "Data de início", + "end_date": "Data de fim", + "clear_filters": "Limpar filtros de tempo", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Etiquetas", + "search_placeholder": "Pesquisar etiquetas...", + "clear_all": "Limpar todas as etiquetas", + "no_tags_found": "Nenhuma etiqueta encontrada" + }, + "search_accounts": { + "label": "Contas de e-mail", + "search_placeholder": "Pesquisar contas de e-mail...", + "clear_accounts": "Limpar contas selecionadas", + "no_accounts_found": "Nenhuma conta encontrada" + }, + "search_mailbox": { + "label": "Caixas de correio", + "search_placeholder": "Pesquisar pastas", + "clear_mailboxes": "Limpar pastas selecionadas", + "select_account_first": "Selecione primeiro uma conta de e-mail", + "no_mailbox_found": "Nenhuma pasta encontrada" + }, + "search_contacts": { + "label": "Contatos", + "label_with_count": "Contatos ({{count}})", + "any": "Qualquer", + "search_placeholder": "Pesquisar {{field}}...", + "reset_all": "Redefinir todos os contatos", + "no_contact_found": "Nenhum contato encontrado", + "loading": "Carregando...", + "showing_limit": "Mostrando apenas os primeiros 100 resultados • Total {{total}}" + }, + "search_more": { + "trigger_label": "Avançado", + "title": "Filtros avançados", + "reset": "Redefinir", + "has_attachment": "Contém anexo", + "attachment_name_label": "Nome do anexo", + "attachment_name_placeholder": "Ex.: invoice.pdf", + "message_size_label": "Tamanho da mensagem", + "message_id_label": "Message-ID original", + "message_id_description": "Pesquisar pelo Message-ID no cabeçalho do e-mail", + "apply": "Aplicar filtros", + "size_presets": { + "any": "Qualquer tamanho", + "tiny": "Muito pequeno (< 15 KB)", + "small": "Pequeno (< 2 MB)", + "medium": "Médio (2 – 10 MB)", + "large": "Grande (10 – 20 MB)", + "huge": "Muito grande (> 20 MB)" + } + }, + "search_view": { + "button_label": "Visualização", + "menu_title": "Configurações de colunas" + }, + "search_reset": { + "label": "Redefinir", + "tooltip": "Limpar todos os filtros" + }, + "search_input": { + "placeholder": "Pesquisar e-mails... (use \"aspas\" para correspondência exata)", + "button": "Pesquisar", + "recent_title": "Pesquisas recentes", + "clear_history": "Limpar histórico", + "no_history": "Nenhum histórico de pesquisa", + "hint": "Escopo padrão: assunto, conteúdo e nomes dos anexos" } } \ No newline at end of file diff --git a/web/src/locales/ru.json b/web/src/locales/ru.json index 617a5cc..0df9446 100644 --- a/web/src/locales/ru.json +++ b/web/src/locales/ru.json @@ -1461,5 +1461,91 @@ "successDesc": "Выбранные сообщения были успешно восстановлены на IMAP-сервере.", "failed": "Не удалось восстановить сообщения", "failedTitle": "Ошибка восстановления" + }, + "time": { + "label": "Время", + "since": "С", + "before": "До", + "recent_range": "Недавний диапазон (с...)", + "historical": "Исторический диапазон (до...)", + "absolute_range": "Абсолютный диапазон дат", + "last_day": "Последний 1 день", + "last_days": "Последние {{count}} дней", + "last_months": "Последние {{count}} месяцев", + "over_years_ago": "{{count}} {{unit}} назад", + "year": "год", + "years": "лет", + "recent_prefix": "Недавно:", + "days_ago_to_now": "дней назад — по настоящее время", + "apply": "Применить", + "start_date": "Дата начала", + "end_date": "Дата окончания", + "clear_filters": "Очистить фильтр времени", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Теги", + "search_placeholder": "Поиск тегов...", + "clear_all": "Очистить все теги", + "no_tags_found": "Теги не найдены" + }, + "search_accounts": { + "label": "Почтовые аккаунты", + "search_placeholder": "Поиск почтовых аккаунтов...", + "clear_accounts": "Очистить выбранные аккаунты", + "no_accounts_found": "Аккаунты не найдены" + }, + "search_mailbox": { + "label": "Почтовые папки", + "search_placeholder": "Поиск папок", + "clear_mailboxes": "Очистить выбранные папки", + "select_account_first": "Сначала выберите почтовый аккаунт", + "no_mailbox_found": "Папки не найдены" + }, + "search_contacts": { + "label": "Контакты", + "label_with_count": "Контакты ({{count}})", + "any": "Любые", + "search_placeholder": "Поиск по {{field}}...", + "reset_all": "Сбросить все контакты", + "no_contact_found": "Контакты не найдены", + "loading": "Загрузка...", + "showing_limit": "Показаны первые 100 результатов • Всего {{total}}" + }, + "search_more": { + "trigger_label": "Дополнительно", + "title": "Расширенные фильтры", + "reset": "Сброс", + "has_attachment": "Есть вложение", + "attachment_name_label": "Имя вложения", + "attachment_name_placeholder": "Например: invoice.pdf", + "message_size_label": "Размер письма", + "message_id_label": "Исходный Message-ID", + "message_id_description": "Поиск по Message-ID из заголовков письма", + "apply": "Применить фильтры", + "size_presets": { + "any": "Любой размер", + "tiny": "Очень маленький (< 15 КБ)", + "small": "Маленький (< 2 МБ)", + "medium": "Средний (2–10 МБ)", + "large": "Большой (10–20 МБ)", + "huge": "Очень большой (> 20 МБ)" + } + }, + "search_view": { + "button_label": "Вид", + "menu_title": "Настройки отображаемых столбцов" + }, + "search_reset": { + "label": "Сброс", + "tooltip": "Очистить все фильтры" + }, + "search_input": { + "placeholder": "Поиск писем... (используйте \"кавычки\" для точного совпадения)", + "button": "Поиск", + "recent_title": "Недавние поиски", + "clear_history": "Очистить историю", + "no_history": "История поиска пуста", + "hint": "По умолчанию поиск выполняется по теме, содержимому и именам вложений" } } \ No newline at end of file diff --git a/web/src/locales/sv.json b/web/src/locales/sv.json index e6d95da..70cc870 100644 --- a/web/src/locales/sv.json +++ b/web/src/locales/sv.json @@ -1461,5 +1461,91 @@ "successDesc": "De valda meddelandena har återställts till IMAP-servern.", "failed": "Misslyckades med att återställa meddelanden", "failedTitle": "Återställning misslyckades" + }, + "time": { + "label": "Tid", + "since": "Sedan", + "before": "Före", + "recent_range": "Senaste intervall (sedan...)", + "historical": "Historiskt intervall (före...)", + "absolute_range": "Absolut datumintervall", + "last_day": "Senaste 1 dagen", + "last_days": "Senaste {{count}} dagarna", + "last_months": "Senaste {{count}} månaderna", + "over_years_ago": "För {{count}} {{unit}} sedan", + "year": "år", + "years": "år", + "recent_prefix": "Senaste:", + "days_ago_to_now": "dagar sedan till nu", + "apply": "Använd", + "start_date": "Startdatum", + "end_date": "Slutdatum", + "clear_filters": "Rensa tidsfilter", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "Taggar", + "search_placeholder": "Sök taggar...", + "clear_all": "Rensa alla taggar", + "no_tags_found": "Inga taggar hittades" + }, + "search_accounts": { + "label": "E-postkonton", + "search_placeholder": "Sök e-postkonton...", + "clear_accounts": "Rensa valda konton", + "no_accounts_found": "Inga konton hittades" + }, + "search_mailbox": { + "label": "E-postmappar", + "search_placeholder": "Sök mappar", + "clear_mailboxes": "Rensa valda mappar", + "select_account_first": "Välj först ett e-postkonto", + "no_mailbox_found": "Inga mappar hittades" + }, + "search_contacts": { + "label": "Kontakter", + "label_with_count": "Kontakter ({{count}})", + "any": "Alla", + "search_placeholder": "Sök {{field}}...", + "reset_all": "Återställ alla kontakter", + "no_contact_found": "Inga kontakter hittades", + "loading": "Laddar...", + "showing_limit": "Visar endast de första 100 resultaten • Totalt {{total}}" + }, + "search_more": { + "trigger_label": "Avancerat", + "title": "Avancerade filter", + "reset": "Återställ", + "has_attachment": "Har bilaga", + "attachment_name_label": "Bilagans namn", + "attachment_name_placeholder": "t.ex. invoice.pdf", + "message_size_label": "Meddelandestorlek", + "message_id_label": "Ursprungligt Message-ID", + "message_id_description": "Sök via Message-ID i e-posthuvudet", + "apply": "Använd filter", + "size_presets": { + "any": "Valfri storlek", + "tiny": "Mycket liten (< 15 KB)", + "small": "Liten (< 2 MB)", + "medium": "Mellan (2–10 MB)", + "large": "Stor (10–20 MB)", + "huge": "Mycket stor (> 20 MB)" + } + }, + "search_view": { + "button_label": "Vy", + "menu_title": "Inställningar för kolumner" + }, + "search_reset": { + "label": "Återställ", + "tooltip": "Rensa alla filter" + }, + "search_input": { + "placeholder": "Sök e-post... (använd \"citattecken\" för exakt matchning)", + "button": "Sök", + "recent_title": "Senaste sökningar", + "clear_history": "Rensa historik", + "no_history": "Ingen sökhistorik", + "hint": "Standardomfattning: ämne, innehåll och bilagenamn" } } \ No newline at end of file diff --git a/web/src/locales/zh-tw.json b/web/src/locales/zh-tw.json index cc6af79..ce0330c 100644 --- a/web/src/locales/zh-tw.json +++ b/web/src/locales/zh-tw.json @@ -1461,5 +1461,91 @@ "successDesc": "選定的郵件已成功還原到 IMAP 伺服器。", "failed": "還原郵件失敗", "failedTitle": "還原失敗" + }, + "time": { + "label": "時間", + "since": "自", + "before": "早於", + "recent_range": "近期範圍(自…)", + "historical": "歷史範圍(早於…)", + "absolute_range": "絕對日期範圍", + "last_day": "最近 1 天", + "last_days": "最近 {{count}} 天", + "last_months": "最近 {{count}} 個月", + "over_years_ago": "{{count}} {{unit}} 前", + "year": "年", + "years": "年", + "recent_prefix": "最近:", + "days_ago_to_now": "天前至今", + "apply": "套用", + "start_date": "開始日期", + "end_date": "結束日期", + "clear_filters": "清除時間篩選", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "標籤", + "search_placeholder": "搜尋標籤…", + "clear_all": "清除所有標籤", + "no_tags_found": "未找到標籤" + }, + "search_accounts": { + "label": "郵件帳戶", + "search_placeholder": "搜尋郵件帳戶…", + "clear_accounts": "清除已選帳戶", + "no_accounts_found": "未找到帳戶" + }, + "search_mailbox": { + "label": "信箱資料夾", + "search_placeholder": "搜尋信箱資料夾", + "clear_mailboxes": "清除已選資料夾", + "select_account_first": "請先選擇郵件帳戶", + "no_mailbox_found": "未找到資料夾" + }, + "search_contacts": { + "label": "聯絡人", + "label_with_count": "聯絡人({{count}})", + "any": "不限", + "search_placeholder": "搜尋 {{field}}…", + "reset_all": "重設所有聯絡人", + "no_contact_found": "未找到聯絡人", + "loading": "載入中…", + "showing_limit": "僅顯示前 100 筆結果 • 共 {{total}} 筆" + }, + "search_more": { + "trigger_label": "進階", + "title": "進階篩選", + "reset": "重設", + "has_attachment": "包含附件", + "attachment_name_label": "附件名稱", + "attachment_name_placeholder": "例如:invoice.pdf", + "message_size_label": "郵件大小", + "message_id_label": "原始 Message-ID", + "message_id_description": "依郵件標頭中的 Message-ID 搜尋", + "apply": "套用篩選", + "size_presets": { + "any": "不限大小", + "tiny": "極小(< 15 KB)", + "small": "較小(< 2 MB)", + "medium": "一般(2–10 MB)", + "large": "較大(10–20 MB)", + "huge": "極大(> 20 MB)" + } + }, + "search_view": { + "button_label": "檢視", + "menu_title": "顯示欄位設定" + }, + "search_reset": { + "label": "重設", + "tooltip": "清除所有篩選條件" + }, + "search_input": { + "placeholder": "搜尋郵件…(使用「雙引號」進行精確比對)", + "button": "搜尋", + "recent_title": "最近搜尋", + "clear_history": "清除紀錄", + "no_history": "尚無搜尋紀錄", + "hint": "預設搜尋範圍:郵件標題、內容與附件名稱" } } \ No newline at end of file diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index 13fe5fa..f503f29 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -1461,5 +1461,91 @@ "successDesc": "选定的邮件已成功恢复到 IMAP 服务器。", "failed": "还原邮件失败", "failedTitle": "还原失败" + }, + "time": { + "label": "时间", + "since": "自", + "before": "早于", + "recent_range": "近期范围 (自...)", + "historical": "历史范围 (早于...)", + "absolute_range": "绝对日期范围", + "last_day": "最近 1 天", + "last_days": "最近 {{count}} 天", + "last_months": "最近 {{count}} 个月", + "over_years_ago": "{{count}} {{unit}}前", + "year": "年", + "years": "年", + "recent_prefix": "最近:", + "days_ago_to_now": "天前至今", + "apply": "应用", + "start_date": "开始日期", + "end_date": "结束日期", + "clear_filters": "清除时间筛选", + "format": "yyyy-MM-dd" + }, + "tag": { + "label": "标签", + "search_placeholder": "搜索标签...", + "clear_all": "清除所有标签", + "no_tags_found": "未找到标签" + }, + "search_accounts": { + "label": "邮件账户", + "search_placeholder": "搜索邮件账户...", + "clear_accounts": "清除已选账户", + "no_accounts_found": "未找到账户" + }, + "search_mailbox": { + "label": "邮箱文件夹", + "search_placeholder": "搜索邮箱文件夹", + "clear_mailboxes": "清除已选文件夹", + "select_account_first": "请先选择邮件账户", + "no_mailbox_found": "未找到文件夹" + }, + "search_contacts": { + "label": "联系人", + "label_with_count": "联系人 ({{count}})", + "any": "不限", + "search_placeholder": "搜索 {{field}}...", + "reset_all": "重置所有联系人", + "no_contact_found": "未找到联系人", + "loading": "加载中...", + "showing_limit": "仅显示前 100 条结果 • 总计 {{total}} 条" + }, + "search_more": { + "trigger_label": "高级", + "title": "高级筛选", + "reset": "重置", + "has_attachment": "包含附件", + "attachment_name_label": "附件名称", + "attachment_name_placeholder": "例如: invoice.pdf", + "message_size_label": "邮件大小", + "message_id_label": "原始消息 ID", + "message_id_description": "通过邮件头中的 'Message-ID' 进行搜索", + "apply": "应用筛选", + "size_presets": { + "any": "不限大小", + "tiny": "极小 (< 15 KB)", + "small": "较小 (< 2 MB)", + "medium": "普通 (2 - 10 MB)", + "large": "较大 (10 - 20 MB)", + "huge": "极大 (> 20 MB)" + } + }, + "search_view": { + "button_label": "视图", + "menu_title": "显示列设置" + }, + "search_reset": { + "label": "重置", + "tooltip": "清除所有筛选条件" + }, + "search_input": { + "placeholder": "搜索邮件... (使用 \"双引号\" 进行精确匹配)", + "button": "搜索", + "recent_title": "最近搜索", + "clear_history": "清空记录", + "no_history": "暂无搜索记录", + "hint": "默认搜索范围:邮件标题、正文及附件名称" } } \ No newline at end of file