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
76 lines
2.2 KiB
TypeScript
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>
|
|
);
|
|
}
|