From e468b08fb718f05b2f054716e5b4a40441fa7b7a Mon Sep 17 00:00:00 2001 From: lucamzanon Date: Sat, 19 Sep 2026 05:33:10 +0200 Subject: [PATCH] feat: enable push notifications for several accounts at once The notification panel only ever addresses the account on screen, so arming a browser for five accounts meant switching account five times and answering the same permission prompt on each pass. The browser holds a single push subscription: enabling an account registers it against that one subscription rather than creating another. A section in the notification settings now lists every connected account with its current state and turns push on or off for the ones selected, walking them against the same subscription. A refusal at the browser level - unsupported, or permission denied - applies to every account, so it stops the walk instead of failing each account in turn and prompting repeatedly. --- components/settings/multi-account-push.tsx | 111 ++++++++++++++++++ components/settings/notification-settings.tsx | 3 + lib/__tests__/web-push.test.ts | 38 ++++++ lib/web-push.ts | 51 ++++++++ locales/ar/common.json | 12 ++ locales/ca/common.json | 12 ++ locales/cs/common.json | 12 ++ locales/da/common.json | 12 ++ locales/de/common.json | 12 ++ locales/en/common.json | 12 ++ locales/es/common.json | 12 ++ locales/fa/common.json | 12 ++ locales/fr/common.json | 12 ++ locales/he/common.json | 12 ++ locales/hu/common.json | 12 ++ locales/it/common.json | 12 ++ locales/ja/common.json | 12 ++ locales/ko/common.json | 12 ++ locales/lv/common.json | 12 ++ locales/mn/common.json | 12 ++ locales/nb/common.json | 12 ++ locales/nl/common.json | 12 ++ locales/pl/common.json | 12 ++ locales/pt/common.json | 12 ++ locales/ro/common.json | 12 ++ locales/ru/common.json | 12 ++ locales/sk/common.json | 12 ++ locales/tr/common.json | 12 ++ locales/uk/common.json | 12 ++ locales/zh-TW/common.json | 12 ++ locales/zh/common.json | 12 ++ 31 files changed, 527 insertions(+) create mode 100644 components/settings/multi-account-push.tsx diff --git a/components/settings/multi-account-push.tsx b/components/settings/multi-account-push.tsx new file mode 100644 index 000000000..fb0038d20 --- /dev/null +++ b/components/settings/multi-account-push.tsx @@ -0,0 +1,111 @@ +'use client'; + +import { useCallback, useEffect, useState } from 'react'; +import { useTranslations } from 'next-intl'; +import { Loader2 } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { SettingsSection } from './settings-section'; +import { useAccountStore } from '@/stores/account-store'; +import { useAuthStore } from '@/stores/auth-store'; +import { disableWebPush, enableWebPushForAccounts, isWebPushEnabled, isWebPushSupported } from '@/lib/web-push'; + +/** + * Turn push on or off for several accounts at once. + * + * The per-account panel above only ever addresses the account on screen, so + * arming a fresh device used to mean switching account N times. One browser + * subscription already serves every account (see `enableWebPushForAccounts`); + * what was missing was a place to register them all against it in one go. + */ +export function MultiAccountPushSettings({ relayBaseUrl }: { relayBaseUrl?: string }) { + const t = useTranslations('settings.notifications.push_all'); + const accounts = useAccountStore(s => s.accounts); + // Re-read the client map whenever a connection appears or drops. + const revision = useAuthStore(s => s.connectedAccountsRevision); + const [enabled, setEnabled] = useState>({}); + const [chosen, setChosen] = useState>(new Set()); + const [busy, setBusy] = useState(false); + const [notice, setNotice] = useState(''); + + const connected = accounts.filter(a => a.isConnected && useAuthStore.getState().getClientForAccount(a.id)); + + const refresh = useCallback(async () => { + const entries = await Promise.all( + useAccountStore.getState().accounts.map(async a => [a.id, await isWebPushEnabled(a.id)] as const), + ); + setEnabled(Object.fromEntries(entries)); + }, []); + + useEffect(() => { void refresh(); }, [refresh, revision]); + + if (!isWebPushSupported() || connected.length < 2) return null; + + const toggle = (id: string, on: boolean) => setChosen(prev => { + const next = new Set(prev); + if (on) next.add(id); else next.delete(id); + return next; + }); + + const run = async (action: 'enable' | 'disable') => { + const auth = useAuthStore.getState(); + const targets = connected.filter(a => chosen.has(a.id)); + if (!targets.length) return; + setBusy(true); setNotice(''); + try { + if (action === 'enable') { + const outcome = await enableWebPushForAccounts( + targets.map(a => ({ accountId: a.id, client: auth.getClientForAccount(a.id)!, accountLabel: a.username })), + { relayBaseUrl }, + ); + setNotice(outcome.failed.length + ? t('result_partial', { enabled: outcome.enabled.length, failed: outcome.failed.length }) + : t('result_enabled', { count: outcome.enabled.length })); + } else { + // Teardown swallows its own failures, so every account ends up local-off. + for (const account of targets) { + await disableWebPush({ client: auth.getClientForAccount(account.id)!, relayBaseUrl }); + } + setNotice(t('result_disabled', { count: targets.length })); + } + setChosen(new Set()); + } finally { + setBusy(false); + await refresh(); + } + }; + + const allChosen = connected.length > 0 && connected.every(a => chosen.has(a.id)); + + return +
+ +
    + {connected.map(account =>
  • + +
  • )} +
+
+ + +
+ {notice &&

{notice}

} +
+
; +} diff --git a/components/settings/notification-settings.tsx b/components/settings/notification-settings.tsx index 2864b3958..3170c8007 100644 --- a/components/settings/notification-settings.tsx +++ b/components/settings/notification-settings.tsx @@ -11,6 +11,7 @@ import { Loader2, RefreshCw, Volume2, XCircle } from 'lucide-react'; import { usePolicyStore } from '@/stores/policy-store'; import { useAuthStore } from '@/stores/auth-store'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; +import { MultiAccountPushSettings } from './multi-account-push'; import { useConfirmDialog } from '@/hooks/use-confirm-dialog'; import { WebPushUnsupportedError, @@ -251,6 +252,8 @@ export function NotificationSettings() { )} + + {client && (
diff --git a/lib/__tests__/web-push.test.ts b/lib/__tests__/web-push.test.ts index 369266b2a..02fc80183 100644 --- a/lib/__tests__/web-push.test.ts +++ b/lib/__tests__/web-push.test.ts @@ -4,6 +4,7 @@ import type { EmailPushConfig, PushSubscription as JmapPushSubscription } from ' import { disableWebPush, enableWebPush, + enableWebPushForAccounts, listPushDevices, resetWebPushResyncState, resyncWebPush, @@ -454,3 +455,40 @@ describe('revokePushDevice', () => { expect(localStorage.getItem(SUB_KEY)).toBeNull(); }); }); + +describe('enableWebPushForAccounts', () => { + it('registers every account against the one browser subscription', async () => { + const first = makeClient([]); + const second = makeClient([]); + installFetch({}); + + const result = await enableWebPushForAccounts([ + { accountId: 'a', client: first, accountLabel: 'one' }, + { accountId: 'b', client: second, accountLabel: 'two' }, + ], { relayBaseUrl: RELAY }); + + expect(result.enabled).toEqual(['a', 'b']); + expect(result.failed).toEqual([]); + expect(first.createPushSubscription).toHaveBeenCalledTimes(1); + expect(second.createPushSubscription).toHaveBeenCalledTimes(1); + }); + + it('stops at a browser-level refusal instead of prompting once per account', async () => { + const first = makeClient([]); + const second = makeClient([]); + installFetch({}); + // Blocked notifications are a property of the browser, not of an account: + // walking on would fail identically for every one of them. + (globalThis as unknown as { Notification: unknown }).Notification = { permission: 'denied' }; + (window as unknown as { Notification: unknown }).Notification = { permission: 'denied' }; + + const result = await enableWebPushForAccounts([ + { accountId: 'a', client: first }, + { accountId: 'b', client: second }, + ], { relayBaseUrl: RELAY }); + + expect(result.enabled).toEqual([]); + expect(result.failed.map(f => f.accountId)).toEqual(['a', 'b']); + expect(second.createPushSubscription).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/web-push.ts b/lib/web-push.ts index 973e1cd5f..ad4ab123d 100644 --- a/lib/web-push.ts +++ b/lib/web-push.ts @@ -695,3 +695,54 @@ export async function resyncWebPush(params: ResyncWebPushParams): Promise; +} + +/** + * Enable push for several accounts against one browser subscription. + * + * The browser only ever holds a single PushSubscription: `enableWebPush` reuses + * it and registers one server-side subscription per account, which is why every + * account has to be walked separately. Sequential on purpose - the first call + * is the one that may prompt for permission, and parallel prompts race. + * + * A browser-level refusal (unsupported, permission denied or dismissed) applies + * to every account, so it stops the walk instead of failing them one by one. + */ +export async function enableWebPushForAccounts( + targets: BulkWebPushTarget[], + options: { relayBaseUrl?: string; forceRecreate?: boolean } = {}, +): Promise { + const result: BulkWebPushResult = { enabled: [], failed: [] }; + let browserRefusal: unknown; + for (const target of targets) { + if (browserRefusal !== undefined) { + result.failed.push({ accountId: target.accountId, error: browserRefusal }); + continue; + } + try { + await enableWebPush({ + client: target.client, + relayBaseUrl: options.relayBaseUrl, + accountLabel: target.accountLabel, + forceRecreate: options.forceRecreate, + }); + result.enabled.push(target.accountId); + } catch (error) { + if (error instanceof WebPushUnsupportedError || !isWebPushSupported() + || Notification.permission !== 'granted') browserRefusal = error; + result.failed.push({ accountId: target.accountId, error }); + } + } + return result; +} diff --git a/locales/ar/common.json b/locales/ar/common.json index 53d4bf6d8..40027e47e 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -1183,6 +1183,18 @@ "confirm_revoke_message": "سيتوقّف فورًا عن تلقّي الإشعارات في الخلفية. تفعيل الدفع مجددًا على ذلك الجهاز سيسجّله من جديد.", "confirm_revoke_message_this": "سيتوقّف هذا الجهاز فورًا عن تلقّي الإشعارات في الخلفية." }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "صوت الإشعار", "description": "اختر الصوت الذي يُشغَّل للإشعارات", diff --git a/locales/ca/common.json b/locales/ca/common.json index 686039db0..0ce9f1c47 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -1183,6 +1183,18 @@ "confirm_revoke_message": "Deixarà de rebre notificacions en segon pla immediatament. Si hi tornes a activar les notificacions push, es registrarà de nou.", "confirm_revoke_message_this": "Aquest dispositiu deixarà de rebre notificacions en segon pla immediatament." }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "So de notificació", "description": "Trieu quin so es reprodueix per a les notificacions", diff --git a/locales/cs/common.json b/locales/cs/common.json index 5262ee69d..a5f64effd 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -1144,6 +1144,18 @@ "swift": "Rychlý", "relax": "Relaxační" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Zvuk oznámení", "description": "Vyberte zvuk, který se přehraje při oznámení", diff --git a/locales/da/common.json b/locales/da/common.json index 09552435b..15560af42 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -1183,6 +1183,18 @@ "confirm_revoke_message": "Den stopper med at modtage baggrundsnotifikationer med det samme. Slår du push til igen på enheden, registreres den på ny.", "confirm_revoke_message_this": "Denne enhed stopper med at modtage baggrundsnotifikationer med det samme." }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Notifikationslyd", "description": "Vælg hvilken lyd der skal afspilles for notifikationer", diff --git a/locales/de/common.json b/locales/de/common.json index 2d39bfc03..673beedd9 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -1144,6 +1144,18 @@ "swift": "Schnelle Geste", "relax": "Entspannt" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Benachrichtigungston", "description": "Wählen Sie den Ton für Benachrichtigungen", diff --git a/locales/en/common.json b/locales/en/common.json index 78a509e09..19346029c 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -1183,6 +1183,18 @@ "confirm_revoke_message": "It will stop receiving background notifications immediately. Enabling push again on that device registers it anew.", "confirm_revoke_message_this": "This device will stop receiving background notifications immediately." }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Notification Sound", "description": "Choose which sound to play for notifications", diff --git a/locales/es/common.json b/locales/es/common.json index ae2413a7f..85f70d57e 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -1144,6 +1144,18 @@ "swift": "Gesto rápido", "relax": "Relajado" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Sonido de notificación", "description": "Elige qué sonido reproducir para las notificaciones", diff --git a/locales/fa/common.json b/locales/fa/common.json index c454007f8..380af698a 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -1183,6 +1183,18 @@ "confirm_revoke_message": "بی‌درنگ دریافت اعلان‌های پس‌زمینه را متوقف می‌کند. با روشن‌کردن دوبارهٔ پوش روی آن دستگاه، دوباره ثبت می‌شود.", "confirm_revoke_message_this": "این دستگاه بی‌درنگ دریافت اعلان‌های پس‌زمینه را متوقف می‌کند." }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "صدای اعلان", "description": "صدای پخش شده برای اعلان‌ها", diff --git a/locales/fr/common.json b/locales/fr/common.json index 738fd0b3f..5e1237dce 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -1144,6 +1144,18 @@ "swift": "Geste rapide", "relax": "Détente" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Son de notification", "description": "Choisissez le son à jouer pour les notifications", diff --git a/locales/he/common.json b/locales/he/common.json index a03914d4f..4b6432e52 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -1109,6 +1109,18 @@ "swift": "מחווה מהירה", "relax": "תירגע" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "צליל התראה", "description": "בחר איזה צליל להפעיל עבור התראות", diff --git a/locales/hu/common.json b/locales/hu/common.json index d348bccb5..5482d43fe 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -1183,6 +1183,18 @@ "confirm_revoke_message": "Azonnal nem kap több háttérértesítést. Ha az eszközön újra bekapcsolja a push értesítéseket, újra regisztrálódik.", "confirm_revoke_message_this": "Ez az eszköz azonnal nem kap több háttérértesítést." }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Értesítési hang", "description": "Válaszd ki az értesítésekhez használt hangot", diff --git a/locales/it/common.json b/locales/it/common.json index 30c7ab7af..bbffa0958 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -1144,6 +1144,18 @@ "swift": "Gesto veloce", "relax": "Rilassante" }, + "push_all": { + "title": "Tutti gli account", + "description": "Attiva o disattiva le notifiche per più account in una volta sola. Una sola iscrizione del browser li serve tutti.", + "select_all": "Seleziona tutti gli account collegati", + "state_on": "notifiche attive", + "state_off": "notifiche non attive", + "enable_selected": "Attiva per i selezionati", + "disable_selected": "Disattiva per i selezionati", + "result_enabled": "Notifiche attivate per {count} account.", + "result_partial": "Attivate per {enabled} account; {failed} non è stato possibile attivarle.", + "result_disabled": "Notifiche disattivate per {count} account." + }, "sound_selection": { "title": "Suono di notifica", "description": "Scegli quale suono riprodurre per le notifiche", diff --git a/locales/ja/common.json b/locales/ja/common.json index c6555f190..bed548ec7 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -1144,6 +1144,18 @@ "swift": "スウィフトジェスチャー", "relax": "リラックス" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "通知音", "description": "通知に使用する音を選択", diff --git a/locales/ko/common.json b/locales/ko/common.json index 93f56bda1..52073f6b1 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -1144,6 +1144,18 @@ "swift": "빠르게", "relax": "차분하게" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "알림음", "description": "알림이 올 때 재생할 소리를 선택해 주세요", diff --git a/locales/lv/common.json b/locales/lv/common.json index b31dd7047..bdab467b3 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -1144,6 +1144,18 @@ "swift": "Ātrs", "relax": "Relaksējošs" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Paziņojuma skaņa", "description": "Izvēlieties skaņu paziņojumiem", diff --git a/locales/mn/common.json b/locales/mn/common.json index 0fc2b17f2..22053f418 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -1183,6 +1183,18 @@ "confirm_revoke_message": "Тэр даруй араарх мэдэгдэл хүлээн авахаа болино. Тухайн төхөөрөмж дээр push-ыг дахин асаавал дахин бүртгэгднэ.", "confirm_revoke_message_this": "Энэ төхөөрөмж тэр даруй араарх мэдэгдэл хүлээн авахаа болино." }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Мэдэгдэлийн дуу", "description": "Мэдэгдэл авахын тулд ямар дууг тоглуулахаа сонгоно уу", diff --git a/locales/nb/common.json b/locales/nb/common.json index 3648dccfc..555de1f2f 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -1183,6 +1183,18 @@ "confirm_revoke_message": "Enheten slutter umiddelbart å motta bakgrunnsvarsler. Hvis pushvarsler aktiveres på nytt på enheten, registreres den på nytt.", "confirm_revoke_message_this": "Denne enheten slutter umiddelbart å motta bakgrunnsvarsler." }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Varslingslyd", "description": "Velg hvilken lyd som skal spilles av for varsler", diff --git a/locales/nl/common.json b/locales/nl/common.json index 12e564a77..4b629004b 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -1144,6 +1144,18 @@ "swift": "Snel gebaar", "relax": "Ontspannen" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Meldingsgeluid", "description": "Kies welk geluid wordt afgespeeld voor meldingen", diff --git a/locales/pl/common.json b/locales/pl/common.json index e903226ee..cfc7d6dd5 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -1144,6 +1144,18 @@ "swift": "Szybki gest", "relax": "Relaks" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Dźwięk powiadomienia", "description": "Wybierz dźwięk odtwarzany dla powiadomień", diff --git a/locales/pt/common.json b/locales/pt/common.json index bb8f8c8fc..d623f1cde 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -1144,6 +1144,18 @@ "swift": "Gesto rápido", "relax": "Relaxante" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Som de notificação", "description": "Escolha qual som reproduzir para notificações", diff --git a/locales/ro/common.json b/locales/ro/common.json index 0af8fbf90..f9690457c 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -1183,6 +1183,18 @@ "confirm_revoke_message": "Va înceta imediat să primească notificări în fundal. Reactivarea push pe acel dispozitiv îl va înregistra din nou.", "confirm_revoke_message_this": "Acest dispozitiv va înceta imediat să primească notificări în fundal." }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Sunet de notificare", "description": "Alegeți ce sunet să se redea pentru notificări", diff --git a/locales/ru/common.json b/locales/ru/common.json index bd08b879c..fb8648b06 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -1144,6 +1144,18 @@ "swift": "Быстрый жест", "relax": "Релакс" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Звук уведомления", "description": "Выберите звук для уведомлений", diff --git a/locales/sk/common.json b/locales/sk/common.json index 2e17b787a..43879446a 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -1147,6 +1147,18 @@ "swift": "Rýchly", "relax": "Relaxačný" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Zvuk oznámenia", "description": "Vyberte zvuk, ktorý sa prehrá pri oznámení", diff --git a/locales/tr/common.json b/locales/tr/common.json index f6755de49..4cc0783c1 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -1144,6 +1144,18 @@ "swift": "Hızlı Jest", "relax": "Rahatla" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Bildirim Sesi", "description": "Bildirimler için hangi sesin çalınacağını seçin", diff --git a/locales/uk/common.json b/locales/uk/common.json index a896c863c..63d19c065 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -1144,6 +1144,18 @@ "swift": "Швидкий жест", "relax": "Розслабтеся" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "Звук сповіщення", "description": "Виберіть звук для сповіщень", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 289ce6d53..6f8818fb6 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -1183,6 +1183,18 @@ "confirm_revoke_message": "此裝置會立即停止接收背景通知。若在該裝置上再次啟用推送,將重新註冊。", "confirm_revoke_message_this": "此裝置會立即停止接收背景通知。" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "通知聲音", "description": "選擇通知時播放的聲音", diff --git a/locales/zh/common.json b/locales/zh/common.json index 2cf732396..fba5f24d2 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -1144,6 +1144,18 @@ "swift": "轻快手势", "relax": "放松" }, + "push_all": { + "title": "All accounts", + "description": "Turn push on or off for several accounts at once. One browser subscription serves them all.", + "select_all": "Select every connected account", + "state_on": "notifications on", + "state_off": "notifications off", + "enable_selected": "Enable for selected", + "disable_selected": "Disable for selected", + "result_enabled": "Notifications enabled for {count} accounts.", + "result_partial": "Enabled for {enabled} accounts; {failed} could not be enabled.", + "result_disabled": "Notifications disabled for {count} accounts." + }, "sound_selection": { "title": "通知声音", "description": "选择通知时播放的声音",