saafe-redirection-flow 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,982 @@
1
+ # Theming and Internationalization Module Documentation
2
+
3
+ ## Overview
4
+
5
+ The Theming and Internationalization module provides comprehensive support for multiple visual themes and languages in the SAAFE Redirection Flow. This module ensures the application can adapt to different user preferences, regional requirements, and accessibility needs while maintaining brand consistency.
6
+
7
+ ## Theming System
8
+
9
+ ### Theme Context (`ThemeContext.tsx`)
10
+
11
+ **Location**: `src/contexts/ThemeContext.tsx`
12
+
13
+ **Core Features**:
14
+ - Light, dark, and system theme support
15
+ - Dynamic theme switching
16
+ - Platform-specific theme detection
17
+ - CSS custom properties integration
18
+ - Theme persistence across sessions
19
+
20
+ **Theme Types**:
21
+ ```typescript
22
+ type Theme = "light" | "dark" | "system";
23
+
24
+ interface ThemeContextType {
25
+ theme: Theme;
26
+ setTheme: (theme: Theme) => void;
27
+ effectiveTheme: "light" | "dark";
28
+ isSystemTheme: boolean;
29
+ }
30
+ ```
31
+
32
+ ### Theme Implementation
33
+
34
+ **Theme Provider Setup**:
35
+ ```tsx
36
+ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
37
+ children,
38
+ defaultTheme = "system",
39
+ storageKey = "saafe-ui-theme",
40
+ ...props
41
+ }) => {
42
+ const [theme, setTheme] = useState<Theme>(
43
+ () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
44
+ );
45
+
46
+ const value = {
47
+ theme,
48
+ setTheme: (theme: Theme) => {
49
+ localStorage.setItem(storageKey, theme);
50
+ setTheme(theme);
51
+ applyThemeToDocument(theme);
52
+ },
53
+ effectiveTheme: getEffectiveTheme(theme),
54
+ isSystemTheme: theme === "system"
55
+ };
56
+
57
+ return (
58
+ <ThemeContext.Provider {...props} value={value}>
59
+ {children}
60
+ </ThemeContext.Provider>
61
+ );
62
+ };
63
+ ```
64
+
65
+ ### CSS Variables System
66
+
67
+ **Theme Variables**:
68
+ ```css
69
+ :root {
70
+ /* Light theme variables */
71
+ --background: 0 0% 100%;
72
+ --foreground: 222.2 84% 4.9%;
73
+ --card: 0 0% 100%;
74
+ --card-foreground: 222.2 84% 4.9%;
75
+ --popover: 0 0% 100%;
76
+ --popover-foreground: 222.2 84% 4.9%;
77
+ --primary: 221.2 83.2% 53.3%;
78
+ --primary-foreground: 210 40% 98%;
79
+ --secondary: 210 40% 96%;
80
+ --secondary-foreground: 222.2 84% 4.9%;
81
+ --muted: 210 40% 96%;
82
+ --muted-foreground: 215.4 16.3% 46.9%;
83
+ --accent: 210 40% 96%;
84
+ --accent-foreground: 222.2 84% 4.9%;
85
+ --destructive: 0 84.2% 60.2%;
86
+ --destructive-foreground: 210 40% 98%;
87
+ --border: 214.3 31.8% 91.4%;
88
+ --input: 214.3 31.8% 91.4%;
89
+ --ring: 221.2 83.2% 53.3%;
90
+ --radius: 0.5rem;
91
+ }
92
+
93
+ .dark {
94
+ /* Dark theme variables */
95
+ --background: 222.2 84% 4.9%;
96
+ --foreground: 210 40% 98%;
97
+ --card: 222.2 84% 4.9%;
98
+ --card-foreground: 210 40% 98%;
99
+ --popover: 222.2 84% 4.9%;
100
+ --popover-foreground: 210 40% 98%;
101
+ --primary: 217.2 91.2% 59.8%;
102
+ --primary-foreground: 222.2 84% 4.9%;
103
+ --secondary: 217.2 32.6% 17.5%;
104
+ --secondary-foreground: 210 40% 98%;
105
+ --muted: 217.2 32.6% 17.5%;
106
+ --muted-foreground: 215 20.2% 65.1%;
107
+ --accent: 217.2 32.6% 17.5%;
108
+ --accent-foreground: 210 40% 98%;
109
+ --destructive: 0 62.8% 30.6%;
110
+ --destructive-foreground: 210 40% 98%;
111
+ --border: 217.2 32.6% 17.5%;
112
+ --input: 217.2 32.6% 17.5%;
113
+ --ring: 224.3 76.3% 94.1%;
114
+ }
115
+ ```
116
+
117
+ ### Theme Toggle Component
118
+
119
+ **Mode Toggle Component**:
120
+ ```tsx
121
+ // src/components/mode-toggle.tsx
122
+ export function ModeToggle() {
123
+ const { theme, setTheme } = useTheme();
124
+
125
+ return (
126
+ <DropdownMenu>
127
+ <DropdownMenuTrigger asChild>
128
+ <Button variant="outline" size="icon">
129
+ <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
130
+ <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
131
+ <span className="sr-only">Toggle theme</span>
132
+ </Button>
133
+ </DropdownMenuTrigger>
134
+ <DropdownMenuContent align="end">
135
+ <DropdownMenuItem onClick={() => setTheme("light")}>
136
+ Light
137
+ </DropdownMenuItem>
138
+ <DropdownMenuItem onClick={() => setTheme("dark")}>
139
+ Dark
140
+ </DropdownMenuItem>
141
+ <DropdownMenuItem onClick={() => setTheme("system")}>
142
+ System
143
+ </DropdownMenuItem>
144
+ </DropdownMenuContent>
145
+ </DropdownMenu>
146
+ );
147
+ }
148
+ ```
149
+
150
+ ### Platform-Specific Theme Detection
151
+
152
+ **URL Parameter Theme Override**:
153
+ ```typescript
154
+ const getThemeFromParams = (): Theme | null => {
155
+ const params = new URLSearchParams(window.location.search);
156
+ const themeParam = params.get('theme');
157
+
158
+ if (themeParam && ['light', 'dark', 'system'].includes(themeParam)) {
159
+ return themeParam as Theme;
160
+ }
161
+
162
+ return null;
163
+ };
164
+
165
+ const initializeTheme = () => {
166
+ const urlTheme = getThemeFromParams();
167
+ const storedTheme = localStorage.getItem('saafe-ui-theme');
168
+ const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
169
+
170
+ return urlTheme || storedTheme || systemTheme;
171
+ };
172
+ ```
173
+
174
+ ## Internationalization (i18n)
175
+
176
+ ### i18n Configuration (`i18n.ts`)
177
+
178
+ **Location**: `src/lib/i18n.ts`
179
+
180
+ **Setup and Configuration**:
181
+ ```typescript
182
+ import i18n from 'i18next';
183
+ import { initReactI18next } from 'react-i18next';
184
+ import LanguageDetector from 'i18next-browser-languagedetector';
185
+
186
+ // Import translation files
187
+ import enTranslation from '../locales/en/common.json';
188
+ import hiTranslation from '../locales/hi/common.json';
189
+ import taTranslation from '../locales/ta/common.json';
190
+ import mlTranslation from '../locales/ml/common.json';
191
+ import teTranslation from '../locales/te/common.json';
192
+ import knTranslation from '../locales/kn/common.json';
193
+ import urTranslation from '../locales/ur/common.json';
194
+
195
+ const resources = {
196
+ en: { translation: enTranslation },
197
+ hi: { translation: hiTranslation },
198
+ ta: { translation: taTranslation },
199
+ ml: { translation: mlTranslation },
200
+ te: { translation: teTranslation },
201
+ kn: { translation: knTranslation },
202
+ ur: { translation: urTranslation },
203
+ };
204
+
205
+ i18n
206
+ .use(LanguageDetector)
207
+ .use(initReactI18next)
208
+ .init({
209
+ resources,
210
+ fallbackLng: 'en',
211
+ debug: import.meta.env.DEV,
212
+
213
+ interpolation: {
214
+ escapeValue: false,
215
+ },
216
+
217
+ detection: {
218
+ order: ['querystring', 'localStorage', 'navigator'],
219
+ caches: ['localStorage'],
220
+ lookupQuerystring: 'lang',
221
+ lookupLocalStorage: 'i18nextLng',
222
+ },
223
+ });
224
+
225
+ export default i18n;
226
+ ```
227
+
228
+ ### Language Context (`LanguageContext.tsx`)
229
+
230
+ **Location**: `src/contexts/LanguageContext.tsx`
231
+
232
+ **Language Management**:
233
+ ```typescript
234
+ interface LanguageContextType {
235
+ currentLanguage: string;
236
+ setLanguage: (lang: string) => void;
237
+ availableLanguages: Language[];
238
+ isRTL: boolean;
239
+ t: TFunction;
240
+ }
241
+
242
+ export const LanguageProvider: React.FC<LanguageProviderProps> = ({ children }) => {
243
+ const [currentLanguage, setCurrentLanguage] = useState(i18n.language);
244
+ const { t } = useTranslation();
245
+
246
+ const setLanguage = useCallback((lang: string) => {
247
+ i18n.changeLanguage(lang);
248
+ setCurrentLanguage(lang);
249
+
250
+ // Update document direction for RTL languages
251
+ updateDocumentDirection(lang);
252
+
253
+ // Track language change
254
+ trackEvent('LANGUAGE_CHANGED', {
255
+ from: currentLanguage,
256
+ to: lang
257
+ });
258
+ }, [currentLanguage]);
259
+
260
+ const value = {
261
+ currentLanguage,
262
+ setLanguage,
263
+ availableLanguages: SUPPORTED_LANGUAGES,
264
+ isRTL: RTL_LANGUAGES.includes(currentLanguage),
265
+ t
266
+ };
267
+
268
+ return (
269
+ <LanguageContext.Provider value={value}>
270
+ {children}
271
+ </LanguageContext.Provider>
272
+ );
273
+ };
274
+ ```
275
+
276
+ ### Supported Languages
277
+
278
+ **Language Configuration**:
279
+ ```typescript
280
+ export const SUPPORTED_LANGUAGES = [
281
+ { code: 'en', name: 'English', nativeName: 'English' },
282
+ { code: 'hi', name: 'Hindi', nativeName: 'हिन्दी' },
283
+ { code: 'ta', name: 'Tamil', nativeName: 'தமிழ்' },
284
+ { code: 'ml', name: 'Malayalam', nativeName: 'മലയാളം' },
285
+ { code: 'te', name: 'Telugu', nativeName: 'తెలుగు' },
286
+ { code: 'kn', name: 'Kannada', nativeName: 'ಕನ್ನಡ' },
287
+ { code: 'ur', name: 'Urdu', nativeName: 'اردو' }
288
+ ];
289
+
290
+ export const RTL_LANGUAGES = ['ur', 'ar'];
291
+ export const DEFAULT_LANGUAGE = 'en';
292
+ ```
293
+
294
+ ### Language Switcher Component
295
+
296
+ **Location**: `src/components/language/LanguageSwitcher.tsx`
297
+
298
+ **Component Features**:
299
+ ```tsx
300
+ export function LanguageSwitcher() {
301
+ const { currentLanguage, setLanguage, availableLanguages } = useLanguage();
302
+
303
+ return (
304
+ <DropdownMenu>
305
+ <DropdownMenuTrigger asChild>
306
+ <Button variant="outline" size="sm">
307
+ <Globe className="mr-2 h-4 w-4" />
308
+ {availableLanguages.find(lang => lang.code === currentLanguage)?.nativeName}
309
+ </Button>
310
+ </DropdownMenuTrigger>
311
+ <DropdownMenuContent align="end">
312
+ {availableLanguages.map((language) => (
313
+ <DropdownMenuItem
314
+ key={language.code}
315
+ onClick={() => setLanguage(language.code)}
316
+ className={currentLanguage === language.code ? 'bg-accent' : ''}
317
+ >
318
+ <span className="text-sm font-medium">{language.nativeName}</span>
319
+ <span className="ml-2 text-xs text-muted-foreground">{language.name}</span>
320
+ </DropdownMenuItem>
321
+ ))}
322
+ </DropdownMenuContent>
323
+ </DropdownMenu>
324
+ );
325
+ }
326
+ ```
327
+
328
+ ## RTL (Right-to-Left) Support
329
+
330
+ ### RTL Context (`RTLContext.tsx`)
331
+
332
+ **Location**: `src/contexts/RTLContext.tsx`
333
+
334
+ **RTL Management**:
335
+ ```typescript
336
+ interface RTLContextType {
337
+ isRTL: boolean;
338
+ direction: 'ltr' | 'rtl';
339
+ toggleDirection: () => void;
340
+ setDirection: (direction: 'ltr' | 'rtl') => void;
341
+ }
342
+
343
+ export const RTLProvider: React.FC<RTLProviderProps> = ({ children }) => {
344
+ const { currentLanguage } = useLanguage();
345
+ const [isRTL, setIsRTL] = useState(RTL_LANGUAGES.includes(currentLanguage));
346
+
347
+ useEffect(() => {
348
+ const shouldBeRTL = RTL_LANGUAGES.includes(currentLanguage);
349
+ setIsRTL(shouldBeRTL);
350
+ updateDocumentDirection(shouldBeRTL ? 'rtl' : 'ltr');
351
+ }, [currentLanguage]);
352
+
353
+ const value = {
354
+ isRTL,
355
+ direction: isRTL ? 'rtl' : 'ltr',
356
+ toggleDirection: () => setIsRTL(!isRTL),
357
+ setDirection: (direction: 'ltr' | 'rtl') => setIsRTL(direction === 'rtl')
358
+ };
359
+
360
+ return (
361
+ <RTLContext.Provider value={value}>
362
+ {children}
363
+ </RTLContext.Provider>
364
+ );
365
+ };
366
+ ```
367
+
368
+ ### RTL Styling
369
+
370
+ **RTL CSS Utilities**:
371
+ ```css
372
+ /* src/styles/rtl.css */
373
+ .rtl {
374
+ direction: rtl;
375
+ }
376
+
377
+ .ltr {
378
+ direction: ltr;
379
+ }
380
+
381
+ /* RTL-aware margins and paddings */
382
+ .ms-auto {
383
+ margin-inline-start: auto;
384
+ }
385
+
386
+ .me-auto {
387
+ margin-inline-end: auto;
388
+ }
389
+
390
+ .ps-4 {
391
+ padding-inline-start: 1rem;
392
+ }
393
+
394
+ .pe-4 {
395
+ padding-inline-end: 1rem;
396
+ }
397
+
398
+ /* RTL-aware text alignment */
399
+ .text-start {
400
+ text-align: start;
401
+ }
402
+
403
+ .text-end {
404
+ text-align: end;
405
+ }
406
+
407
+ /* RTL-aware transforms */
408
+ .rtl .transform-flip-x {
409
+ transform: scaleX(-1);
410
+ }
411
+
412
+ /* RTL-aware animations */
413
+ .rtl .animate-slide-in-right {
414
+ animation: slideInLeft 0.3s ease-out;
415
+ }
416
+
417
+ .rtl .animate-slide-in-left {
418
+ animation: slideInRight 0.3s ease-out;
419
+ }
420
+ ```
421
+
422
+ **RTL Utility Classes**:
423
+ ```css
424
+ /* src/styles/rtl-utils.css */
425
+ .border-s {
426
+ border-inline-start-width: 1px;
427
+ }
428
+
429
+ .border-e {
430
+ border-inline-end-width: 1px;
431
+ }
432
+
433
+ .rounded-s {
434
+ border-start-start-radius: 0.375rem;
435
+ border-end-start-radius: 0.375rem;
436
+ }
437
+
438
+ .rounded-e {
439
+ border-start-end-radius: 0.375rem;
440
+ border-end-end-radius: 0.375rem;
441
+ }
442
+ ```
443
+
444
+ ### RTL Hooks
445
+
446
+ **useRTL Hook**:
447
+ ```typescript
448
+ // src/hooks/use-rtl.ts
449
+ export function useRTL() {
450
+ const context = useContext(RTLContext);
451
+ if (!context) {
452
+ throw new Error('useRTL must be used within an RTLProvider');
453
+ }
454
+ return context;
455
+ }
456
+
457
+ // Helper hook for conditional RTL styling
458
+ export function useRTLClass(baseClass: string, rtlClass?: string) {
459
+ const { isRTL } = useRTL();
460
+ return isRTL && rtlClass ? `${baseClass} ${rtlClass}` : baseClass;
461
+ }
462
+ ```
463
+
464
+ ## Translation Management
465
+
466
+ ### Translation File Structure
467
+
468
+ **Common Translation Keys**:
469
+ ```json
470
+ // src/locales/en/common.json
471
+ {
472
+ "common": {
473
+ "continue": "Continue",
474
+ "cancel": "Cancel",
475
+ "back": "Back",
476
+ "next": "Next",
477
+ "save": "Save",
478
+ "loading": "Loading...",
479
+ "error": "Something went wrong",
480
+ "retry": "Retry",
481
+ "close": "Close"
482
+ },
483
+ "authentication": {
484
+ "login": "Login",
485
+ "username": "Username",
486
+ "password": "Password",
487
+ "otp": "Enter OTP",
488
+ "loginButton": "Sign In",
489
+ "forgotPassword": "Forgot Password?",
490
+ "invalidCredentials": "Invalid username or password",
491
+ "otpExpired": "OTP has expired. Please request a new one.",
492
+ "sessionExpired": "Your session has expired. Please login again."
493
+ },
494
+ "accounts": {
495
+ "selectAccounts": "Select Accounts",
496
+ "noAccountsFound": "No accounts found",
497
+ "accountType": "Account Type",
498
+ "accountNumber": "Account Number",
499
+ "balance": "Balance",
500
+ "selectAll": "Select All",
501
+ "deselectAll": "Deselect All",
502
+ "linkedAccounts": "Linked Accounts"
503
+ },
504
+ "categories": {
505
+ "banks": "Banks",
506
+ "nbfc": "NBFCs",
507
+ "insurance": "Insurance",
508
+ "gst": "GST",
509
+ "investments": "Investments",
510
+ "banksDescription": "Traditional banking institutions",
511
+ "nbfcDescription": "Non-Banking Financial Companies",
512
+ "insuranceDescription": "Insurance providers",
513
+ "gstDescription": "GST-related financial data",
514
+ "investmentsDescription": "Investment and mutual fund companies"
515
+ },
516
+ "consent": {
517
+ "reviewConsent": "Review Consent",
518
+ "dataSharing": "Data Sharing",
519
+ "approve": "Approve",
520
+ "reject": "Reject",
521
+ "consentGiven": "Consent has been granted",
522
+ "consentRejected": "Consent has been rejected",
523
+ "dataScope": "Data that will be shared:",
524
+ "purpose": "Purpose of data sharing:",
525
+ "duration": "Data will be retained for:",
526
+ "termsAndConditions": "Terms and Conditions",
527
+ "privacyPolicy": "Privacy Policy"
528
+ },
529
+ "errors": {
530
+ "networkError": "Please check your internet connection",
531
+ "serverError": "Server error occurred. Please try again later.",
532
+ "validationError": "Please check the entered information",
533
+ "sessionTimeout": "Session timeout. Please login again.",
534
+ "accessDenied": "Access denied. Please contact support."
535
+ },
536
+ "success": {
537
+ "accountsLinked": "Accounts successfully linked",
538
+ "consentApproved": "Consent approved successfully",
539
+ "dataWillBeShared": "Your selected data will now be shared",
540
+ "returnToApp": "Return to Application",
541
+ "downloadReceipt": "Download Consent Receipt"
542
+ }
543
+ }
544
+ ```
545
+
546
+ ### Pluralization Support
547
+
548
+ **Plural Forms**:
549
+ ```json
550
+ {
551
+ "accountCount": "{{count}} account",
552
+ "accountCount_other": "{{count}} accounts",
553
+ "institutionCount": "{{count}} institution",
554
+ "institutionCount_other": "{{count}} institutions",
555
+ "dayCount": "{{count}} day",
556
+ "dayCount_other": "{{count}} days"
557
+ }
558
+ ```
559
+
560
+ **Usage in Components**:
561
+ ```tsx
562
+ const AccountSummary: React.FC<{ count: number }> = ({ count }) => {
563
+ const { t } = useTranslation();
564
+
565
+ return (
566
+ <div>
567
+ {t('accountCount', { count })} selected
568
+ </div>
569
+ );
570
+ };
571
+ ```
572
+
573
+ ### Interpolation and Formatting
574
+
575
+ **Variable Interpolation**:
576
+ ```json
577
+ {
578
+ "welcomeMessage": "Welcome, {{userName}}!",
579
+ "accountBalance": "Your balance is {{amount, currency}}",
580
+ "lastLogin": "Last login: {{date, datetime}}",
581
+ "expiryWarning": "Consent expires in {{days}} days"
582
+ }
583
+ ```
584
+
585
+ **Usage Examples**:
586
+ ```tsx
587
+ // Simple interpolation
588
+ const welcome = t('welcomeMessage', { userName: 'John Doe' });
589
+
590
+ // Currency formatting
591
+ const balance = t('accountBalance', {
592
+ amount: 25000.50,
593
+ formatParams: {
594
+ amount: { style: 'currency', currency: 'INR' }
595
+ }
596
+ });
597
+
598
+ // Date formatting
599
+ const lastLogin = t('lastLogin', {
600
+ date: new Date(),
601
+ formatParams: {
602
+ date: {
603
+ year: 'numeric',
604
+ month: 'long',
605
+ day: 'numeric'
606
+ }
607
+ }
608
+ });
609
+ ```
610
+
611
+ ## Custom Styling and Branding
612
+
613
+ ### Dynamic Styling Support
614
+
615
+ **Custom Style Options**:
616
+ ```typescript
617
+ interface CustomStyling {
618
+ primaryColor?: string;
619
+ secondaryColor?: string;
620
+ backgroundColor?: string;
621
+ textColor?: string;
622
+ borderRadius?: string;
623
+ fontFamily?: string;
624
+ logoUrl?: string;
625
+ brandName?: string;
626
+ }
627
+
628
+ const applyCustomStyling = (styles: CustomStyling) => {
629
+ const root = document.documentElement;
630
+
631
+ if (styles.primaryColor) {
632
+ root.style.setProperty('--primary', styles.primaryColor);
633
+ }
634
+
635
+ if (styles.secondaryColor) {
636
+ root.style.setProperty('--secondary', styles.secondaryColor);
637
+ }
638
+
639
+ if (styles.backgroundColor) {
640
+ root.style.setProperty('--background', styles.backgroundColor);
641
+ }
642
+
643
+ if (styles.fontFamily) {
644
+ root.style.setProperty('--font-family', styles.fontFamily);
645
+ }
646
+ };
647
+ ```
648
+
649
+ ### Brand Integration
650
+
651
+ **Logo and Branding**:
652
+ ```tsx
653
+ interface BrandingProps {
654
+ logoUrl?: string;
655
+ brandName?: string;
656
+ primaryColor?: string;
657
+ }
658
+
659
+ const BrandHeader: React.FC<BrandingProps> = ({
660
+ logoUrl,
661
+ brandName,
662
+ primaryColor
663
+ }) => {
664
+ return (
665
+ <header
666
+ className="flex items-center space-x-4 p-4"
667
+ style={{ color: primaryColor }}
668
+ >
669
+ {logoUrl && (
670
+ <img
671
+ src={logoUrl}
672
+ alt={`${brandName} logo`}
673
+ className="h-8 w-auto"
674
+ />
675
+ )}
676
+ {brandName && (
677
+ <h1 className="text-xl font-semibold">{brandName}</h1>
678
+ )}
679
+ </header>
680
+ );
681
+ };
682
+ ```
683
+
684
+ ## Accessibility Features
685
+
686
+ ### Theme-Based Accessibility
687
+
688
+ **High Contrast Mode**:
689
+ ```css
690
+ @media (prefers-contrast: high) {
691
+ :root {
692
+ --background: 0 0% 100%;
693
+ --foreground: 0 0% 0%;
694
+ --border: 0 0% 0%;
695
+ --primary: 220 100% 40%;
696
+ }
697
+
698
+ .dark {
699
+ --background: 0 0% 0%;
700
+ --foreground: 0 0% 100%;
701
+ --border: 0 0% 100%;
702
+ --primary: 220 100% 60%;
703
+ }
704
+ }
705
+ ```
706
+
707
+ **Reduced Motion Support**:
708
+ ```css
709
+ @media (prefers-reduced-motion: reduce) {
710
+ *,
711
+ *::before,
712
+ *::after {
713
+ animation-duration: 0.01ms !important;
714
+ animation-iteration-count: 1 !important;
715
+ transition-duration: 0.01ms !important;
716
+ scroll-behavior: auto !important;
717
+ }
718
+ }
719
+ ```
720
+
721
+ ### Language-Specific Accessibility
722
+
723
+ **Screen Reader Support**:
724
+ ```tsx
725
+ const AccessibleText: React.FC<{ children: React.ReactNode }> = ({ children }) => {
726
+ const { currentLanguage } = useLanguage();
727
+
728
+ return (
729
+ <span lang={currentLanguage} role="text">
730
+ {children}
731
+ </span>
732
+ );
733
+ };
734
+ ```
735
+
736
+ ## Platform-Specific Adaptations
737
+
738
+ ### Mobile Theme Adaptations
739
+
740
+ **Mobile-Specific Variables**:
741
+ ```css
742
+ @media (max-width: 768px) {
743
+ :root {
744
+ --mobile-header-height: 60px;
745
+ --mobile-footer-height: 80px;
746
+ --mobile-padding: 1rem;
747
+ --mobile-border-radius: 0.75rem;
748
+ }
749
+ }
750
+ ```
751
+
752
+ ### Web Theme Adaptations
753
+
754
+ **Desktop Enhancements**:
755
+ ```css
756
+ @media (min-width: 1024px) {
757
+ :root {
758
+ --desktop-sidebar-width: 280px;
759
+ --desktop-content-max-width: 1200px;
760
+ --desktop-border-radius: 0.5rem;
761
+ }
762
+ }
763
+ ```
764
+
765
+ ## Performance Optimizations
766
+
767
+ ### Lazy Loading of Translations
768
+
769
+ **Dynamic Import Strategy**:
770
+ ```typescript
771
+ const loadTranslations = async (language: string) => {
772
+ try {
773
+ const module = await import(`../locales/${language}/common.json`);
774
+ return module.default;
775
+ } catch (error) {
776
+ console.warn(`Failed to load translations for ${language}`);
777
+ return await import('../locales/en/common.json').then(m => m.default);
778
+ }
779
+ };
780
+ ```
781
+
782
+ ### Theme Caching
783
+
784
+ **Theme Persistence**:
785
+ ```typescript
786
+ const THEME_CACHE_KEY = 'saafe-theme-cache';
787
+ const THEME_CACHE_VERSION = '1.0';
788
+
789
+ const cacheTheme = (theme: Theme, customStyles?: CustomStyling) => {
790
+ const cacheData = {
791
+ version: THEME_CACHE_VERSION,
792
+ theme,
793
+ customStyles,
794
+ timestamp: Date.now()
795
+ };
796
+
797
+ localStorage.setItem(THEME_CACHE_KEY, JSON.stringify(cacheData));
798
+ };
799
+
800
+ const getCachedTheme = (): { theme: Theme; customStyles?: CustomStyling } | null => {
801
+ try {
802
+ const cached = localStorage.getItem(THEME_CACHE_KEY);
803
+ if (!cached) return null;
804
+
805
+ const data = JSON.parse(cached);
806
+ if (data.version !== THEME_CACHE_VERSION) return null;
807
+
808
+ return { theme: data.theme, customStyles: data.customStyles };
809
+ } catch {
810
+ return null;
811
+ }
812
+ };
813
+ ```
814
+
815
+ ## Testing Strategy
816
+
817
+ ### Theme Testing
818
+
819
+ **Theme Switch Testing**:
820
+ ```typescript
821
+ describe('Theme System', () => {
822
+ test('switches between light and dark themes', () => {
823
+ render(<ThemeProvider><TestComponent /></ThemeProvider>);
824
+
825
+ const toggleButton = screen.getByRole('button', { name: /toggle theme/i });
826
+
827
+ // Test initial theme
828
+ expect(document.documentElement).not.toHaveClass('dark');
829
+
830
+ // Switch to dark
831
+ fireEvent.click(toggleButton);
832
+ expect(document.documentElement).toHaveClass('dark');
833
+
834
+ // Switch back to light
835
+ fireEvent.click(toggleButton);
836
+ expect(document.documentElement).not.toHaveClass('dark');
837
+ });
838
+ });
839
+ ```
840
+
841
+ ### i18n Testing
842
+
843
+ **Translation Testing**:
844
+ ```typescript
845
+ describe('Internationalization', () => {
846
+ test('displays correct language text', () => {
847
+ render(
848
+ <I18nextProvider i18n={i18n}>
849
+ <TestComponent />
850
+ </I18nextProvider>
851
+ );
852
+
853
+ expect(screen.getByText('Continue')).toBeInTheDocument();
854
+
855
+ // Change language
856
+ act(() => {
857
+ i18n.changeLanguage('hi');
858
+ });
859
+
860
+ expect(screen.getByText('जारी रखें')).toBeInTheDocument();
861
+ });
862
+ });
863
+ ```
864
+
865
+ ### RTL Testing
866
+
867
+ **RTL Layout Testing**:
868
+ ```typescript
869
+ describe('RTL Support', () => {
870
+ test('applies RTL styles for RTL languages', () => {
871
+ render(
872
+ <RTLProvider>
873
+ <LanguageProvider>
874
+ <TestComponent />
875
+ </LanguageProvider>
876
+ </RTLProvider>
877
+ );
878
+
879
+ // Switch to RTL language
880
+ act(() => {
881
+ i18n.changeLanguage('ur');
882
+ });
883
+
884
+ expect(document.documentElement).toHaveAttribute('dir', 'rtl');
885
+ expect(document.documentElement).toHaveClass('rtl');
886
+ });
887
+ });
888
+ ```
889
+
890
+ ## Configuration
891
+
892
+ ### Theme Configuration
893
+
894
+ ```typescript
895
+ export const THEME_CONFIG = {
896
+ defaultTheme: 'system' as Theme,
897
+ storageKey: 'saafe-ui-theme',
898
+ enableSystemDetection: true,
899
+ enableCustomStyling: true,
900
+ supportedThemes: ['light', 'dark', 'system'] as Theme[],
901
+ transitionDuration: '0.3s',
902
+ };
903
+ ```
904
+
905
+ ### i18n Configuration
906
+
907
+ ```typescript
908
+ export const I18N_CONFIG = {
909
+ defaultLanguage: 'en',
910
+ fallbackLanguage: 'en',
911
+ supportedLanguages: SUPPORTED_LANGUAGES,
912
+ rtlLanguages: RTL_LANGUAGES,
913
+ storageKey: 'i18nextLng',
914
+ enableDebug: import.meta.env.DEV,
915
+ loadPath: '/locales/{{lng}}/{{ns}}.json',
916
+ interpolation: {
917
+ escapeValue: false,
918
+ formatSeparator: ',',
919
+ },
920
+ };
921
+ ```
922
+
923
+ ## Future Enhancements
924
+
925
+ ### Planned Features
926
+
927
+ 1. **Advanced Theming**
928
+ - Multiple theme variants
929
+ - User-created themes
930
+ - Theme marketplace
931
+ - Animation themes
932
+
933
+ 2. **Enhanced i18n**
934
+ - Real-time translation updates
935
+ - Context-aware translations
936
+ - AI-powered translations
937
+ - Voice-based language switching
938
+
939
+ 3. **Accessibility Improvements**
940
+ - Better screen reader support
941
+ - Voice navigation
942
+ - Enhanced keyboard navigation
943
+ - Color-blind friendly themes
944
+
945
+ 4. **Performance Optimizations**
946
+ - Tree-shaking for unused translations
947
+ - Preloading strategies
948
+ - CDN-based theme delivery
949
+ - Runtime optimization
950
+
951
+ ## Troubleshooting
952
+
953
+ ### Common Issues
954
+
955
+ 1. **Theme Not Applying**
956
+ - Check CSS variable definitions
957
+ - Verify theme provider wrapping
958
+ - Clear localStorage cache
959
+
960
+ 2. **Translation Missing**
961
+ - Verify translation key exists
962
+ - Check fallback language setup
963
+ - Validate JSON syntax
964
+
965
+ 3. **RTL Layout Issues**
966
+ - Use logical CSS properties
967
+ - Test with RTL browser tools
968
+ - Verify direction attribute
969
+
970
+ ### Debug Mode
971
+
972
+ ```typescript
973
+ // Enable theme debugging
974
+ localStorage.setItem('theme-debug', 'true');
975
+
976
+ // Enable i18n debugging
977
+ localStorage.setItem('i18n-debug', 'true');
978
+ ```
979
+
980
+ ## Conclusion
981
+
982
+ The Theming and Internationalization module provides comprehensive support for creating accessible, multi-language, and visually adaptable applications. It ensures the SAAFE Redirection Flow can serve users across different regions, languages, and accessibility needs while maintaining a consistent and professional appearance.