feat: hold toasts while focused and jump to the newest with Alt+Shift+N

This commit is contained in:
Linus Rath
2026-10-29 17:53:45 +01:00
parent e2fc2c750a
commit be5e2dbc53
32 changed files with 389 additions and 182 deletions
@@ -0,0 +1,102 @@
import { act, fireEvent, render, screen, within } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ToastContainer } from '../toast';
import { toast, useToastStore } from '@/stores/toast-store';
function Toasts() {
const toasts = useToastStore((s) => s.toasts);
const removeToast = useToastStore((s) => s.removeToast);
return <ToastContainer toasts={toasts} onClose={removeToast} />;
}
// The toasts themselves, not the live region that reads them out.
const toasts = () => within(document.querySelector('[data-toast-container]') as HTMLElement);
const pressFocusShortcut = () =>
fireEvent.keyDown(window, { key: 'N', code: 'KeyN', altKey: true, shiftKey: true });
describe('toasts and the keyboard', () => {
beforeEach(() => {
vi.useFakeTimers();
useToastStore.setState({ toasts: [] });
});
const extra: HTMLElement[] = [];
const add = <T extends HTMLElement>(el: T): T => {
document.body.appendChild(el);
extra.push(el);
return el;
};
afterEach(() => {
vi.useRealTimers();
for (const el of extra.splice(0)) el.remove();
});
it('holds the countdown while the focus is inside the toast', () => {
render(<Toasts />);
const onClick = vi.fn();
act(() => { toast.success('Email archived', { action: { label: 'Undo', onClick }, duration: 5000 }); });
const undo = screen.getByRole('button', { name: 'Undo' });
act(() => { undo.focus(); });
act(() => { vi.advanceTimersByTime(20000); });
expect(toasts().getByText('Email archived')).toBeInTheDocument();
// Focus leaves: the rest of the time runs out.
act(() => { undo.blur(); });
act(() => { vi.advanceTimersByTime(6000); });
expect(toasts().queryByText('Email archived')).not.toBeInTheDocument();
});
it('does not hold a toast whose action ends at a fixed time', () => {
render(<Toasts />);
act(() => {
toast.info('Sending', { action: { label: 'Undo', onClick: vi.fn() }, duration: 5000, pauseOnHover: false });
});
act(() => { screen.getByRole('button', { name: 'Undo' }).focus(); });
act(() => { vi.advanceTimersByTime(6000); });
expect(toasts().queryByText('Sending')).not.toBeInTheDocument();
});
it('moves the focus to the newest toast\'s action with Alt+Shift+N, and back when it closes', () => {
const before = document.createElement('button');
before.textContent = 'Message';
add(before);
before.focus();
render(<Toasts />);
const onClick = vi.fn();
act(() => { toast.info('Older'); });
act(() => { toast.success('Email deleted', { action: { label: 'Undo', onClick } }); });
pressFocusShortcut();
expect(document.activeElement).toBe(screen.getByRole('button', { name: 'Undo' }));
fireEvent.click(screen.getByRole('button', { name: 'Undo' }));
expect(onClick).toHaveBeenCalled();
expect(document.activeElement).toBe(before);
});
it('closes the focused toast with Escape without the page hearing it', () => {
const pageEscape = vi.fn();
window.addEventListener('keydown', pageEscape);
render(<Toasts />);
act(() => { toast.success('Saved'); });
pressFocusShortcut();
pageEscape.mockClear();
fireEvent.keyDown(document.activeElement!, { key: 'Escape' });
act(() => { vi.advanceTimersByTime(400); });
expect(toasts().queryByText('Saved')).not.toBeInTheDocument();
expect(pageEscape).not.toHaveBeenCalled();
window.removeEventListener('keydown', pageEscape);
});
it('leaves the focus alone while a modal dialog is open', () => {
const dialog = document.createElement('div');
dialog.setAttribute('aria-modal', 'true');
const inside = document.createElement('button');
dialog.appendChild(inside);
add(dialog);
inside.focus();
render(<Toasts />);
act(() => { toast.success('Saved', { action: { label: 'Undo', onClick: vi.fn() } }); });
pressFocusShortcut();
expect(document.activeElement).toBe(inside);
});
});
+83 -9
View File
@@ -1,6 +1,6 @@
"use client";
import { useEffect, useState, useCallback, useRef } from "react";
import { useEffect, useState, useCallback, useRef, useId } from "react";
import { createPortal } from "react-dom";
import { useTranslations } from "next-intl";
import { X, CheckCircle, AlertCircle, Info, AlertTriangle, Loader2 } from "@/components/icons";
@@ -55,6 +55,21 @@ interface ToastProps {
onClose: (id: string) => void;
/** Kept out of sight for now (a menu is open over it): the countdown waits. */
held?: boolean;
/** The toast goes away with the keyboard focus in it: put the focus back. */
onFocusLost?: () => void;
}
/**
* Jumps to the newest toast from anywhere (Alt+Shift+N), so its Undo can be
* reached without tabbing through the page. Listed in lib/keyboard-shortcuts.
*/
export function isFocusToastShortcut(e: KeyboardEvent): boolean {
return e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey && e.code === "KeyN";
}
/** A modal dialog is open: keys and focus belong to it. */
function modalOpen(): boolean {
return document.querySelector('[aria-modal="true"]') !== null;
}
const icons = {
@@ -83,7 +98,7 @@ const progressBarStyles = {
// A sideways swipe this far (px) on a touch screen dismisses the toast.
const SWIPE_DISMISS_DISTANCE = 72;
export function ToastItem({ toast, onClose, held = false }: ToastProps) {
export function ToastItem({ toast, onClose, held = false, onFocusLost }: ToastProps) {
const tCommon = useTranslations("common");
const Icon = icons[toast.type];
const [exiting, setExiting] = useState(false);
@@ -91,7 +106,14 @@ export function ToastItem({ toast, onClose, held = false }: ToastProps) {
const [dragX, setDragX] = useState(0);
const [flungTo, setFlungTo] = useState<number | null>(null);
const swipeRef = useRef<{ id: number; x: number; y: number; horizontal: boolean | null } | null>(null);
// Swiping holds the countdown; so do the pointer over the toast and the
// keyboard focus in it (2.2.1), unless the action stops working at a fixed
// time (pauseOnHover: false).
const [paused, setPaused] = useState(false);
const [hovered, setHovered] = useState(false);
const [focused, setFocused] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);
const titleId = useId();
const remainingRef = useRef(toast.duration ?? 5000);
const startRef = useRef(Date.now());
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -106,8 +128,10 @@ export function ToastItem({ toast, onClose, held = false }: ToastProps) {
const dismiss = useCallback(() => {
setExiting(true);
// Removing the toast would drop the focus onto <body>.
if (rootRef.current?.contains(document.activeElement)) onFocusLost?.();
setTimeout(() => onClose(toast.id), 280);
}, [onClose, toast.id]);
}, [onClose, toast.id, onFocusLost]);
const dismissByUser = useCallback(() => {
try {
@@ -118,7 +142,8 @@ export function ToastItem({ toast, onClose, held = false }: ToastProps) {
dismiss();
}, [toast, dismiss]);
const waiting = paused || held;
const canPause = toast.pauseOnHover !== false;
const waiting = paused || held || (canPause && (hovered || focused));
useEffect(() => {
if (!toast.duration || toast.duration <= 0 || isProgress) return;
@@ -217,8 +242,22 @@ export function ToastItem({ toast, onClose, held = false }: ToastProps) {
onPointerMove={onPointerMove}
onPointerUp={endSwipe}
onPointerCancel={endSwipe}
onMouseEnter={() => { if (toast.pauseOnHover !== false) setPaused(true); }}
onMouseLeave={() => setPaused(false)}
ref={rootRef}
role="group"
aria-labelledby={titleId}
tabIndex={-1}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
onFocus={() => setFocused(true)}
onBlur={(e) => {
if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setFocused(false);
}}
onKeyDown={(e) => {
if (e.key !== "Escape") return;
// The toast's, not the page's Escape (closing the open message).
e.stopPropagation();
dismissByUser();
}}
onClick={() => {
if (swiped) return;
if (toast.onClick && !toast.action) {
@@ -238,7 +277,7 @@ export function ToastItem({ toast, onClose, held = false }: ToastProps) {
{/* Content */}
<div className="flex-1 min-w-0 pt-px">
<p className="text-[13.5px] font-medium text-foreground leading-snug">{toast.title}</p>
<p id={titleId} className="text-[13.5px] font-medium text-foreground leading-snug">{toast.title}</p>
{toast.message && (
<p className="text-[12.5px] mt-0.5 text-muted-foreground leading-snug">{toast.message}</p>
)}
@@ -261,6 +300,7 @@ export function ToastItem({ toast, onClose, held = false }: ToastProps) {
{/* Close button. Phones swipe the toast away instead. */}
<button
type="button"
data-toast-close=""
aria-label={tCommon("close")}
title={tCommon("close")}
onClick={(e) => {
@@ -300,7 +340,7 @@ export function ToastItem({ toast, onClose, held = false }: ToastProps) {
className={cn("h-full opacity-60", progressBarStyles[toast.type])}
style={{
animation: `toast-progress ${toast.duration}ms linear forwards`,
animationPlayState: paused ? "paused" : "running",
animationPlayState: waiting ? "paused" : "running",
}}
/>
</div>
@@ -318,11 +358,44 @@ export function ToastContainer({ toasts, onClose }: { toasts: Toast[]; onClose:
const menuOpen = useContextMenuOpen();
const phone = useMediaQuery("(max-width: 639px)");
const held = menuOpen && phone;
const containerRef = useRef<HTMLDivElement>(null);
// Where the focus was before the shortcut moved it into a toast.
const returnFocusRef = useRef<HTMLElement | null>(null);
useEffect(() => {
setMounted(true);
}, []);
const hasToasts = toasts.length > 0;
useEffect(() => {
if (!hasToasts) return;
const onKeyDown = (e: KeyboardEvent) => {
if (!isFocusToastShortcut(e) || modalOpen()) return;
const items = containerRef.current?.querySelectorAll<HTMLElement>(".toast-item");
const newest = items?.[items.length - 1];
if (!newest) return;
e.preventDefault();
const active = document.activeElement as HTMLElement | null;
if (active && active !== document.body && !containerRef.current?.contains(active)) {
returnFocusRef.current = active;
}
// Its first action (Undo), else the close button, else the toast.
const target =
newest.querySelector<HTMLElement>("button:not([data-toast-close])") ??
Array.from(newest.querySelectorAll<HTMLElement>("button[data-toast-close]")).find((b) => b.offsetParent !== null || b.getClientRects().length > 0) ??
newest;
target.focus();
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [hasToasts]);
const returnFocus = useCallback(() => {
const back = returnFocusRef.current;
returnFocusRef.current = null;
if (back && back.isConnected) back.focus();
}, []);
if (!mounted) return null;
return createPortal(
@@ -341,6 +414,7 @@ export function ToastContainer({ toasts, onClose }: { toasts: Toast[]; onClose:
// Not a live region: the toast store reads each toast out through
// lib/announce (errors assertively). A labelled region while toasts
// are up, so screen reader users can find them.
ref={containerRef}
data-toast-container=""
role={toasts.length > 0 ? "region" : undefined}
aria-label={toasts.length > 0 ? tCenter("title") : undefined}
@@ -348,7 +422,7 @@ export function ToastContainer({ toasts, onClose }: { toasts: Toast[]; onClose:
>
{toasts.map((toast) => (
<div key={toast.id} style={{ pointerEvents: "auto" }}>
<ToastItem toast={toast} onClose={onClose} held={held} />
<ToastItem toast={toast} onClose={onClose} held={held} onFocusLost={returnFocus} />
</div>
))}
</div>,
+2
View File
@@ -34,6 +34,8 @@ export const GENERAL_SHORTCUTS: readonly ShortcutEntry[] = [
{ key: "Mod + K", description: "command_palette.open", shell: "standard" },
{ key: "?", description: "shortcuts.global.help" },
{ key: "Alt + Shift + A", description: "shortcuts.global.accessibility_panel" },
// components/ui/toast.tsx (isFocusToastShortcut)
{ key: "Alt + Shift + N", description: "shortcuts.global.focus_notification" },
];
/** An app's sections with the general keys added (see GENERAL_SHORTCUTS). */
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "إدارة القواعد",
"senders_count": "{count, plural, one {مرسل واحد} two {مرسلَين} few {# مرسلين} many {# مرسلًا} other {# مرسل}}",
"cross_account": "حدد رسائل من حساب واحد",
"opaque_hint": "تم تعديل مرشحاتك يدويًا. افتح إعدادات المرشحات" ,
"loading": "جارٍ تحميل القواعد…",
"load_failed": "تعذّر تحميل القواعد",
"none": "لا توجد قواعد بعد",
"new_mail_only": "للرسائل الجديدة فقط",
"running": "جارٍ تشغيل «{name}» على {folder}…"
"opaque_hint": "تم تعديل مرشحاتك يدويًا. افتح إعدادات المرشحات" ,
"loading": "جارٍ تحميل القواعد…",
"load_failed": "تعذّر تحميل القواعد",
"none": "لا توجد قواعد بعد",
"new_mail_only": "للرسائل الجديدة فقط",
"running": "جارٍ تشغيل «{name}» على {folder}…"
},
"find_emails_from": "البحث عن رسائل من {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "تحديد الكل",
"global_search": "البحث في كل شيء",
"extend_selection": "توسيع التحديد لأسفل / لأعلى",
"accessibility_panel": "فتح خيارات إمكانية الوصول"
"accessibility_panel": "فتح خيارات إمكانية الوصول",
"focus_notification": "الانتقال إلى أحدث إشعار"
},
"threads": {
"expand_collapse": "توسيع/طي المحادثة",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Gestiona les regles",
"senders_count": "{count, plural, one {# remitent} other {# remitents}}",
"cross_account": "Selecciona missatges d'un sol compte",
"opaque_hint": "Els teus filtres s'han editat a mà. Obre la configuració dels filtres" ,
"loading": "S'estan carregant les regles…",
"load_failed": "No s'han pogut carregar les regles",
"none": "Encara no hi ha regles",
"new_mail_only": "Només per a missatges nous",
"running": "S'està executant «{name}» a {folder}…"
"opaque_hint": "Els teus filtres s'han editat a mà. Obre la configuració dels filtres" ,
"loading": "S'estan carregant les regles…",
"load_failed": "No s'han pogut carregar les regles",
"none": "Encara no hi ha regles",
"new_mail_only": "Només per a missatges nous",
"running": "S'està executant «{name}» a {folder}…"
},
"find_emails_from": "Cerca correus de {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Selecciona-ho tot",
"global_search": "Cerca-ho tot",
"extend_selection": "Amplia la selecció cap avall / amunt",
"accessibility_panel": "Obre les opcions d'accessibilitat"
"accessibility_panel": "Obre les opcions d'accessibilitat",
"focus_notification": "Ves a la notificació més recent"
},
"threads": {
"expand_collapse": "Desplega/replega el fil",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Spravovat pravidla",
"senders_count": "{count, plural, one {# odesílatele} few {# odesílatelů} many {# odesílatele} other {# odesílatelů}}",
"cross_account": "Vyberte zprávy z jednoho účtu",
"opaque_hint": "Vaše filtry byly upraveny ručně. Otevřete nastavení filtrů" ,
"loading": "Načítání pravidel…",
"load_failed": "Pravidla se nepodařilo načíst",
"none": "Zatím žádná pravidla",
"new_mail_only": "Pouze pro nové zprávy",
"running": "Spouštění „{name}“ ve složce {folder}…"
"opaque_hint": "Vaše filtry byly upraveny ručně. Otevřete nastavení filtrů" ,
"loading": "Načítání pravidel…",
"load_failed": "Pravidla se nepodařilo načíst",
"none": "Zatím žádná pravidla",
"new_mail_only": "Pouze pro nové zprávy",
"running": "Spouštění „{name}“ ve složce {folder}…"
},
"find_emails_from": "Najít e-maily od {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Vybrat vše",
"global_search": "Hledat ve všem",
"extend_selection": "Rozšířit výběr dolů / nahoru",
"accessibility_panel": "Otevřít možnosti přístupnosti"
"accessibility_panel": "Otevřít možnosti přístupnosti",
"focus_notification": "Přejít na nejnovější oznámení"
},
"threads": {
"expand_collapse": "Rozbalit/sbalit vlákno",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Administrer regler",
"senders_count": "{count, plural, one {# afsender} other {# afsendere}}",
"cross_account": "Vælg beskeder fra én konto",
"opaque_hint": "Dine filtre er redigeret manuelt. Åbn filterindstillingerne" ,
"loading": "Indlæser regler…",
"load_failed": "Reglerne kunne ikke indlæses",
"none": "Ingen regler endnu",
"new_mail_only": "Kun for nye beskeder",
"running": "Kører „{name}“ på {folder}…"
"opaque_hint": "Dine filtre er redigeret manuelt. Åbn filterindstillingerne" ,
"loading": "Indlæser regler…",
"load_failed": "Reglerne kunne ikke indlæses",
"none": "Ingen regler endnu",
"new_mail_only": "Kun for nye beskeder",
"running": "Kører „{name}“ på {folder}…"
},
"find_emails_from": "Find e-mails fra {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Vælg alle",
"global_search": "Søg i alt",
"extend_selection": "Udvid markering ned / op",
"accessibility_panel": "Åbn indstillinger for tilgængelighed"
"accessibility_panel": "Åbn indstillinger for tilgængelighed",
"focus_notification": "Gå til den seneste notifikation"
},
"threads": {
"expand_collapse": "Udvid/skjul tråd",
+2 -1
View File
@@ -3496,7 +3496,8 @@
"select_all": "Alle auswählen",
"global_search": "Alles durchsuchen",
"extend_selection": "Auswahl nach unten / oben erweitern",
"accessibility_panel": "Optionen zur Barrierefreiheit öffnen"
"accessibility_panel": "Optionen zur Barrierefreiheit öffnen",
"focus_notification": "Zur neuesten Benachrichtigung springen"
},
"threads": {
"expand_collapse": "Unterhaltung erweitern/einklappen",
+2 -1
View File
@@ -3496,7 +3496,8 @@
"select_all": "Select all",
"global_search": "Search everything",
"extend_selection": "Extend selection down / up",
"accessibility_panel": "Open the accessibility options"
"accessibility_panel": "Open the accessibility options",
"focus_notification": "Go to the latest notification"
},
"threads": {
"expand_collapse": "Expand/collapse thread",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Administrar reglas",
"senders_count": "{count, plural, one {# remitente} other {# remitentes}}",
"cross_account": "Selecciona mensajes de una sola cuenta",
"opaque_hint": "Tus filtros se editaron a mano. Abre la configuración de filtros" ,
"loading": "Cargando reglas…",
"load_failed": "No se pudieron cargar las reglas",
"none": "Aún no hay reglas",
"new_mail_only": "Solo para mensajes nuevos",
"running": "Ejecutando «{name}» en {folder}…"
"opaque_hint": "Tus filtros se editaron a mano. Abre la configuración de filtros" ,
"loading": "Cargando reglas…",
"load_failed": "No se pudieron cargar las reglas",
"none": "Aún no hay reglas",
"new_mail_only": "Solo para mensajes nuevos",
"running": "Ejecutando «{name}» en {folder}…"
},
"find_emails_from": "Buscar correos de {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Seleccionar todo",
"global_search": "Buscar en todo",
"extend_selection": "Ampliar la selección hacia abajo / arriba",
"accessibility_panel": "Abrir las opciones de accesibilidad"
"accessibility_panel": "Abrir las opciones de accesibilidad",
"focus_notification": "Ir a la notificación más reciente"
},
"threads": {
"expand_collapse": "Expandir/contraer conversación",
+2 -1
View File
@@ -3496,7 +3496,8 @@
"select_all": "Vali kõik",
"global_search": "Otsi kõike",
"extend_selection": "Laienda valikut alla / üles",
"accessibility_panel": "Ava juurdepääsetavuse valikud"
"accessibility_panel": "Ava juurdepääsetavuse valikud",
"focus_notification": "Mine viimase teavituse juurde"
},
"threads": {
"expand_collapse": "Ava/sulge vestlus",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "مدیریت قانون‌ها",
"senders_count": "{count, plural, one {# فرستنده} other {# فرستنده}}",
"cross_account": "پیام‌هایی از یک حساب انتخاب کنید",
"opaque_hint": "فیلترهای شما دستی ویرایش شده‌اند. تنظیمات فیلترها را باز کنید" ,
"loading": "در حال بارگذاری قوانین…",
"load_failed": "بارگذاری قوانین ممکن نشد",
"none": "هنوز قانونی وجود ندارد",
"new_mail_only": "فقط برای پیام‌های جدید",
"running": "در حال اجرای «{name}» روی {folder}…"
"opaque_hint": "فیلترهای شما دستی ویرایش شده‌اند. تنظیمات فیلترها را باز کنید" ,
"loading": "در حال بارگذاری قوانین…",
"load_failed": "بارگذاری قوانین ممکن نشد",
"none": "هنوز قانونی وجود ندارد",
"new_mail_only": "فقط برای پیام‌های جدید",
"running": "در حال اجرای «{name}» روی {folder}…"
},
"find_emails_from": "یافتن ایمیل‌های {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "انتخاب همه",
"global_search": "جستجو در همه جا",
"extend_selection": "گسترش انتخاب به پایین / بالا",
"accessibility_panel": "باز کردن گزینه‌های دسترس‌پذیری"
"accessibility_panel": "باز کردن گزینه‌های دسترس‌پذیری",
"focus_notification": "رفتن به آخرین اعلان"
},
"threads": {
"expand_collapse": "باز/بسته کردن رشته",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Gérer les règles",
"senders_count": "{count, plural, one {# expéditeur} other {# expéditeurs}}",
"cross_account": "Sélectionnez des messages d'un seul compte",
"opaque_hint": "Vos filtres ont été modifiés à la main. Ouvrez les réglages des filtres" ,
"loading": "Chargement des règles…",
"load_failed": "Impossible de charger les règles",
"none": "Aucune règle pour l'instant",
"new_mail_only": "Uniquement pour les nouveaux messages",
"running": "Exécution de « {name} » sur {folder}…"
"opaque_hint": "Vos filtres ont été modifiés à la main. Ouvrez les réglages des filtres" ,
"loading": "Chargement des règles…",
"load_failed": "Impossible de charger les règles",
"none": "Aucune règle pour l'instant",
"new_mail_only": "Uniquement pour les nouveaux messages",
"running": "Exécution de « {name} » sur {folder}…"
},
"find_emails_from": "Rechercher les e-mails de {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Tout sélectionner",
"global_search": "Tout rechercher",
"extend_selection": "Étendre la sélection vers le bas / le haut",
"accessibility_panel": "Ouvrir les options d'accessibilité"
"accessibility_panel": "Ouvrir les options d'accessibilité",
"focus_notification": "Aller à la dernière notification"
},
"threads": {
"expand_collapse": "Développer/réduire la conversation",
+8 -7
View File
@@ -3366,12 +3366,12 @@
"manage": "ניהול כללים",
"senders_count": "{count, plural, one {שולח אחד} other {# שולחים}}",
"cross_account": "בחר הודעות מחשבון אחד",
"opaque_hint": "המסננים שלך נערכו ידנית. פתח את הגדרות המסננים" ,
"loading": "טוען כללים…",
"load_failed": "לא ניתן לטעון את הכללים",
"none": "אין עדיין כללים",
"new_mail_only": "רק להודעות חדשות",
"running": "מריץ את „{name}“ על {folder}…"
"opaque_hint": "המסננים שלך נערכו ידנית. פתח את הגדרות המסננים" ,
"loading": "טוען כללים…",
"load_failed": "לא ניתן לטעון את הכללים",
"none": "אין עדיין כללים",
"new_mail_only": "רק להודעות חדשות",
"running": "מריץ את „{name}“ על {folder}…"
},
"find_emails_from": "חיפוש הודעות מ{name}"
},
@@ -3414,7 +3414,8 @@
"select_all": "בחר הכל",
"global_search": "חיפוש בכל מקום",
"extend_selection": "הרחבת הבחירה למטה / למעלה",
"accessibility_panel": "פתיחת אפשרויות הנגישות"
"accessibility_panel": "פתיחת אפשרויות הנגישות",
"focus_notification": "מעבר להתראה האחרונה"
},
"threads": {
"expand_collapse": "הרחב/כווץ את השרשור",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Szabályok kezelése",
"senders_count": "{count, plural, one {# feladó} other {# feladó}}",
"cross_account": "Egyetlen fiók üzeneteit válaszd ki",
"opaque_hint": "A szűrőidet kézzel szerkesztették. Nyisd meg a szűrőbeállításokat" ,
"loading": "Szabályok betöltése…",
"load_failed": "Nem sikerült betölteni a szabályokat",
"none": "Még nincsenek szabályok",
"new_mail_only": "Csak új üzenetekre",
"running": "„{name}” futtatása itt: {folder}…"
"opaque_hint": "A szűrőidet kézzel szerkesztették. Nyisd meg a szűrőbeállításokat" ,
"loading": "Szabályok betöltése…",
"load_failed": "Nem sikerült betölteni a szabályokat",
"none": "Még nincsenek szabályok",
"new_mail_only": "Csak új üzenetekre",
"running": "„{name}” futtatása itt: {folder}…"
},
"find_emails_from": "{name} e-mailjeinek keresése"
},
@@ -3496,7 +3496,8 @@
"select_all": "Összes kijelölése",
"global_search": "Keresés mindenben",
"extend_selection": "Kijelölés bővítése lefelé / felfelé",
"accessibility_panel": "Kisegítő lehetőségek megnyitása"
"accessibility_panel": "Kisegítő lehetőségek megnyitása",
"focus_notification": "Ugrás a legújabb értesítésre"
},
"threads": {
"expand_collapse": "Beszélgetés kibontása/összecsukása",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Gestisci regole",
"senders_count": "{count, plural, one {# mittente} other {# mittenti}}",
"cross_account": "Seleziona messaggi di un solo account",
"opaque_hint": "I tuoi filtri sono stati modificati a mano. Apri le impostazioni dei filtri" ,
"loading": "Caricamento delle regole…",
"load_failed": "Impossibile caricare le regole",
"none": "Nessuna regola",
"new_mail_only": "Solo per i nuovi messaggi",
"running": "Esecuzione di «{name}» su {folder}…"
"opaque_hint": "I tuoi filtri sono stati modificati a mano. Apri le impostazioni dei filtri" ,
"loading": "Caricamento delle regole…",
"load_failed": "Impossibile caricare le regole",
"none": "Nessuna regola",
"new_mail_only": "Solo per i nuovi messaggi",
"running": "Esecuzione di «{name}» su {folder}…"
},
"find_emails_from": "Trova email da {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Seleziona tutto",
"global_search": "Cerca ovunque",
"extend_selection": "Estendi la selezione in basso / in alto",
"accessibility_panel": "Apri le opzioni di accessibilità"
"accessibility_panel": "Apri le opzioni di accessibilità",
"focus_notification": "Vai all'ultima notifica"
},
"threads": {
"expand_collapse": "Espandi/comprimi conversazione",
+2 -1
View File
@@ -3496,7 +3496,8 @@
"select_all": "すべて選択",
"global_search": "すべてを検索",
"extend_selection": "選択範囲を下 / 上に広げる",
"accessibility_panel": "アクセシビリティのオプションを開く"
"accessibility_panel": "アクセシビリティのオプションを開く",
"focus_notification": "最新の通知に移動"
},
"threads": {
"expand_collapse": "スレッドの展開/折りたたみ",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "규칙 관리",
"senders_count": "{count, plural, other {보낸 사람 #명}}",
"cross_account": "한 계정의 메일만 선택하세요",
"opaque_hint": "필터가 직접 편집되었어요. 필터 설정을 여세요" ,
"loading": "규칙을 불러오는 중…",
"load_failed": "규칙을 불러올 수 없습니다",
"none": "아직 규칙이 없습니다",
"new_mail_only": "새 메시지에만 적용",
"running": "{folder}에서 \"{name}\" 실행 중…"
"opaque_hint": "필터가 직접 편집되었어요. 필터 설정을 여세요" ,
"loading": "규칙을 불러오는 중…",
"load_failed": "규칙을 불러올 수 없습니다",
"none": "아직 규칙이 없습니다",
"new_mail_only": "새 메시지에만 적용",
"running": "{folder}에서 \"{name}\" 실행 중…"
},
"find_emails_from": "{name}님이 보낸 이메일 찾기"
},
@@ -3496,7 +3496,8 @@
"select_all": "모두 선택",
"global_search": "전체 검색",
"extend_selection": "선택 범위를 아래 / 위로 확장",
"accessibility_panel": "접근성 옵션 열기"
"accessibility_panel": "접근성 옵션 열기",
"focus_notification": "최근 알림으로 이동"
},
"threads": {
"expand_collapse": "대화 펼치기/접기",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Pārvaldīt noteikumus",
"senders_count": "{count, plural, zero {# sūtītājiem} one {# sūtītāja} other {# sūtītājiem}}",
"cross_account": "Izvēlieties vēstules no viena konta",
"opaque_hint": "Jūsu filtri ir rediģēti manuāli. Atveriet filtru iestatījumus" ,
"loading": "Ielādē kārtulas…",
"load_failed": "Neizdevās ielādēt kārtulas",
"none": "Vēl nav kārtulu",
"new_mail_only": "Tikai jaunām ziņām",
"running": "Izpilda “{name}” mapē {folder}…"
"opaque_hint": "Jūsu filtri ir rediģēti manuāli. Atveriet filtru iestatījumus" ,
"loading": "Ielādē kārtulas…",
"load_failed": "Neizdevās ielādēt kārtulas",
"none": "Vēl nav kārtulu",
"new_mail_only": "Tikai jaunām ziņām",
"running": "Izpilda “{name}” mapē {folder}…"
},
"find_emails_from": "Atrast e-pastus no {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Atlasīt visas",
"global_search": "Meklēt visā",
"extend_selection": "Paplašināt atlasi uz leju / uz augšu",
"accessibility_panel": "Atvērt pieejamības opcijas"
"accessibility_panel": "Atvērt pieejamības opcijas",
"focus_notification": "Pāriet uz jaunāko paziņojumu"
},
"threads": {
"expand_collapse": "Izvērst/sairt sarunu",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Дүрмүүдийг удирдах",
"senders_count": "{count, plural, one {# илгээгч} other {# илгээгч}}",
"cross_account": "Нэг бүртгэлийн захиануудыг сонгоно уу",
"opaque_hint": "Таны шүүлтүүрийг гараар засварласан байна. Шүүлтүүрийн тохиргоог нээнэ үү" ,
"loading": "Дүрмүүдийг ачаалж байна…",
"load_failed": "Дүрмүүдийг ачаалж чадсангүй",
"none": "Одоогоор дүрэм алга",
"new_mail_only": "Зөвхөн шинэ мессежүүдэд",
"running": "{folder} дээр \"{name}\"-г ажиллуулж байна…"
"opaque_hint": "Таны шүүлтүүрийг гараар засварласан байна. Шүүлтүүрийн тохиргоог нээнэ үү" ,
"loading": "Дүрмүүдийг ачаалж байна…",
"load_failed": "Дүрмүүдийг ачаалж чадсангүй",
"none": "Одоогоор дүрэм алга",
"new_mail_only": "Зөвхөн шинэ мессежүүдэд",
"running": "{folder} дээр \"{name}\"-г ажиллуулж байна…"
},
"find_emails_from": "{name}-с ирсэн имэйлүүдийг хайх"
},
@@ -3496,7 +3496,8 @@
"select_all": "Бүгдийг сонго",
"global_search": "Бүгдийг хайх",
"extend_selection": "Сонголтыг доош / дээш өргөтгөх",
"accessibility_panel": "Хүртээмжийн сонголтуудыг нээх"
"accessibility_panel": "Хүртээмжийн сонголтуудыг нээх",
"focus_notification": "Хамгийн сүүлийн мэдэгдэл рүү очих"
},
"threads": {
"expand_collapse": "Хэлхээсийг дэлгэх/хумих",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Administrer regler",
"senders_count": "{count, plural, one {# avsender} other {# avsendere}}",
"cross_account": "Velg meldinger fra én konto",
"opaque_hint": "Filtrene dine er redigert for hånd. Åpne filterinnstillingene" ,
"loading": "Laster inn regler…",
"load_failed": "Kunne ikke laste inn reglene",
"none": "Ingen regler ennå",
"new_mail_only": "Bare for nye meldinger",
"running": "Kjører «{name}» på {folder}…"
"opaque_hint": "Filtrene dine er redigert for hånd. Åpne filterinnstillingene" ,
"loading": "Laster inn regler…",
"load_failed": "Kunne ikke laste inn reglene",
"none": "Ingen regler ennå",
"new_mail_only": "Bare for nye meldinger",
"running": "Kjører «{name}» på {folder}…"
},
"find_emails_from": "Finn e-poster fra {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Velg alle",
"global_search": "Søk i alt",
"extend_selection": "Utvid markeringen ned / opp",
"accessibility_panel": "Åpne alternativer for tilgjengelighet"
"accessibility_panel": "Åpne alternativer for tilgjengelighet",
"focus_notification": "Gå til det nyeste varselet"
},
"threads": {
"expand_collapse": "Utvid eller fold sammen tråden",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Regels beheren",
"senders_count": "{count, plural, one {# afzender} other {# afzenders}}",
"cross_account": "Selecteer berichten uit één account",
"opaque_hint": "Je filters zijn met de hand bewerkt. Open de filterinstellingen" ,
"loading": "Regels laden…",
"load_failed": "De regels konden niet worden geladen",
"none": "Nog geen regels",
"new_mail_only": "Alleen voor nieuwe berichten",
"running": "\"{name}\" wordt uitgevoerd op {folder}…"
"opaque_hint": "Je filters zijn met de hand bewerkt. Open de filterinstellingen" ,
"loading": "Regels laden…",
"load_failed": "De regels konden niet worden geladen",
"none": "Nog geen regels",
"new_mail_only": "Alleen voor nieuwe berichten",
"running": "\"{name}\" wordt uitgevoerd op {folder}…"
},
"find_emails_from": "E-mails van {name} zoeken"
},
@@ -3496,7 +3496,8 @@
"select_all": "Alles selecteren",
"global_search": "Alles doorzoeken",
"extend_selection": "Selectie omlaag / omhoog uitbreiden",
"accessibility_panel": "Toegankelijkheidsopties openen"
"accessibility_panel": "Toegankelijkheidsopties openen",
"focus_notification": "Naar de nieuwste melding gaan"
},
"threads": {
"expand_collapse": "Gesprek uitklappen/inklappen",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Zarządzaj regułami",
"senders_count": "{count, plural, one {# nadawcy} few {# nadawców} many {# nadawców} other {# nadawcy}}",
"cross_account": "Wybierz wiadomości z jednego konta",
"opaque_hint": "Twoje filtry zostały edytowane ręcznie. Otwórz ustawienia filtrów" ,
"loading": "Wczytywanie reguł…",
"load_failed": "Nie udało się wczytać reguł",
"none": "Brak reguł",
"new_mail_only": "Tylko dla nowych wiadomości",
"running": "Uruchamianie „{name}” w folderze {folder}…"
"opaque_hint": "Twoje filtry zostały edytowane ręcznie. Otwórz ustawienia filtrów" ,
"loading": "Wczytywanie reguł…",
"load_failed": "Nie udało się wczytać reguł",
"none": "Brak reguł",
"new_mail_only": "Tylko dla nowych wiadomości",
"running": "Uruchamianie „{name}” w folderze {folder}…"
},
"find_emails_from": "Znajdź e-maile od {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Zaznacz wszystko",
"global_search": "Szukaj wszędzie",
"extend_selection": "Rozszerz zaznaczenie w dół / w górę",
"accessibility_panel": "Otwórz opcje dostępności"
"accessibility_panel": "Otwórz opcje dostępności",
"focus_notification": "Przejdź do najnowszego powiadomienia"
},
"threads": {
"expand_collapse": "Rozwiń/zwiń wątek",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Gerenciar regras",
"senders_count": "{count, plural, one {# remetente} other {# remetentes}}",
"cross_account": "Selecione mensagens de uma única conta",
"opaque_hint": "Seus filtros foram editados manualmente. Abra as configurações de filtros" ,
"loading": "A carregar regras…",
"load_failed": "Não foi possível carregar as regras",
"none": "Ainda não há regras",
"new_mail_only": "Apenas para mensagens novas",
"running": "A executar \"{name}\" em {folder}…"
"opaque_hint": "Seus filtros foram editados manualmente. Abra as configurações de filtros" ,
"loading": "A carregar regras…",
"load_failed": "Não foi possível carregar as regras",
"none": "Ainda não há regras",
"new_mail_only": "Apenas para mensagens novas",
"running": "A executar \"{name}\" em {folder}…"
},
"find_emails_from": "Encontrar e-mails de {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Selecionar todos",
"global_search": "Pesquisar em tudo",
"extend_selection": "Expandir a seleção para baixo / cima",
"accessibility_panel": "Abrir as opções de acessibilidade"
"accessibility_panel": "Abrir as opções de acessibilidade",
"focus_notification": "Ir para a notificação mais recente"
},
"threads": {
"expand_collapse": "Expandir/recolher conversa",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Gestionați regulile",
"senders_count": "{count, plural, one {# expeditor} few {# expeditori} other {# de expeditori}}",
"cross_account": "Selectați mesaje dintr-un singur cont",
"opaque_hint": "Filtrele dvs. au fost editate manual. Deschideți setările filtrelor" ,
"loading": "Se încarcă regulile…",
"load_failed": "Regulile nu au putut fi încărcate",
"none": "Nicio regulă încă",
"new_mail_only": "Doar pentru mesajele noi",
"running": "Se rulează „{name}” în {folder}…"
"opaque_hint": "Filtrele dvs. au fost editate manual. Deschideți setările filtrelor" ,
"loading": "Se încarcă regulile…",
"load_failed": "Regulile nu au putut fi încărcate",
"none": "Nicio regulă încă",
"new_mail_only": "Doar pentru mesajele noi",
"running": "Se rulează „{name}” în {folder}…"
},
"find_emails_from": "Găsește e-mailurile de la {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Selectează tot",
"global_search": "Caută în tot",
"extend_selection": "Extinde selecția în jos / în sus",
"accessibility_panel": "Deschide opțiunile de accesibilitate"
"accessibility_panel": "Deschide opțiunile de accesibilitate",
"focus_notification": "Mergi la cea mai recentă notificare"
},
"threads": {
"expand_collapse": "Extindeți/reduceți firul de discuție",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Управление правилами",
"senders_count": "{count, plural, one {# отправителя} few {# отправителей} many {# отправителей} other {# отправителя}}",
"cross_account": "Выберите письма из одной учётной записи",
"opaque_hint": "Ваши фильтры отредактированы вручную. Откройте настройки фильтров" ,
"loading": "Загрузка правил…",
"load_failed": "Не удалось загрузить правила",
"none": "Правил пока нет",
"new_mail_only": "Только для новых писем",
"running": "Выполняется «{name}» в папке {folder}…"
"opaque_hint": "Ваши фильтры отредактированы вручную. Откройте настройки фильтров" ,
"loading": "Загрузка правил…",
"load_failed": "Не удалось загрузить правила",
"none": "Правил пока нет",
"new_mail_only": "Только для новых писем",
"running": "Выполняется «{name}» в папке {folder}…"
},
"find_emails_from": "Найти письма от {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Выбрать все",
"global_search": "Искать везде",
"extend_selection": "Расширить выделение вниз / вверх",
"accessibility_panel": "Открыть параметры специальных возможностей"
"accessibility_panel": "Открыть параметры специальных возможностей",
"focus_notification": "Перейти к последнему уведомлению"
},
"threads": {
"expand_collapse": "Развернуть/свернуть цепочку",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Spravovať pravidlá",
"senders_count": "{count, plural, one {# odosielateľa} few {# odosielateľov} many {# odosielateľa} other {# odosielateľov}}",
"cross_account": "Vyberte správy z jedného účtu",
"opaque_hint": "Vaše filtre boli upravené ručne. Otvorte nastavenia filtrov" ,
"loading": "Načítavajú sa pravidlá…",
"load_failed": "Pravidlá sa nepodarilo načítať",
"none": "Zatiaľ žiadne pravidlá",
"new_mail_only": "Len pre nové správy",
"running": "Spúšťa sa „{name}“ v priečinku {folder}…"
"opaque_hint": "Vaše filtre boli upravené ručne. Otvorte nastavenia filtrov" ,
"loading": "Načítavajú sa pravidlá…",
"load_failed": "Pravidlá sa nepodarilo načítať",
"none": "Zatiaľ žiadne pravidlá",
"new_mail_only": "Len pre nové správy",
"running": "Spúšťa sa „{name}“ v priečinku {folder}…"
},
"find_emails_from": "Nájsť e-maily od {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Vybrať všetko",
"global_search": "Hľadať všade",
"extend_selection": "Rozšíriť výber nadol / nahor",
"accessibility_panel": "Otvoriť možnosti prístupnosti"
"accessibility_panel": "Otvoriť možnosti prístupnosti",
"focus_notification": "Prejsť na najnovšie upozornenie"
},
"threads": {
"expand_collapse": "Rozbaliť/zbaliť vlákno",
+2 -1
View File
@@ -776,7 +776,8 @@
"select_all": "เลือกทั้งหมด",
"global_search": "ค้นหาทุกรายการ",
"extend_selection": "ขยายการเลือกลง / ขึ้น",
"accessibility_panel": "เปิดตัวเลือกการเข้าถึง"
"accessibility_panel": "เปิดตัวเลือกการเข้าถึง",
"focus_notification": "ไปที่การแจ้งเตือนล่าสุด"
},
"threads": {
"expand_collapse": "ขยาย/ย่อการสนทนา",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Kuralları yönet",
"senders_count": "{count, plural, one {# gönderen} other {# gönderen}}",
"cross_account": "Tek bir hesaptan iletiler seçin",
"opaque_hint": "Filtreleriniz elle düzenlenmiş. Filtre ayarlarını açın" ,
"loading": "Kurallar yükleniyor…",
"load_failed": "Kurallar yüklenemedi",
"none": "Henüz kural yok",
"new_mail_only": "Yalnızca yeni iletiler için",
"running": "\"{name}\" {folder} klasöründe çalıştırılıyor…"
"opaque_hint": "Filtreleriniz elle düzenlenmiş. Filtre ayarlarını açın" ,
"loading": "Kurallar yükleniyor…",
"load_failed": "Kurallar yüklenemedi",
"none": "Henüz kural yok",
"new_mail_only": "Yalnızca yeni iletiler için",
"running": "\"{name}\" {folder} klasöründe çalıştırılıyor…"
},
"find_emails_from": "{name} adlı kişiden gelen e-postaları bul"
},
@@ -3496,7 +3496,8 @@
"select_all": "Tümünü seç",
"global_search": "Her yerde ara",
"extend_selection": "Seçimi aşağı / yukarı genişlet",
"accessibility_panel": "Erişilebilirlik seçeneklerini aç"
"accessibility_panel": "Erişilebilirlik seçeneklerini aç",
"focus_notification": "En son bildirime git"
},
"threads": {
"expand_collapse": "İleti dizisini genişlet/daralt",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "Керувати правилами",
"senders_count": "{count, plural, one {# відправника} few {# відправників} many {# відправників} other {# відправника}}",
"cross_account": "Виберіть листи з одного облікового запису",
"opaque_hint": "Ваші фільтри відредаговано вручну. Відкрийте налаштування фільтрів" ,
"loading": "Завантаження правил…",
"load_failed": "Не вдалося завантажити правила",
"none": "Правил поки немає",
"new_mail_only": "Лише для нових листів",
"running": "Виконується «{name}» у теці {folder}…"
"opaque_hint": "Ваші фільтри відредаговано вручну. Відкрийте налаштування фільтрів" ,
"loading": "Завантаження правил…",
"load_failed": "Не вдалося завантажити правила",
"none": "Правил поки немає",
"new_mail_only": "Лише для нових листів",
"running": "Виконується «{name}» у теці {folder}…"
},
"find_emails_from": "Знайти листи від {name}"
},
@@ -3496,7 +3496,8 @@
"select_all": "Вибрати все",
"global_search": "Шукати скрізь",
"extend_selection": "Розширити виділення вниз / вгору",
"accessibility_panel": "Відкрити параметри доступності"
"accessibility_panel": "Відкрити параметри доступності",
"focus_notification": "Перейти до останнього сповіщення"
},
"threads": {
"expand_collapse": "Розгорнути/згорнути ланцюжок",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "管理規則",
"senders_count": "{count, plural, other {# 位寄件者}}",
"cross_account": "請選取同一個帳號中的郵件",
"opaque_hint": "你的篩選規則曾被手動編輯。請開啟篩選規則設定" ,
"loading": "正在載入規則…",
"load_failed": "無法載入規則",
"none": "尚無規則",
"new_mail_only": "僅限新郵件",
"running": "正在 {folder} 中執行「{name}」…"
"opaque_hint": "你的篩選規則曾被手動編輯。請開啟篩選規則設定" ,
"loading": "正在載入規則…",
"load_failed": "無法載入規則",
"none": "尚無規則",
"new_mail_only": "僅限新郵件",
"running": "正在 {folder} 中執行「{name}」…"
},
"find_emails_from": "尋找來自 {name} 的郵件"
},
@@ -3496,7 +3496,8 @@
"select_all": "全部選取",
"global_search": "全域搜尋",
"extend_selection": "向下 / 向上擴展選取",
"accessibility_panel": "開啟無障礙選項"
"accessibility_panel": "開啟無障礙選項",
"focus_notification": "前往最新通知"
},
"threads": {
"expand_collapse": "展開/收合對話串",
+8 -7
View File
@@ -3400,12 +3400,12 @@
"manage": "管理规则",
"senders_count": "{count, plural, other {# 个发件人}}",
"cross_account": "请选择同一账户中的邮件",
"opaque_hint": "你的过滤器已被手动编辑。请打开过滤器设置"␍,
"loading": "正在加载规则…",
"load_failed": "无法加载规则",
"none": "暂无规则",
"new_mail_only": "仅限新邮件",
"running": "正在 {folder} 中运行“{name}”…"
"opaque_hint": "你的过滤器已被手动编辑。请打开过滤器设置"␍,
"loading": "正在加载规则…",
"load_failed": "无法加载规则",
"none": "暂无规则",
"new_mail_only": "仅限新邮件",
"running": "正在 {folder} 中运行“{name}”…"
},
"find_emails_from": "查找来自 {name} 的邮件"
},
@@ -3496,7 +3496,8 @@
"select_all": "全选",
"global_search": "全局搜索",
"extend_selection": "向下 / 向上扩展选择",
"accessibility_panel": "打开无障碍选项"
"accessibility_panel": "打开无障碍选项",
"focus_notification": "转到最新通知"
},
"threads": {
"expand_collapse": "展开/折叠会话",