feat: expand internationalization and add identity management

This release significantly expands internationalization support and adds comprehensive identity management features.

Internationalization (i18n):
- Add 5 new languages: Spanish, Italian, German, Dutch, Portuguese
- Expand from 3 to 8 total supported languages
- Redesign language switcher for better scalability (dropdown UI)
- Complete translations for all features across all languages

Identity Management:
- Multiple sender identities with per-identity signatures
- Sub-addressing support (user+tag@domain.com)
- Context-aware tag suggestions for sub-addresses
- Identity badges in email viewer and list
- Full CRUD operations for managing identities

Newsletter Management:
- RFC 2369 List-Unsubscribe support (one-click unsubscribe)
- HTTP and mailto unsubscribe methods
- Security validation prevents XSS attacks
- Two-step confirmation with persistent dismissal

Security & Accessibility:
- Dark mode email readability (intelligent color transformation)
- WCAG 2.0 Level AA color contrast compliance
- Comprehensive XSS prevention with validation utilities
- Unit test coverage for security-critical code (57 validation tests)

Testing:
- Add unit tests for validation utilities
- Add unit tests for email sanitization
- Add unit tests for color transformation
- Full test coverage for XSS attack vectors
This commit is contained in:
Matthieu MALVACHE
2026-01-08 22:15:32 +01:00
committed by Matthieu MALVACHE
parent 0d68851b63
commit 5d273e2109
49 changed files with 11018 additions and 432 deletions
+22 -7
View File
@@ -10,6 +10,9 @@ import { toast } from "@/stores/toast-store";
interface UseMailboxDropOptions {
mailbox: Mailbox;
onDropComplete?: () => void;
// Translation callbacks for toast messages
onSuccess?: (count: number, mailboxName: string) => void;
onError?: (error: string) => void;
}
interface UseMailboxDropReturn {
@@ -24,7 +27,7 @@ interface UseMailboxDropReturn {
isInvalidDropTarget: boolean;
}
export function useMailboxDrop({ mailbox, onDropComplete }: UseMailboxDropOptions): UseMailboxDropReturn {
export function useMailboxDrop({ mailbox, onDropComplete, onSuccess, onError }: UseMailboxDropOptions): UseMailboxDropReturn {
const [isOver, setIsOver] = useState(false);
const { client } = useAuthStore();
const { moveToMailbox, selectedEmailIds, clearSelection, fetchEmails, selectedMailbox } = useEmailStore();
@@ -119,21 +122,33 @@ export function useMailboxDrop({ mailbox, onDropComplete }: UseMailboxDropOption
// Refresh the current mailbox view
await fetchEmails(client, selectedMailbox);
// Show success toast
if (emailIds.length === 1) {
toast.success("Email moved", `Moved to ${mailbox.name}`);
// Call success callback if provided, otherwise use fallback
if (onSuccess) {
onSuccess(emailIds.length, mailbox.name);
} else {
toast.success("Emails moved", `${emailIds.length} emails moved to ${mailbox.name}`);
// Fallback for backward compatibility
if (emailIds.length === 1) {
toast.success("Email moved", `Moved to ${mailbox.name}`);
} else {
toast.success("Emails moved", `${emailIds.length} emails moved to ${mailbox.name}`);
}
}
onDropComplete?.();
} catch (error) {
console.error("Failed to move emails:", error);
toast.error("Move failed", "Could not move emails to the selected folder");
// Call error callback if provided, otherwise use fallback
if (onError) {
onError(error instanceof Error ? error.message : 'Unknown error');
} else {
// Fallback for backward compatibility
toast.error("Move failed", "Could not move emails to the selected folder");
}
} finally {
endDrag();
}
}, [client, mailbox, isValidTarget, moveToMailbox, selectedEmailIds, clearSelection, fetchEmails, selectedMailbox, endDrag, onDropComplete]);
}, [client, mailbox, isValidTarget, moveToMailbox, selectedEmailIds, clearSelection, fetchEmails, selectedMailbox, endDrag, onDropComplete, onSuccess, onError]);
const valid = isValidTarget();