fix: name every icon button and switch in Settings for what it does

This commit is contained in:
Linus Rath
2026-10-28 04:50:27 +01:00
parent ab3addefc3
commit d0134c3f46
35 changed files with 215 additions and 79 deletions
+12 -13
View File
@@ -4,7 +4,8 @@ import { useState, useCallback, useMemo, useRef } from "react";
import { useTranslations } from "next-intl";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Plus, Trash2 } from "@/components/icons";
import { Plus, X } from "@/components/icons";
import { IconButton } from "@/components/ui/icon-button";
import { Dialog } from "@/components/ui/dialog";
import { useDiscardChanges } from "@/components/settings/use-discard-changes";
import type {
@@ -602,15 +603,14 @@ export function FilterRuleModal({
/>
)}
<button
type="button"
<IconButton
label={t("remove_condition")}
onClick={() => removeCondition(index)}
disabled={conditions.length <= 1}
className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-destructive transition-colors disabled:opacity-30 disabled:pointer-events-none"
aria-label={t("delete_rule")}
className="text-muted-foreground hover:text-destructive disabled:opacity-30"
>
<Trash2 className="w-4 h-4" />
</button>
<X />
</IconButton>
</div>
))}
</div>
@@ -727,15 +727,14 @@ export function FilterRuleModal({
</select>
)}
<button
type="button"
<IconButton
label={t("remove_action")}
onClick={() => removeAction(index)}
disabled={actions.length <= 1}
className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-destructive transition-colors disabled:opacity-30 disabled:pointer-events-none"
aria-label={t("delete_rule")}
className="text-muted-foreground hover:text-destructive disabled:opacity-30"
>
<Trash2 className="w-4 h-4" />
</button>
<X />
</IconButton>
</div>
))}
</div>
@@ -0,0 +1,60 @@
import { render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { FilterRule } from '@/lib/jmap/sieve-types';
import { FilterRuleModal } from '@/components/filters/filter-rule-modal';
import { useAuthStore } from '@/stores/auth-store';
import { useEmailStore } from '@/stores/email-store';
import { useFilterStore } from '@/stores/filter-store';
import { FilterSettings } from '../filter-settings';
// SET-11: icon buttons and switches in Settings have a name that says what
// they do. The rule dialog's remove buttons were both named "Delete Rule";
// the rule switches on the Filters page had none.
vi.mock('@/stores/toast-store', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() } }));
vi.mock('../settings-section', async (importOriginal) => ({
...(await importOriginal<typeof import('../settings-section')>()),
SettingsSection: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
const rule: FilterRule = {
id: 'r1', name: 'Newsletters', enabled: true, matchType: 'all',
conditions: [{ field: 'from', comparator: 'contains', value: 'a' }, { field: 'subject', comparator: 'contains', value: 'b' }],
actions: [{ type: 'star' }, { type: 'mark_read' }], stopProcessing: false,
};
const initial = { auth: useAuthStore.getState(), email: useEmailStore.getState(), filter: useFilterStore.getState() };
afterEach(() => {
useAuthStore.setState(initial.auth, true);
useEmailStore.setState(initial.email, true);
useFilterStore.setState(initial.filter, true);
});
function unnamedButtons(root: HTMLElement) {
return Array.from(root.querySelectorAll('button, [role="switch"]')).filter((b) => {
const name = b.getAttribute('aria-label') || b.getAttribute('aria-labelledby') || b.textContent?.trim() || b.getAttribute('title');
return !name;
});
}
describe('names of settings controls', () => {
it('names the remove buttons of a rule for what they remove', () => {
render(<FilterRuleModal rule={rule} mailboxes={[]} onSave={vi.fn()} onClose={vi.fn()} />);
expect(screen.getAllByRole('button', { name: 'remove_condition' })).toHaveLength(2);
expect(screen.getAllByRole('button', { name: 'remove_action' })).toHaveLength(2);
expect(screen.queryByRole('button', { name: 'delete_rule' })).toBeNull();
expect(unnamedButtons(screen.getByRole('dialog'))).toEqual([]);
});
it('names each rule switch on the Filters page after its rule', () => {
useAuthStore.setState({ client: { updateSieveScript: vi.fn(), getSieveAccountId: () => 'primary' } as never });
useEmailStore.setState({ fetchMailboxes: vi.fn(async () => {}) as never });
useFilterStore.setState({
rules: [rule], isSupported: true, isLoading: false, isOpaque: false, activeScriptId: 's',
selectedAccountId: 'primary', sieveCapabilities: null, selectAccount: vi.fn(async () => {}),
});
const { container } = render(<FilterSettings />);
expect(screen.getByRole('switch', { name: 'Newsletters' })).toBeTruthy();
expect(unnamedButtons(container)).toEqual([]);
});
});
@@ -20,10 +20,12 @@ import { IS_LITE } from '@/lib/lite';
import { useConfig } from '@/hooks/use-config';
import { totpIssuer } from '@/lib/totp-issuer';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { IconButton } from '@/components/ui/icon-button';
import { useConfirmDialog } from '@/hooks/use-confirm-dialog';
function PasswordChangeSection() {
const t = useTranslations('settings.security');
const tLogin = useTranslations('login');
const { changePassword, isSaving, otpEnabled } = useAccountSecurityStore();
const [currentPassword, setCurrentPassword] = useState('');
const [otpCode, setOtpCode] = useState('');
@@ -78,13 +80,14 @@ function PasswordChangeSection() {
autoComplete="current-password"
className="pe-10"
/>
<button
type="button"
<IconButton
label={showCurrent ? tLogin('hide_password') : tLogin('show_password')}
aria-pressed={showCurrent}
onClick={() => setShowCurrent(!showCurrent)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
className="absolute end-1 top-1/2 -translate-y-1/2 text-muted-foreground"
>
{showCurrent ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
{showCurrent ? <EyeOff /> : <Eye />}
</IconButton>
</div>
</div>
{otpEnabled && (
@@ -112,13 +115,14 @@ function PasswordChangeSection() {
autoComplete="new-password"
className="pe-10"
/>
<button
type="button"
<IconButton
label={showNew ? tLogin('hide_password') : tLogin('show_password')}
aria-pressed={showNew}
onClick={() => setShowNew(!showNew)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
className="absolute end-1 top-1/2 -translate-y-1/2 text-muted-foreground"
>
{showNew ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
{showNew ? <EyeOff /> : <Eye />}
</IconButton>
</div>
</div>
<div>
@@ -392,7 +396,7 @@ function parseIpList(raw: string): string[] {
.filter(Boolean);
}
function CredentialRow({ entry, onRemove, isSaving }: { entry: AppPasswordInfo | ApiKeyInfo; onRemove: (id: string) => void; isSaving: boolean }) {
function CredentialRow({ entry, onRemove, isSaving, deleteLabel }: { entry: AppPasswordInfo | ApiKeyInfo; onRemove: (id: string) => void; isSaving: boolean; deleteLabel: string }) {
return (
<div className="flex items-start justify-between py-2 px-3 bg-muted/50 rounded-md gap-2">
<div className="flex flex-col min-w-0 flex-1">
@@ -416,15 +420,14 @@ function CredentialRow({ entry, onRemove, isSaving }: { entry: AppPasswordInfo |
</div>
)}
</div>
<Button
variant="ghost"
size="sm"
<IconButton
label={deleteLabel}
onClick={() => onRemove(entry.id)}
disabled={isSaving}
className="text-destructive hover:text-destructive shrink-0"
>
<Trash2 className="w-3 h-3" />
</Button>
<Trash2 />
</IconButton>
</div>
);
}
@@ -582,7 +585,7 @@ function CredentialSection({ icon: Icon, i18nNamespace, entries, onCreate, onRem
{entries.length > 0 ? (
<div className="space-y-1">
{entries.map((entry) => (
<CredentialRow key={entry.id} entry={entry} onRemove={handleRemove} isSaving={isSaving} />
<CredentialRow key={entry.id} entry={entry} onRemove={handleRemove} isSaving={isSaving} deleteLabel={tCommon('delete')} />
))}
</div>
) : (
@@ -869,30 +872,27 @@ function PublicKeysSection() {
</div>
<div className="flex items-center gap-1 shrink-0">
<Button
variant="ghost"
size="sm"
<IconButton
label={isEncryptedWithThisKey ? t('encryption.disable_tooltip') : t('encryption.enable_tooltip')}
onClick={() => handleToggleEncryption(key.id)}
disabled={isSaving}
title={isEncryptedWithThisKey ? t('encryption.disable_tooltip') : t('encryption.enable_tooltip')}
className={cn(
isEncryptedWithThisKey
? 'text-green-600 hover:text-green-700 dark:text-green-400'
: 'text-muted-foreground hover:text-foreground'
)}
>
{isEncryptedWithThisKey ? <Lock className="w-4 h-4" /> : <Unlock className="w-4 h-4" />}
</Button>
{isEncryptedWithThisKey ? <Lock /> : <Unlock />}
</IconButton>
<Button
variant="ghost"
size="sm"
<IconButton
label={tCommon('delete')}
onClick={() => handleRemove(key.id)}
disabled={isSaving || isEncryptedWithThisKey}
className="text-destructive hover:text-destructive"
>
<Trash2 className="w-3 h-3" />
</Button>
<Trash2 />
</IconButton>
</div>
</div>
);
+2
View File
@@ -672,6 +672,7 @@ export function FilterSettings() {
<ToggleSwitch
checked={rule.enabled}
onChange={() => handleToggle(rule.id)}
ariaLabel={rule.name}
/>
</div>
@@ -755,6 +756,7 @@ export function FilterSettings() {
<ToggleSwitch
checked={expandedFilterView}
onChange={(v) => updateSetting("expandedFilterView", v)}
ariaLabel={t("expanded_view")}
/>
</div>
)}
+17 -28
View File
@@ -28,6 +28,7 @@ import {
type AppIcon,
} from '@/components/icons';
import { Button } from '@/components/ui/button';
import { IconButton } from '@/components/ui/icon-button';
import { Input } from '@/components/ui/input';
import { AppearanceSettings } from '@/components/settings/appearance-settings';
import { AppTopBannerSlot } from '@/components/plugins/app-top-banner-slot';
@@ -868,14 +869,9 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
<div className={cn("flex flex-col bg-background pt-[env(safe-area-inset-top)]", isEmbedded ? "h-full" : "h-dvh")}>
<AppTopBannerSlot />
<div className="flex items-center gap-2 px-4 h-14 border-b border-border bg-background shrink-0">
<Button
variant="ghost"
size="icon"
onClick={() => window.history.back()}
className="h-10 w-10"
>
<ArrowLeft className="w-5 h-5" />
</Button>
<IconButton label={t('back_to_settings')} size="icon-lg" onClick={() => window.history.back()}>
<ArrowLeft />
</IconButton>
<h1 className="font-semibold text-lg truncate">{activeTabLabel}</h1>
</div>
@@ -901,14 +897,9 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
<div className={cn("flex flex-col bg-background pt-[env(safe-area-inset-top)]", isEmbedded ? "h-full" : "h-dvh")}>
<AppTopBannerSlot />
<div className="flex items-center gap-2 px-4 h-14 border-b border-border bg-background shrink-0">
<Button
variant="ghost"
size="icon"
onClick={() => router.push('/')}
className="h-10 w-10"
>
<ArrowLeft className="w-5 h-5" />
</Button>
<IconButton label={t('back_to_mail')} size="icon-lg" onClick={() => router.push('/')}>
<ArrowLeft />
</IconButton>
<div className="flex items-center gap-2">
<SettingsIcon className="w-5 h-5 text-muted-foreground" />
<h1 className="font-semibold text-lg">{t('title')}</h1>
@@ -928,14 +919,13 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
aria-label={t('search_placeholder')}
/>
{searchQuery && (
<button
type="button"
<IconButton
label={t('search_clear')}
onClick={() => setSearchQuery('')}
className="absolute end-2 top-1/2 -translate-y-1/2 p-1 rounded-full hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
aria-label={t('search_clear')}
className="absolute end-1 top-1/2 -translate-y-1/2 h-7 w-7 rounded-full text-muted-foreground"
>
<X className="w-4 h-4" />
</button>
<X />
</IconButton>
)}
</div>
</div>
@@ -1090,14 +1080,13 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
aria-label={t('search_placeholder')}
/>
{searchQuery && (
<button
type="button"
<IconButton
label={t('search_clear')}
onClick={() => setSearchQuery('')}
className="absolute end-2 top-1/2 -translate-y-1/2 p-1 rounded-full hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
aria-label={t('search_clear')}
className="absolute end-1 top-1/2 -translate-y-1/2 h-7 w-7 rounded-full text-muted-foreground"
>
<X className="w-4 h-4" />
</button>
<X />
</IconButton>
)}
</div>
</div>
+9 -10
View File
@@ -5,6 +5,7 @@ import { useTranslations } from "next-intl";
import { Plus, Pencil, Trash2, ExternalLink, PanelRight, GripVertical, Lock } from "@/components/icons";
import { iconForName } from "@/components/icons";
import { Button } from "@/components/ui/button";
import { IconButton } from "@/components/ui/icon-button";
import { Input } from "@/components/ui/input";
import { SettingsSection, SettingItem, ToggleSwitch } from "./settings-section";
import { IconPicker } from "@/components/layout/icon-picker";
@@ -341,18 +342,16 @@ export function SidebarAppsSettings() {
)}>
{app.openMode === "inline" ? tApps("inline_badge") : tApps("tab_badge")}
</span>
<button
onClick={() => setEditingApp(app.id)}
className="p-1.5 rounded-md hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
>
<Pencil className="w-3.5 h-3.5" />
</button>
<button
<IconButton label={tApps("edit_app")} onClick={() => setEditingApp(app.id)} className="text-muted-foreground">
<Pencil />
</IconButton>
<IconButton
label={tApps("delete")}
onClick={() => handleDelete(app)}
className="p-1.5 rounded-md hover:bg-destructive/10 text-muted-foreground hover:text-destructive transition-colors"
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
<Trash2 />
</IconButton>
</div>
);
})}
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "الإعدادات",
"back_to_mail": "العودة إلى البريد",
"back_to_settings": "العودة إلى الإعدادات",
"save_success": "تم حفظ الإعدادات بنجاح",
"import_success": "تم استيراد الإعدادات بنجاح",
"import_error": "فشل استيراد الإعدادات",
@@ -2362,6 +2363,8 @@
"add_condition": "إضافة شرط",
"actions": "الإجراءات",
"add_action": "إضافة إجراء",
"remove_condition": "إزالة الشرط",
"remove_action": "إزالة الإجراء",
"stop_processing": "إيقاف معالجة القواعد التالية",
"include_spam": "نقل الرسائل المصنفة كرسائل مزعجة أيضًا",
"attachment_type_placeholder": "مثال: pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Configuració",
"back_to_mail": "Torna al correu",
"back_to_settings": "Torna als paràmetres",
"save_success": "Configuració desada correctament",
"import_success": "Configuració importada correctament",
"import_error": "No s'ha pogut importar la configuració",
@@ -2362,6 +2363,8 @@
"add_condition": "Afegeix una condició",
"actions": "Accions",
"add_action": "Afegeix una acció",
"remove_condition": "Treu la condició",
"remove_action": "Treu l'acció",
"stop_processing": "Atura el processament de les regles següents",
"include_spam": "Mou també els missatges marcats com a correu brossa",
"attachment_type_placeholder": "p. ex. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Nastavení",
"back_to_mail": "Zpět na poštu",
"back_to_settings": "Zpět do nastavení",
"save_success": "Nastavení bylo uloženo",
"import_success": "Nastavení bylo importováno",
"import_error": "Import nastavení selhal",
@@ -2355,6 +2356,8 @@
"add_condition": "Přidat podmínku",
"actions": "Akce",
"add_action": "Přidat akci",
"remove_condition": "Odebrat podmínku",
"remove_action": "Odebrat akci",
"stop_processing": "Zastavit zpracování dalších pravidel",
"include_spam": "Přesouvat i zprávy označené jako spam",
"attachment_type_placeholder": "např. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Indstillinger",
"back_to_mail": "Tilbage til mail",
"back_to_settings": "Tilbage til indstillinger",
"save_success": "Indstillinger gemt succesfuldt",
"import_success": "Indstillinger importeret succesfuldt",
"import_error": "Kunne ikke importere indstillinger",
@@ -2358,6 +2359,8 @@
"add_condition": "Tilføj betingelse",
"actions": "Handlinger",
"add_action": "Tilføj handling",
"remove_condition": "Fjern betingelse",
"remove_action": "Fjern handling",
"stop_processing": "Stop behandling af efterfølgende regler",
"include_spam": "Flyt også beskeder, der er markeret som spam",
"attachment_type_placeholder": "f.eks. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Einstellungen",
"back_to_mail": "Zurück zu E-Mails",
"back_to_settings": "Zurück zu den Einstellungen",
"save_success": "Einstellungen erfolgreich gespeichert",
"import_success": "Einstellungen erfolgreich importiert",
"import_error": "Import der Einstellungen fehlgeschlagen",
@@ -2355,6 +2356,8 @@
"add_condition": "Bedingung hinzufügen",
"actions": "Aktionen",
"add_action": "Aktion hinzufügen",
"remove_condition": "Bedingung entfernen",
"remove_action": "Aktion entfernen",
"stop_processing": "Verarbeitung nachfolgender Regeln stoppen",
"include_spam": "Auch als Spam markierte Nachrichten verschieben",
"attachment_type_placeholder": "z.B. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Settings",
"back_to_mail": "Back to Mail",
"back_to_settings": "Back to settings",
"save_success": "Settings saved successfully",
"import_success": "Settings imported",
"import_error": "Couldn't import settings",
@@ -2362,6 +2363,8 @@
"add_condition": "Add Condition",
"actions": "Actions",
"add_action": "Add Action",
"remove_condition": "Remove condition",
"remove_action": "Remove action",
"stop_processing": "Stop processing subsequent rules",
"include_spam": "Also move messages marked as spam",
"attachment_type_placeholder": "e.g. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Configuración",
"back_to_mail": "Volver al Correo",
"back_to_settings": "Volver a los ajustes",
"save_success": "Configuración guardada exitosamente",
"import_success": "Configuración importada exitosamente",
"import_error": "Error al importar la configuración",
@@ -2355,6 +2356,8 @@
"add_condition": "Agregar condición",
"actions": "Acciones",
"add_action": "Agregar acción",
"remove_condition": "Quitar condición",
"remove_action": "Quitar acción",
"stop_processing": "Detener el procesamiento de reglas posteriores",
"include_spam": "Mover también los mensajes marcados como spam",
"attachment_type_placeholder": "p. ej. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Seaded",
"back_to_mail": "Tagasi postkasti",
"back_to_settings": "Tagasi seadete juurde",
"save_success": "Seaded salvestatud",
"import_success": "Seaded imporditud",
"import_error": "Seadete importimine ebaõnnestus",
@@ -2362,6 +2363,8 @@
"add_condition": "Lisa tingimus",
"actions": "Tegevused",
"add_action": "Lisa tegevus",
"remove_condition": "Eemalda tingimus",
"remove_action": "Eemalda toiming",
"stop_processing": "Peata järgmiste reeglite töötlemine",
"include_spam": "Liiguta ka rämpspostiks märgitud kirjad",
"attachment_type_placeholder": "nt pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "تنظیمات",
"back_to_mail": "بازگشت به ایمیل",
"back_to_settings": "بازگشت به تنظیمات",
"save_success": "تنظیمات با موفقیت ذخیره شد",
"import_success": "تنظیمات با موفقیت وارد شد",
"import_error": "وارد کردن تنظیمات ناموفق بود",
@@ -2362,6 +2363,8 @@
"add_condition": "افزودن شرط",
"actions": "عملیات‌ها",
"add_action": "افزودن عملیات",
"remove_condition": "حذف شرط",
"remove_action": "حذف کنش",
"stop_processing": "توقف پردازش قوانین بعدی",
"include_spam": "پیام‌های علامت‌خورده به‌عنوان هرزنامه هم منتقل شوند",
"attachment_type_placeholder": "مثال: pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Paramètres",
"back_to_mail": "Retour aux emails",
"back_to_settings": "Retour aux paramètres",
"save_success": "Paramètres enregistrés avec succès",
"import_success": "Paramètres importés avec succès",
"import_error": "Échec de l'importation des paramètres",
@@ -2355,6 +2356,8 @@
"add_condition": "Ajouter une condition",
"actions": "Actions",
"add_action": "Ajouter une action",
"remove_condition": "Retirer la condition",
"remove_action": "Retirer l'action",
"stop_processing": "Arrêter le traitement des règles suivantes",
"include_spam": "Déplacer aussi les messages marqués comme spam",
"attachment_type_placeholder": "p. ex. pdf, doc, jpg",
+3
View File
@@ -1013,6 +1013,7 @@
"settings": {
"title": "הגדרות",
"back_to_mail": "חזרה לדואר",
"back_to_settings": "חזרה להגדרות",
"save_success": "ההגדרות נשמרו בהצלחה",
"import_success": "ההגדרות יובאו בהצלחה",
"import_error": "ייבוא ההגדרות נכשל",
@@ -2277,6 +2278,8 @@
"add_condition": "הוסף מצב",
"actions": "פעולות",
"add_action": "הוסף פעולה",
"remove_condition": "הסרת תנאי",
"remove_action": "הסרת פעולה",
"stop_processing": "הפסק לעבד את הכללים הבאים",
"include_spam": "להעביר גם הודעות שסומנו כדואר זבל",
"condition_fields": {
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Beállítások",
"back_to_mail": "Vissza a levelekhez",
"back_to_settings": "Vissza a beállításokhoz",
"save_success": "Beállítások sikeresen elmentve",
"import_success": "Beállítások sikeresen importálva",
"import_error": "Nem sikerült importálni a beállításokat",
@@ -2358,6 +2359,8 @@
"add_condition": "Feltétel hozzáadása",
"actions": "Műveletek",
"add_action": "Művelet hozzáadása",
"remove_condition": "Feltétel eltávolítása",
"remove_action": "Művelet eltávolítása",
"stop_processing": "További szabályok feldolgozásának leállítása",
"include_spam": "A levélszemétnek jelölt üzenetek áthelyezése is",
"attachment_type_placeholder": "pl. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Impostazioni",
"back_to_mail": "Torna alla posta",
"back_to_settings": "Torna alle impostazioni",
"save_success": "Impostazioni salvate con successo",
"import_success": "Impostazioni importate con successo",
"import_error": "Impossibile importare le impostazioni",
@@ -2355,6 +2356,8 @@
"add_condition": "Aggiungi condizione",
"actions": "Azioni",
"add_action": "Aggiungi azione",
"remove_condition": "Rimuovi condizione",
"remove_action": "Rimuovi azione",
"stop_processing": "Interrompere l'elaborazione delle regole successive",
"include_spam": "Sposta anche i messaggi contrassegnati come spam",
"attachment_type_placeholder": "es. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "設定",
"back_to_mail": "メールに戻る",
"back_to_settings": "設定に戻る",
"save_success": "設定を保存しました",
"import_success": "設定をインポートしました",
"import_error": "設定のインポートに失敗しました",
@@ -2355,6 +2356,8 @@
"add_condition": "条件を追加",
"actions": "アクション",
"add_action": "アクションを追加",
"remove_condition": "条件を削除",
"remove_action": "操作を削除",
"stop_processing": "以降のルールの処理を停止",
"include_spam": "迷惑メールと判定されたメッセージも移動する",
"attachment_type_placeholder": "例: pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "설정",
"back_to_mail": "메일로 돌아가기",
"back_to_settings": "설정으로 돌아가기",
"save_success": "설정이 저장되었어요",
"import_success": "설정을 성공적으로 가져왔어요",
"import_error": "설정 가져오기에 실패했어요",
@@ -2355,6 +2356,8 @@
"add_condition": "조건 추가",
"actions": "동작",
"add_action": "동작 추가",
"remove_condition": "조건 제거",
"remove_action": "동작 제거",
"stop_processing": "이후 규칙 무시하기",
"include_spam": "스팸으로 표시된 메시지도 이동",
"attachment_type_placeholder": "예: pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Iestatījumi",
"back_to_mail": "Atgriezties pie pasta",
"back_to_settings": "Atpakaļ uz iestatījumiem",
"save_success": "Iestatījumi veiksmīgi saglabāti",
"import_success": "Iestatījumi veiksmīgi importēti",
"import_error": "Neizdevās importēt iestatījumus",
@@ -2355,6 +2356,8 @@
"add_condition": "Pievienot nosacījumu",
"actions": "Darbības",
"add_action": "Pievienot darbību",
"remove_condition": "Noņemt nosacījumu",
"remove_action": "Noņemt darbību",
"stop_processing": "Pārtraukt nākamo noteikumu apstrādi",
"include_spam": "Pārvietot arī ziņas, kas atzīmētas kā mēstules",
"attachment_type_placeholder": "piem. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Тохиргоо",
"back_to_mail": "Мэйл рүү буцах",
"back_to_settings": "Тохиргоо руу буцах",
"save_success": "Тохиргоог амжилттай хадгаллаа",
"import_success": "Тохиргоог амжилттай импортоллоо",
"import_error": "Тохиргоог оруулж чадсангүй",
@@ -2362,6 +2363,8 @@
"add_condition": "Нөхцөл нэмэх",
"actions": "Үйлдэл",
"add_action": "Үйлдэл нэмэх",
"remove_condition": "Нөхцөлийг хасах",
"remove_action": "Үйлдлийг хасах",
"stop_processing": "Дараагийн дүрмийг боловсруулахаа зогсоох",
"include_spam": "Спам гэж тэмдэглэсэн захидлыг мөн зөөх",
"attachment_type_placeholder": "жишээ нь pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Innstillinger",
"back_to_mail": "Tilbake til e-post",
"back_to_settings": "Tilbake til innstillinger",
"save_success": "Innstillingene er lagret",
"import_success": "Innstillingene er importert",
"import_error": "Kunne ikke importere innstillinger",
@@ -2362,6 +2363,8 @@
"add_condition": "Legg til vilkår",
"actions": "Handlinger",
"add_action": "Legg til handling",
"remove_condition": "Fjern betingelse",
"remove_action": "Fjern handling",
"stop_processing": "Ikke behandle flere regler",
"include_spam": "Flytt også meldinger som er merket som søppelpost",
"attachment_type_placeholder": "f.eks. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Instellingen",
"back_to_mail": "Terug naar Mail",
"back_to_settings": "Terug naar instellingen",
"save_success": "Instellingen succesvol opgeslagen",
"import_success": "Instellingen succesvol geïmporteerd",
"import_error": "Kan instellingen niet importeren",
@@ -2355,6 +2356,8 @@
"add_condition": "Voorwaarde toevoegen",
"actions": "Acties",
"add_action": "Actie toevoegen",
"remove_condition": "Voorwaarde verwijderen",
"remove_action": "Actie verwijderen",
"stop_processing": "Verwerking van volgende regels stoppen",
"include_spam": "Ook berichten verplaatsen die als spam zijn gemarkeerd",
"attachment_type_placeholder": "bijv. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Ustawienia",
"back_to_mail": "Powrót do poczty",
"back_to_settings": "Wróć do ustawień",
"save_success": "Ustawienia zostały zapisane",
"import_success": "Ustawienia zostały zaimportowane",
"import_error": "Nie udało się zaimportować ustawień",
@@ -2355,6 +2356,8 @@
"add_condition": "Dodaj warunek",
"actions": "Akcje",
"add_action": "Dodaj akcję",
"remove_condition": "Usuń warunek",
"remove_action": "Usuń akcję",
"stop_processing": "Zatrzymaj przetwarzanie kolejnych reguł",
"include_spam": "Przenoś również wiadomości oznaczone jako spam",
"attachment_type_placeholder": "np. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Configurações",
"back_to_mail": "Voltar para E-mails",
"back_to_settings": "Voltar às definições",
"save_success": "Configurações salvas com sucesso",
"import_success": "Configurações importadas com sucesso",
"import_error": "Falha ao importar configurações",
@@ -2355,6 +2356,8 @@
"add_condition": "Adicionar condição",
"actions": "Ações",
"add_action": "Adicionar ação",
"remove_condition": "Remover condição",
"remove_action": "Remover ação",
"stop_processing": "Parar o processamento das regras seguintes",
"include_spam": "Mover também as mensagens marcadas como spam",
"attachment_type_placeholder": "ex.: pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Setări",
"back_to_mail": "Înapoi la Mail",
"back_to_settings": "Înapoi la setări",
"save_success": "Setările au fost salvate cu succes",
"import_success": "Setările au fost importate cu succes",
"import_error": "Nu s-au putut importa setările",
@@ -2362,6 +2363,8 @@
"add_condition": "Adăugați condiție",
"actions": "Acțiuni",
"add_action": "Adăugare acțiune",
"remove_condition": "Elimină condiția",
"remove_action": "Elimină acțiunea",
"stop_processing": "Opriți procesarea regulilor următoare",
"include_spam": "Mută și mesajele marcate ca spam",
"attachment_type_placeholder": "de ex. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Настройки",
"back_to_mail": "Вернуться к почте",
"back_to_settings": "Назад к настройкам",
"save_success": "Настройки успешно сохранены",
"import_success": "Настройки успешно импортированы",
"import_error": "Не удалось импортировать настройки",
@@ -2355,6 +2356,8 @@
"add_condition": "Добавить условие",
"actions": "Действия",
"add_action": "Добавить действие",
"remove_condition": "Убрать условие",
"remove_action": "Убрать действие",
"stop_processing": "Прекратить обработку последующих правил",
"include_spam": "Перемещать также письма, помеченные как спам",
"attachment_type_placeholder": "напр. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Nastavenia",
"back_to_mail": "Späť na poštu",
"back_to_settings": "Späť do nastavení",
"save_success": "Nastavenia boli úspešne uložené",
"import_success": "Nastavenia boli úspešne importované",
"import_error": "Import nastavení zlyhal",
@@ -2362,6 +2363,8 @@
"add_condition": "Pridať podmienku",
"actions": "Akcie",
"add_action": "Pridať akciu",
"remove_condition": "Odobrať podmienku",
"remove_action": "Odobrať akciu",
"stop_processing": "Zastaviť spracovanie ďalších pravidiel",
"include_spam": "Presúvať aj správy označené ako spam",
"attachment_type_placeholder": "napr. pdf, doc, jpg",
+3
View File
@@ -3058,6 +3058,7 @@
"settings": {
"title": "การตั้งค่า",
"back_to_mail": "กลับไปยังอีเมล",
"back_to_settings": "กลับไปที่การตั้งค่า",
"save_success": "บันทึกการตั้งค่าเรียบร้อยแล้ว",
"import_success": "นำเข้าการตั้งค่าเรียบร้อยแล้ว",
"import_error": "นำเข้าการตั้งค่าไม่สำเร็จ",
@@ -4372,6 +4373,8 @@
"add_condition": "เพิ่มเงื่อนไข",
"actions": "การดำเนินการ",
"add_action": "เพิ่มการดำเนินการ",
"remove_condition": "นำเงื่อนไขออก",
"remove_action": "นำการทำงานออก",
"stop_processing": "หยุดการประมวลผลกฎข้อถัดไป",
"include_spam": "ย้ายข้อความที่ทำเครื่องหมายว่าเป็นสแปมด้วย",
"attachment_type_placeholder": "เช่น pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Ayarlar",
"back_to_mail": "Postaya Dön",
"back_to_settings": "Ayarlara dön",
"save_success": "Ayarlar başarıyla kaydedildi",
"import_success": "Ayarlar başarıyla içe aktarıldı",
"import_error": "Ayarlar içe aktarılamadı",
@@ -2355,6 +2356,8 @@
"add_condition": "Koşul Ekle",
"actions": "İşlemler",
"add_action": "İşlem Ekle",
"remove_condition": "Koşulu kaldır",
"remove_action": "Eylemi kaldır",
"stop_processing": "Sonraki kuralları işlemeyi durdur",
"include_spam": "İstenmeyen olarak işaretlenen iletileri de taşı",
"attachment_type_placeholder": "örn. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "Налаштування",
"back_to_mail": "Назад до пошти",
"back_to_settings": "Назад до налаштувань",
"save_success": "Налаштування успішно збережено",
"import_success": "Налаштування успішно імпортовано",
"import_error": "Не вдалося імпортувати налаштування",
@@ -2355,6 +2356,8 @@
"add_condition": "Додати умову",
"actions": "Дії",
"add_action": "Додати дію",
"remove_condition": "Прибрати умову",
"remove_action": "Прибрати дію",
"stop_processing": "Зупинити обробку наступних правил",
"include_spam": "Переміщувати також листи, позначені як спам",
"attachment_type_placeholder": "напр. pdf, doc, jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "設定",
"back_to_mail": "回到郵件",
"back_to_settings": "返回設定",
"save_success": "設定已成功儲存",
"import_success": "已成功匯入設定",
"import_error": "無法匯入設定",
@@ -2362,6 +2363,8 @@
"add_condition": "新增條件",
"actions": "動作",
"add_action": "新增動作",
"remove_condition": "移除條件",
"remove_action": "移除動作",
"stop_processing": "停止處理後續規則",
"include_spam": "同時移動被標記為垃圾郵件的郵件",
"attachment_type_placeholder": "例如:pdf、doc、jpg",
+3
View File
@@ -1048,6 +1048,7 @@
"settings": {
"title": "设置",
"back_to_mail": "返回邮件",
"back_to_settings": "返回设置",
"save_success": "设置保存成功",
"import_success": "设置导入成功",
"import_error": "导入设置失败",
@@ -2355,6 +2356,8 @@
"add_condition": "添加条件",
"actions": "操作",
"add_action": "添加操作",
"remove_condition": "移除条件",
"remove_action": "移除操作",
"stop_processing": "停止处理后续规则",
"include_spam": "同时移动被标记为垃圾邮件的邮件",
"attachment_type_placeholder": "例如:pdf、doc、jpg",