mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-05 08:16:22 +00:00
fix: count every account's unread mail in the rail's Mail badge and the tab title, as the favicon does
This commit is contained in:
@@ -1,13 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useEmailStore } from "@/stores/email-store";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
import { useFaviconBadge } from "@/hooks/use-favicon-badge";
|
||||
import { useAppBadge } from "@/hooks/use-app-badge";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { useAccountStore } from "@/stores/account-store";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { totalInboxUnread } from "@/lib/account-inbox-unread";
|
||||
import { useAllInboxesUnread } from "@/hooks/use-all-inboxes-unread";
|
||||
|
||||
/**
|
||||
* Badges the browser-tab favicon, and — where supported — the installed PWA's
|
||||
@@ -31,33 +27,14 @@ import { totalInboxUnread } from "@/lib/account-inbox-unread";
|
||||
* Renders nothing.
|
||||
*/
|
||||
export function FaviconBadge() {
|
||||
// The store's canonical inbox selector. `role === 'inbox'` alone is not
|
||||
// enough: shared and group inboxes ship in the same `mailboxes` array, so on
|
||||
// a delegated setup the first match can be somebody else's inbox.
|
||||
const inboxUnread = useEmailStore(
|
||||
(s) => s.mailboxes.find((m) => m.role === "inbox" && !m.isShared)?.unreadEmails ?? 0,
|
||||
);
|
||||
const enabled = useSettingsStore((s) => s.faviconUnreadBadge);
|
||||
const allAccounts = useSettingsStore((s) => s.unreadBadgeAllAccounts);
|
||||
// With several accounts the badge counts the inbox of each (on by default):
|
||||
// new mail in an account that is not on screen still shows on the icon and
|
||||
// in the desktop tray. The other accounts' folder lists are what the
|
||||
// account switcher reads (useProMultiAccountMailboxes); one not loaded yet
|
||||
// adds nothing.
|
||||
const othersUnread = useOtherAccountsUnread(allAccounts);
|
||||
const count = inboxUnread + othersUnread;
|
||||
// in the desktop tray.
|
||||
const count = useAllInboxesUnread();
|
||||
|
||||
useFaviconBadge(count, enabled);
|
||||
useAppBadge(count, enabled);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function useOtherAccountsUnread(enabled: boolean): number {
|
||||
const activeAccountId = useAuthStore((s) => s.activeAccountId);
|
||||
const otherIds = useAccountStore(
|
||||
useShallow((s) => (enabled ? s.accounts.filter((a) => a.isConnected && a.id !== activeAccountId).map((a) => a.id) : [])),
|
||||
);
|
||||
const accountMailboxes = useEmailStore((s) => s.accountMailboxes);
|
||||
return totalInboxUnread(otherIds, accountMailboxes);
|
||||
}
|
||||
|
||||
@@ -63,3 +63,29 @@ describe.each([
|
||||
expect(link.textContent).not.toContain('2');
|
||||
});
|
||||
});
|
||||
|
||||
// Usability test 2026-11-04, mail-6: with two accounts the rail said 10 while
|
||||
// 30 were unread in all; the favicon already counted every account.
|
||||
describe('Mail entry with several accounts', () => {
|
||||
const twoAccounts = async () => {
|
||||
const { useAccountStore } = await import('@/stores/account-store');
|
||||
const { useAuthStore } = await import('@/stores/auth-store');
|
||||
useAccountStore.setState({ accounts: [{ id: 'acc-a', isConnected: true }, { id: 'acc-b', isConnected: true }] } as never);
|
||||
useAuthStore.setState({ activeAccountId: 'acc-a' } as never);
|
||||
useEmailStore.setState({ accountMailboxes: { 'acc-b': [{ id: 'b-in', role: 'inbox', unreadEmails: 5 }] } } as never);
|
||||
};
|
||||
|
||||
it('counts the other accounts\' inboxes too', async () => {
|
||||
await twoAccounts();
|
||||
useSettingsStore.setState({ unreadBadgeAllAccounts: true });
|
||||
render(<NavigationRail orientation="vertical" collapsed />);
|
||||
expect(screen.getAllByRole('link', { name: 'mail, 7 unread' }).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('counts the open account only when Notifications says so', async () => {
|
||||
await twoAccounts();
|
||||
useSettingsStore.setState({ unreadBadgeAllAccounts: false });
|
||||
render(<NavigationRail orientation="vertical" collapsed />);
|
||||
expect(screen.getAllByRole('link', { name: 'mail, 2 unread' }).length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@ import { useShallow } from "zustand/react/shallow";
|
||||
import { usePathname, Link, useRouter } from "@/i18n/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCalendarStore } from "@/stores/calendar-store";
|
||||
import { useEmailStore } from "@/stores/email-store";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
import { usePolicyStore } from "@/stores/policy-store";
|
||||
import { useResolvedSidebarApps } from "@/hooks/use-resolved-sidebar-apps";
|
||||
@@ -34,6 +33,7 @@ import { toUnicodeEmail } from "@/lib/idn";
|
||||
import { usePluginPages, pluginPageHref, isPluginPageActive } from "@/hooks/use-plugin-pages";
|
||||
import { NotificationBell } from "@/components/notifications/notification-center";
|
||||
import { useBottomControls } from "@/hooks/use-bottom-controls";
|
||||
import { useAllInboxesUnread } from "@/hooks/use-all-inboxes-unread";
|
||||
import { writeRailHints, type RailHint } from "@/lib/boot-skeleton/hints";
|
||||
|
||||
interface NavItem {
|
||||
@@ -260,7 +260,6 @@ export function NavigationRail({
|
||||
const { appLogoLightUrl, appLogoDarkUrl } = useConfig();
|
||||
const resolvedTheme = useThemeStore((s) => s.resolvedTheme);
|
||||
const { supportsCalendar } = useCalendarStore(useShallow((state) => ({ supportsCalendar: state.supportsCalendar })));
|
||||
const { mailboxes } = useEmailStore(useShallow((state) => ({ mailboxes: state.mailboxes })));
|
||||
const client = useAuthStore((s) => s.client);
|
||||
const supportsFiles = client?.supportsFiles() ?? false;
|
||||
const supportsContacts = client?.supportsContacts() ?? false;
|
||||
@@ -277,7 +276,9 @@ export function NavigationRail({
|
||||
const visibleSidebarApps = useResolvedSidebarApps();
|
||||
// Pages plugins added (api.ui.setPages); listed after the built-in apps.
|
||||
const pluginPages = usePluginPages();
|
||||
const inboxUnread = mailboxes.find(m => m.role === "inbox")?.unreadEmails || 0;
|
||||
// Every signed-in account's inbox, as the favicon and the tab title count
|
||||
// it (mail-6), unless Notifications says to count the open one only.
|
||||
const inboxUnread = useAllInboxesUnread();
|
||||
const isStalwartAdmin = useStalwartAdmin();
|
||||
const hasUpdate = useUpdateStore(selectHasUpdate);
|
||||
const updateSeverity = useUpdateStore((s) => s.status?.severity);
|
||||
|
||||
@@ -152,7 +152,8 @@ import { ResizeHandle } from "@/components/layout/resize-handle";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ToolbarButton, ToolbarSearchAction } from "@/components/ui/toolbar";
|
||||
import { useDocumentTitle } from "@/hooks/use-document-title";
|
||||
import { mailTitleContext } from "@/lib/tab-title";
|
||||
import { mailTitleContext, mailboxTitleLine } from "@/lib/tab-title";
|
||||
import { useOtherAccountsUnread } from "@/hooks/use-all-inboxes-unread";
|
||||
import { usePluginStore } from "@/stores/plugin-store";
|
||||
import { AppTopBannerSlot } from "@/components/plugins/app-top-banner-slot";
|
||||
import { canDownload } from "@/lib/device-policy";
|
||||
@@ -1472,6 +1473,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
// The subject is kept out until the policy has loaded: a gate that keeps
|
||||
// subjects out of browser history must not let one through while unknown.
|
||||
const subjectInTabTitle = usePolicyStore((s) => s.loaded && s.isFeatureEnabled('tabTitleSubjectEnabled'));
|
||||
const otherAccountsUnread = useOtherAccountsUnread();
|
||||
const titleContext = useMemo(() => {
|
||||
// Composing email
|
||||
const composer = showComposer
|
||||
@@ -1491,12 +1493,17 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
const mailbox = selectedMailbox ? mailboxes.find(mb => mb.id === selectedMailbox) : undefined;
|
||||
if (mailbox) {
|
||||
const mailboxName = localizeMailboxName(mailbox.role, mailbox.name, (k) => t(`sidebar.mailboxes.${k}`));
|
||||
const unreadCount = mailbox.unreadEmails || 0;
|
||||
mailboxLine = unreadCount > 0 ? `${mailboxName} (${unreadCount})` : mailboxName;
|
||||
// The open account's own Inbox also counts the other accounts' (mail-6).
|
||||
mailboxLine = mailboxTitleLine({
|
||||
name: mailboxName,
|
||||
unread: mailbox.unreadEmails || 0,
|
||||
otherUnread: otherAccountsUnread,
|
||||
ownInbox: mailbox.role === 'inbox' && !mailbox.isShared && !isUnifiedView,
|
||||
}, t);
|
||||
}
|
||||
|
||||
return mailTitleContext({ composer, subject, mailbox: mailboxLine }, { showSubject: subjectInTabTitle });
|
||||
}, [showComposer, composerMode, selectedEmail, selectedMailbox, mailboxes, t, subjectInTabTitle]);
|
||||
}, [showComposer, composerMode, selectedEmail, selectedMailbox, mailboxes, t, subjectInTabTitle, otherAccountsUnread, isUnifiedView]);
|
||||
useDocumentTitle(titleContext);
|
||||
|
||||
// When this page is rendered inside the Pro shell as the Mail tab body,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { useEmailStore } from "@/stores/email-store";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
import { useAccountStore } from "@/stores/account-store";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { totalInboxUnread } from "@/lib/account-inbox-unread";
|
||||
|
||||
/**
|
||||
* Unread mail in the inboxes of the signed-in accounts that aren't on
|
||||
* screen, while "count every account" is on in Notifications (the default).
|
||||
* The other accounts' folder lists are what the account switcher reads
|
||||
* (useProMultiAccountMailboxes); one not loaded yet adds nothing.
|
||||
*/
|
||||
export function useOtherAccountsUnread(): number {
|
||||
const enabled = useSettingsStore((s) => s.unreadBadgeAllAccounts);
|
||||
const activeAccountId = useAuthStore((s) => s.activeAccountId);
|
||||
const otherIds = useAccountStore(
|
||||
useShallow((s) => (enabled ? s.accounts.filter((a) => a.isConnected && a.id !== activeAccountId).map((a) => a.id) : [])),
|
||||
);
|
||||
const accountMailboxes = useEmailStore((s) => s.accountMailboxes);
|
||||
return totalInboxUnread(otherIds, accountMailboxes);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number the app shows for new mail: the open account's own inbox plus,
|
||||
* by the same setting, every other account's. The favicon, the app badge,
|
||||
* the rail's Mail entry and the tab title all show this one number
|
||||
* (usability test 2026-11-04, mail-6).
|
||||
*/
|
||||
export function useAllInboxesUnread(): number {
|
||||
// `role === 'inbox'` alone is not enough: shared and group inboxes ship in
|
||||
// the same `mailboxes` array, so on a delegated setup the first match can
|
||||
// be somebody else's inbox.
|
||||
const own = useEmailStore((s) => s.mailboxes.find((m) => m.role === "inbox" && !m.isShared)?.unreadEmails ?? 0);
|
||||
return own + useOtherAccountsUnread();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { formatTabTitle, mailTitleContext } from '../tab-title';
|
||||
import { formatTabTitle, mailTitleContext, mailboxTitleLine } from '../tab-title';
|
||||
|
||||
describe('formatTabTitle', () => {
|
||||
it('puts the context first, the account next and the app name last', () => {
|
||||
@@ -50,3 +50,25 @@ describe('mailTitleContext', () => {
|
||||
expect(mailTitleContext(view, { showSubject: false })).toBe('Reply');
|
||||
});
|
||||
});
|
||||
|
||||
describe('mailboxTitleLine', () => {
|
||||
const t = (key: string, values?: Record<string, string | number>) => `${key} ${JSON.stringify(values)}`;
|
||||
|
||||
it('names the folder with its unread count', () => {
|
||||
expect(mailboxTitleLine({ name: 'Family', unread: 2 }, t)).toBe('Family (2)');
|
||||
expect(mailboxTitleLine({ name: 'Family', unread: 0 }, t)).toBe('Family');
|
||||
});
|
||||
|
||||
// mail-6: the title said 10 while 30 were unread in all accounts.
|
||||
it('adds the other accounts on the Inbox', () => {
|
||||
expect(mailboxTitleLine({ name: 'Inbox', unread: 10, otherUnread: 20, ownInbox: true }, t))
|
||||
.toBe('mail_p2_folders.title_all_accounts {"name":"Inbox","count":10,"total":30}');
|
||||
expect(mailboxTitleLine({ name: 'Inbox', unread: 0, otherUnread: 20, ownInbox: true }, t))
|
||||
.toBe('mail_p2_folders.title_all_accounts_only {"name":"Inbox","total":20}');
|
||||
});
|
||||
|
||||
it('leaves other folders and an Inbox with no other unread mail alone', () => {
|
||||
expect(mailboxTitleLine({ name: 'Family', unread: 1, otherUnread: 20 }, t)).toBe('Family (1)');
|
||||
expect(mailboxTitleLine({ name: 'Inbox', unread: 10, otherUnread: 0, ownInbox: true }, t)).toBe('Inbox (10)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,3 +34,25 @@ export function mailTitleContext(
|
||||
): string | null {
|
||||
return view.composer || (showSubject ? view.subject : null) || view.mailbox || null;
|
||||
}
|
||||
|
||||
type Translate = (key: string, values?: Record<string, string | number>) => string;
|
||||
|
||||
/**
|
||||
* The mailbox line of the tab title: "Inbox (10)", or for the open account's
|
||||
* own Inbox while other accounts have unread mail too, "Inbox (10, 30 in all
|
||||
* accounts)", the number the favicon and the rail show (usability test
|
||||
* 2026-11-04, mail-6). `otherUnread` is 0 when Notifications counts the open
|
||||
* account only.
|
||||
*/
|
||||
export function mailboxTitleLine(
|
||||
{ name, unread, otherUnread = 0, ownInbox = false }: { name: string; unread: number; otherUnread?: number; ownInbox?: boolean },
|
||||
t: Translate,
|
||||
): string {
|
||||
if (ownInbox && otherUnread > 0) {
|
||||
const total = unread + otherUnread;
|
||||
return unread > 0
|
||||
? t('mail_p2_folders.title_all_accounts', { name, count: unread, total })
|
||||
: t('mail_p2_folders.title_all_accounts_only', { name, total });
|
||||
}
|
||||
return unread > 0 ? `${name} (${unread})` : name;
|
||||
}
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "إفراغ المهملات",
|
||||
"empty_spam": "إفراغ البريد العشوائي",
|
||||
"empty_trash_confirm": "حذف {count, plural, zero {الرسائل} one {الرسالة} two {الرسالتين} few {كل الرسائل الـ#} many {كل الرسائل الـ#} other {كل الرسائل الـ#}} في المهملات نهائيًا؟ لا يمكن التراجع عن ذلك.",
|
||||
"empty_spam_confirm": "حذف {count, plural, zero {الرسائل} one {الرسالة} two {الرسالتين} few {كل الرسائل الـ#} many {كل الرسائل الـ#} other {كل الرسائل الـ#}} في البريد العشوائي نهائيًا؟ لا يمكن التراجع عن ذلك."
|
||||
"empty_spam_confirm": "حذف {count, plural, zero {الرسائل} one {الرسالة} two {الرسالتين} few {كل الرسائل الـ#} many {كل الرسائل الـ#} other {كل الرسائل الـ#}} في البريد العشوائي نهائيًا؟ لا يمكن التراجع عن ذلك.",
|
||||
"title_all_accounts": "{name} ({count}، {total} في كل الحسابات)",
|
||||
"title_all_accounts_only": "{name} ({total} في كل الحسابات)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "إدارة الحسابات…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Buida la paperera",
|
||||
"empty_spam": "Buida el correu brossa",
|
||||
"empty_trash_confirm": "Vols suprimir per sempre {count, plural, one {el missatge} other {els # missatges}} de la paperera? No es pot desfer.",
|
||||
"empty_spam_confirm": "Vols suprimir per sempre {count, plural, one {el missatge} other {els # missatges}} del correu brossa? No es pot desfer."
|
||||
"empty_spam_confirm": "Vols suprimir per sempre {count, plural, one {el missatge} other {els # missatges}} del correu brossa? No es pot desfer.",
|
||||
"title_all_accounts": "{name} ({count}, {total} a tots els comptes)",
|
||||
"title_all_accounts_only": "{name} ({total} a tots els comptes)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Gestiona els comptes…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Vysypat koš",
|
||||
"empty_spam": "Vyprázdnit spam",
|
||||
"empty_trash_confirm": "Trvale smazat {count, plural, one {zprávu} few {všechny # zprávy} many {všech # zprávy} other {všech # zpráv}} v koši? Nelze to vrátit zpět.",
|
||||
"empty_spam_confirm": "Trvale smazat {count, plural, one {zprávu} few {všechny # zprávy} many {všech # zprávy} other {všech # zpráv}} ve spamu? Nelze to vrátit zpět."
|
||||
"empty_spam_confirm": "Trvale smazat {count, plural, one {zprávu} few {všechny # zprávy} many {všech # zprávy} other {všech # zpráv}} ve spamu? Nelze to vrátit zpět.",
|
||||
"title_all_accounts": "{name} ({count}, {total} ve všech účtech)",
|
||||
"title_all_accounts_only": "{name} ({total} ve všech účtech)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Spravovat účty…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Tøm papirkurven",
|
||||
"empty_spam": "Tøm spam",
|
||||
"empty_trash_confirm": "Slet {count, plural, one {beskeden} other {alle # beskeder}} i papirkurven for altid? Det kan ikke fortrydes.",
|
||||
"empty_spam_confirm": "Slet {count, plural, one {beskeden} other {alle # beskeder}} i spam for altid? Det kan ikke fortrydes."
|
||||
"empty_spam_confirm": "Slet {count, plural, one {beskeden} other {alle # beskeder}} i spam for altid? Det kan ikke fortrydes.",
|
||||
"title_all_accounts": "{name} ({count}, {total} i alle konti)",
|
||||
"title_all_accounts_only": "{name} ({total} i alle konti)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Administrer konti…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Papierkorb leeren",
|
||||
"empty_spam": "Spam leeren",
|
||||
"empty_trash_confirm": "{count, plural, one {Die Nachricht} other {Alle # Nachrichten}} im Papierkorb endgültig löschen? Das lässt sich nicht rückgängig machen.",
|
||||
"empty_spam_confirm": "{count, plural, one {Die Nachricht} other {Alle # Nachrichten}} im Spam endgültig löschen? Das lässt sich nicht rückgängig machen."
|
||||
"empty_spam_confirm": "{count, plural, one {Die Nachricht} other {Alle # Nachrichten}} im Spam endgültig löschen? Das lässt sich nicht rückgängig machen.",
|
||||
"title_all_accounts": "{name} ({count}, {total} in allen Konten)",
|
||||
"title_all_accounts_only": "{name} ({total} in allen Konten)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Konten verwalten…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Empty Trash",
|
||||
"empty_spam": "Empty Spam",
|
||||
"empty_trash_confirm": "Delete {count, plural, one {the message} other {all # messages}} in Trash forever? You can't undo this.",
|
||||
"empty_spam_confirm": "Delete {count, plural, one {the message} other {all # messages}} in Spam forever? You can't undo this."
|
||||
"empty_spam_confirm": "Delete {count, plural, one {the message} other {all # messages}} in Spam forever? You can't undo this.",
|
||||
"title_all_accounts": "{name} ({count}, {total} in all accounts)",
|
||||
"title_all_accounts_only": "{name} ({total} in all accounts)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Manage accounts…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Vaciar papelera",
|
||||
"empty_spam": "Vaciar spam",
|
||||
"empty_trash_confirm": "¿Eliminar para siempre {count, plural, one {el mensaje} other {los # mensajes}} de la papelera? No se puede deshacer.",
|
||||
"empty_spam_confirm": "¿Eliminar para siempre {count, plural, one {el mensaje} other {los # mensajes}} de spam? No se puede deshacer."
|
||||
"empty_spam_confirm": "¿Eliminar para siempre {count, plural, one {el mensaje} other {los # mensajes}} de spam? No se puede deshacer.",
|
||||
"title_all_accounts": "{name} ({count}, {total} en todas las cuentas)",
|
||||
"title_all_accounts_only": "{name} ({total} en todas las cuentas)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Gestionar cuentas…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Tühjenda prügikast",
|
||||
"empty_spam": "Tühjenda rämpspost",
|
||||
"empty_trash_confirm": "Kas kustutada prügikastist {count, plural, one {kiri} other {kõik # kirja}} jäädavalt? Seda ei saa tagasi võtta.",
|
||||
"empty_spam_confirm": "Kas kustutada rämpspostist {count, plural, one {kiri} other {kõik # kirja}} jäädavalt? Seda ei saa tagasi võtta."
|
||||
"empty_spam_confirm": "Kas kustutada rämpspostist {count, plural, one {kiri} other {kõik # kirja}} jäädavalt? Seda ei saa tagasi võtta.",
|
||||
"title_all_accounts": "{name} ({count}, {total} kõigil kontodel)",
|
||||
"title_all_accounts_only": "{name} ({total} kõigil kontodel)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Halda kontosid…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "خالی کردن زبالهدان",
|
||||
"empty_spam": "خالی کردن هرزنامه",
|
||||
"empty_trash_confirm": "{count, plural, one {پیام} other {همه # پیام}} زبالهدان برای همیشه حذف شود؟ این کار برگشتپذیر نیست.",
|
||||
"empty_spam_confirm": "{count, plural, one {پیام} other {همه # پیام}} هرزنامه برای همیشه حذف شود؟ این کار برگشتپذیر نیست."
|
||||
"empty_spam_confirm": "{count, plural, one {پیام} other {همه # پیام}} هرزنامه برای همیشه حذف شود؟ این کار برگشتپذیر نیست.",
|
||||
"title_all_accounts": "{name} ({count}، {total} در همه حسابها)",
|
||||
"title_all_accounts_only": "{name} ({total} در همه حسابها)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "مدیریت حسابها…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Vider la corbeille",
|
||||
"empty_spam": "Vider le spam",
|
||||
"empty_trash_confirm": "Supprimer définitivement {count, plural, one {le message} other {les # messages}} de la corbeille ? Impossible de revenir en arrière.",
|
||||
"empty_spam_confirm": "Supprimer définitivement {count, plural, one {le message} other {les # messages}} du spam ? Impossible de revenir en arrière."
|
||||
"empty_spam_confirm": "Supprimer définitivement {count, plural, one {le message} other {les # messages}} du spam ? Impossible de revenir en arrière.",
|
||||
"title_all_accounts": "{name} ({count}, {total} dans tous les comptes)",
|
||||
"title_all_accounts_only": "{name} ({total} dans tous les comptes)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Gérer les comptes…",
|
||||
|
||||
@@ -5544,7 +5544,9 @@
|
||||
"empty_trash": "ריקון האשפה",
|
||||
"empty_spam": "ריקון הספאם",
|
||||
"empty_trash_confirm": "למחוק לצמיתות {count, plural, one {את ההודעה} two {את שתי ההודעות} other {את כל # ההודעות}} באשפה? אי אפשר לבטל את זה.",
|
||||
"empty_spam_confirm": "למחוק לצמיתות {count, plural, one {את ההודעה} two {את שתי ההודעות} other {את כל # ההודעות}} בספאם? אי אפשר לבטל את זה."
|
||||
"empty_spam_confirm": "למחוק לצמיתות {count, plural, one {את ההודעה} two {את שתי ההודעות} other {את כל # ההודעות}} בספאם? אי אפשר לבטל את זה.",
|
||||
"title_all_accounts": "{name} ({count}, {total} בכל החשבונות)",
|
||||
"title_all_accounts_only": "{name} ({total} בכל החשבונות)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "ניהול חשבונות…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Kuka ürítése",
|
||||
"empty_spam": "Spam ürítése",
|
||||
"empty_trash_confirm": "Véglegesen törli {count, plural, one {az üzenetet} other {mind a # üzenetet}} a kukából? Ez nem vonható vissza.",
|
||||
"empty_spam_confirm": "Véglegesen törli {count, plural, one {az üzenetet} other {mind a # üzenetet}} a spamből? Ez nem vonható vissza."
|
||||
"empty_spam_confirm": "Véglegesen törli {count, plural, one {az üzenetet} other {mind a # üzenetet}} a spamből? Ez nem vonható vissza.",
|
||||
"title_all_accounts": "{name} ({count}, {total} az összes fiókban)",
|
||||
"title_all_accounts_only": "{name} ({total} az összes fiókban)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Fiókok kezelése…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Svuota cestino",
|
||||
"empty_spam": "Svuota spam",
|
||||
"empty_trash_confirm": "Eliminare per sempre {count, plural, one {il messaggio} other {tutti i # messaggi}} nel cestino? Non si può annullare.",
|
||||
"empty_spam_confirm": "Eliminare per sempre {count, plural, one {il messaggio} other {tutti i # messaggi}} nello spam? Non si può annullare."
|
||||
"empty_spam_confirm": "Eliminare per sempre {count, plural, one {il messaggio} other {tutti i # messaggi}} nello spam? Non si può annullare.",
|
||||
"title_all_accounts": "{name} ({count}, {total} in tutti gli account)",
|
||||
"title_all_accounts_only": "{name} ({total} in tutti gli account)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Gestisci account…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "ゴミ箱を空にする",
|
||||
"empty_spam": "迷惑メールを空にする",
|
||||
"empty_trash_confirm": "ゴミ箱の{count, plural, other {# 件のメッセージ}}をすべて完全に削除しますか?元に戻せません。",
|
||||
"empty_spam_confirm": "迷惑メールの{count, plural, other {# 件のメッセージ}}をすべて完全に削除しますか?元に戻せません。"
|
||||
"empty_spam_confirm": "迷惑メールの{count, plural, other {# 件のメッセージ}}をすべて完全に削除しますか?元に戻せません。",
|
||||
"title_all_accounts": "{name} ({count}、すべてのアカウントで {total})",
|
||||
"title_all_accounts_only": "{name} (すべてのアカウントで {total})"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "アカウントを管理…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "휴지통 비우기",
|
||||
"empty_spam": "스팸 비우기",
|
||||
"empty_trash_confirm": "휴지통의 {count, plural, other {메시지 #개}}를 모두 영구 삭제할까요? 되돌릴 수 없어요.",
|
||||
"empty_spam_confirm": "스팸의 {count, plural, other {메시지 #개}}를 모두 영구 삭제할까요? 되돌릴 수 없어요."
|
||||
"empty_spam_confirm": "스팸의 {count, plural, other {메시지 #개}}를 모두 영구 삭제할까요? 되돌릴 수 없어요.",
|
||||
"title_all_accounts": "{name} ({count}, 모든 계정에 {total})",
|
||||
"title_all_accounts_only": "{name} (모든 계정에 {total})"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "계정 관리…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Iztukšot miskasti",
|
||||
"empty_spam": "Iztukšot mēstules",
|
||||
"empty_trash_confirm": "Neatgriezeniski dzēst {count, plural, =1 {vēstuli} zero {visas # vēstules} one {visas # vēstuli} other {visas # vēstules}} miskastē? To nevar atsaukt.",
|
||||
"empty_spam_confirm": "Neatgriezeniski dzēst {count, plural, =1 {vēstuli} zero {visas # vēstules} one {visas # vēstuli} other {visas # vēstules}} mēstulēs? To nevar atsaukt."
|
||||
"empty_spam_confirm": "Neatgriezeniski dzēst {count, plural, =1 {vēstuli} zero {visas # vēstules} one {visas # vēstuli} other {visas # vēstules}} mēstulēs? To nevar atsaukt.",
|
||||
"title_all_accounts": "{name} ({count}, {total} visos kontos)",
|
||||
"title_all_accounts_only": "{name} ({total} visos kontos)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Pārvaldīt kontus…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Хогийн савыг хоослох",
|
||||
"empty_spam": "Спамыг хоослох",
|
||||
"empty_trash_confirm": "Хогийн саван дахь {count, plural, one {захидлыг} other {бүх # захидлыг}} бүрмөсөн устгах уу? Буцаах боломжгүй.",
|
||||
"empty_spam_confirm": "Спам дахь {count, plural, one {захидлыг} other {бүх # захидлыг}} бүрмөсөн устгах уу? Буцаах боломжгүй."
|
||||
"empty_spam_confirm": "Спам дахь {count, plural, one {захидлыг} other {бүх # захидлыг}} бүрмөсөн устгах уу? Буцаах боломжгүй.",
|
||||
"title_all_accounts": "{name} ({count}, бүх бүртгэлд {total})",
|
||||
"title_all_accounts_only": "{name} (бүх бүртгэлд {total})"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Данснуудыг удирдах…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Tøm papirkurven",
|
||||
"empty_spam": "Tøm søppelpost",
|
||||
"empty_trash_confirm": "Slette {count, plural, one {meldingen} other {alle # meldingene}} i papirkurven for godt? Dette kan ikke angres.",
|
||||
"empty_spam_confirm": "Slette {count, plural, one {meldingen} other {alle # meldingene}} i søppelpost for godt? Dette kan ikke angres."
|
||||
"empty_spam_confirm": "Slette {count, plural, one {meldingen} other {alle # meldingene}} i søppelpost for godt? Dette kan ikke angres.",
|
||||
"title_all_accounts": "{name} ({count}, {total} i alle kontoer)",
|
||||
"title_all_accounts_only": "{name} ({total} i alle kontoer)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Administrer kontoer…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Prullenbak legen",
|
||||
"empty_spam": "Spam legen",
|
||||
"empty_trash_confirm": "{count, plural, one {Het bericht} other {Alle # berichten}} in de prullenbak definitief verwijderen? Dit kan niet ongedaan worden.",
|
||||
"empty_spam_confirm": "{count, plural, one {Het bericht} other {Alle # berichten}} in spam definitief verwijderen? Dit kan niet ongedaan worden."
|
||||
"empty_spam_confirm": "{count, plural, one {Het bericht} other {Alle # berichten}} in spam definitief verwijderen? Dit kan niet ongedaan worden.",
|
||||
"title_all_accounts": "{name} ({count}, {total} in alle accounts)",
|
||||
"title_all_accounts_only": "{name} ({total} in alle accounts)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Accounts beheren…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Opróżnij kosz",
|
||||
"empty_spam": "Opróżnij spam",
|
||||
"empty_trash_confirm": "Trwale usunąć {count, plural, one {wiadomość} few {wszystkie # wiadomości} many {wszystkie # wiadomości} other {wszystkie # wiadomości}} z kosza? Nie można tego cofnąć.",
|
||||
"empty_spam_confirm": "Trwale usunąć {count, plural, one {wiadomość} few {wszystkie # wiadomości} many {wszystkie # wiadomości} other {wszystkie # wiadomości}} ze spamu? Nie można tego cofnąć."
|
||||
"empty_spam_confirm": "Trwale usunąć {count, plural, one {wiadomość} few {wszystkie # wiadomości} many {wszystkie # wiadomości} other {wszystkie # wiadomości}} ze spamu? Nie można tego cofnąć.",
|
||||
"title_all_accounts": "{name} ({count}, {total} we wszystkich kontach)",
|
||||
"title_all_accounts_only": "{name} ({total} we wszystkich kontach)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Zarządzaj kontami…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Esvaziar lixeira",
|
||||
"empty_spam": "Esvaziar spam",
|
||||
"empty_trash_confirm": "Excluir para sempre {count, plural, one {a mensagem} other {as # mensagens}} da lixeira? Não é possível desfazer.",
|
||||
"empty_spam_confirm": "Excluir para sempre {count, plural, one {a mensagem} other {as # mensagens}} do spam? Não é possível desfazer."
|
||||
"empty_spam_confirm": "Excluir para sempre {count, plural, one {a mensagem} other {as # mensagens}} do spam? Não é possível desfazer.",
|
||||
"title_all_accounts": "{name} ({count}, {total} em todas as contas)",
|
||||
"title_all_accounts_only": "{name} ({total} em todas as contas)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Gerir contas…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Golește coșul de gunoi",
|
||||
"empty_spam": "Golește spamul",
|
||||
"empty_trash_confirm": "Ștergeți definitiv {count, plural, one {mesajul} few {toate cele # mesaje} other {toate cele # de mesaje}} din coșul de gunoi? Nu se poate anula.",
|
||||
"empty_spam_confirm": "Ștergeți definitiv {count, plural, one {mesajul} few {toate cele # mesaje} other {toate cele # de mesaje}} din spam? Nu se poate anula."
|
||||
"empty_spam_confirm": "Ștergeți definitiv {count, plural, one {mesajul} few {toate cele # mesaje} other {toate cele # de mesaje}} din spam? Nu se poate anula.",
|
||||
"title_all_accounts": "{name} ({count}, {total} în toate conturile)",
|
||||
"title_all_accounts_only": "{name} ({total} în toate conturile)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Gestionați conturile…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Очистить корзину",
|
||||
"empty_spam": "Очистить спам",
|
||||
"empty_trash_confirm": "Удалить {count, plural, =1 {письмо} one {все # письмо} few {все # письма} many {все # писем} other {все # письма}} в корзине навсегда? Это нельзя отменить.",
|
||||
"empty_spam_confirm": "Удалить {count, plural, =1 {письмо} one {все # письмо} few {все # письма} many {все # писем} other {все # письма}} в спаме навсегда? Это нельзя отменить."
|
||||
"empty_spam_confirm": "Удалить {count, plural, =1 {письмо} one {все # письмо} few {все # письма} many {все # писем} other {все # письма}} в спаме навсегда? Это нельзя отменить.",
|
||||
"title_all_accounts": "{name} ({count}, {total} во всех аккаунтах)",
|
||||
"title_all_accounts_only": "{name} ({total} во всех аккаунтах)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Управление аккаунтами…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Vyprázdniť kôš",
|
||||
"empty_spam": "Vyprázdniť spam",
|
||||
"empty_trash_confirm": "Natrvalo odstrániť {count, plural, one {správu} few {všetky # správy} many {všetkých # správy} other {všetkých # správ}} v koši? Nedá sa to vrátiť späť.",
|
||||
"empty_spam_confirm": "Natrvalo odstrániť {count, plural, one {správu} few {všetky # správy} many {všetkých # správy} other {všetkých # správ}} v spame? Nedá sa to vrátiť späť."
|
||||
"empty_spam_confirm": "Natrvalo odstrániť {count, plural, one {správu} few {všetky # správy} many {všetkých # správy} other {všetkých # správ}} v spame? Nedá sa to vrátiť späť.",
|
||||
"title_all_accounts": "{name} ({count}, {total} vo všetkých účtoch)",
|
||||
"title_all_accounts_only": "{name} ({total} vo všetkých účtoch)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Spravovať účty…",
|
||||
|
||||
@@ -1034,7 +1034,9 @@
|
||||
"empty_trash": "ล้างถังขยะ",
|
||||
"empty_spam": "ล้างสแปม",
|
||||
"empty_trash_confirm": "ลบข้อความ {count, plural, other {ทั้ง # ฉบับ}} ในถังขยะอย่างถาวรไหม ย้อนกลับไม่ได้",
|
||||
"empty_spam_confirm": "ลบข้อความ {count, plural, other {ทั้ง # ฉบับ}} ในสแปมอย่างถาวรไหม ย้อนกลับไม่ได้"
|
||||
"empty_spam_confirm": "ลบข้อความ {count, plural, other {ทั้ง # ฉบับ}} ในสแปมอย่างถาวรไหม ย้อนกลับไม่ได้",
|
||||
"title_all_accounts": "{name} ({count}, {total} ในทุกบัญชี)",
|
||||
"title_all_accounts_only": "{name} ({total} ในทุกบัญชี)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "จัดการบัญชี…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Çöp Kutusunu boşalt",
|
||||
"empty_spam": "Spam klasörünü boşalt",
|
||||
"empty_trash_confirm": "Çöp Kutusundaki {count, plural, one {ileti} other {# iletinin tümü}} kalıcı olarak silinsin mi? Bu geri alınamaz.",
|
||||
"empty_spam_confirm": "Spam klasöründeki {count, plural, one {ileti} other {# iletinin tümü}} kalıcı olarak silinsin mi? Bu geri alınamaz."
|
||||
"empty_spam_confirm": "Spam klasöründeki {count, plural, one {ileti} other {# iletinin tümü}} kalıcı olarak silinsin mi? Bu geri alınamaz.",
|
||||
"title_all_accounts": "{name} ({count}, tüm hesaplarda {total})",
|
||||
"title_all_accounts_only": "{name} (tüm hesaplarda {total})"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Hesapları yönet…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "Очистити кошик",
|
||||
"empty_spam": "Очистити спам",
|
||||
"empty_trash_confirm": "Видалити {count, plural, =1 {лист} one {усі # лист} few {усі # листи} many {усі # листів} other {усі # листа}} в кошику назавжди? Це не можна скасувати.",
|
||||
"empty_spam_confirm": "Видалити {count, plural, =1 {лист} one {усі # лист} few {усі # листи} many {усі # листів} other {усі # листа}} у спамі назавжди? Це не можна скасувати."
|
||||
"empty_spam_confirm": "Видалити {count, plural, =1 {лист} one {усі # лист} few {усі # листи} many {усі # листів} other {усі # листа}} у спамі назавжди? Це не можна скасувати.",
|
||||
"title_all_accounts": "{name} ({count}, {total} в усіх акаунтах)",
|
||||
"title_all_accounts_only": "{name} ({total} в усіх акаунтах)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Керувати обліковими записами…",
|
||||
|
||||
@@ -3850,7 +3850,9 @@
|
||||
"empty_trash": "Dọn sạch Thùng rác",
|
||||
"empty_spam": "Dọn sạch Thư rác",
|
||||
"empty_trash_confirm": "Xóa vĩnh viễn {count, plural, other {tất cả # thư}} trong Thùng rác? Không thể hoàn tác.",
|
||||
"empty_spam_confirm": "Xóa vĩnh viễn {count, plural, other {tất cả # thư}} trong Thư rác? Không thể hoàn tác."
|
||||
"empty_spam_confirm": "Xóa vĩnh viễn {count, plural, other {tất cả # thư}} trong Thư rác? Không thể hoàn tác.",
|
||||
"title_all_accounts": "{name} ({count}, {total} trong mọi tài khoản)",
|
||||
"title_all_accounts_only": "{name} ({total} trong mọi tài khoản)"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "Quản lý tài khoản…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "清空垃圾桶",
|
||||
"empty_spam": "清空垃圾郵件",
|
||||
"empty_trash_confirm": "要永久刪除垃圾桶中的{count, plural, other {全部 # 封郵件}}嗎?此動作無法復原。",
|
||||
"empty_spam_confirm": "要永久刪除垃圾郵件中的{count, plural, other {全部 # 封郵件}}嗎?此動作無法復原。"
|
||||
"empty_spam_confirm": "要永久刪除垃圾郵件中的{count, plural, other {全部 # 封郵件}}嗎?此動作無法復原。",
|
||||
"title_all_accounts": "{name} ({count}、所有帳號共 {total})",
|
||||
"title_all_accounts_only": "{name} (所有帳號共 {total})"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "管理帳號…",
|
||||
|
||||
@@ -3838,7 +3838,9 @@
|
||||
"empty_trash": "清空已删除",
|
||||
"empty_spam": "清空垃圾邮件",
|
||||
"empty_trash_confirm": "要永久删除已删除中的{count, plural, other {全部 # 封邮件}}吗?此操作无法撤销。",
|
||||
"empty_spam_confirm": "要永久删除垃圾邮件中的{count, plural, other {全部 # 封邮件}}吗?此操作无法撤销。"
|
||||
"empty_spam_confirm": "要永久删除垃圾邮件中的{count, plural, other {全部 # 封邮件}}吗?此操作无法撤销。",
|
||||
"title_all_accounts": "{name} ({count}、所有账户共 {total})",
|
||||
"title_all_accounts_only": "{name} (所有账户共 {total})"
|
||||
},
|
||||
"uxs_folders": {
|
||||
"manage_accounts": "管理账户…",
|
||||
|
||||
Reference in New Issue
Block a user