diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx index 75081f83f..f2aaf9870 100644 --- a/components/mail/mail-app.tsx +++ b/components/mail/mail-app.tsx @@ -90,6 +90,7 @@ import { jmapErrorType } from "@/lib/jmap/jmap-error"; import { isFilterEmpty, activeFilterCount, DEFAULT_SEARCH_FILTERS } from "@/lib/jmap/search-utils"; import { SearchBox, type ContactSearchField } from "@/components/search/search-box"; import { FilterPanelHost } from "@/components/search/filter-panel-host"; +import { SearchFilterPanel, SELECT_CLASS } from "@/components/search/search-filter-panel"; import type { ContactSuggestion } from "@/lib/search-suggestions"; import type { Attachment, SendEmailResult } from "@/lib/jmap/types"; import { peekListAttachments, requestListAttachments, type ListAttachmentSource, type LoadListAttachments } from "@/lib/list-attachments"; @@ -110,7 +111,6 @@ import { useIdentitySync } from "@/hooks/use-identity-sync"; import { useIsEmbedded } from "@/hooks/use-is-embedded"; import { useProTabStore } from "@/stores/pro-tab-store"; import { useProMultiAccountMailboxes } from "@/hooks/use-pro-multi-account-mailboxes"; -import { Input } from "@/components/ui/input"; import { FilePreviewModal } from "@/components/files/file-preview-modal"; import { WopiEditor } from "@/components/files/wopi-editor"; import { useWopiStatus, canWopiOpen } from "@/hooks/use-wopi-status"; @@ -120,7 +120,7 @@ import { computeReplyThreadingHeaders } from "@/lib/email-threading"; import { EML_IMPORT_ACCEPT, expandImportableEmails } from "@/lib/eml-import"; import { resolveComposeAccountEmail } from "@/lib/reply-identity"; import { useProMultiAccountIdentities } from "@/hooks/use-pro-multi-account-identities"; -import { Filter, ChevronDown, X, Paperclip, Star, Mail, MailOpen, RotateCcw, PenSquare, CheckSquare, Square, AlertTriangle, ArrowLeft, RefreshCw, Reply, ReplyAll, Forward, Archive, Trash2, FolderInput, ShieldAlert, ShieldCheck, Folder } from "@/components/icons"; +import { Filter, X, Mail, MailOpen, PenSquare, CheckSquare, Square, AlertTriangle, ArrowLeft, RefreshCw, Reply, ReplyAll, Forward, Archive, Trash2, FolderInput, ShieldAlert, ShieldCheck, Folder } from "@/components/icons"; import { useRegisterCommands } from "@/lib/command-registry"; import { MoveToDialog } from "@/components/email/move-to-dialog"; import { ResizeHandle } from "@/components/layout/resize-handle"; @@ -259,7 +259,6 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { const [moveDialogEmail, setMoveDialogEmail] = useState(null); // The scheduled message Reschedule was picked for in the list's menu. const [rescheduleTarget, setRescheduleTarget] = useState(null); - const [showAdvancedFields, setShowAdvancedFields] = useState(false); // Column resize state (disable transitions during drag) const [isResizing, setIsResizing] = useState(false); const dragStartWidth = useRef(0); @@ -3781,10 +3780,20 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { clearTimeout(advancedSearchDebounceRef.current); } advancedSearchDebounceRef.current = setTimeout(() => { + advancedSearchDebounceRef.current = null; if (client) advancedSearch(client); }, 300); }, [client, advancedSearch]); + // Runs the search at once, dropping a run still waiting on the debounce. + const runAdvancedSearchNow = async () => { + if (advancedSearchDebounceRef.current) { + clearTimeout(advancedSearchDebounceRef.current); + advancedSearchDebounceRef.current = null; + } + await handleAdvancedSearch(); + }; + useEffect(() => { return () => { if (advancedSearchDebounceRef.current) { @@ -4580,22 +4589,6 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { }); }; - const ToggleChip = ({ icon, label, value, onClick }: { icon: React.ReactNode; label: string; value: boolean | null; onClick: () => void }) => ( - - ); - // The folder the advanced search runs in. Scopes the search only - it does // not navigate the mail list. `rerun` re-queries straight away when a search // is running; Mountain View's form waits for its Search button instead. @@ -5118,165 +5111,22 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { false, ) : undefined} /> - ) : (<> - {/* Quick toggle filters + clear */} -
-
- } - label={t("advanced_search.has_attachment")} - value={searchFilters.hasAttachment} - onClick={() => { const next = searchFilters.hasAttachment === null ? true : searchFilters.hasAttachment ? false : null; setSearchFilters({ hasAttachment: next }); handleAdvancedSearch(); }} - /> - } - label={t("advanced_search.starred")} - value={searchFilters.isStarred} - onClick={() => { const next = searchFilters.isStarred === null ? true : searchFilters.isStarred ? false : null; setSearchFilters({ isStarred: next }); handleAdvancedSearch(); }} - /> - : } - label={searchFilters.isUnread === false ? t("advanced_search.read") : t("advanced_search.unread")} - value={searchFilters.isUnread} - onClick={() => { const next = searchFilters.isUnread === null ? true : searchFilters.isUnread ? false : null; setSearchFilters({ isUnread: next }); handleAdvancedSearch(); }} - /> -
-
- -
-
- - {/* "More" expand for advanced fields */} - - - {/* Advanced fields */} - {showAdvancedFields && ( -
-
-
- - { setSearchFilters({ from: e.target.value }); handleAdvancedSearchDebounced(); }} - placeholder={t("advanced_search.from_placeholder")} - className="h-8 text-sm" - /> -
-
- - { setSearchFilters({ to: e.target.value }); handleAdvancedSearchDebounced(); }} - placeholder={t("advanced_search.to_placeholder")} - className="h-8 text-sm" - /> -
-
- -
- - { setSearchFilters({ subject: e.target.value }); handleAdvancedSearchDebounced(); }} - placeholder={t("advanced_search.subject_placeholder")} - className="h-8 text-sm" - /> -
- -
- - { setSearchFilters({ body: e.target.value }); handleAdvancedSearchDebounced(); }} - placeholder={t("advanced_search.body_placeholder")} - className="h-8 text-sm" - /> -
- - {/* Folder selector. Scopes the search only - it does not - navigate the mail list. It defaults to "All folders - except Spam and Trash", or to Spam/Trash itself while - that folder is open (defaultSearchScopeFor), and keeps - whatever the user picked. Both folder-less scopes span - the own AND the group/shared accounts' folders (#1082); - "All folders" also searches Trash and Junk. The shared - folders are listed under their owner, and subfolders - are indented under their parent as in the sidebar - (a native select has no tree, so the indent is - non-breaking spaces, as in the filter rule modal). - The unified views already search across every - account's folders, so it is hidden there. */} - {!isUnifiedView && ( -
- - {renderSearchFolderSelect( - "w-full h-8 text-sm rounded-md border border-input bg-background px-3 text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1", - true, - )} -
- )} - -
-
- - { setSearchFilters({ dateAfter: e.target.value }); handleAdvancedSearch(); }} - className="h-8 text-sm" - /> -
-
- - { setSearchFilters({ dateBefore: e.target.value }); handleAdvancedSearch(); }} - className="h-8 text-sm" - /> -
-
- - {/* Message size (Email/query minSize / maxSize, RFC 8621 §4.4.1) */} -
-
- - { setSearchFilters({ minSizeKb: e.target.value }); handleAdvancedSearch(); }} - className="h-8 text-sm" - data-testid="advanced-search-min-size" - /> -
-
- - { setSearchFilters({ maxSizeKb: e.target.value }); handleAdvancedSearch(); }} - className="h-8 text-sm" - data-testid="advanced-search-max-size" - /> -
-
-
+ ) : ( + { + setSearchFilters(patch); + if (mode === "debounced") handleAdvancedSearchDebounced(); + else void runAdvancedSearchNow(); + }} + onClearAll={() => { clearSearchFilters(); void runAdvancedSearchNow(); }} + onDone={() => { + if (advancedSearchDebounceRef.current) void runAdvancedSearchNow(); + toggleAdvancedSearch(); + }} + folderSelect={!isUnifiedView ? renderSearchFolderSelect(SELECT_CLASS, true) : undefined} + /> )} - )} )} diff --git a/components/search/__tests__/search-filter-panel.test.tsx b/components/search/__tests__/search-filter-panel.test.tsx new file mode 100644 index 000000000..c4f654ea5 --- /dev/null +++ b/components/search/__tests__/search-filter-panel.test.tsx @@ -0,0 +1,90 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import { DEFAULT_SEARCH_FILTERS, type SearchFilters } from '@/lib/jmap/search-utils'; +import { SearchFilterPanel, daysAgo } from '../search-filter-panel'; + +function renderPanel(filters: Partial = {}) { + const onChange = vi.fn(); + const onClearAll = vi.fn(); + const onDone = vi.fn(); + render( + , + ); + return { onChange, onClearAll, onDone }; +} + +describe('SearchFilterPanel', () => { + it('turns Unread and Read on and off, without a hidden third state', () => { + const { onChange } = renderPanel(); + fireEvent.click(screen.getByRole('button', { name: 'unread' })); + expect(onChange).toHaveBeenLastCalledWith({ isUnread: true }, 'now'); + fireEvent.click(screen.getByRole('button', { name: 'read' })); + expect(onChange).toHaveBeenLastCalledWith({ isUnread: false }, 'now'); + }); + + it('marks an active chip as pressed and turns it off on the next click', () => { + const { onChange } = renderPanel({ isStarred: true }); + const starred = screen.getByRole('button', { name: 'starred' }); + expect(starred.getAttribute('aria-pressed')).toBe('true'); + fireEvent.click(starred); + expect(onChange).toHaveBeenLastCalledWith({ isStarred: null }, 'now'); + }); + + it('shows every field at once, and debounces the text ones', () => { + const { onChange } = renderPanel(); + fireEvent.change(screen.getByLabelText('from'), { target: { value: 'alice' } }); + expect(onChange).toHaveBeenLastCalledWith({ from: 'alice' }, 'debounced'); + expect(screen.getByLabelText('has_words')).toBeTruthy(); + expect(screen.getByLabelText('date_within')).toBeTruthy(); + }); + + it('turns a date range pick into an after date', () => { + const { onChange } = renderPanel(); + fireEvent.change(screen.getByLabelText('date_within'), { target: { value: 'past_week' } }); + expect(onChange).toHaveBeenLastCalledWith({ dateAfter: daysAgo(7), dateBefore: '' }, 'now'); + }); + + it('recognises its own range and offers the two dates for anything else', () => { + renderPanel({ dateAfter: daysAgo(30) }); + expect((screen.getByLabelText('date_within') as HTMLSelectElement).value).toBe('past_month'); + expect(screen.queryByLabelText('date_after')).toBeNull(); + }); + + it('opens the custom range with both dates', () => { + renderPanel({ dateAfter: '2026-01-01', dateBefore: '2026-02-01' }); + expect((screen.getByLabelText('date_within') as HTMLSelectElement).value).toBe('custom'); + expect((screen.getByLabelText('date_after') as HTMLInputElement).value).toBe('2026-01-01'); + }); + + it('shows the custom fields as soon as Custom is picked, before any date is set', () => { + const { onChange } = renderPanel(); + fireEvent.change(screen.getByLabelText('date_within'), { target: { value: 'custom' } }); + expect(screen.getByLabelText('date_after')).toBeTruthy(); + expect(onChange).not.toHaveBeenCalled(); + }); + + it('turns a size pick into a lower bound in KB', () => { + const { onChange } = renderPanel(); + fireEvent.change(screen.getByLabelText('size'), { target: { value: 'mb5' } }); + expect(onChange).toHaveBeenLastCalledWith({ minSizeKb: '5120', maxSizeKb: '' }, 'now'); + }); + + it('offers Clear all only when a filter is set', () => { + renderPanel(); + expect(screen.queryByRole('button', { name: /clear_all/ })).toBeNull(); + }); + + it('clears everything and closes on Done or Enter', () => { + const { onClearAll, onDone } = renderPanel({ subject: 'invoice' }); + fireEvent.click(screen.getByRole('button', { name: /clear_all/ })); + expect(onClearAll).toHaveBeenCalledTimes(1); + fireEvent.submit(screen.getByLabelText('subject').closest('form')!); + fireEvent.click(screen.getByRole('button', { name: 'done' })); + expect(onDone).toHaveBeenCalledTimes(2); + }); +}); diff --git a/components/search/search-filter-panel.tsx b/components/search/search-filter-panel.tsx new file mode 100644 index 000000000..42697bbc1 --- /dev/null +++ b/components/search/search-filter-panel.tsx @@ -0,0 +1,274 @@ +"use client"; + +import { useId, useState, type ReactNode } from "react"; +import { useTranslations } from "next-intl"; +import { Check, Mail, MailOpen, Paperclip, RotateCcw, Star } from "@/components/icons"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { activeFilterCount, type SearchFilters } from "@/lib/jmap/search-utils"; +import { cn } from "@/lib/utils"; + +/** How a change runs the search: straight away, or after typing pauses. */ +export type FilterChangeMode = "now" | "debounced"; + +interface SearchFilterPanelProps { + filters: SearchFilters; + onChange: (patch: Partial, mode: FilterChangeMode) => void; + onClearAll: () => void; + /** Runs any pending search and closes the panel (Done, or Enter in a field). */ + onDone: () => void; + /** The folder picker, shared with Mountain View's form. */ + folderSelect?: ReactNode; +} + +const DATE_PRESETS = [ + { key: "past_week", days: 7 }, + { key: "past_month", days: 30 }, + { key: "past_year", days: 365 }, +] as const; + +const SIZE_PRESETS_MB = [1, 5, 10, 25] as const; + +export const SELECT_CLASS = + "w-full h-8 text-sm rounded-md border border-input bg-background px-2 text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1"; + +/** `days` before today as the yyyy-mm-dd a date input holds, in local time. */ +export function daysAgo(days: number, now = new Date()): string { + const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - days); + const pad = (n: number) => String(n).padStart(2, "0"); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; +} + +function datePresetOf(filters: SearchFilters): string { + if (!filters.dateAfter && !filters.dateBefore) return "any"; + if (!filters.dateBefore) { + const preset = DATE_PRESETS.find((p) => daysAgo(p.days) === filters.dateAfter); + if (preset) return preset.key; + } + return "custom"; +} + +function sizePresetOf(filters: SearchFilters): string { + if (!filters.minSizeKb && !filters.maxSizeKb) return "any"; + if (!filters.maxSizeKb) { + const mb = SIZE_PRESETS_MB.find((m) => String(m * 1024) === filters.minSizeKb); + if (mb) return `mb${mb}`; + } + return "custom"; +} + +/** + * The default layout's filter panel, under the list header's search field. + * Every criterion is in view at once, labelled on the left; the yes/no ones + * are on/off chips, the date and size offer the usual ranges with a custom + * one behind them. Changes run the search as they are made, so the list + * behind already shows the result when Done closes the panel. + */ +export function SearchFilterPanel({ filters, onChange, onClearAll, onDone, folderSelect }: SearchFilterPanelProps) { + const t = useTranslations("advanced_search"); + const id = useId(); + // "Custom" with nothing typed yet has no value to tell it from "Any". + const [customDate, setCustomDate] = useState(false); + const [customSize, setCustomSize] = useState(false); + const datePreset = customDate ? "custom" : datePresetOf(filters); + const sizePreset = customSize ? "custom" : sizePresetOf(filters); + const count = activeFilterCount(filters); + + const row = (key: string, label: string, field: ReactNode) => ( + <> + +
{field}
+ + ); + + const text = (key: "from" | "to" | "subject" | "body", placeholder?: string) => ( + onChange({ [key]: e.target.value }, "debounced")} + className="h-8 text-sm" + /> + ); + + return ( +
{ + e.preventDefault(); + onDone(); + }} + className="space-y-3" + > +
+ } + label={t("unread")} + on={filters.isUnread === true} + onClick={() => onChange({ isUnread: filters.isUnread === true ? null : true }, "now")} + /> + } + label={t("read")} + on={filters.isUnread === false} + onClick={() => onChange({ isUnread: filters.isUnread === false ? null : false }, "now")} + /> + } + // "Not starred" only comes from elsewhere; shown so it can be undone. + label={filters.isStarred === false ? t("not_starred") : t("starred")} + on={filters.isStarred !== null} + onClick={() => onChange({ isStarred: filters.isStarred === null ? true : null }, "now")} + /> + } + label={filters.hasAttachment === false ? t("no_attachment") : t("has_attachment")} + on={filters.hasAttachment !== null} + onClick={() => onChange({ hasAttachment: filters.hasAttachment === null ? true : null }, "now")} + /> +
+ +
+ {row("from", t("from"), text("from", t("name_or_address")))} + {row("to", t("to"), text("to", t("name_or_address")))} + {row("subject", t("subject"), text("subject"))} + {row("body", t("has_words"), text("body"))} + + {row("date", t("date_within"), ( + + ))} + {datePreset === "custom" && ( +
+ onChange({ dateAfter: e.target.value }, "now")} + className="h-8 text-sm" + /> + onChange({ dateBefore: e.target.value }, "now")} + className="h-8 text-sm" + /> +
+ )} + + {row("size", t("size"), ( + + ))} + {sizePreset === "custom" && ( +
+ onChange({ minSizeKb: e.target.value }, "debounced")} + className="h-8 text-sm" + data-testid="advanced-search-min-size" + /> + onChange({ maxSizeKb: e.target.value }, "debounced")} + className="h-8 text-sm" + data-testid="advanced-search-max-size" + /> +
+ )} + + {folderSelect && row("folder", t("search_in"), folderSelect)} +
+ +
+ {count > 0 && ( + + )} + +
+
+ ); +} + +function Chip({ icon, label, on, onClick }: { icon: ReactNode; label: string; on: boolean; onClick: () => void }) { + return ( + + ); +} diff --git a/locales/ar/common.json b/locales/ar/common.json index 83e45cbc2..a29e7908d 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -4452,7 +4452,19 @@ "date_within": "التاريخ", "search_in": "البحث في", "has_attachment_check": "يحتوي على مرفق", - "search": "بحث" + "search": "بحث", + "not_starred": "بدون نجمة", + "no_attachment": "بدون مرفقات", + "any_time": "أي وقت", + "past_week": "الأسبوع الماضي", + "past_month": "الشهر الماضي", + "past_year": "السنة الماضية", + "custom_range": "نطاق مخصص", + "any_size": "أي حجم", + "larger_than_mb": "أكبر من {size} ميغابايت", + "custom_size": "حجم مخصص", + "done": "تم", + "name_or_address": "الاسم أو العنوان" }, "welcome": { "title": "مرحبًا بك في صندوق بريدك", diff --git a/locales/ca/common.json b/locales/ca/common.json index d0d12aeca..cf7a73317 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -4452,7 +4452,19 @@ "date_within": "Data", "search_in": "Cerca a", "has_attachment_check": "Té fitxers adjunts", - "search": "Cerca" + "search": "Cerca", + "not_starred": "Sense destacar", + "no_attachment": "Sense adjunts", + "any_time": "Qualsevol data", + "past_week": "Darrera setmana", + "past_month": "Darrer mes", + "past_year": "Darrer any", + "custom_range": "Interval personalitzat", + "any_size": "Qualsevol mida", + "larger_than_mb": "Més de {size} MB", + "custom_size": "Mida personalitzada", + "done": "Fet", + "name_or_address": "Nom o adreça" }, "welcome": { "title": "Us donem la benvinguda a la bústia", diff --git a/locales/cs/common.json b/locales/cs/common.json index de79c5af3..5b06f2464 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -4323,7 +4323,19 @@ "date_within": "Datum", "search_in": "Hledat v", "has_attachment_check": "Má přílohu", - "search": "Hledat" + "search": "Hledat", + "not_starred": "Bez hvězdičky", + "no_attachment": "Bez příloh", + "any_time": "Kdykoli", + "past_week": "Poslední týden", + "past_month": "Poslední měsíc", + "past_year": "Poslední rok", + "custom_range": "Vlastní období", + "any_size": "Libovolná velikost", + "larger_than_mb": "Větší než {size} MB", + "custom_size": "Vlastní velikost", + "done": "Hotovo", + "name_or_address": "Jméno nebo adresa" }, "welcome": { "title": "Vítejte ve své poštovní schránce", diff --git a/locales/da/common.json b/locales/da/common.json index ece82af06..1621b2ea9 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -4452,7 +4452,19 @@ "date_within": "Dato", "search_in": "Søg i", "has_attachment_check": "Har vedhæftet fil", - "search": "Søg" + "search": "Søg", + "not_starred": "Uden stjerne", + "no_attachment": "Uden vedhæftninger", + "any_time": "Når som helst", + "past_week": "Seneste uge", + "past_month": "Seneste måned", + "past_year": "Seneste år", + "custom_range": "Tilpasset periode", + "any_size": "Alle størrelser", + "larger_than_mb": "Større end {size} MB", + "custom_size": "Tilpasset størrelse", + "done": "Færdig", + "name_or_address": "Navn eller adresse" }, "welcome": { "title": "Velkommen til din postkasse", diff --git a/locales/de/common.json b/locales/de/common.json index 6bdf40c8c..65639ed57 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -4323,7 +4323,19 @@ "date_within": "Datum", "search_in": "Suchen in", "has_attachment_check": "Mit Anhang", - "search": "Suchen" + "search": "Suchen", + "not_starred": "Nicht markiert", + "no_attachment": "Ohne Anhänge", + "any_time": "Beliebig", + "past_week": "Letzte Woche", + "past_month": "Letzter Monat", + "past_year": "Letztes Jahr", + "custom_range": "Eigener Zeitraum", + "any_size": "Beliebig", + "larger_than_mb": "Größer als {size} MB", + "custom_size": "Eigene Größe", + "done": "Fertig", + "name_or_address": "Name oder Adresse" }, "welcome": { "title": "Willkommen in Ihrem Postfach", diff --git a/locales/en/common.json b/locales/en/common.json index 046cac2f5..cb3c6cada 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -4452,7 +4452,19 @@ "date_within": "Date", "search_in": "Search in", "has_attachment_check": "Has attachment", - "search": "Search" + "search": "Search", + "not_starred": "Not starred", + "no_attachment": "No attachments", + "any_time": "Any time", + "past_week": "Past week", + "past_month": "Past month", + "past_year": "Past year", + "custom_range": "Custom range", + "any_size": "Any size", + "larger_than_mb": "Larger than {size} MB", + "custom_size": "Custom size", + "done": "Done", + "name_or_address": "Name or address" }, "welcome": { "title": "Welcome to your mailbox", diff --git a/locales/es/common.json b/locales/es/common.json index a36831e2f..9510a0bff 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -4323,7 +4323,19 @@ "date_within": "Fecha", "search_in": "Buscar en", "has_attachment_check": "Tiene archivos adjuntos", - "search": "Buscar" + "search": "Buscar", + "not_starred": "Sin destacar", + "no_attachment": "Sin adjuntos", + "any_time": "Cualquier fecha", + "past_week": "Última semana", + "past_month": "Último mes", + "past_year": "Último año", + "custom_range": "Intervalo personalizado", + "any_size": "Cualquier tamaño", + "larger_than_mb": "Más de {size} MB", + "custom_size": "Tamaño personalizado", + "done": "Listo", + "name_or_address": "Nombre o dirección" }, "welcome": { "title": "Bienvenido a tu buzón", diff --git a/locales/et/common.json b/locales/et/common.json index 579745c1d..7583bcee6 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -4452,7 +4452,19 @@ "date_within": "Kuupäev", "search_in": "Otsi kaustast", "has_attachment_check": "Sisaldab manust", - "search": "Otsi" + "search": "Otsi", + "not_starred": "Tähistamata", + "no_attachment": "Manusteta", + "any_time": "Igal ajal", + "past_week": "Viimane nädal", + "past_month": "Viimane kuu", + "past_year": "Viimane aasta", + "custom_range": "Kohandatud vahemik", + "any_size": "Iga suurus", + "larger_than_mb": "Suurem kui {size} MB", + "custom_size": "Kohandatud suurus", + "done": "Valmis", + "name_or_address": "Nimi või aadress" }, "welcome": { "title": "Tere tulemast sinu postkasti", diff --git a/locales/fa/common.json b/locales/fa/common.json index 7692e6639..c1897196a 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -4452,7 +4452,19 @@ "date_within": "تاریخ", "search_in": "جستجو در", "has_attachment_check": "دارای پیوست", - "search": "جستجو" + "search": "جستجو", + "not_starred": "بدون ستاره", + "no_attachment": "بدون پیوست", + "any_time": "هر زمان", + "past_week": "هفته گذشته", + "past_month": "ماه گذشته", + "past_year": "سال گذشته", + "custom_range": "بازه دلخواه", + "any_size": "هر اندازه", + "larger_than_mb": "بزرگ‌تر از {size} مگابایت", + "custom_size": "اندازه دلخواه", + "done": "انجام شد", + "name_or_address": "نام یا نشانی" }, "welcome": { "title": "به صندوق پستی خود خوش آمدید", diff --git a/locales/fr/common.json b/locales/fr/common.json index 61e115a4d..bfd168d29 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -4323,7 +4323,19 @@ "date_within": "Date", "search_in": "Rechercher dans", "has_attachment_check": "Contient une pièce jointe", - "search": "Rechercher" + "search": "Rechercher", + "not_starred": "Non suivis", + "no_attachment": "Sans pièces jointes", + "any_time": "N'importe quand", + "past_week": "Semaine dernière", + "past_month": "Mois dernier", + "past_year": "Année dernière", + "custom_range": "Période personnalisée", + "any_size": "Toutes tailles", + "larger_than_mb": "Plus de {size} Mo", + "custom_size": "Taille personnalisée", + "done": "Terminé", + "name_or_address": "Nom ou adresse" }, "welcome": { "title": "Bienvenue dans votre boite mail", diff --git a/locales/he/common.json b/locales/he/common.json index 7737965bc..885bd196c 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -4241,7 +4241,19 @@ "date_within": "תאריך", "search_in": "חיפוש ב", "has_attachment_check": "יש קובץ מצורף", - "search": "חיפוש" + "search": "חיפוש", + "not_starred": "ללא כוכב", + "no_attachment": "ללא קבצים מצורפים", + "any_time": "בכל זמן", + "past_week": "השבוע האחרון", + "past_month": "החודש האחרון", + "past_year": "השנה האחרונה", + "custom_range": "טווח מותאם", + "any_size": "כל גודל", + "larger_than_mb": "גדול מ-{size} MB", + "custom_size": "גודל מותאם", + "done": "סיום", + "name_or_address": "שם או כתובת" }, "welcome": { "title": "ברוך הבא לתיבת הדואר שלך", diff --git a/locales/hu/common.json b/locales/hu/common.json index 3826cf31a..50f0878e6 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -4452,7 +4452,19 @@ "date_within": "Dátum", "search_in": "Keresés itt", "has_attachment_check": "Van melléklete", - "search": "Keresés" + "search": "Keresés", + "not_starred": "Csillag nélkül", + "no_attachment": "Csatolmány nélkül", + "any_time": "Bármikor", + "past_week": "Elmúlt hét", + "past_month": "Elmúlt hónap", + "past_year": "Elmúlt év", + "custom_range": "Egyéni időszak", + "any_size": "Bármilyen méret", + "larger_than_mb": "Nagyobb mint {size} MB", + "custom_size": "Egyéni méret", + "done": "Kész", + "name_or_address": "Név vagy cím" }, "welcome": { "title": "Üdvözlünk a postafiókodban", diff --git a/locales/it/common.json b/locales/it/common.json index 3f2567732..ad9be542c 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -4323,7 +4323,19 @@ "date_within": "Data", "search_in": "Cerca in", "has_attachment_check": "Con allegati", - "search": "Cerca" + "search": "Cerca", + "not_starred": "Non preferiti", + "no_attachment": "Senza allegati", + "any_time": "Qualsiasi data", + "past_week": "Ultima settimana", + "past_month": "Ultimo mese", + "past_year": "Ultimo anno", + "custom_range": "Intervallo personalizzato", + "any_size": "Qualsiasi dimensione", + "larger_than_mb": "Più di {size} MB", + "custom_size": "Dimensione personalizzata", + "done": "Fatto", + "name_or_address": "Nome o indirizzo" }, "welcome": { "title": "Benvenuto nella tua casella di posta", diff --git a/locales/ja/common.json b/locales/ja/common.json index 54cfca8e8..1e2286886 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -4323,7 +4323,19 @@ "date_within": "日付", "search_in": "検索対象", "has_attachment_check": "添付ファイルあり", - "search": "検索" + "search": "検索", + "not_starred": "スターなし", + "no_attachment": "添付ファイルなし", + "any_time": "期間指定なし", + "past_week": "過去 1 週間", + "past_month": "過去 1 か月", + "past_year": "過去 1 年", + "custom_range": "期間を指定", + "any_size": "サイズ指定なし", + "larger_than_mb": "{size} MB より大きい", + "custom_size": "サイズを指定", + "done": "完了", + "name_or_address": "名前またはアドレス" }, "welcome": { "title": "メールボックスへようこそ", diff --git a/locales/ko/common.json b/locales/ko/common.json index c7e2aa1d2..661c9c8cd 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -4323,7 +4323,19 @@ "date_within": "날짜", "search_in": "검색 위치", "has_attachment_check": "첨부파일 있음", - "search": "검색" + "search": "검색", + "not_starred": "별표 없음", + "no_attachment": "첨부파일 없음", + "any_time": "모든 기간", + "past_week": "지난 1주", + "past_month": "지난 1개월", + "past_year": "지난 1년", + "custom_range": "기간 직접 지정", + "any_size": "모든 크기", + "larger_than_mb": "{size}MB 초과", + "custom_size": "크기 직접 지정", + "done": "완료", + "name_or_address": "이름 또는 주소" }, "welcome": { "title": "웹메일에 오신 것을 환영해요!", diff --git a/locales/lv/common.json b/locales/lv/common.json index 87a863717..aecc82a38 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -4323,7 +4323,19 @@ "date_within": "Datums", "search_in": "Meklēt", "has_attachment_check": "Ar pielikumu", - "search": "Meklēt" + "search": "Meklēt", + "not_starred": "Bez zvaigznītes", + "no_attachment": "Bez pielikumiem", + "any_time": "Jebkurā laikā", + "past_week": "Pēdējā nedēļa", + "past_month": "Pēdējais mēnesis", + "past_year": "Pēdējais gads", + "custom_range": "Pielāgots periods", + "any_size": "Jebkurš izmērs", + "larger_than_mb": "Lielāks par {size} MB", + "custom_size": "Pielāgots izmērs", + "done": "Gatavs", + "name_or_address": "Vārds vai adrese" }, "welcome": { "title": "Laipni lūdzam jūsu pastkastē", diff --git a/locales/mn/common.json b/locales/mn/common.json index fbfa189cf..0cfe2e9ae 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -4452,7 +4452,19 @@ "date_within": "Огноо", "search_in": "Хайх газар", "has_attachment_check": "Хавсралттай", - "search": "Хайх" + "search": "Хайх", + "not_starred": "Одгүй", + "no_attachment": "Хавсралтгүй", + "any_time": "Хэзээ ч", + "past_week": "Өнгөрсөн долоо хоног", + "past_month": "Өнгөрсөн сар", + "past_year": "Өнгөрсөн жил", + "custom_range": "Өөрийн хугацаа", + "any_size": "Ямар ч хэмжээ", + "larger_than_mb": "{size} MB-аас их", + "custom_size": "Өөрийн хэмжээ", + "done": "Болсон", + "name_or_address": "Нэр эсвэл хаяг" }, "welcome": { "title": "Таны шуудангийн хайрцагт тавтай морил", diff --git a/locales/nb/common.json b/locales/nb/common.json index b8ac013fe..a0a27a3a1 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -4452,7 +4452,19 @@ "date_within": "Dato", "search_in": "Søk i", "has_attachment_check": "Har vedlegg", - "search": "Søk" + "search": "Søk", + "not_starred": "Uten stjerne", + "no_attachment": "Uten vedlegg", + "any_time": "Når som helst", + "past_week": "Siste uke", + "past_month": "Siste måned", + "past_year": "Siste år", + "custom_range": "Egendefinert periode", + "any_size": "Alle størrelser", + "larger_than_mb": "Større enn {size} MB", + "custom_size": "Egendefinert størrelse", + "done": "Ferdig", + "name_or_address": "Navn eller adresse" }, "welcome": { "title": "Velkommen til postkassen din", diff --git a/locales/nl/common.json b/locales/nl/common.json index 21115da73..ab9f4c522 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -4323,7 +4323,19 @@ "date_within": "Datum", "search_in": "Zoeken in", "has_attachment_check": "Met bijlage", - "search": "Zoeken" + "search": "Zoeken", + "not_starred": "Zonder ster", + "no_attachment": "Zonder bijlagen", + "any_time": "Altijd", + "past_week": "Afgelopen week", + "past_month": "Afgelopen maand", + "past_year": "Afgelopen jaar", + "custom_range": "Aangepaste periode", + "any_size": "Elke grootte", + "larger_than_mb": "Groter dan {size} MB", + "custom_size": "Aangepaste grootte", + "done": "Klaar", + "name_or_address": "Naam of adres" }, "welcome": { "title": "Welkom bij uw mailbox", diff --git a/locales/pl/common.json b/locales/pl/common.json index 2d35d2f3a..ae908f057 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -4323,7 +4323,19 @@ "date_within": "Data", "search_in": "Szukaj w", "has_attachment_check": "Z załącznikiem", - "search": "Szukaj" + "search": "Szukaj", + "not_starred": "Bez gwiazdki", + "no_attachment": "Bez załączników", + "any_time": "Dowolny czas", + "past_week": "Ostatni tydzień", + "past_month": "Ostatni miesiąc", + "past_year": "Ostatni rok", + "custom_range": "Własny zakres", + "any_size": "Dowolny rozmiar", + "larger_than_mb": "Większe niż {size} MB", + "custom_size": "Własny rozmiar", + "done": "Gotowe", + "name_or_address": "Imię lub adres" }, "welcome": { "title": "Witaj w swojej skrzynce pocztowej", diff --git a/locales/pt/common.json b/locales/pt/common.json index 3df4d187d..87ce6069c 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -4323,7 +4323,19 @@ "date_within": "Data", "search_in": "Pesquisar em", "has_attachment_check": "Com anexo", - "search": "Pesquisar" + "search": "Pesquisar", + "not_starred": "Não favoritos", + "no_attachment": "Sem anexos", + "any_time": "Qualquer data", + "past_week": "Última semana", + "past_month": "Último mês", + "past_year": "Último ano", + "custom_range": "Período personalizado", + "any_size": "Qualquer tamanho", + "larger_than_mb": "Maior que {size} MB", + "custom_size": "Tamanho personalizado", + "done": "Concluído", + "name_or_address": "Nome ou endereço" }, "welcome": { "title": "Bem-vindo à sua caixa de entrada", diff --git a/locales/ro/common.json b/locales/ro/common.json index ec612e530..7bc3b07c0 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -4452,7 +4452,19 @@ "date_within": "Dată", "search_in": "Caută în", "has_attachment_check": "Are atașament", - "search": "Caută" + "search": "Caută", + "not_starred": "Fără stea", + "no_attachment": "Fără anexe", + "any_time": "Oricând", + "past_week": "Ultima săptămână", + "past_month": "Ultima lună", + "past_year": "Ultimul an", + "custom_range": "Interval personalizat", + "any_size": "Orice dimensiune", + "larger_than_mb": "Mai mare de {size} MB", + "custom_size": "Dimensiune personalizată", + "done": "Gata", + "name_or_address": "Nume sau adresă" }, "welcome": { "title": "Bine ați venit în căsuța dvs. poștală", diff --git a/locales/ru/common.json b/locales/ru/common.json index 72dd94db7..9f1069091 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -4323,7 +4323,19 @@ "date_within": "Дата", "search_in": "Искать в", "has_attachment_check": "Есть вложения", - "search": "Найти" + "search": "Найти", + "not_starred": "Без звёздочки", + "no_attachment": "Без вложений", + "any_time": "За всё время", + "past_week": "За неделю", + "past_month": "За месяц", + "past_year": "За год", + "custom_range": "Свой период", + "any_size": "Любой размер", + "larger_than_mb": "Больше {size} МБ", + "custom_size": "Свой размер", + "done": "Готово", + "name_or_address": "Имя или адрес" }, "welcome": { "title": "Добро пожаловать в ваш почтовый ящик", diff --git a/locales/sk/common.json b/locales/sk/common.json index 199207bd7..6205ed76d 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -4452,7 +4452,19 @@ "date_within": "Dátum", "search_in": "Hľadať v", "has_attachment_check": "Má prílohu", - "search": "Hľadať" + "search": "Hľadať", + "not_starred": "Bez hviezdičky", + "no_attachment": "Bez príloh", + "any_time": "Kedykoľvek", + "past_week": "Posledný týždeň", + "past_month": "Posledný mesiac", + "past_year": "Posledný rok", + "custom_range": "Vlastné obdobie", + "any_size": "Ľubovoľná veľkosť", + "larger_than_mb": "Väčšie ako {size} MB", + "custom_size": "Vlastná veľkosť", + "done": "Hotovo", + "name_or_address": "Meno alebo adresa" }, "welcome": { "title": "Vitajte vo svojej poštovej schránke", diff --git a/locales/th/common.json b/locales/th/common.json index 0eb060cc5..397083769 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -953,7 +953,19 @@ "date_within": "วันที่", "search_in": "ค้นหาใน", "has_attachment_check": "มีไฟล์แนบ", - "search": "ค้นหา" + "search": "ค้นหา", + "not_starred": "ไม่ติดดาว", + "no_attachment": "ไม่มีไฟล์แนบ", + "any_time": "ทุกเวลา", + "past_week": "สัปดาห์ที่แล้ว", + "past_month": "เดือนที่แล้ว", + "past_year": "ปีที่แล้ว", + "custom_range": "กำหนดช่วงเอง", + "any_size": "ทุกขนาด", + "larger_than_mb": "ใหญ่กว่า {size} MB", + "custom_size": "กำหนดขนาดเอง", + "done": "เสร็จสิ้น", + "name_or_address": "ชื่อหรือที่อยู่" }, "global_search": { "title": "ค้นหาทุกรายการ", diff --git a/locales/tr/common.json b/locales/tr/common.json index 009fcff00..1e0898c33 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -4452,7 +4452,19 @@ "date_within": "Tarih", "search_in": "Şurada ara", "has_attachment_check": "Eki var", - "search": "Ara" + "search": "Ara", + "not_starred": "Yıldızsız", + "no_attachment": "Eki olmayan", + "any_time": "Herhangi bir zaman", + "past_week": "Geçen hafta", + "past_month": "Geçen ay", + "past_year": "Geçen yıl", + "custom_range": "Özel aralık", + "any_size": "Herhangi bir boyut", + "larger_than_mb": "{size} MB'tan büyük", + "custom_size": "Özel boyut", + "done": "Bitti", + "name_or_address": "Ad veya adres" }, "welcome": { "title": "Posta kutunuza hoş geldiniz", diff --git a/locales/uk/common.json b/locales/uk/common.json index 0243594a5..cb868b1aa 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -4323,7 +4323,19 @@ "date_within": "Дата", "search_in": "Шукати в", "has_attachment_check": "Є вкладення", - "search": "Шукати" + "search": "Шукати", + "not_starred": "Без зірочки", + "no_attachment": "Без додатків", + "any_time": "За весь час", + "past_week": "За тиждень", + "past_month": "За місяць", + "past_year": "За рік", + "custom_range": "Власний період", + "any_size": "Будь-який розмір", + "larger_than_mb": "Більше {size} МБ", + "custom_size": "Власний розмір", + "done": "Готово", + "name_or_address": "Ім'я або адреса" }, "welcome": { "title": "Ласкаво просимо до вашої поштової скриньки", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 50b65dca4..aa66308f9 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -4452,7 +4452,19 @@ "date_within": "日期", "search_in": "搜尋範圍", "has_attachment_check": "包含附件", - "search": "搜尋" + "search": "搜尋", + "not_starred": "未加星號", + "no_attachment": "無附件", + "any_time": "任何時間", + "past_week": "過去一週", + "past_month": "過去一個月", + "past_year": "過去一年", + "custom_range": "自訂範圍", + "any_size": "任意大小", + "larger_than_mb": "大於 {size} MB", + "custom_size": "自訂大小", + "done": "完成", + "name_or_address": "姓名或地址" }, "welcome": { "title": "歡迎使用您的信件匣", diff --git a/locales/zh/common.json b/locales/zh/common.json index 6b3287925..889b2fd90 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -4323,7 +4323,19 @@ "date_within": "日期", "search_in": "搜索范围", "has_attachment_check": "包含附件", - "search": "搜索" + "search": "搜索", + "not_starred": "未加星标", + "no_attachment": "无附件", + "any_time": "任何时间", + "past_week": "过去一周", + "past_month": "过去一个月", + "past_year": "过去一年", + "custom_range": "自定义范围", + "any_size": "任意大小", + "larger_than_mb": "大于 {size} MB", + "custom_size": "自定义大小", + "done": "完成", + "name_or_address": "姓名或地址" }, "welcome": { "title": "欢迎使用邮箱",