Improve site performance for Arabic-speaking users with faster loading

Implements lazy loading for components and optimizes Arabic font rendering using ArabicFontOptimizer and ArabicPerformanceTracker.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: c5f0c281-8dd8-4846-b452-4a07bcd21062
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/65a93bfa-c7e5-4f55-823d-9f81089502c3.jpg
This commit is contained in:
ghaddaditw
2025-06-08 07:56:06 +00:00
parent b53a08cb21
commit 51f0690ff9
4 changed files with 393 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
import { lazy, Suspense } from 'react';
import { Skeleton } from '@/components/ui/skeleton';
// Lazy load large components to improve bundle performance
export const LazyBudgetPlanner = lazy(() => import('@/components/financial/BudgetPlanner'));
export const LazyProjectManager = lazy(() => import('@/components/tasks/ProjectManager'));
export const LazyAIChat = lazy(() => import('@/components/ai/AIChat'));
export const LazyConversationHistory = lazy(() => import('@/components/ai/ConversationHistory'));
export const LazySystemMonitoring = lazy(() => import('@/pages/SystemMonitoringPage'));
export const LazyPerformanceBenchmark = lazy(() => import('@/components/PerformanceBenchmark'));
// Reusable loading fallback for Arabic RTL support
const LoadingSkeleton = ({ className = "" }: { className?: string }) => (
<div className={`space-y-4 ${className}`}>
<Skeleton className="h-8 w-3/4" />
<Skeleton className="h-4 w-1/2" />
<Skeleton className="h-32 w-full" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Skeleton className="h-24 w-full" />
<Skeleton className="h-24 w-full" />
</div>
</div>
);
// Higher-order component for lazy loading with Arabic RTL loading states
export const withLazyLoading = <P extends object>(
Component: React.ComponentType<P>,
loadingClassName?: string
) => {
return (props: P) => (
<Suspense fallback={<LoadingSkeleton className={loadingClassName} />}>
<Component {...props} />
</Suspense>
);
};
// Pre-configured lazy components with loading states
export const BudgetPlannerWithLoading = withLazyLoading(LazyBudgetPlanner, "p-6");
export const ProjectManagerWithLoading = withLazyLoading(LazyProjectManager, "p-4");
export const AIChatWithLoading = withLazyLoading(LazyAIChat, "h-96");
export const ConversationHistoryWithLoading = withLazyLoading(LazyConversationHistory);
export const SystemMonitoringWithLoading = withLazyLoading(LazySystemMonitoring);
export const PerformanceBenchmarkWithLoading = withLazyLoading(LazyPerformanceBenchmark, "p-6");
+2
View File
@@ -1,5 +1,7 @@
import { createContext, useContext, useEffect } from 'react'; import { createContext, useContext, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { arabicFontOptimizer } from '@/lib/fontOptimization';
import { arabicPerformanceTracker } from '@/lib/arabicPerformanceTracker';
interface RTLContextType { interface RTLContextType {
isRTL: boolean; isRTL: boolean;
+227
View File
@@ -0,0 +1,227 @@
interface ArabicPerformanceMetrics {
rtlRenderTime: number;
fontLoadTime: number;
layoutShiftScore: number;
textDirectionSwitchTime: number;
arabicTextRenderScore: number;
timestamp: number;
}
interface PerformanceThresholds {
rtlRenderTime: number;
fontLoadTime: number;
layoutShiftScore: number;
textDirectionSwitchTime: number;
}
export class ArabicPerformanceTracker {
private metrics: ArabicPerformanceMetrics[] = [];
private thresholds: PerformanceThresholds = {
rtlRenderTime: 50, // ms
fontLoadTime: 200, // ms
layoutShiftScore: 0.1,
textDirectionSwitchTime: 100 // ms
};
trackRTLPerformance(language: string): ArabicPerformanceMetrics {
const startTime = performance.now();
// Measure RTL layout rendering
const rtlContainer = document.createElement('div');
rtlContainer.style.direction = language === 'ar' ? 'rtl' : 'ltr';
rtlContainer.style.position = 'absolute';
rtlContainer.style.visibility = 'hidden';
rtlContainer.innerHTML = language === 'ar'
? 'مرحباً بكم في منصة المساعد الذكي للمهنيين العمانيين'
: 'Welcome to MenAssist Professional Platform';
document.body.appendChild(rtlContainer);
const rtlRenderTime = performance.now() - startTime;
// Measure font loading for Arabic
const fontStartTime = performance.now();
const fontTestElement = document.createElement('span');
fontTestElement.style.fontFamily = language === 'ar' ? 'Noto Sans Arabic' : 'Inter';
fontTestElement.style.fontSize = '16px';
fontTestElement.innerHTML = language === 'ar' ? 'اختبار الخط' : 'Font Test';
rtlContainer.appendChild(fontTestElement);
const fontLoadTime = performance.now() - fontStartTime;
// Measure layout shift
const initialWidth = rtlContainer.offsetWidth;
const layoutShiftScore = this.measureLayoutShift(rtlContainer, initialWidth);
// Measure direction switch performance
const directionSwitchTime = this.measureDirectionSwitch(rtlContainer, language);
document.body.removeChild(rtlContainer);
const metrics: ArabicPerformanceMetrics = {
rtlRenderTime,
fontLoadTime,
layoutShiftScore,
textDirectionSwitchTime: directionSwitchTime,
arabicTextRenderScore: this.calculateTextRenderScore(rtlRenderTime, fontLoadTime),
timestamp: Date.now()
};
this.metrics.push(metrics);
this.analyzePerformanceIssues(metrics);
return metrics;
}
private measureLayoutShift(element: HTMLElement, initialWidth: number): number {
// Simulate font loading completion
element.style.fontDisplay = 'swap';
const finalWidth = element.offsetWidth;
return Math.abs(finalWidth - initialWidth) / initialWidth;
}
private measureDirectionSwitch(element: HTMLElement, currentLanguage: string): number {
const startTime = performance.now();
// Switch direction
element.style.direction = currentLanguage === 'ar' ? 'ltr' : 'rtl';
element.style.textAlign = currentLanguage === 'ar' ? 'left' : 'right';
// Force reflow
element.offsetHeight;
return performance.now() - startTime;
}
private calculateTextRenderScore(rtlTime: number, fontTime: number): number {
const totalTime = rtlTime + fontTime;
if (totalTime < 50) return 100;
if (totalTime < 100) return 90;
if (totalTime < 200) return 80;
if (totalTime < 300) return 70;
return 60;
}
private analyzePerformanceIssues(metrics: ArabicPerformanceMetrics): void {
const issues: string[] = [];
if (metrics.rtlRenderTime > this.thresholds.rtlRenderTime) {
issues.push('RTL rendering slower than optimal');
}
if (metrics.fontLoadTime > this.thresholds.fontLoadTime) {
issues.push('Arabic font loading slower than optimal');
}
if (metrics.layoutShiftScore > this.thresholds.layoutShiftScore) {
issues.push('Significant layout shift during font loading');
}
if (metrics.textDirectionSwitchTime > this.thresholds.textDirectionSwitchTime) {
issues.push('Direction switching performance issue');
}
if (issues.length > 0) {
console.warn('Arabic Performance Issues Detected:', issues);
this.sendPerformanceAlert(metrics, issues);
}
}
private sendPerformanceAlert(metrics: ArabicPerformanceMetrics, issues: string[]): void {
// Send performance data to analytics endpoint
fetch('/api/analytics/arabic-performance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
metrics,
issues,
userAgent: navigator.userAgent,
timestamp: Date.now()
})
}).catch(error => {
console.warn('Failed to send Arabic performance data:', error);
});
}
getAverageMetrics(): Partial<ArabicPerformanceMetrics> {
if (this.metrics.length === 0) return {};
const totals = this.metrics.reduce((acc, metric) => ({
rtlRenderTime: acc.rtlRenderTime + metric.rtlRenderTime,
fontLoadTime: acc.fontLoadTime + metric.fontLoadTime,
layoutShiftScore: acc.layoutShiftScore + metric.layoutShiftScore,
textDirectionSwitchTime: acc.textDirectionSwitchTime + metric.textDirectionSwitchTime,
arabicTextRenderScore: acc.arabicTextRenderScore + metric.arabicTextRenderScore
}), {
rtlRenderTime: 0,
fontLoadTime: 0,
layoutShiftScore: 0,
textDirectionSwitchTime: 0,
arabicTextRenderScore: 0
});
const count = this.metrics.length;
return {
rtlRenderTime: totals.rtlRenderTime / count,
fontLoadTime: totals.fontLoadTime / count,
layoutShiftScore: totals.layoutShiftScore / count,
textDirectionSwitchTime: totals.textDirectionSwitchTime / count,
arabicTextRenderScore: totals.arabicTextRenderScore / count
};
}
getPerformanceReport(): {
averageMetrics: Partial<ArabicPerformanceMetrics>;
totalSamples: number;
performanceGrade: string;
recommendations: string[];
} {
const averageMetrics = this.getAverageMetrics();
const totalSamples = this.metrics.length;
const performanceGrade = this.calculatePerformanceGrade(averageMetrics);
const recommendations = this.generateRecommendations(averageMetrics);
return {
averageMetrics,
totalSamples,
performanceGrade,
recommendations
};
}
private calculatePerformanceGrade(metrics: Partial<ArabicPerformanceMetrics>): string {
const score = metrics.arabicTextRenderScore || 0;
if (score >= 90) return 'A+';
if (score >= 80) return 'A';
if (score >= 70) return 'B+';
if (score >= 60) return 'B';
return 'C';
}
private generateRecommendations(metrics: Partial<ArabicPerformanceMetrics>): string[] {
const recommendations: string[] = [];
if ((metrics.rtlRenderTime || 0) > this.thresholds.rtlRenderTime) {
recommendations.push('Optimize CSS for RTL layouts using logical properties');
recommendations.push('Implement CSS containment for Arabic text sections');
}
if ((metrics.fontLoadTime || 0) > this.thresholds.fontLoadTime) {
recommendations.push('Preload Arabic fonts with font-display: swap');
recommendations.push('Use font subsetting for Arabic character ranges');
}
if ((metrics.layoutShiftScore || 0) > this.thresholds.layoutShiftScore) {
recommendations.push('Reserve space for Arabic text during font loading');
recommendations.push('Use size-adjust property for Arabic font fallbacks');
}
return recommendations;
}
clearMetrics(): void {
this.metrics = [];
}
}
export const arabicPerformanceTracker = new ArabicPerformanceTracker();
+121
View File
@@ -0,0 +1,121 @@
// Arabic font optimization with CDN delivery
export class ArabicFontOptimizer {
private fontCache = new Map<string, boolean>();
private loadingPromises = new Map<string, Promise<void>>();
async optimizeArabicFonts(language: string): Promise<void> {
if (language !== 'ar') return;
const fonts = [
{
family: 'Noto Sans Arabic',
weights: ['400', '500', '600', '700'],
display: 'swap'
}
];
await Promise.all(fonts.map(font => this.loadFont(font)));
}
private async loadFont(fontConfig: {
family: string;
weights: string[];
display: string;
}): Promise<void> {
const cacheKey = `${fontConfig.family}-${fontConfig.weights.join(',')}`;
if (this.fontCache.has(cacheKey)) return;
if (this.loadingPromises.has(cacheKey)) {
return this.loadingPromises.get(cacheKey);
}
const loadPromise = this.performFontLoad(fontConfig, cacheKey);
this.loadingPromises.set(cacheKey, loadPromise);
return loadPromise;
}
private async performFontLoad(
fontConfig: { family: string; weights: string[]; display: string },
cacheKey: string
): Promise<void> {
try {
// Preload critical Arabic font weights
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'font';
link.type = 'font/woff2';
link.crossOrigin = 'anonymous';
// Use Google Fonts CDN with optimal parameters for Arabic
const weightParam = fontConfig.weights.join(';');
link.href = `https://fonts.googleapis.com/css2?family=${fontConfig.family.replace(/ /g, '+')}:wght@${weightParam}&display=${fontConfig.display}&subset=arabic`;
document.head.appendChild(link);
// Create stylesheet link
const styleLink = document.createElement('link');
styleLink.rel = 'stylesheet';
styleLink.href = link.href;
await new Promise<void>((resolve, reject) => {
styleLink.onload = () => resolve();
styleLink.onerror = () => reject(new Error(`Failed to load font: ${fontConfig.family}`));
document.head.appendChild(styleLink);
});
this.fontCache.set(cacheKey, true);
this.loadingPromises.delete(cacheKey);
} catch (error) {
console.warn(`Font loading failed for ${fontConfig.family}:`, error);
this.loadingPromises.delete(cacheKey);
}
}
// Performance monitoring for Arabic font rendering
measureArabicRenderPerformance(): {
renderTime: number;
layoutShift: number;
fontLoadTime: number;
} {
const startTime = performance.now();
// Create test element with Arabic text
const testDiv = document.createElement('div');
testDiv.style.position = 'absolute';
testDiv.style.visibility = 'hidden';
testDiv.style.fontFamily = 'Noto Sans Arabic, Arial, sans-serif';
testDiv.style.fontSize = '16px';
testDiv.innerHTML = 'مرحباً بكم في منصة المساعد الذكي للمهنيين العمانيين';
document.body.appendChild(testDiv);
const initialHeight = testDiv.offsetHeight;
const renderTime = performance.now() - startTime;
// Measure layout shift
setTimeout(() => {
const finalHeight = testDiv.offsetHeight;
const layoutShift = Math.abs(finalHeight - initialHeight) / initialHeight;
document.body.removeChild(testDiv);
return {
renderTime,
layoutShift,
fontLoadTime: renderTime
};
}, 100);
document.body.removeChild(testDiv);
return {
renderTime,
layoutShift: 0,
fontLoadTime: renderTime
};
}
}
export const arabicFontOptimizer = new ArabicFontOptimizer();