refactor(search): search filtering and sorting

This commit is contained in:
rustmailer
2026-01-07 16:40:45 +08:00
parent 4ee44daf0d
commit b490923e17
27 changed files with 241 additions and 56 deletions
+16 -14
View File
@@ -24,7 +24,7 @@ use std::{
time::Duration, time::Duration,
}; };
use crate::modules::message::tags::TagCount; use crate::modules::message::{search::SortBy, tags::TagCount};
use crate::{ use crate::{
modules::{ modules::{
account::migration::AccountModel, account::migration::AccountModel,
@@ -621,7 +621,7 @@ impl EnvelopeIndexManager {
page: u64, page: u64,
page_size: u64, page_size: u64,
desc: bool, desc: bool,
sort_by: String sort_by: SortBy,
) -> BichonResult<DataPage<Envelope>> { ) -> BichonResult<DataPage<Envelope>> {
assert!(page > 0, "Page number must be greater than 0"); assert!(page > 0, "Page number must be greater than 0");
assert!(page_size > 0, "Page size must be greater than 0"); assert!(page_size > 0, "Page size must be greater than 0");
@@ -656,17 +656,8 @@ impl EnvelopeIndexManager {
let order = if desc { Order::Desc } else { Order::Asc }; let order = if desc { Order::Desc } else { Order::Asc };
let mailbox_docs: Vec<DocAddress>; let mailbox_docs: Vec<DocAddress>;
if sort_by == "size" { match sort_by {
let size_docs: Vec<(u64, DocAddress)> = searcher SortBy::DATE => {
.search(
&query,
&TopDocs::with_limit(page_size as usize)
.and_offset(offset as usize)
.order_by_fast_field(F_SIZE, order),
)
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
mailbox_docs = size_docs.into_iter().map(|(_, addr)| addr).collect();
} else {
let date_docs: Vec<(i64, DocAddress)> = searcher let date_docs: Vec<(i64, DocAddress)> = searcher
.search( .search(
&query, &query,
@@ -677,6 +668,18 @@ impl EnvelopeIndexManager {
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
mailbox_docs = date_docs.into_iter().map(|(_, addr)| addr).collect(); mailbox_docs = date_docs.into_iter().map(|(_, addr)| addr).collect();
} }
SortBy::SIZE => {
let size_docs: Vec<(u64, DocAddress)> = searcher
.search(
&query,
&TopDocs::with_limit(page_size as usize)
.and_offset(offset as usize)
.order_by_fast_field(F_SIZE, order),
)
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
mailbox_docs = size_docs.into_iter().map(|(_, addr)| addr).collect();
}
}
let mut result = Vec::new(); let mut result = Vec::new();
@@ -866,7 +869,6 @@ impl EnvelopeIndexManager {
} }
} }
pub async fn top_10_largest_emails( pub async fn top_10_largest_emails(
&self, &self,
accounts: &Option<HashSet<u64>>, accounts: &Option<HashSet<u64>>,
+12 -4
View File
@@ -18,7 +18,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use poem_openapi::Object; use poem_openapi::{Enum, Object};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
@@ -49,12 +49,20 @@ pub struct SearchFilter {
pub tags: Option<Vec<String>>, pub tags: Option<Vec<String>>,
} }
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Enum)]
pub enum SortBy {
#[default]
DATE,
SIZE,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] #[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
pub struct SearchRequest { pub struct SearchRequest {
filter: SearchFilter, filter: SearchFilter,
page: u64, page: u64,
page_size: u64, page_size: u64,
sort_by: String sort_by: Option<SortBy>,
desc: Option<bool>,
} }
impl SearchRequest { impl SearchRequest {
pub fn validate(&self) -> BichonResult<()> { pub fn validate(&self) -> BichonResult<()> {
@@ -85,8 +93,8 @@ pub async fn search_messages_impl(
request.filter, request.filter,
request.page, request.page,
request.page_size, request.page_size,
true, request.desc.unwrap_or(true),
request.sort_by request.sort_by.unwrap_or(SortBy::DATE),
) )
.await .await
} }
+2
View File
@@ -39,6 +39,8 @@
"@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.1.4", "@radix-ui/react-tooltip": "^1.1.4",
"@radix-ui/react-visually-hidden": "^1.1.0", "@radix-ui/react-visually-hidden": "^1.1.0",
"@react-spring/web": "^10.0.3", "@react-spring/web": "^10.0.3",
+6
View File
@@ -86,6 +86,12 @@ importers:
'@radix-ui/react-toast': '@radix-ui/react-toast':
specifier: ^1.2.2 specifier: ^1.2.2
version: 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-toggle':
specifier: ^1.1.10
version: 1.1.10(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-toggle-group':
specifier: ^1.1.11
version: 1.1.11(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.1.4 specifier: ^1.1.4
version: 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+61
View File
@@ -0,0 +1,61 @@
"use client"
import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { toggleVariants } from "@/components/ui/toggle"
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
})
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, children, ...props }, ref) => (
<ToggleGroupPrimitive.Root
ref={ref}
className={cn("flex items-center justify-center gap-1", className)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
))
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext)
return (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
className
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
)
})
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
export { ToggleGroup, ToggleGroupItem }
+43
View File
@@ -0,0 +1,43 @@
import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-9 px-2 min-w-9",
sm: "h-8 px-1.5 min-w-8",
lg: "h-10 px-2.5 min-w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
))
Toggle.displayName = TogglePrimitive.Root.displayName
export { Toggle, toggleVariants }
+50 -24
View File
@@ -26,7 +26,7 @@ import { EnvelopeListPagination } from '@/components/pagination';
import { MailList } from './mail-list'; import { MailList } from './mail-list';
import React from 'react'; import React from 'react';
import { EmailEnvelope } from '@/api'; import { EmailEnvelope } from '@/api';
import { Filter, SearchIcon } from 'lucide-react'; import { ArrowDownWideNarrow, ArrowUpWideNarrow, Filter, SearchIcon } from 'lucide-react';
import { MailDisplayDrawer } from './mail-display-dialog'; import { MailDisplayDrawer } from './mail-display-dialog';
import { EnvelopeDeleteDialog } from './delete-dialog'; import { EnvelopeDeleteDialog } from './delete-dialog';
import SearchProvider, { SearchDialogType } from './context'; import SearchProvider, { SearchDialogType } from './context';
@@ -39,14 +39,8 @@ import { EditTagsDialog } from './add-tag-dialog';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Logo from '@/assets/logo.svg' import Logo from '@/assets/logo.svg'
import { RestoreMessageDialog } from './restore-message-dialog'; import { RestoreMessageDialog } from './restore-message-dialog';
import { import { Separator } from '@/components/ui/separator';
Select, import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Label } from '@/components/ui/label';
export default function Search() { export default function Search() {
const { t } = useTranslation() const { t } = useTranslation()
@@ -65,9 +59,11 @@ export default function Search() {
page, page,
pageSize, pageSize,
sortBy, sortBy,
sortOrder,
setPage, setPage,
setPageSize, setPageSize,
setSortBy, setSortBy,
setSortOrder,
onSubmit, onSubmit,
reset, reset,
filter filter
@@ -131,26 +127,56 @@ export default function Search() {
</div> </div>
</aside> </aside>
<div className="flex-1 min-w-0 space-y-4"> <div className="flex-1 min-w-0 space-y-4">
<div className="flex flex-row gap-4 items-end"> <div className="flex flex-row items-center justify-between w-full border-b pb-4">
<Button size="sm" onClick={() => setOpen("search-form")}> <Button
size="sm"
variant="default"
onClick={() => setOpen("search-form")}
className="px-4 shadow-sm"
>
<SearchIcon className="mr-2 h-4 w-4" /> <SearchIcon className="mr-2 h-4 w-4" />
{t('common.search')} {t('common.search')}
</Button> </Button>
<Label className=""> <div className="flex items-center gap-2 bg-muted/50 p-1 rounded-lg border">
{t('search.sortBy')} <span className="text-xs font-medium text-muted-foreground px-2">
<Select {t('search.sort')}
</span>
<Separator orientation="vertical" className="h-4" />
<ToggleGroup
type="single"
value={sortBy} value={sortBy}
onValueChange={(value: "date" | "size") => setSortBy(value)} onValueChange={(value) => value && setSortBy(value as "DATE" | "SIZE")}
className="gap-1"
> >
<SelectTrigger className='h-8 mt-2'> <ToggleGroupItem
<SelectValue placeholder="Placeholder" /> value="DATE"
</SelectTrigger> size="sm"
<SelectContent side='top'> className="h-7 px-3 text-xs data-[state=on]:bg-background data-[state=on]:shadow-sm"
<SelectItem value="date">{t('search.date')}</SelectItem> >
<SelectItem value="size">{t('search.size')}</SelectItem> {t('search.date')}
</SelectContent> </ToggleGroupItem>
</Select> <ToggleGroupItem
</Label> value="SIZE"
size="sm"
className="h-7 px-3 text-xs data-[state=on]:bg-background data-[state=on]:shadow-sm"
>
{t('search.size')}
</ToggleGroupItem>
</ToggleGroup>
<Separator orientation="vertical" className="h-4" />
<Button
variant="ghost"
size="icon"
className="h-7 w-7 hover:bg-background"
onClick={() => setSortOrder(sortOrder === "asc" ? "desc" : "asc")}
>
{sortOrder === "asc" ? (
<ArrowUpWideNarrow className="h-4 w-4 text-primary" />
) : (
<ArrowDownWideNarrow className="h-4 w-4 text-primary" />
)}
</Button>
</div>
</div> </div>
{isLoading && ( {isLoading && (
<Card> <Card>
+1
View File
@@ -457,6 +457,7 @@ export function SearchFormDialog({ onSubmit, isLoading, reset, open, onOpenChang
<SelectItem value="small">{t('search.small')}</SelectItem> <SelectItem value="small">{t('search.small')}</SelectItem>
<SelectItem value="medium">{t('search.medium')}</SelectItem> <SelectItem value="medium">{t('search.medium')}</SelectItem>
<SelectItem value="large">{t('search.large')}</SelectItem> <SelectItem value="large">{t('search.large')}</SelectItem>
<SelectItem value="huge">{t('search.huge')}</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<FormMessage /> <FormMessage />
+7 -3
View File
@@ -29,7 +29,8 @@ export function useSearchMessages() {
const [filter, setFilter] = useState<Record<string, any>>({}); const [filter, setFilter] = useState<Record<string, any>>({});
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(30); const [pageSize, setPageSize] = useState(30);
const [sortBy, setSortBy] = useState<"date" | "size">("date"); const [sortBy, setSortBy] = useState<"DATE" | "SIZE">("DATE");
const [sortOrder, setSortOrder] = useState<"desc" | "asc">("desc");
const onSubmit = (cleaned: Record<string, any>) => { const onSubmit = (cleaned: Record<string, any>) => {
if ('has_attachment' in cleaned && cleaned.has_attachment === false) { if ('has_attachment' in cleaned && cleaned.has_attachment === false) {
@@ -60,13 +61,14 @@ export function useSearchMessages() {
error, error,
isFetching, isFetching,
} = useQuery<PaginatedResponse<EmailEnvelope>>({ } = useQuery<PaginatedResponse<EmailEnvelope>>({
queryKey: ['search-messages', filter, page, pageSize, sortBy], queryKey: ['search-messages', filter, page, pageSize, sortBy, sortOrder],
queryFn: () => queryFn: () =>
search_messages({ search_messages({
filter: filter, filter: filter,
page, page,
page_size: pageSize, page_size: pageSize,
sort_by: sortBy sort_by: sortBy,
desc: sortOrder === "desc"
}), }),
staleTime: 1000, staleTime: 1000,
retry: false, retry: false,
@@ -80,6 +82,8 @@ export function useSearchMessages() {
setPageSize, setPageSize,
sortBy, sortBy,
setSortBy, setSortBy,
sortOrder,
setSortOrder,
isLoading, isLoading,
isError, isError,
error: error as Error | null, error: error as Error | null,
+2
View File
@@ -414,6 +414,8 @@
"small": "صغير (<2 ميغابايت)", "small": "صغير (<2 ميغابايت)",
"medium": "متوسط (2 - 10 ميغابايت)", "medium": "متوسط (2 - 10 ميغابايت)",
"large": "كبير (10 - 20 ميغابايت)", "large": "كبير (10 - 20 ميغابايت)",
"huge": "ضخم (≥20 MB)",
"sort": "فرز",
"sizeDescription": "يشير الحجم إلى الحجم الإجمالي للبريد الإلكتروني، بما في ذلك المرفقات.", "sizeDescription": "يشير الحجم إلى الحجم الإجمالي للبريد الإلكتروني، بما في ذلك المرفقات.",
"title": "بحث", "title": "بحث",
"searching": "جارٍ البحث، يرجى الانتظار...", "searching": "جارٍ البحث، يرجى الانتظار...",
+2
View File
@@ -414,6 +414,8 @@
"small": "Lille (<2 MB)", "small": "Lille (<2 MB)",
"medium": "Mellem (2 - 10 MB)", "medium": "Mellem (2 - 10 MB)",
"large": "Stor (10 - 20 MB)", "large": "Stor (10 - 20 MB)",
"huge": "Kæmpestor (≥20 MB)",
"sort": "Sortér",
"sizeDescription": "Størrelsen henviser til den samlede e-mailstørrelse, inklusive vedhæftede filer.", "sizeDescription": "Størrelsen henviser til den samlede e-mailstørrelse, inklusive vedhæftede filer.",
"title": "Søg", "title": "Søg",
"searching": "Søger, vent venligst...", "searching": "Søger, vent venligst...",
+2
View File
@@ -414,6 +414,8 @@
"small": "Klein (<2 MB)", "small": "Klein (<2 MB)",
"medium": "Mittel (2 - 10 MB)", "medium": "Mittel (2 - 10 MB)",
"large": "Groß (10 - 20 MB)", "large": "Groß (10 - 20 MB)",
"huge": "Riesig (≥20 MB)",
"sort": "Sortieren",
"sizeDescription": "Die Größe bezieht sich auf die gesamte E-Mail inklusive Anhängen.", "sizeDescription": "Die Größe bezieht sich auf die gesamte E-Mail inklusive Anhängen.",
"title": "Suchen", "title": "Suchen",
"searching": "Wird gesucht, bitte warten Sie...", "searching": "Wird gesucht, bitte warten Sie...",
+1 -1
View File
@@ -415,7 +415,7 @@
"medium": "Medium (2 - 10 MB)", "medium": "Medium (2 - 10 MB)",
"large": "Large (10 - 20 MB)", "large": "Large (10 - 20 MB)",
"huge": "Huge (≥20 MB)", "huge": "Huge (≥20 MB)",
"sortBy": "Sort by", "sort": "Sort",
"sizeDescription": "The size refers to the total email size, including attachments.", "sizeDescription": "The size refers to the total email size, including attachments.",
"title": "Search", "title": "Search",
"searching": "Searching, please wait…", "searching": "Searching, please wait…",
+2
View File
@@ -414,6 +414,8 @@
"small": "Pequeño (<2 MB)", "small": "Pequeño (<2 MB)",
"medium": "Mediano (2 - 10 MB)", "medium": "Mediano (2 - 10 MB)",
"large": "Grande (10 - 20 MB)", "large": "Grande (10 - 20 MB)",
"huge": "Muy grande (≥20 MB)",
"sort": "Ordenar",
"sizeDescription": "El tamaño se refiere al tamaño total del correo electrónico, incluidos los archivos adjuntos.", "sizeDescription": "El tamaño se refiere al tamaño total del correo electrónico, incluidos los archivos adjuntos.",
"title": "Buscar", "title": "Buscar",
"searching": "Buscando, por favor espera...", "searching": "Buscando, por favor espera...",
+1 -1
View File
@@ -415,7 +415,7 @@
"medium": "Keskikokoinen (2 - 10 MB)", "medium": "Keskikokoinen (2 - 10 MB)",
"large": "Suuri (10 - 20 MB)", "large": "Suuri (10 - 20 MB)",
"huge": "Valtava (≥20 MB)", "huge": "Valtava (≥20 MB)",
"sortBy": "Lajittele", "sort": "Lajittele",
"sizeDescription": "Koko tarkoittaa sähköpostin kokonaiskokoa, liitteet mukaan lukien.", "sizeDescription": "Koko tarkoittaa sähköpostin kokonaiskokoa, liitteet mukaan lukien.",
"title": "Hae", "title": "Hae",
"searching": "Haetaan, odota hetki...", "searching": "Haetaan, odota hetki...",
+2
View File
@@ -414,6 +414,8 @@
"small": "Petite (<2 MB)", "small": "Petite (<2 MB)",
"medium": "Moyenne (2 - 10 MB)", "medium": "Moyenne (2 - 10 MB)",
"large": "Grande (10 - 20 MB)", "large": "Grande (10 - 20 MB)",
"huge": "Énorme (≥20 Mo)",
"sort": "Trier",
"sizeDescription": "La taille correspond à la taille totale de le-mail, pièces jointes incluses.", "sizeDescription": "La taille correspond à la taille totale de le-mail, pièces jointes incluses.",
"title": "Recherche", "title": "Recherche",
"searching": "Recherche en cours, veuillez patienter...", "searching": "Recherche en cours, veuillez patienter...",
+2
View File
@@ -414,6 +414,8 @@
"small": "Piccolo (<2 MB)", "small": "Piccolo (<2 MB)",
"medium": "Medio (2 - 10 MB)", "medium": "Medio (2 - 10 MB)",
"large": "Grande (10 - 20 MB)", "large": "Grande (10 - 20 MB)",
"huge": "Enorme (≥20 MB)",
"sort": "Ordina",
"sizeDescription": "La dimensione indica la dimensione totale dellemail, inclusi gli allegati.", "sizeDescription": "La dimensione indica la dimensione totale dellemail, inclusi gli allegati.",
"title": "Cerca", "title": "Cerca",
"searching": "Ricerca in corso, attendere prego...", "searching": "Ricerca in corso, attendere prego...",
+2
View File
@@ -414,6 +414,8 @@
"small": "小(2 MB 未満)", "small": "小(2 MB 未満)",
"medium": "中(2 - 10 MB", "medium": "中(2 - 10 MB",
"large": "大(10 - 20 MB", "large": "大(10 - 20 MB",
"huge": "巨大 (≥20 MB)",
"sort": "並べ替え",
"sizeDescription": "サイズは添付ファイルを含むメール全体の容量を指します。", "sizeDescription": "サイズは添付ファイルを含むメール全体の容量を指します。",
"title": "検索", "title": "検索",
"searching": "検索中です。お待ちください…", "searching": "検索中です。お待ちください…",
+2
View File
@@ -414,6 +414,8 @@
"small": "작음 (<2 MB)", "small": "작음 (<2 MB)",
"medium": "중간 (2 - 10 MB)", "medium": "중간 (2 - 10 MB)",
"large": "큼 (10 - 20 MB)", "large": "큼 (10 - 20 MB)",
"huge": "대용량 (≥20 MB)",
"sort": "정렬",
"sizeDescription": "크기는 첨부 파일을 포함한 이메일 전체 크기를 의미합니다.", "sizeDescription": "크기는 첨부 파일을 포함한 이메일 전체 크기를 의미합니다.",
"title": "검색", "title": "검색",
"searching": "검색 중입니다. 잠시 기다려 주십시오...", "searching": "검색 중입니다. 잠시 기다려 주십시오...",
+2
View File
@@ -414,6 +414,8 @@
"small": "Klein (<2 MB)", "small": "Klein (<2 MB)",
"medium": "Middelgroot (2 - 10 MB)", "medium": "Middelgroot (2 - 10 MB)",
"large": "Groot (10 - 20 MB)", "large": "Groot (10 - 20 MB)",
"huge": "Zeer groot (≥20 MB)",
"sort": "Sorteren",
"sizeDescription": "De grootte verwijst naar de totale e-mailgrootte, inclusief bijlagen.", "sizeDescription": "De grootte verwijst naar de totale e-mailgrootte, inclusief bijlagen.",
"title": "Zoeken", "title": "Zoeken",
"searching": "Bezig met zoeken, even geduld alstublieft…", "searching": "Bezig met zoeken, even geduld alstublieft…",
+2
View File
@@ -414,6 +414,8 @@
"small": "Liten (<2 MB)", "small": "Liten (<2 MB)",
"medium": "Middels (2 - 10 MB)", "medium": "Middels (2 - 10 MB)",
"large": "Stor (10 - 20 MB)", "large": "Stor (10 - 20 MB)",
"huge": "Kjempestor (≥20 MB)",
"sort": "Sorter",
"sizeDescription": "Størrelsen viser til e-postens totale størrelse, inkludert vedlegg.", "sizeDescription": "Størrelsen viser til e-postens totale størrelse, inkludert vedlegg.",
"title": "Søk", "title": "Søk",
"searching": "Søker, vennligst vent…", "searching": "Søker, vennligst vent…",
+2
View File
@@ -414,6 +414,8 @@
"small": "Mały (<2 MB)", "small": "Mały (<2 MB)",
"medium": "Średni (2 - 10 MB)", "medium": "Średni (2 - 10 MB)",
"large": "Duży (10 - 20 MB)", "large": "Duży (10 - 20 MB)",
"huge": "Bardzo duży (≥20 MB)",
"sort": "Sortuj",
"sizeDescription": "Rozmiar odnosi się do całkowitego rozmiaru wiadomości e-mail, łącznie z załącznikami.", "sizeDescription": "Rozmiar odnosi się do całkowitego rozmiaru wiadomości e-mail, łącznie z załącznikami.",
"title": "Szukaj", "title": "Szukaj",
"searching": "Wyszukiwanie, proszę czekać...", "searching": "Wyszukiwanie, proszę czekać...",
+2
View File
@@ -414,6 +414,8 @@
"small": "Pequeno (<2 MB)", "small": "Pequeno (<2 MB)",
"medium": "Médio (2 - 10 MB)", "medium": "Médio (2 - 10 MB)",
"large": "Grande (10 - 20 MB)", "large": "Grande (10 - 20 MB)",
"huge": "Muito grande (≥20 MB)",
"sort": "Ordenar",
"sizeDescription": "O tamanho refere-se ao tamanho total do e-mail, incluindo anexos.", "sizeDescription": "O tamanho refere-se ao tamanho total do e-mail, incluindo anexos.",
"title": "Pesquisa", "title": "Pesquisa",
"searching": "Pesquisando, por favor, aguarde...", "searching": "Pesquisando, por favor, aguarde...",
+2
View File
@@ -414,6 +414,8 @@
"small": "Маленький (<2 МБ)", "small": "Маленький (<2 МБ)",
"medium": "Средний (2 - 10 МБ)", "medium": "Средний (2 - 10 МБ)",
"large": "Большой (10 - 20 МБ)", "large": "Большой (10 - 20 МБ)",
"huge": "Огромный (≥20 MB)",
"sort": "Сортировка",
"sizeDescription": "Размер означает общий размер электронного письма, включая вложения.", "sizeDescription": "Размер означает общий размер электронного письма, включая вложения.",
"title": "Поиск", "title": "Поиск",
"searching": "Поиск, пожалуйста, подождите...", "searching": "Поиск, пожалуйста, подождите...",
+2
View File
@@ -414,6 +414,8 @@
"small": "Liten (<2 MB)", "small": "Liten (<2 MB)",
"medium": "Medelstor (2 - 10 MB)", "medium": "Medelstor (2 - 10 MB)",
"large": "Stor (10 - 20 MB)", "large": "Stor (10 - 20 MB)",
"huge": "Mycket stor (≥20 MB)",
"sort": "Sortera",
"sizeDescription": "Storleken avser e-postens totala storlek, inklusive bilagor.", "sizeDescription": "Storleken avser e-postens totala storlek, inklusive bilagor.",
"title": "Sök", "title": "Sök",
"searching": "Söker, vänligen vänta…", "searching": "Söker, vänligen vänta…",
+2
View File
@@ -414,6 +414,8 @@
"small": "小(<2 MB", "small": "小(<2 MB",
"medium": "中(2 - 10 MB", "medium": "中(2 - 10 MB",
"large": "大(10 - 20 MB", "large": "大(10 - 20 MB",
"huge": "極大 (≥20 MB)",
"sort": "排序",
"sizeDescription": "大小指的是整封郵件的大小,包含附件。", "sizeDescription": "大小指的是整封郵件的大小,包含附件。",
"title": "搜尋", "title": "搜尋",
"searching": "正在搜尋,請稍候...", "searching": "正在搜尋,請稍候...",
+2
View File
@@ -414,6 +414,8 @@
"small": "小(<2 MB", "small": "小(<2 MB",
"medium": "中(2 - 10 MB", "medium": "中(2 - 10 MB",
"large": "大(10 - 20 MB", "large": "大(10 - 20 MB",
"huge": "极大 (≥20 MB)",
"sort": "排序",
"sizeDescription": "大小指的是邮件整体大小,包含附件。", "sizeDescription": "大小指的是邮件整体大小,包含附件。",
"title": "搜索", "title": "搜索",
"searching": "搜索中,请稍候…", "searching": "搜索中,请稍候…",