feat: announce how much new mail came and flash the tab title slowly with visual alerts

This commit is contained in:
Linus Rath
2026-10-29 17:53:49 +01:00
parent a7ce265ac8
commit d713d31af3
37 changed files with 454 additions and 74 deletions
+4 -16
View File
@@ -70,7 +70,7 @@ import { useConfirmDialog } from "@/hooks/use-confirm-dialog";
import { usePromptDialog } from "@/hooks/use-prompt-dialog";
import { useBrowserNavigation, type NavSnapshot } from "@/hooks/use-browser-navigation";
import { debug } from "@/lib/debug";
import { playNotificationSound } from "@/lib/notification-sound";
import { useNewMailAlerts } from "@/hooks/use-new-mail-alerts";
import { cn, formatDateTime, getMailboxPath } from "@/lib/utils";
import { localizeMailboxName } from "@/lib/mailbox-label";
import { KEYWORD_PREFIX, KEYWORD_PREFIX_LEGACY, emailKeyFor, groupEmailsByThread, threadKeyFor } from "@/lib/thread-utils";
@@ -420,7 +420,6 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
selectedMailbox,
quota,
isPushConnected,
newEmailNotification,
selectEmail,
selectMailbox,
selectedEmailKeys,
@@ -447,7 +446,6 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
finishOpenEmail,
abandonOpenEmail,
handleStateChange,
clearNewEmailNotification,
markAsSpam,
undoSpam,
searchFilters,
@@ -509,7 +507,6 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
selectedMailbox: state.selectedMailbox,
quota: state.quota,
isPushConnected: state.isPushConnected,
newEmailNotification: state.newEmailNotification,
selectEmail: state.selectEmail,
selectMailbox: state.selectMailbox,
selectedEmailKeys: state.selectedEmailKeys,
@@ -536,7 +533,6 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
finishOpenEmail: state.finishOpenEmail,
abandonOpenEmail: state.abandonOpenEmail,
handleStateChange: state.handleStateChange,
clearNewEmailNotification: state.clearNewEmailNotification,
markAsSpam: state.markAsSpam,
undoSpam: state.undoSpam,
searchFilters: state.searchFilters,
@@ -2068,17 +2064,9 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedEmail?.id, isScheduledView, mvShell, cursorOnly]);
// Handle new email notifications - play sound
useEffect(() => {
if (newEmailNotification) {
const { emailNotificationsEnabled, emailNotificationSound, notificationSoundChoice } = useSettingsStore.getState();
if (emailNotificationsEnabled && emailNotificationSound) {
playNotificationSound(notificationSoundChoice);
}
debug.log('email', 'New email received:', newEmailNotification.subject);
clearNewEmailNotification();
}
}, [newEmailNotification, clearNewEmailNotification]);
// New mail: the sound, the screen reader count and the visual alert, all
// silenced by Do not disturb.
useNewMailAlerts();
// Lock body scroll when sidebar is open on mobile/tablet
useEffect(() => {
@@ -0,0 +1,89 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { act, renderHook } from '@testing-library/react';
import { useNewMailAlerts, NEW_MAIL_BATCH_MS } from '../use-new-mail-alerts';
import { useEmailStore } from '@/stores/email-store';
import { useSettingsStore } from '@/stores/settings-store';
import { LIVE_REGION_IDS, resetAnnouncerForTests } from '@/lib/announce';
import { stopTitleAlert, TITLE_ALERT_INTERVAL_MS, TITLE_ALERT_MAX_SWAPS } from '@/lib/title-alert';
import type { Email } from '@/lib/jmap/types';
// The count must reach the words, so t shows its values.
vi.mock('next-intl', () => ({
useTranslations: () => (key: string, values?: Record<string, unknown>) =>
values ? `${key}:${JSON.stringify(values)}` : key,
}));
const playNotificationSound = vi.hoisted(() => vi.fn());
vi.mock('@/lib/notification-sound', () => ({ playNotificationSound }));
const polite = () => document.getElementById(LIVE_REGION_IDS.polite)?.textContent ?? '';
const arrive = (id: string) => act(() => {
useEmailStore.getState().handleNewEmailNotification({ id, subject: `Secret ${id}` } as Email);
});
describe('new mail alerts', () => {
beforeEach(() => {
vi.useFakeTimers();
resetAnnouncerForTests();
playNotificationSound.mockClear();
useEmailStore.setState({ newEmailNotification: null });
useSettingsStore.setState({
emailNotificationsEnabled: true, emailNotificationSound: true, announceNewMail: true,
doNotDisturb: false, visualAlerts: false,
});
document.title = 'Inbox - Bulwark';
});
afterEach(() => {
stopTitleAlert();
vi.restoreAllMocks();
vi.useRealTimers();
});
it('tells screen readers once how many messages came, never what they say', () => {
renderHook(() => useNewMailAlerts());
arrive('a');
arrive('b');
arrive('c');
expect(polite()).toBe('');
act(() => { vi.advanceTimersByTime(NEW_MAIL_BATCH_MS + 100); });
expect(polite()).toBe('new_mail_count:{"count":3}');
expect(polite()).not.toContain('Secret');
// One sound for the batch, not three.
expect(playNotificationSound).toHaveBeenCalledTimes(1);
expect(useEmailStore.getState().newEmailNotification).toBeNull();
});
it('stays quiet for screen readers with "Announce new mail" off', () => {
useSettingsStore.setState({ announceNewMail: false });
renderHook(() => useNewMailAlerts());
arrive('a');
act(() => { vi.advanceTimersByTime(NEW_MAIL_BATCH_MS + 100); });
expect(polite()).toBe('');
expect(playNotificationSound).toHaveBeenCalledTimes(1);
});
it('plays no sound, says nothing and leaves the title alone with Do not disturb on', () => {
useSettingsStore.setState({ doNotDisturb: true, visualAlerts: true });
vi.spyOn(document, 'hasFocus').mockReturnValue(false);
renderHook(() => useNewMailAlerts());
arrive('a');
act(() => { vi.advanceTimersByTime(NEW_MAIL_BATCH_MS + 100); });
expect(playNotificationSound).not.toHaveBeenCalled();
expect(polite()).toBe('');
expect(document.title).toBe('Inbox - Bulwark');
});
it('swaps the tab title slowly with Visual alerts on while the tab is in the background', () => {
useSettingsStore.setState({ visualAlerts: true });
vi.spyOn(document, 'hasFocus').mockReturnValue(false);
renderHook(() => useNewMailAlerts());
arrive('a');
expect(document.title).toBe('new_mail_count:{"count":1}');
arrive('b');
expect(document.title).toBe('new_mail_count:{"count":2}');
act(() => { vi.advanceTimersByTime(TITLE_ALERT_INTERVAL_MS); });
expect(document.title).toBe('Inbox - Bulwark');
act(() => { vi.advanceTimersByTime(TITLE_ALERT_INTERVAL_MS * TITLE_ALERT_MAX_SWAPS); });
expect(document.title).toBe('Inbox - Bulwark');
});
});
+12
View File
@@ -12,6 +12,7 @@ import { useToastStore } from '@/stores/toast-store';
import { getPendingAlerts, getPendingTaskAlerts, getNextAlertFireTime, buildAlertKey } from '@/lib/calendar-alerts';
import { playNotificationSound } from '@/lib/notification-sound';
import { lastingToastDuration } from '@/lib/toast-duration';
import { startTitleAlert } from '@/lib/title-alert';
import { getPathPrefix } from '@/lib/browser-navigation';
import type { CalendarEvent } from '@/lib/jmap/types';
import { calendarHooks } from '@/lib/plugin-hooks';
@@ -43,6 +44,15 @@ function reminderSoundOn(soundSetting: boolean): boolean {
return soundSetting && !useSettingsStore.getState().doNotDisturb;
}
/**
* Visual alerts: the tab title swaps slowly to the reminder while the tab
* is in the background. Reminders were asked for, so Do not disturb keeps
* this too.
*/
function reminderTitleAlert(text: string): void {
if (useSettingsStore.getState().visualAlerts) startTitleAlert(text);
}
export function useCalendarAlerts() {
const { isAuthenticated, client } = useAuthStore();
const { events, calendars, supportsCalendar } = useCalendarStore();
@@ -107,6 +117,7 @@ export function useCalendarAlerts() {
? `${timeLabel} · ${alert.calendarName}`
: timeLabel;
reminderTitleAlert(t('title_alert', { title: alert.event.title || t('alert_title') }));
addToast({
type: 'info',
title: alert.event.title || t('alert_title'),
@@ -139,6 +150,7 @@ export function useCalendarAlerts() {
? `${t('task_due')} · ${taskAlert.calendarName}`
: t('task_due');
reminderTitleAlert(t('title_alert', { title: taskAlert.task.title || t('alert_title') }));
addToast({
type: 'info',
title: taskAlert.task.title || t('alert_title'),
+4
View File
@@ -5,6 +5,7 @@ import { useConfig } from '@/hooks/use-config';
import { useIsFocusedProTab, usePaneId } from '@/hooks/use-pane-context';
import { useAccountStore } from '@/stores/account-store';
import { formatTabTitle } from '@/lib/tab-title';
import { isTitleAlertActive } from '@/lib/title-alert';
// The surface that set the title last owns it. One that is still mounted
// (during a route transition) stops re-applying, so two titles never fight.
@@ -45,6 +46,9 @@ export function useDocumentTitle(context?: string | null): void {
let readBack: string | null = null;
const apply = () => {
if (owner !== self) return;
// A visual alert swaps the title for now (lib/title-alert); it puts
// the title back when it stops, and this runs again then.
if (isTitleAlertActive()) return;
const current = document.title;
if (current === title || current === readBack) return;
document.title = title;
+83
View File
@@ -0,0 +1,83 @@
"use client";
import { useEffect } from "react";
import { useTranslations } from "next-intl";
import { useEmailStore } from "@/stores/email-store";
import { useSettingsStore } from "@/stores/settings-store";
import { playNotificationSound } from "@/lib/notification-sound";
import { announce } from "@/lib/announce";
import { startTitleAlert } from "@/lib/title-alert";
import { debug } from "@/lib/debug";
/**
* Mail that arrives within this time is told about once ("3 new messages"),
* not message by message.
*/
export const NEW_MAIL_BATCH_MS = 3000;
/**
* What the app does when new mail reaches the Inbox (the store sets
* `newEmailNotification` for each message; muted folders never do):
*
* - plays the new-mail sound once per batch, when mail notifications and
* their sound are on;
* - tells screen reader users how many messages came, never what they say,
* when "Announce new mail" is on;
* - with Visual alerts on, swaps the tab title slowly while the tab is in
* the background (lib/title-alert).
*
* Do not disturb silences all of it until it is turned off.
*/
export function useNewMailAlerts(): void {
const t = useTranslations("notifications");
useEffect(() => {
let batch = 0;
let batchTimer: ReturnType<typeof setTimeout> | null = null;
// Messages the title alert counts: since the tab was last in front.
let unseen = 0;
const flush = () => {
batchTimer = null;
const count = batch;
batch = 0;
const s = useSettingsStore.getState();
if (count > 0 && s.announceNewMail && !s.doNotDisturb) {
announce(t("new_mail_count", { count }), { key: "new-mail" });
}
};
const onNewMail = () => {
const s = useSettingsStore.getState();
if (s.doNotDisturb) return;
if (batchTimer === null) {
if (s.emailNotificationsEnabled && s.emailNotificationSound) {
playNotificationSound(s.notificationSoundChoice);
}
batchTimer = setTimeout(flush, NEW_MAIL_BATCH_MS);
}
batch += 1;
if (s.visualAlerts) {
unseen += 1;
startTitleAlert(t("new_mail_count", { count: unseen }));
}
};
const resetUnseen = () => { unseen = 0; };
window.addEventListener("focus", resetUnseen);
const unsubscribe = useEmailStore.subscribe((state, prev) => {
const email = state.newEmailNotification;
if (!email || email === prev.newEmailNotification) return;
debug.log("email", "New email received:", email.subject);
state.clearNewEmailNotification();
onNewMail();
});
return () => {
unsubscribe();
window.removeEventListener("focus", resetUnseen);
if (batchTimer) clearTimeout(batchTimer);
};
}, [t]);
}
+53
View File
@@ -0,0 +1,53 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
isTitleAlertActive, startTitleAlert, stopTitleAlert, TITLE_ALERT_INTERVAL_MS, TITLE_ALERT_MAX_SWAPS,
} from '../title-alert';
describe('title alert', () => {
let focused = false;
beforeEach(() => {
vi.useFakeTimers();
focused = false;
vi.spyOn(document, 'hasFocus').mockImplementation(() => focused);
document.title = 'Inbox';
});
afterEach(() => {
stopTitleAlert();
vi.restoreAllMocks();
vi.useRealTimers();
});
it('never swaps faster than once a second', () => {
expect(TITLE_ALERT_INTERVAL_MS).toBeGreaterThanOrEqual(1000);
startTitleAlert('1 new message');
expect(document.title).toBe('1 new message');
vi.advanceTimersByTime(999);
expect(document.title).toBe('1 new message');
vi.advanceTimersByTime(TITLE_ALERT_INTERVAL_MS - 999);
expect(document.title).toBe('Inbox');
});
it('stops by itself after a few rounds and puts the title back', () => {
startTitleAlert('1 new message');
vi.advanceTimersByTime(TITLE_ALERT_INTERVAL_MS * TITLE_ALERT_MAX_SWAPS);
expect(isTitleAlertActive()).toBe(false);
expect(document.title).toBe('Inbox');
vi.advanceTimersByTime(TITLE_ALERT_INTERVAL_MS * 4);
expect(document.title).toBe('Inbox');
});
it('stops when the tab gets the focus', () => {
startTitleAlert('1 new message');
focused = true;
window.dispatchEvent(new Event('focus'));
expect(isTitleAlertActive()).toBe(false);
expect(document.title).toBe('Inbox');
});
it('does nothing while the tab is in front', () => {
focused = true;
startTitleAlert('1 new message');
expect(isTitleAlertActive()).toBe(false);
expect(document.title).toBe('Inbox');
});
});
+18
View File
@@ -7,6 +7,7 @@ import { useAuthStore } from '@/stores/auth-store';
import { useCalendarStore } from '@/stores/calendar-store';
import { useCalendarNotificationStore } from '@/stores/calendar-notification-store';
import { useCalendarAlerts } from '@/hooks/use-calendar-alerts';
import { stopTitleAlert } from '@/lib/title-alert';
import type { CalendarEvent } from '@/lib/jmap/types';
const playNotificationSound = vi.hoisted(() => vi.fn());
@@ -110,6 +111,8 @@ describe('reminder toasts', () => {
useCalendarStore.setState({ events: [], calendars: [], supportsCalendar: true });
});
afterEach(() => {
// A reminder with Visual alerts on may have started the title alert.
stopTitleAlert();
vi.useRealTimers();
});
@@ -133,6 +136,21 @@ describe('reminder toasts', () => {
expect(showReminder('r4').duration).toBe(0);
});
it('swaps the tab title to the reminder with Visual alerts on, in the background', () => {
const hasFocus = vi.spyOn(document, 'hasFocus').mockReturnValue(false);
document.title = 'Calendar';
try {
useSettingsStore.setState({ visualAlerts: true, doNotDisturb: true });
const shown = showReminder('r7');
expect(shown?.title).toBe('Meeting r7');
expect(document.title).toBe('title_alert');
} finally {
stopTitleAlert();
hasFocus.mockRestore();
}
expect(document.title).toBe('Calendar');
});
it('still shows with Do not disturb on, but without a sound', () => {
useSettingsStore.setState({ doNotDisturb: true });
expect(showReminder('r5')).toBeDefined();
+75
View File
@@ -0,0 +1,75 @@
/**
* Visual alerts (Settings > Accessibility): the browser-tab title swaps
* slowly between the page's own title and a short alert ("3 new messages")
* while the tab is in the background, for people who can't hear the sound.
*
* It never swaps faster than once a second and never flashes colours
* (WCAG 2.3.1), and it stops after a few rounds or as soon as the tab gets
* the focus. hooks/use-document-title leaves the title alone while it runs.
*/
/** Time between two swaps. At least a second. */
export const TITLE_ALERT_INTERVAL_MS = 1500;
/** Swaps before it stops by itself: five rounds of alert and title. */
export const TITLE_ALERT_MAX_SWAPS = 10;
let timer: ReturnType<typeof setInterval> | null = null;
let baseTitle = '';
let alertText = '';
let swaps = 0;
let showingAlert = false;
export function isTitleAlertActive(): boolean {
return timer !== null;
}
function tabInView(): boolean {
return !document.hidden && document.hasFocus();
}
function onVisibilityChange() {
if (tabInView()) stopTitleAlert();
}
/**
* Starts the alert, or changes its text and starts its rounds again when
* it is already running. Nothing happens while the tab is in front: the
* toast or the list says it there.
*/
export function startTitleAlert(text: string): void {
if (typeof document === 'undefined' || !text) return;
if (tabInView()) return;
alertText = text;
swaps = 0;
if (timer !== null) {
if (showingAlert) document.title = alertText;
return;
}
baseTitle = document.title;
showingAlert = true;
document.title = alertText;
timer = setInterval(() => {
swaps += 1;
if (swaps >= TITLE_ALERT_MAX_SWAPS) {
stopTitleAlert();
return;
}
showingAlert = !showingAlert;
document.title = showingAlert ? alertText : baseTitle;
}, TITLE_ALERT_INTERVAL_MS);
window.addEventListener('focus', stopTitleAlert);
document.addEventListener('visibilitychange', onVisibilityChange);
}
/** Stops the alert and puts the page's title back. */
export function stopTitleAlert(): void {
if (timer === null) return;
clearInterval(timer);
timer = null;
showingAlert = false;
window.removeEventListener('focus', stopTitleAlert);
document.removeEventListener('visibilitychange', onVisibilityChange);
// useDocumentTitle sees the change and writes the current title if the
// page moved on in the meantime.
document.title = baseTitle;
}
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {تم تمييز رسالة واحدة كمقروءة} other {تم تمييز # رسائل كمقروءة}}",
"emails_marked_unread": "{count, plural, one {تم تمييز رسالة واحدة كغير مقروءة} other {تم تمييز # رسائل كغير مقروءة}}",
"tag_removed": "تمت إزالة الوسم",
"tag_removed_count": "{count, plural, one {تمت إزالة الوسم من رسالة واحدة} other {تمت إزالة الوسم من # رسائل}}"
"tag_removed_count": "{count, plural, one {تمت إزالة الوسم من رسالة واحدة} other {تمت إزالة الوسم من # رسائل}}",
"new_mail_count": "{count, plural, one {رسالة جديدة واحدة} other {# رسائل جديدة}}"
},
"third_party": {
"push_needs_relay": "تحتاج الإشعارات الفورية إلى خادم ترحيل تديره مؤسستك، ولم يُعدّ أي خادم. يستمر ظهور البريد الجديد ما دام التطبيق مفتوحًا.",
@@ -4477,7 +4478,8 @@
"task_due": "موعد استحقاق المهمة",
"event_exported": "تم تصدير الحدث",
"title_copied": "تم نسخ العنوان",
"link_copied": "تم نسخ الرابط"
"link_copied": "تم نسخ الرابط",
"title_alert": "تذكير: {title}"
},
"status": {
"loading_calendars": "جارٍ تحميل التقويمات...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# correu marcat com a llegit} other {# correus marcats com a llegits}}",
"emails_marked_unread": "{count, plural, one {# correu marcat com a no llegit} other {# correus marcats com a no llegits}}",
"tag_removed": "S'ha tret l'etiqueta",
"tag_removed_count": "{count, plural, one {S'ha tret l'etiqueta de # correu} other {S'ha tret l'etiqueta de # correus}}"
"tag_removed_count": "{count, plural, one {S'ha tret l'etiqueta de # correu} other {S'ha tret l'etiqueta de # correus}}",
"new_mail_count": "{count, plural, one {# missatge nou} other {# missatges nous}}"
},
"third_party": {
"push_needs_relay": "Les notificacions push necessiten un repetidor push gestionat per la vostra organització, i no n'hi ha cap de configurat. El correu nou es continua mostrant mentre l'aplicació està oberta.",
@@ -4477,7 +4478,8 @@
"task_due": "Tasca pendent",
"event_exported": "Esdeveniment exportat",
"title_copied": "Títol copiat",
"link_copied": "Enllaç copiat"
"link_copied": "Enllaç copiat",
"title_alert": "Recordatori: {title}"
},
"status": {
"loading_calendars": "Carregant els calendaris...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# zpráva označena jako přečtená} few {# zprávy označeny jako přečtené} other {# zpráv označeno jako přečtené}}",
"emails_marked_unread": "{count, plural, one {# zpráva označena jako nepřečtená} few {# zprávy označeny jako nepřečtené} other {# zpráv označeno jako nepřečtené}}",
"tag_removed": "Štítek odebrán",
"tag_removed_count": "{count, plural, one {Štítek odebrán z # zprávy} few {Štítek odebrán ze # zpráv} other {Štítek odebrán z # zpráv}}"
"tag_removed_count": "{count, plural, one {Štítek odebrán z # zprávy} few {Štítek odebrán ze # zpráv} other {Štítek odebrán z # zpráv}}",
"new_mail_count": "{count, plural, one {# nová zpráva} few {# nové zprávy} other {# nových zpráv}}"
},
"third_party": {
"push_needs_relay": "Push oznámení potřebují push relay provozovaný vaší organizací a žádný není nastaven. Nová pošta se dál zobrazuje, dokud je aplikace otevřená.",
@@ -4451,7 +4452,8 @@
"task_due": "Termín úkolu",
"event_exported": "Událost exportována",
"title_copied": "Název zkopírován",
"link_copied": "Odkaz zkopírován"
"link_copied": "Odkaz zkopírován",
"title_alert": "Připomenutí: {title}"
},
"status": {
"loading_calendars": "Načítání kalendářů...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# e-mail markeret som læst} other {# e-mails markeret som læst}}",
"emails_marked_unread": "{count, plural, one {# e-mail markeret som ulæst} other {# e-mails markeret som ulæst}}",
"tag_removed": "Tag fjernet",
"tag_removed_count": "{count, plural, one {Tag fjernet fra # e-mail} other {Tag fjernet fra # e-mails}}"
"tag_removed_count": "{count, plural, one {Tag fjernet fra # e-mail} other {Tag fjernet fra # e-mails}}",
"new_mail_count": "{count, plural, one {# ny besked} other {# nye beskeder}}"
},
"third_party": {
"push_needs_relay": "Push-notifikationer kræver et push-relæ, som din organisation driver, og der er ikke sat noget op. Ny mail vises stadig, mens appen er åben.",
@@ -4477,7 +4478,8 @@
"task_due": "Opgave forfalder",
"event_exported": "Begivenhed eksporteret",
"title_copied": "Titel kopieret",
"link_copied": "Link kopieret"
"link_copied": "Link kopieret",
"title_alert": "Påmindelse: {title}"
},
"status": {
"loading_calendars": "Indlæser kalendere...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# E-Mail als gelesen markiert} other {# E-Mails als gelesen markiert}}",
"emails_marked_unread": "{count, plural, one {# E-Mail als ungelesen markiert} other {# E-Mails als ungelesen markiert}}",
"tag_removed": "Markierung entfernt",
"tag_removed_count": "{count, plural, one {Markierung von # E-Mail entfernt} other {Markierung von # E-Mails entfernt}}"
"tag_removed_count": "{count, plural, one {Markierung von # E-Mail entfernt} other {Markierung von # E-Mails entfernt}}",
"new_mail_count": "{count, plural, one {# neue Nachricht} other {# neue Nachrichten}}"
},
"third_party": {
"push_needs_relay": "Push-Benachrichtigungen brauchen ein Push-Relay Ihrer Organisation, und es ist keines eingerichtet. Neue E-Mails werden weiterhin angezeigt, solange die App geöffnet ist.",
@@ -4451,7 +4452,8 @@
"task_due": "Aufgabe fällig",
"event_exported": "Termin exportiert",
"title_copied": "Titel kopiert",
"link_copied": "Link kopiert"
"link_copied": "Link kopiert",
"title_alert": "Erinnerung: {title}"
},
"status": {
"loading_calendars": "Kalender werden geladen...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# email marked as read} other {# emails marked as read}}",
"emails_marked_unread": "{count, plural, one {# email marked as unread} other {# emails marked as unread}}",
"tag_removed": "Tag removed",
"tag_removed_count": "{count, plural, one {Tag removed from # email} other {Tag removed from # emails}}"
"tag_removed_count": "{count, plural, one {Tag removed from # email} other {Tag removed from # emails}}",
"new_mail_count": "{count, plural, one {# new message} other {# new messages}}"
},
"third_party": {
"push_needs_relay": "Push notifications need a push relay run by your organisation, and none is set up. New mail still shows while the app is open.",
@@ -4477,7 +4478,8 @@
"task_due": "Task due",
"event_exported": "Event exported",
"title_copied": "Title copied",
"link_copied": "Link copied"
"link_copied": "Link copied",
"title_alert": "Reminder: {title}"
},
"status": {
"loading_calendars": "Loading calendars…",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# correo marcado como leído} other {# correos marcados como leídos}}",
"emails_marked_unread": "{count, plural, one {# correo marcado como no leído} other {# correos marcados como no leídos}}",
"tag_removed": "Etiqueta quitada",
"tag_removed_count": "{count, plural, one {Etiqueta quitada de # correo} other {Etiqueta quitada de # correos}}"
"tag_removed_count": "{count, plural, one {Etiqueta quitada de # correo} other {Etiqueta quitada de # correos}}",
"new_mail_count": "{count, plural, one {# mensaje nuevo} other {# mensajes nuevos}}"
},
"third_party": {
"push_needs_relay": "Las notificaciones push necesitan un relay push gestionado por tu organización, y no hay ninguno configurado. El correo nuevo se sigue mostrando mientras la aplicación está abierta.",
@@ -4451,7 +4452,8 @@
"task_due": "Tarea vencida",
"event_exported": "Evento exportado",
"title_copied": "Título copiado",
"link_copied": "Enlace copiado"
"link_copied": "Enlace copiado",
"title_alert": "Recordatorio: {title}"
},
"status": {
"loading_calendars": "Cargando calendarios...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# kiri märgiti loetuks} other {# kirja märgiti loetuks}}",
"emails_marked_unread": "{count, plural, one {# kiri märgiti lugemata} other {# kirja märgiti lugemata}}",
"tag_removed": "Silt eemaldati",
"tag_removed_count": "{count, plural, one {Silt eemaldati # kirjalt} other {Silt eemaldati # kirjalt}}"
"tag_removed_count": "{count, plural, one {Silt eemaldati # kirjalt} other {Silt eemaldati # kirjalt}}",
"new_mail_count": "{count, plural, one {# uus kiri} other {# uut kirja}}"
},
"third_party": {
"push_needs_relay": "Tõuketeavitused vajavad sinu organisatsiooni hallatavat push-releed, kuid ühtegi pole seadistatud. Uued kirjad kuvatakse endiselt, kuni rakendus on avatud.",
@@ -4477,7 +4478,8 @@
"task_due": "Ülesande tähtaeg",
"event_exported": "Sündmus eksporditud",
"title_copied": "Pealkiri kopeeritud",
"link_copied": "Link kopeeritud"
"link_copied": "Link kopeeritud",
"title_alert": "Meeldetuletus: {title}"
},
"status": {
"loading_calendars": "Kalendrite laadimine...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, other {# ایمیل خوانده شده علامت‌گذاری شد}}",
"emails_marked_unread": "{count, plural, other {# ایمیل خوانده نشده علامت‌گذاری شد}}",
"tag_removed": "برچسب برداشته شد",
"tag_removed_count": "{count, plural, other {برچسب از # ایمیل برداشته شد}}"
"tag_removed_count": "{count, plural, other {برچسب از # ایمیل برداشته شد}}",
"new_mail_count": "{count, plural, other {# پیام جدید}}"
},
"third_party": {
"push_needs_relay": "اعلان‌های push به یک رلهٔ push نیاز دارند که سازمان شما اداره کند، و هیچ‌کدام راه‌اندازی نشده است. تا وقتی برنامه باز است، ایمیل‌های جدید همچنان نمایش داده می‌شوند.",
@@ -4477,7 +4478,8 @@
"task_due": "موعد وظیفه",
"event_exported": "رویداد خروجی گرفته شد",
"title_copied": "عنوان کپی شد",
"link_copied": "لینک کپی شد"
"link_copied": "لینک کپی شد",
"title_alert": "یادآوری: {title}"
},
"status": {
"loading_calendars": "در حال بارگذاری تقویم‌ها...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# e-mail marqué comme lu} other {# e-mails marqués comme lus}}",
"emails_marked_unread": "{count, plural, one {# e-mail marqué comme non lu} other {# e-mails marqués comme non lus}}",
"tag_removed": "Étiquette retirée",
"tag_removed_count": "{count, plural, one {Étiquette retirée de # e-mail} other {Étiquette retirée de # e-mails}}"
"tag_removed_count": "{count, plural, one {Étiquette retirée de # e-mail} other {Étiquette retirée de # e-mails}}",
"new_mail_count": "{count, plural, one {# nouveau message} other {# nouveaux messages}}"
},
"third_party": {
"push_needs_relay": "Les notifications push nécessitent un relais push géré par votre organisation, et aucun n'est configuré. Les nouveaux messages s'affichent toujours tant que l'application est ouverte.",
@@ -4477,7 +4478,8 @@
"task_due": "Tâche à échéance",
"event_exported": "Événement exporté",
"title_copied": "Titre copié",
"link_copied": "Lien copié"
"link_copied": "Lien copié",
"title_alert": "Rappel : {title}"
},
"status": {
"loading_calendars": "Chargement des calendriers...",
+4 -2
View File
@@ -1131,7 +1131,8 @@
"emails_marked_read": "{count, plural, one {הודעה אחת סומנה כנקראה} other {# הודעות סומנו כנקראו}}",
"emails_marked_unread": "{count, plural, one {הודעה אחת סומנה כלא נקראה} other {# הודעות סומנו כלא נקראו}}",
"tag_removed": "התגית הוסרה",
"tag_removed_count": "{count, plural, one {התגית הוסרה מהודעה אחת} other {התגית הוסרה מ-# הודעות}}"
"tag_removed_count": "{count, plural, one {התגית הוסרה מהודעה אחת} other {התגית הוסרה מ-# הודעות}}",
"new_mail_count": "{count, plural, one {הודעה חדשה אחת} other {# הודעות חדשות}}"
},
"third_party": {
"push_needs_relay": "התראות Push דורשות ממסר Push שהארגון שלך מפעיל, ולא הוגדר אף אחד. דואר חדש עדיין מוצג כל עוד האפליקציה פתוחה.",
@@ -4369,7 +4370,8 @@
"task_due": "משימה בשל",
"event_exported": "אירוע ייצא",
"title_copied": "כותרת הועתקה",
"link_copied": "קישור הועתק"
"link_copied": "קישור הועתק",
"title_alert": "תזכורת: {title}"
},
"status": {
"loading_calendars": "טוען יומנים...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, other {# e-mail olvasottnak jelölve}}",
"emails_marked_unread": "{count, plural, other {# e-mail olvasatlannak jelölve}}",
"tag_removed": "Címke eltávolítva",
"tag_removed_count": "{count, plural, other {Címke eltávolítva # e-mailről}}"
"tag_removed_count": "{count, plural, other {Címke eltávolítva # e-mailről}}",
"new_mail_count": "{count, plural, other {# új üzenet}}"
},
"third_party": {
"push_needs_relay": "A push-értesítésekhez a szervezeted által üzemeltetett push relé kell, és nincs beállítva egy sem. Az új levelek továbbra is megjelennek, amíg az alkalmazás nyitva van.",
@@ -4477,7 +4478,8 @@
"task_due": "Feladat határidős",
"event_exported": "Esemény exportálva",
"title_copied": "Cím másolva",
"link_copied": "Link másolva"
"link_copied": "Link másolva",
"title_alert": "Emlékeztető: {title}"
},
"status": {
"loading_calendars": "Naptárak betöltése...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# email segnata come letta} other {# email segnate come lette}}",
"emails_marked_unread": "{count, plural, one {# email segnata come non letta} other {# email segnate come non lette}}",
"tag_removed": "Etichetta rimossa",
"tag_removed_count": "{count, plural, one {Etichetta rimossa da # email} other {Etichetta rimossa da # email}}"
"tag_removed_count": "{count, plural, one {Etichetta rimossa da # email} other {Etichetta rimossa da # email}}",
"new_mail_count": "{count, plural, one {# nuovo messaggio} other {# nuovi messaggi}}"
},
"third_party": {
"push_needs_relay": "Le notifiche push richiedono un relay push gestito dalla tua organizzazione, e non ne è configurato nessuno. La nuova posta compare comunque finché l'app è aperta.",
@@ -4451,7 +4452,8 @@
"task_due": "Attività in scadenza",
"event_exported": "Evento esportato",
"title_copied": "Titolo copiato",
"link_copied": "Link copiato"
"link_copied": "Link copiato",
"title_alert": "Promemoria: {title}"
},
"status": {
"loading_calendars": "Caricamento calendari...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, other {#件のメールを既読にしました}}",
"emails_marked_unread": "{count, plural, other {#件のメールを未読にしました}}",
"tag_removed": "タグを外しました",
"tag_removed_count": "{count, plural, other {#件のメールからタグを外しました}}"
"tag_removed_count": "{count, plural, other {#件のメールからタグを外しました}}",
"new_mail_count": "{count, plural, other {新着メッセージ #件}}"
},
"third_party": {
"push_needs_relay": "プッシュ通知には組織が運用するプッシュリレーが必要ですが、設定されていません。アプリを開いている間は、新着メールが引き続き表示されます。",
@@ -4451,7 +4452,8 @@
"task_due": "タスクの期限です",
"event_exported": "イベントをエクスポートしました",
"title_copied": "タイトルをコピーしました",
"link_copied": "リンクをコピーしました"
"link_copied": "リンクをコピーしました",
"title_alert": "リマインダー: {title}"
},
"status": {
"loading_calendars": "カレンダーを読み込み中...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, other {메일 #개를 읽음으로 표시했어요}}",
"emails_marked_unread": "{count, plural, other {메일 #개를 읽지 않음으로 표시했어요}}",
"tag_removed": "태그를 뺐어요",
"tag_removed_count": "{count, plural, other {메일 #개에서 태그를 뺐어요}}"
"tag_removed_count": "{count, plural, other {메일 #개에서 태그를 뺐어요}}",
"new_mail_count": "{count, plural, other {새 메시지 #개}}"
},
"third_party": {
"push_needs_relay": "푸시 알림을 받으려면 조직에서 운영하는 푸시 릴레이가 필요한데, 설정된 릴레이가 없어요. 앱이 열려 있는 동안에는 새 메일이 계속 표시돼요.",
@@ -4451,7 +4452,8 @@
"task_due": "할 일 기한이 다가와요",
"event_exported": "이벤트 내보내기 완료",
"title_copied": "제목이 복사되었습니다",
"link_copied": "링크가 복사되었습니다"
"link_copied": "링크가 복사되었습니다",
"title_alert": "알림: {title}"
},
"status": {
"loading_calendars": "캘린더를 불러오는 중...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, zero {# vēstules atzīmētas kā izlasītas} one {# vēstule atzīmēta kā izlasīta} other {# vēstules atzīmētas kā izlasītas}}",
"emails_marked_unread": "{count, plural, zero {# vēstules atzīmētas kā neizlasītas} one {# vēstule atzīmēta kā neizlasīta} other {# vēstules atzīmētas kā neizlasītas}}",
"tag_removed": "Tags noņemts",
"tag_removed_count": "{count, plural, zero {Tags noņemts # vēstulēm} one {Tags noņemts # vēstulei} other {Tags noņemts # vēstulēm}}"
"tag_removed_count": "{count, plural, zero {Tags noņemts # vēstulēm} one {Tags noņemts # vēstulei} other {Tags noņemts # vēstulēm}}",
"new_mail_count": "{count, plural, zero {# jaunas vēstules} one {# jauna vēstule} other {# jaunas vēstules}}"
},
"third_party": {
"push_needs_relay": "Push paziņojumiem vajadzīgs jūsu organizācijas uzturēts push relejs, un neviens nav iestatīts. Jauns pasts joprojām tiek rādīts, kamēr lietotne ir atvērta.",
@@ -4450,7 +4451,8 @@
"task_due": "Uzdevuma termiņš",
"event_exported": "Notikums eksportēts",
"title_copied": "Nosaukums nokopēts",
"link_copied": "Saite nokopēta"
"link_copied": "Saite nokopēta",
"title_alert": "Atgādinājums: {title}"
},
"status": {
"loading_calendars": "Ielādē kalendārus...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, other {# имэйлийг уншсан гэж тэмдэглэлээ}}",
"emails_marked_unread": "{count, plural, other {# имэйлийг уншаагүй гэж тэмдэглэлээ}}",
"tag_removed": "Хаяг хасагдлаа",
"tag_removed_count": "{count, plural, other {# имэйлээс хаяг хасагдлаа}}"
"tag_removed_count": "{count, plural, other {# имэйлээс хаяг хасагдлаа}}",
"new_mail_count": "{count, plural, other {# шинэ зурвас}}"
},
"third_party": {
"push_needs_relay": "Push мэдэгдэлд таны байгууллагын ажиллуулдаг push буухиа хэрэгтэй, гэвч нэг ч тохируулагдаагүй байна. Апп нээлттэй байх үед шинэ имэйл харагдсаар байна.",
@@ -4477,7 +4478,8 @@
"task_due": "Даалгавар дуусах хугацаа",
"event_exported": "Үйл явдал экспортлогдсон",
"title_copied": "Гарчиг хуулсан",
"link_copied": "Холбоос хуулсан"
"link_copied": "Холбоос хуулсан",
"title_alert": "Сануулга: {title}"
},
"status": {
"loading_calendars": "Хуанли ачаалж байна...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# e-post merket som lest} other {# e-poster merket som lest}}",
"emails_marked_unread": "{count, plural, one {# e-post merket som ulest} other {# e-poster merket som ulest}}",
"tag_removed": "Etikett fjernet",
"tag_removed_count": "{count, plural, one {Etikett fjernet fra # e-post} other {Etikett fjernet fra # e-poster}}"
"tag_removed_count": "{count, plural, one {Etikett fjernet fra # e-post} other {Etikett fjernet fra # e-poster}}",
"new_mail_count": "{count, plural, one {# ny melding} other {# nye meldinger}}"
},
"third_party": {
"push_needs_relay": "Push-varsler trenger et push-relé som organisasjonen din driver, og det er ikke satt opp noe. Ny e-post vises fortsatt mens appen er åpen.",
@@ -4477,7 +4478,8 @@
"task_due": "Oppgaven forfaller",
"event_exported": "Hendelsen er eksportert",
"title_copied": "Tittelen er kopiert",
"link_copied": "Lenken er kopiert"
"link_copied": "Lenken er kopiert",
"title_alert": "Påminnelse: {title}"
},
"status": {
"loading_calendars": "Laster inn kalendere...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# e-mail gemarkeerd als gelezen} other {# e-mails gemarkeerd als gelezen}}",
"emails_marked_unread": "{count, plural, one {# e-mail gemarkeerd als ongelezen} other {# e-mails gemarkeerd als ongelezen}}",
"tag_removed": "Label verwijderd",
"tag_removed_count": "{count, plural, one {Label verwijderd van # e-mail} other {Label verwijderd van # e-mails}}"
"tag_removed_count": "{count, plural, one {Label verwijderd van # e-mail} other {Label verwijderd van # e-mails}}",
"new_mail_count": "{count, plural, one {# nieuw bericht} other {# nieuwe berichten}}"
},
"third_party": {
"push_needs_relay": "Pushmeldingen hebben een pushrelay van je organisatie nodig, en er is er geen ingesteld. Nieuwe e-mail wordt nog steeds getoond zolang de app open is.",
@@ -4451,7 +4452,8 @@
"task_due": "Taak vervalt",
"event_exported": "Afspraak geëxporteerd",
"title_copied": "Titel gekopieerd",
"link_copied": "Link gekopieerd"
"link_copied": "Link gekopieerd",
"title_alert": "Herinnering: {title}"
},
"status": {
"loading_calendars": "Agenda's laden...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# wiadomość oznaczona jako przeczytana} few {# wiadomości oznaczone jako przeczytane} many {# wiadomości oznaczonych jako przeczytane} other {# wiadomości oznaczonych jako przeczytane}}",
"emails_marked_unread": "{count, plural, one {# wiadomość oznaczona jako nieprzeczytana} few {# wiadomości oznaczone jako nieprzeczytane} many {# wiadomości oznaczonych jako nieprzeczytane} other {# wiadomości oznaczonych jako nieprzeczytane}}",
"tag_removed": "Etykieta usunięta",
"tag_removed_count": "{count, plural, one {Usunięto etykietę z # wiadomości} few {Usunięto etykietę z # wiadomości} many {Usunięto etykietę z # wiadomości} other {Usunięto etykietę z # wiadomości}}"
"tag_removed_count": "{count, plural, one {Usunięto etykietę z # wiadomości} few {Usunięto etykietę z # wiadomości} many {Usunięto etykietę z # wiadomości} other {Usunięto etykietę z # wiadomości}}",
"new_mail_count": "{count, plural, one {# nowa wiadomość} few {# nowe wiadomości} many {# nowych wiadomości} other {# nowych wiadomości}}"
},
"third_party": {
"push_needs_relay": "Powiadomienia push wymagają przekaźnika push prowadzonego przez Twoją organizację, a żaden nie jest skonfigurowany. Nowe wiadomości nadal pojawiają się, gdy aplikacja jest otwarta.",
@@ -4451,7 +4452,8 @@
"task_due": "Termin zadania",
"event_exported": "Wydarzenie wyeksportowane",
"title_copied": "Tytuł skopiowany",
"link_copied": "Link skopiowany"
"link_copied": "Link skopiowany",
"title_alert": "Przypomnienie: {title}"
},
"status": {
"loading_calendars": "Ładowanie kalendarzy...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# e-mail marcado como lido} other {# e-mails marcados como lidos}}",
"emails_marked_unread": "{count, plural, one {# e-mail marcado como não lido} other {# e-mails marcados como não lidos}}",
"tag_removed": "Etiqueta removida",
"tag_removed_count": "{count, plural, one {Etiqueta removida de # e-mail} other {Etiqueta removida de # e-mails}}"
"tag_removed_count": "{count, plural, one {Etiqueta removida de # e-mail} other {Etiqueta removida de # e-mails}}",
"new_mail_count": "{count, plural, one {# mensagem nova} other {# mensagens novas}}"
},
"third_party": {
"push_needs_relay": "As notificações push precisam de um relay push mantido pela sua organização, e nenhum está configurado. Os novos e-mails continuam aparecendo enquanto o app estiver aberto.",
@@ -4477,7 +4478,8 @@
"task_due": "Tarefa vencendo",
"event_exported": "Evento exportado",
"title_copied": "Título copiado",
"link_copied": "Link copiado"
"link_copied": "Link copiado",
"title_alert": "Lembrete: {title}"
},
"status": {
"loading_calendars": "Carregando calendários...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# e-mail marcat ca citit} few {# e-mailuri marcate ca citite} other {# de e-mailuri marcate ca citite}}",
"emails_marked_unread": "{count, plural, one {# e-mail marcat ca necitit} few {# e-mailuri marcate ca necitite} other {# de e-mailuri marcate ca necitite}}",
"tag_removed": "Etichetă eliminată",
"tag_removed_count": "{count, plural, one {Eticheta a fost eliminată de pe # e-mail} few {Eticheta a fost eliminată de pe # e-mailuri} other {Eticheta a fost eliminată de pe # de e-mailuri}}"
"tag_removed_count": "{count, plural, one {Eticheta a fost eliminată de pe # e-mail} few {Eticheta a fost eliminată de pe # e-mailuri} other {Eticheta a fost eliminată de pe # de e-mailuri}}",
"new_mail_count": "{count, plural, one {# mesaj nou} few {# mesaje noi} other {# de mesaje noi}}"
},
"third_party": {
"push_needs_relay": "Notificările push au nevoie de un releu push administrat de organizația dvs., iar niciunul nu este configurat. E-mailurile noi apar în continuare cât timp aplicația este deschisă.",
@@ -4477,7 +4478,8 @@
"task_due": "Termen de finalizare a sarcinii",
"event_exported": "Eveniment exportat",
"title_copied": "Titlu copiat",
"link_copied": "Link copiat"
"link_copied": "Link copiat",
"title_alert": "Memento: {title}"
},
"status": {
"loading_calendars": "Se încarcă calendarele...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# письмо отмечено прочитанным} few {# письма отмечены прочитанными} many {# писем отмечено прочитанными} other {# письма отмечено прочитанными}}",
"emails_marked_unread": "{count, plural, one {# письмо отмечено непрочитанным} few {# письма отмечены непрочитанными} many {# писем отмечено непрочитанными} other {# письма отмечено непрочитанными}}",
"tag_removed": "Тег снят",
"tag_removed_count": "{count, plural, one {Тег снят с # письма} few {Тег снят с # писем} many {Тег снят с # писем} other {Тег снят с # письма}}"
"tag_removed_count": "{count, plural, one {Тег снят с # письма} few {Тег снят с # писем} many {Тег снят с # писем} other {Тег снят с # письма}}",
"new_mail_count": "{count, plural, one {# новое сообщение} few {# новых сообщения} many {# новых сообщений} other {# новых сообщения}}"
},
"third_party": {
"push_needs_relay": "Для push-уведомлений нужен push-релей вашей организации, а он не настроен. Новые письма по-прежнему видны, пока приложение открыто.",
@@ -4451,7 +4452,8 @@
"task_due": "Срок задачи",
"event_exported": "Событие экспортировано",
"title_copied": "Название скопировано",
"link_copied": "Ссылка скопирована"
"link_copied": "Ссылка скопирована",
"title_alert": "Напоминание: {title}"
},
"status": {
"loading_calendars": "Загрузка календарей...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# e-mail označený ako prečítaný} few {# e-maily označené ako prečítané} other {# e-mailov označených ako prečítané}}",
"emails_marked_unread": "{count, plural, one {# e-mail označený ako neprečítaný} few {# e-maily označené ako neprečítané} other {# e-mailov označených ako neprečítané}}",
"tag_removed": "Štítok odstránený",
"tag_removed_count": "{count, plural, one {Štítok odstránený z # e-mailu} few {Štítok odstránený z # e-mailov} other {Štítok odstránený z # e-mailov}}"
"tag_removed_count": "{count, plural, one {Štítok odstránený z # e-mailu} few {Štítok odstránený z # e-mailov} other {Štítok odstránený z # e-mailov}}",
"new_mail_count": "{count, plural, one {# nová správa} few {# nové správy} other {# nových správ}}"
},
"third_party": {
"push_needs_relay": "Push oznámenia potrebujú push relay prevádzkovaný vašou organizáciou a žiadny nie je nastavený. Nová pošta sa naďalej zobrazuje, kým je aplikácia otvorená.",
@@ -4477,7 +4478,8 @@
"task_due": "Termín úlohy",
"event_exported": "Udalosť exportovaná",
"title_copied": "Názov skopírovaný",
"link_copied": "Odkaz skopírovaný"
"link_copied": "Odkaz skopírovaný",
"title_alert": "Pripomienka: {title}"
},
"status": {
"loading_calendars": "Načítavanie kalendárov...",
+4 -2
View File
@@ -525,7 +525,8 @@
"emails_marked_read": "{count, plural, other {ทำเครื่องหมายอีเมล # ฉบับว่าอ่านแล้ว}}",
"emails_marked_unread": "{count, plural, other {ทำเครื่องหมายอีเมล # ฉบับว่ายังไม่ได้อ่าน}}",
"tag_removed": "นำป้ายกำกับออกแล้ว",
"tag_removed_count": "{count, plural, other {นำป้ายกำกับออกจากอีเมล # ฉบับแล้ว}}"
"tag_removed_count": "{count, plural, other {นำป้ายกำกับออกจากอีเมล # ฉบับแล้ว}}",
"new_mail_count": "{count, plural, other {ข้อความใหม่ # ฉบับ}}"
},
"third_party": {
"push_needs_relay": "การแจ้งเตือนแบบพุชต้องใช้ push relay ที่องค์กรของคุณดูแล แต่ยังไม่ได้ตั้งค่าไว้ อีเมลใหม่จะยังแสดงขณะที่เปิดแอปอยู่",
@@ -3423,7 +3424,8 @@
"task_due": "ถึงกำหนดส่งงาน",
"event_exported": "ส่งออกกิจกรรมแล้ว",
"title_copied": "คัดลอกชื่อเรื่องแล้ว",
"link_copied": "คัดลอกลิงก์แล้ว"
"link_copied": "คัดลอกลิงก์แล้ว",
"title_alert": "การเตือน: {title}"
},
"status": {
"loading_calendars": "กำลังโหลดปฏิทิน...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, other {# e-posta okundu olarak işaretlendi}}",
"emails_marked_unread": "{count, plural, other {# e-posta okunmadı olarak işaretlendi}}",
"tag_removed": "Etiket kaldırıldı",
"tag_removed_count": "{count, plural, other {# e-postadan etiket kaldırıldı}}"
"tag_removed_count": "{count, plural, other {# e-postadan etiket kaldırıldı}}",
"new_mail_count": "{count, plural, other {# yeni ileti}}"
},
"third_party": {
"push_needs_relay": "Anlık bildirimler için kuruluşunuzun işlettiği bir push rölesi gerekir ve hiçbiri ayarlanmamış. Uygulama açıkken yeni postalar yine de görünür.",
@@ -4477,7 +4478,8 @@
"task_due": "Görev süresi doldu",
"event_exported": "Etkinlik dışa aktarıldı",
"title_copied": "Başlık kopyalandı",
"link_copied": "Bağlantı kopyalandı"
"link_copied": "Bağlantı kopyalandı",
"title_alert": "Hatırlatma: {title}"
},
"status": {
"loading_calendars": "Takvimler yükleniyor...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, one {# лист позначено прочитаним} few {# листи позначено прочитаними} many {# листів позначено прочитаними} other {# листа позначено прочитаними}}",
"emails_marked_unread": "{count, plural, one {# лист позначено непрочитаним} few {# листи позначено непрочитаними} many {# листів позначено непрочитаними} other {# листа позначено непрочитаними}}",
"tag_removed": "Тег знято",
"tag_removed_count": "{count, plural, one {Тег знято з # листа} few {Тег знято з # листів} many {Тег знято з # листів} other {Тег знято з # листа}}"
"tag_removed_count": "{count, plural, one {Тег знято з # листа} few {Тег знято з # листів} many {Тег знято з # листів} other {Тег знято з # листа}}",
"new_mail_count": "{count, plural, one {# нове повідомлення} few {# нові повідомлення} many {# нових повідомлень} other {# нового повідомлення}}"
},
"third_party": {
"push_needs_relay": "Для push-сповіщень потрібне push-реле вашої організації, а його не налаштовано. Нові листи й далі з'являються, поки застосунок відкритий.",
@@ -4451,7 +4452,8 @@
"task_due": "Термін виконання завдання",
"event_exported": "Подію експортовано",
"title_copied": "Назву скопійовано",
"link_copied": "Посилання скопійовано"
"link_copied": "Посилання скопійовано",
"title_alert": "Нагадування: {title}"
},
"status": {
"loading_calendars": "Завантаження календарів...",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, other {已將 # 封郵件標示為已讀}}",
"emails_marked_unread": "{count, plural, other {已將 # 封郵件標示為未讀}}",
"tag_removed": "已移除標籤",
"tag_removed_count": "{count, plural, other {已從 # 封郵件移除標籤}}"
"tag_removed_count": "{count, plural, other {已從 # 封郵件移除標籤}}",
"new_mail_count": "{count, plural, other {# 封新郵件}}"
},
"third_party": {
"push_needs_relay": "推送通知需要由您的組織執行的推送中繼,但尚未設定。應用程式開啟時,新郵件仍會顯示。",
@@ -4477,7 +4478,8 @@
"task_due": "任務到期",
"event_exported": "活動已匯出",
"title_copied": "標題已複製",
"link_copied": "連結已複製"
"link_copied": "連結已複製",
"title_alert": "提醒:{title}"
},
"status": {
"loading_calendars": "正在載入行事曆…",
+4 -2
View File
@@ -1166,7 +1166,8 @@
"emails_marked_read": "{count, plural, other {已将 # 封邮件标记为已读}}",
"emails_marked_unread": "{count, plural, other {已将 # 封邮件标记为未读}}",
"tag_removed": "已移除标签",
"tag_removed_count": "{count, plural, other {已从 # 封邮件移除标签}}"
"tag_removed_count": "{count, plural, other {已从 # 封邮件移除标签}}",
"new_mail_count": "{count, plural, other {# 封新邮件}}"
},
"third_party": {
"push_needs_relay": "推送通知需要由您的组织运行的推送中继,但尚未设置。应用打开时,新邮件仍会显示。",
@@ -4451,7 +4452,8 @@
"task_due": "任务到期",
"event_exported": "日程已导出",
"title_copied": "标题已复制",
"link_copied": "链接已复制"
"link_copied": "链接已复制",
"title_alert": "提醒:{title}"
},
"status": {
"loading_calendars": "正在加载日历...",