diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx index 5a15aaff6..b8592cdfa 100644 --- a/components/mail/mail-app.tsx +++ b/components/mail/mail-app.tsx @@ -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(() => { diff --git a/hooks/__tests__/use-new-mail-alerts.test.tsx b/hooks/__tests__/use-new-mail-alerts.test.tsx new file mode 100644 index 000000000..c98b51dca --- /dev/null +++ b/hooks/__tests__/use-new-mail-alerts.test.tsx @@ -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) => + 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'); + }); +}); diff --git a/hooks/use-calendar-alerts.ts b/hooks/use-calendar-alerts.ts index 580371a10..4735ef116 100644 --- a/hooks/use-calendar-alerts.ts +++ b/hooks/use-calendar-alerts.ts @@ -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'), diff --git a/hooks/use-document-title.ts b/hooks/use-document-title.ts index f91593177..7cb10946d 100644 --- a/hooks/use-document-title.ts +++ b/hooks/use-document-title.ts @@ -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; diff --git a/hooks/use-new-mail-alerts.ts b/hooks/use-new-mail-alerts.ts new file mode 100644 index 000000000..0b118372c --- /dev/null +++ b/hooks/use-new-mail-alerts.ts @@ -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 | 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]); +} diff --git a/lib/__tests__/title-alert.test.ts b/lib/__tests__/title-alert.test.ts new file mode 100644 index 000000000..c69c386fe --- /dev/null +++ b/lib/__tests__/title-alert.test.ts @@ -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'); + }); +}); diff --git a/lib/__tests__/toast-duration.test.ts b/lib/__tests__/toast-duration.test.ts index 4de9f10eb..54dbf8b0d 100644 --- a/lib/__tests__/toast-duration.test.ts +++ b/lib/__tests__/toast-duration.test.ts @@ -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(); diff --git a/lib/title-alert.ts b/lib/title-alert.ts new file mode 100644 index 000000000..87aa247e8 --- /dev/null +++ b/lib/title-alert.ts @@ -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 | 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; +} diff --git a/locales/ar/common.json b/locales/ar/common.json index 691c599a2..77b7dc12b 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -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": "جارٍ تحميل التقويمات...", diff --git a/locales/ca/common.json b/locales/ca/common.json index a89a99cf4..264baa4b4 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -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...", diff --git a/locales/cs/common.json b/locales/cs/common.json index 5da3e886f..dc6e8d84c 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -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ářů...", diff --git a/locales/da/common.json b/locales/da/common.json index a22fba1fb..5dd1f4a8e 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -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...", diff --git a/locales/de/common.json b/locales/de/common.json index 877309c0b..6b0ee8aa5 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -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...", diff --git a/locales/en/common.json b/locales/en/common.json index 9e570d032..c7bc228d5 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -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…", diff --git a/locales/es/common.json b/locales/es/common.json index 60e182ac7..8c3ab6d0c 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -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...", diff --git a/locales/et/common.json b/locales/et/common.json index 51c641869..2ff585e01 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -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...", diff --git a/locales/fa/common.json b/locales/fa/common.json index 6be635575..7a6dc1951 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -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": "در حال بارگذاری تقویم‌ها...", diff --git a/locales/fr/common.json b/locales/fr/common.json index 7b2b9f972..85c690fdb 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -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...", diff --git a/locales/he/common.json b/locales/he/common.json index e024e82ac..b3415a375 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -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": "טוען יומנים...", diff --git a/locales/hu/common.json b/locales/hu/common.json index 553cd9d15..4876aaedd 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -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...", diff --git a/locales/it/common.json b/locales/it/common.json index 7fa866710..8f343a2a3 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -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...", diff --git a/locales/ja/common.json b/locales/ja/common.json index a244e6bbf..c918b182e 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -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": "カレンダーを読み込み中...", diff --git a/locales/ko/common.json b/locales/ko/common.json index e626e9868..924294e8c 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -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": "캘린더를 불러오는 중...", diff --git a/locales/lv/common.json b/locales/lv/common.json index 25790a5c9..a24012dff 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -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...", diff --git a/locales/mn/common.json b/locales/mn/common.json index 340a29f9d..e3315be43 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -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": "Хуанли ачаалж байна...", diff --git a/locales/nb/common.json b/locales/nb/common.json index 064459686..dbaf51b61 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -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...", diff --git a/locales/nl/common.json b/locales/nl/common.json index 6814a8318..2e15a847b 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -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...", diff --git a/locales/pl/common.json b/locales/pl/common.json index e29750e17..18a979cf6 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -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...", diff --git a/locales/pt/common.json b/locales/pt/common.json index 43b99f94a..e9c47187e 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -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...", diff --git a/locales/ro/common.json b/locales/ro/common.json index 836b8775b..6aa23725a 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -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...", diff --git a/locales/ru/common.json b/locales/ru/common.json index 76ebd56fa..d23d50d01 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -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": "Загрузка календарей...", diff --git a/locales/sk/common.json b/locales/sk/common.json index fcf4678a1..6a303fad2 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -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...", diff --git a/locales/th/common.json b/locales/th/common.json index d435bd0f6..15146f45e 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -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": "กำลังโหลดปฏิทิน...", diff --git a/locales/tr/common.json b/locales/tr/common.json index dc781390f..2a6832682 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -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...", diff --git a/locales/uk/common.json b/locales/uk/common.json index 51ec4f88a..a3c51b83a 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -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": "Завантаження календарів...", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index d9138a6a7..bed7908ab 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -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": "正在載入行事曆…", diff --git a/locales/zh/common.json b/locales/zh/common.json index cf514d5bf..5ef64acfa 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -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": "正在加载日历...",