Files
SRCmail/components/providers/intl-provider.tsx
T
Matthieu MALVACHEandMatthieu MALVACHE 5d273e2109 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
2026-01-08 22:15:32 +01:00

76 lines
2.2 KiB
TypeScript

"use client";
import { useEffect, useState } from 'react';
import { NextIntlClientProvider } from 'next-intl';
import { useLocaleStore } from '@/stores/locale-store';
import enMessages from '@/locales/en/common.json';
import frMessages from '@/locales/fr/common.json';
import jaMessages from '@/locales/ja/common.json';
import esMessages from '@/locales/es/common.json';
import itMessages from '@/locales/it/common.json';
import deMessages from '@/locales/de/common.json';
import nlMessages from '@/locales/nl/common.json';
import ptMessages from '@/locales/pt/common.json';
// Pre-loaded translations (loaded at build time, not runtime)
const ALL_MESSAGES = {
en: enMessages,
fr: frMessages,
ja: jaMessages,
es: esMessages,
it: itMessages,
de: deMessages,
nl: nlMessages,
pt: ptMessages,
};
interface IntlProviderProps {
locale: string;
messages: Record<string, unknown>;
children: React.ReactNode;
}
export function IntlProvider({ locale: initialLocale, children }: IntlProviderProps) {
const currentLocale = useLocaleStore((state) => state.locale);
const setLocale = useLocaleStore((state) => state.setLocale);
const [activeLocale, setActiveLocale] = useState(currentLocale || initialLocale);
const [timeZone, setTimeZone] = useState<string>('UTC');
// Detect user's timezone on mount
useEffect(() => {
try {
const detectedTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
setTimeZone(detectedTimeZone);
} catch (error) {
// Fallback to UTC if detection fails
console.warn('Failed to detect timezone, using UTC:', error);
setTimeZone('UTC');
}
}, []);
// Sync initial locale with store on first mount only
useEffect(() => {
if (!currentLocale) {
setLocale(initialLocale);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Switch locale immediately when store changes
useEffect(() => {
if (currentLocale) {
setActiveLocale(currentLocale);
}
}, [currentLocale]);
return (
<NextIntlClientProvider
locale={activeLocale}
messages={ALL_MESSAGES[activeLocale as keyof typeof ALL_MESSAGES]}
timeZone={timeZone}
>
{children}
</NextIntlClientProvider>
);
}