diff --git a/client/src/components/LanguageSwitcher.tsx b/client/src/components/LanguageSwitcher.tsx new file mode 100644 index 0000000..ba6155c --- /dev/null +++ b/client/src/components/LanguageSwitcher.tsx @@ -0,0 +1,50 @@ +import { useTranslation } from 'react-i18next'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { Languages } from 'lucide-react'; + +export function LanguageSwitcher() { + const { i18n } = useTranslation(); + + const languages = [ + { code: 'en', name: 'English', flag: '🇺🇸' }, + { code: 'ar', name: 'العربية', flag: '🇸🇦' } + ]; + + const changeLanguage = (langCode: string) => { + i18n.changeLanguage(langCode); + // Update document direction for Arabic + document.documentElement.dir = langCode === 'ar' ? 'rtl' : 'ltr'; + document.documentElement.lang = langCode; + }; + + const currentLanguage = languages.find(lang => lang.code === i18n.language) || languages[0]; + + return ( + + + + + + {languages.map((language) => ( + changeLanguage(language.code)} + className={`gap-2 ${i18n.language === language.code ? 'bg-accent' : ''}`} + > + {language.flag} + {language.name} + + ))} + + + ); +} \ No newline at end of file diff --git a/client/src/i18n/config.ts b/client/src/i18n/config.ts new file mode 100644 index 0000000..5bb6f11 --- /dev/null +++ b/client/src/i18n/config.ts @@ -0,0 +1,36 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; + +// Import translation files +import enTranslations from './locales/en.json'; +import arTranslations from './locales/ar.json'; + +i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources: { + en: { + translation: enTranslations + }, + ar: { + translation: arTranslations + } + }, + lng: 'en', // default language + fallbackLng: 'en', + debug: false, + + interpolation: { + escapeValue: false, // React already does escaping + }, + + detection: { + order: ['localStorage', 'navigator', 'htmlTag'], + lookupLocalStorage: 'language', + caches: ['localStorage'], + }, + }); + +export default i18n; \ No newline at end of file diff --git a/client/src/i18n/locales/ar.json b/client/src/i18n/locales/ar.json new file mode 100644 index 0000000..7e1de16 --- /dev/null +++ b/client/src/i18n/locales/ar.json @@ -0,0 +1,199 @@ +{ + "common": { + "save": "حفظ", + "cancel": "إلغاء", + "delete": "حذف", + "edit": "تعديل", + "add": "إضافة", + "create": "إنشاء", + "update": "تحديث", + "search": "بحث", + "filter": "تصفية", + "loading": "جاري التحميل...", + "error": "خطأ", + "success": "نجح", + "confirm": "تأكيد", + "back": "رجوع", + "next": "التالي", + "previous": "السابق", + "close": "إغلاق", + "open": "فتح", + "yes": "نعم", + "no": "لا" + }, + "navigation": { + "dashboard": "لوحة التحكم", + "tasks": "المهام", + "finances": "الماليات", + "ai": "المساعد الذكي", + "voice": "الصوت", + "analytics": "التحليلات", + "settings": "الإعدادات", + "logout": "تسجيل الخروج", + "profile": "الملف الشخصي" + }, + "tasks": { + "title": "المهام", + "createTask": "إنشاء مهمة", + "editTask": "تعديل المهمة", + "taskTitle": "عنوان المهمة", + "description": "الوصف", + "priority": "الأولوية", + "dueDate": "تاريخ الاستحقاق", + "status": "الحالة", + "category": "الفئة", + "project": "المشروع", + "assignee": "المكلف", + "tags": "العلامات", + "subtasks": "المهام الفرعية", + "attachments": "المرفقات", + "timeTracking": "تتبع الوقت", + "completed": "مكتمل", + "inProgress": "قيد التنفيذ", + "todo": "للعمل", + "high": "عالية", + "medium": "متوسطة", + "low": "منخفضة", + "addSubtask": "إضافة مهمة فرعية", + "addAttachment": "إضافة مرفق", + "startTimer": "بدء المؤقت", + "stopTimer": "إيقاف المؤقت", + "totalTime": "إجمالي الوقت", + "noTasks": "لا توجد مهام", + "taskCreated": "تم إنشاء المهمة بنجاح", + "taskUpdated": "تم تحديث المهمة بنجاح", + "taskDeleted": "تم حذف المهمة بنجاح" + }, + "finances": { + "title": "الماليات", + "overview": "نظرة عامة مالية", + "income": "الدخل", + "expenses": "المصروفات", + "balance": "الرصيد", + "budget": "الميزانية", + "categories": "الفئات", + "transactions": "المعاملات", + "recurring": "متكررة", + "reports": "التقارير", + "addTransaction": "إضافة معاملة", + "editTransaction": "تعديل المعاملة", + "amount": "المبلغ", + "type": "النوع", + "date": "التاريخ", + "description": "الوصف", + "category": "الفئة", + "tags": "العلامات", + "recurring": "متكررة", + "frequency": "التكرار", + "budgetPlanning": "تخطيط الميزانية", + "budgetLimit": "حد الميزانية", + "budgetUsed": "الميزانية المستخدمة", + "budgetRemaining": "الميزانية المتبقية", + "monthlyBudget": "الميزانية الشهرية", + "yearlyBudget": "الميزانية السنوية", + "exportData": "تصدير البيانات", + "billReminders": "تذكيرات الفواتير", + "upcomingBills": "الفواتير القادمة", + "overdueBills": "الفواتير المتأخرة" + }, + "ai": { + "title": "المساعد الذكي", + "chat": "المحادثة", + "history": "تاريخ المحادثات", + "dailyJoke": "نكتة اليوم", + "insights": "رؤى ذكية", + "askAnything": "اسأل عن أي شيء...", + "generating": "جاري إنشاء الرد...", + "voiceCommand": "أمر صوتي", + "personalizedResponse": "رد شخصي", + "conversationSaved": "تم حفظ المحادثة", + "noHistory": "لا يوجد تاريخ محادثات" + }, + "voice": { + "title": "الأوامر الصوتية", + "listening": "جاري الاستماع...", + "notListening": "غير مفعل", + "startListening": "بدء الاستماع", + "stopListening": "إيقاف الاستماع", + "voiceSettings": "إعدادات الصوت", + "language": "اللغة", + "microphone": "الميكروفون", + "speakers": "السماعات", + "voiceCommands": "الأوامر المتاحة", + "customCommands": "أوامر مخصصة", + "voiceTraining": "تدريب الصوت", + "accuracy": "الدقة", + "confidence": "الثقة", + "transcript": "النص المكتوب", + "voiceActivated": "مفعل صوتياً", + "speakingRate": "معدل الكلام", + "voicePitch": "نبرة الصوت", + "voiceVolume": "مستوى الصوت" + }, + "settings": { + "title": "الإعدادات", + "general": "عام", + "appearance": "المظهر", + "language": "اللغة", + "notifications": "الإشعارات", + "privacy": "الخصوصية", + "account": "الحساب", + "advanced": "متقدم", + "theme": "السمة", + "lightMode": "الوضع الفاتح", + "darkMode": "الوضع الداكن", + "systemTheme": "سمة النظام", + "fontSize": "حجم الخط", + "accessibility": "إمكانية الوصول", + "dataExport": "تصدير البيانات", + "dataImport": "استيراد البيانات", + "changePassword": "تغيير كلمة المرور", + "deleteAccount": "حذف الحساب", + "backup": "نسخ احتياطي", + "restore": "استعادة" + }, + "auth": { + "login": "تسجيل الدخول", + "register": "تسجيل جديد", + "logout": "تسجيل الخروج", + "email": "البريد الإلكتروني", + "password": "كلمة المرور", + "confirmPassword": "تأكيد كلمة المرور", + "firstName": "الاسم الأول", + "lastName": "الاسم الأخير", + "username": "اسم المستخدم", + "forgotPassword": "نسيت كلمة المرور؟", + "resetPassword": "إعادة تعيين كلمة المرور", + "loginSuccess": "تم تسجيل الدخول بنجاح", + "registerSuccess": "تم التسجيل بنجاح", + "invalidCredentials": "بيانات اعتماد غير صحيحة", + "passwordMismatch": "كلمات المرور غير متطابقة" + }, + "dashboard": { + "title": "لوحة التحكم", + "welcome": "مرحباً", + "quickStats": "إحصائيات سريعة", + "recentTasks": "المهام الأخيرة", + "recentTransactions": "المعاملات الأخيرة", + "todaysTasks": "مهام اليوم", + "weeklyOverview": "نظرة أسبوعية", + "monthlyOverview": "نظرة شهرية", + "productivity": "الإنتاجية", + "financial": "الملخص المالي" + }, + "errors": { + "general": "حدث خطأ", + "networkError": "خطأ في الشبكة", + "unauthorized": "وصول غير مصرح", + "notFound": "غير موجود", + "serverError": "خطأ في الخادم", + "validationError": "خطأ في التحقق", + "tryAgain": "حاول مرة أخرى" + }, + "success": { + "saved": "تم الحفظ بنجاح", + "deleted": "تم الحذف بنجاح", + "updated": "تم التحديث بنجاح", + "created": "تم الإنشاء بنجاح" + } +} \ No newline at end of file diff --git a/client/src/i18n/locales/en.json b/client/src/i18n/locales/en.json new file mode 100644 index 0000000..4d77e24 --- /dev/null +++ b/client/src/i18n/locales/en.json @@ -0,0 +1,199 @@ +{ + "common": { + "save": "Save", + "cancel": "Cancel", + "delete": "Delete", + "edit": "Edit", + "add": "Add", + "create": "Create", + "update": "Update", + "search": "Search", + "filter": "Filter", + "loading": "Loading...", + "error": "Error", + "success": "Success", + "confirm": "Confirm", + "back": "Back", + "next": "Next", + "previous": "Previous", + "close": "Close", + "open": "Open", + "yes": "Yes", + "no": "No" + }, + "navigation": { + "dashboard": "Dashboard", + "tasks": "Tasks", + "finances": "Finances", + "ai": "AI Assistant", + "voice": "Voice", + "analytics": "Analytics", + "settings": "Settings", + "logout": "Logout", + "profile": "Profile" + }, + "tasks": { + "title": "Tasks", + "createTask": "Create Task", + "editTask": "Edit Task", + "taskTitle": "Task Title", + "description": "Description", + "priority": "Priority", + "dueDate": "Due Date", + "status": "Status", + "category": "Category", + "project": "Project", + "assignee": "Assignee", + "tags": "Tags", + "subtasks": "Subtasks", + "attachments": "Attachments", + "timeTracking": "Time Tracking", + "completed": "Completed", + "inProgress": "In Progress", + "todo": "To Do", + "high": "High", + "medium": "Medium", + "low": "Low", + "addSubtask": "Add Subtask", + "addAttachment": "Add Attachment", + "startTimer": "Start Timer", + "stopTimer": "Stop Timer", + "totalTime": "Total Time", + "noTasks": "No tasks found", + "taskCreated": "Task created successfully", + "taskUpdated": "Task updated successfully", + "taskDeleted": "Task deleted successfully" + }, + "finances": { + "title": "Finances", + "overview": "Financial Overview", + "income": "Income", + "expenses": "Expenses", + "balance": "Balance", + "budget": "Budget", + "categories": "Categories", + "transactions": "Transactions", + "recurring": "Recurring", + "reports": "Reports", + "addTransaction": "Add Transaction", + "editTransaction": "Edit Transaction", + "amount": "Amount", + "type": "Type", + "date": "Date", + "description": "Description", + "category": "Category", + "tags": "Tags", + "recurring": "Recurring", + "frequency": "Frequency", + "budgetPlanning": "Budget Planning", + "budgetLimit": "Budget Limit", + "budgetUsed": "Budget Used", + "budgetRemaining": "Budget Remaining", + "monthlyBudget": "Monthly Budget", + "yearlyBudget": "Yearly Budget", + "exportData": "Export Data", + "billReminders": "Bill Reminders", + "upcomingBills": "Upcoming Bills", + "overdueBills": "Overdue Bills" + }, + "ai": { + "title": "AI Assistant", + "chat": "Chat", + "history": "Conversation History", + "dailyJoke": "Daily Joke", + "insights": "AI Insights", + "askAnything": "Ask me anything...", + "generating": "Generating response...", + "voiceCommand": "Voice Command", + "personalizedResponse": "Personalized Response", + "conversationSaved": "Conversation saved", + "noHistory": "No conversation history" + }, + "voice": { + "title": "Voice Commands", + "listening": "Listening...", + "notListening": "Not Listening", + "startListening": "Start Listening", + "stopListening": "Stop Listening", + "voiceSettings": "Voice Settings", + "language": "Language", + "microphone": "Microphone", + "speakers": "Speakers", + "voiceCommands": "Available Commands", + "customCommands": "Custom Commands", + "voiceTraining": "Voice Training", + "accuracy": "Accuracy", + "confidence": "Confidence", + "transcript": "Transcript", + "voiceActivated": "Voice Activated", + "speakingRate": "Speaking Rate", + "voicePitch": "Voice Pitch", + "voiceVolume": "Voice Volume" + }, + "settings": { + "title": "Settings", + "general": "General", + "appearance": "Appearance", + "language": "Language", + "notifications": "Notifications", + "privacy": "Privacy", + "account": "Account", + "advanced": "Advanced", + "theme": "Theme", + "lightMode": "Light Mode", + "darkMode": "Dark Mode", + "systemTheme": "System Theme", + "fontSize": "Font Size", + "accessibility": "Accessibility", + "dataExport": "Data Export", + "dataImport": "Data Import", + "changePassword": "Change Password", + "deleteAccount": "Delete Account", + "backup": "Backup", + "restore": "Restore" + }, + "auth": { + "login": "Login", + "register": "Register", + "logout": "Logout", + "email": "Email", + "password": "Password", + "confirmPassword": "Confirm Password", + "firstName": "First Name", + "lastName": "Last Name", + "username": "Username", + "forgotPassword": "Forgot Password?", + "resetPassword": "Reset Password", + "loginSuccess": "Login successful", + "registerSuccess": "Registration successful", + "invalidCredentials": "Invalid credentials", + "passwordMismatch": "Passwords do not match" + }, + "dashboard": { + "title": "Dashboard", + "welcome": "Welcome", + "quickStats": "Quick Stats", + "recentTasks": "Recent Tasks", + "recentTransactions": "Recent Transactions", + "todaysTasks": "Today's Tasks", + "weeklyOverview": "Weekly Overview", + "monthlyOverview": "Monthly Overview", + "productivity": "Productivity", + "financial": "Financial Summary" + }, + "errors": { + "general": "An error occurred", + "networkError": "Network error", + "unauthorized": "Unauthorized access", + "notFound": "Not found", + "serverError": "Server error", + "validationError": "Validation error", + "tryAgain": "Please try again" + }, + "success": { + "saved": "Successfully saved", + "deleted": "Successfully deleted", + "updated": "Successfully updated", + "created": "Successfully created" + } +} \ No newline at end of file diff --git a/client/src/main.tsx b/client/src/main.tsx index 696e0d2..e77c81c 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,5 +1,6 @@ import { createRoot } from "react-dom/client"; import App from "./App"; import "./index.css"; +import "./i18n/config"; createRoot(document.getElementById("root")!).render(); diff --git a/package-lock.json b/package-lock.json index 5fef6db..afbadf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,9 @@ "express": "^4.21.2", "express-session": "^1.18.1", "framer-motion": "^11.13.1", + "i18next": "^25.2.1", + "i18next-browser-languagedetector": "^8.1.0", + "i18next-http-backend": "^3.0.2", "input-otp": "^1.4.2", "lucide-react": "^0.453.0", "memorystore": "^1.6.7", @@ -66,6 +69,7 @@ "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.55.0", + "react-i18next": "^15.5.2", "react-icons": "^5.4.0", "react-resizable-panels": "^2.1.7", "recharts": "^2.15.2", @@ -385,12 +389,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -4225,6 +4227,15 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5808,6 +5819,15 @@ "node": ">= 0.4" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -5833,6 +5853,55 @@ "ms": "^2.0.0" } }, + "node_modules/i18next": { + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz", + "integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.1.0.tgz", + "integrity": "sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", + "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", + "license": "MIT", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7395,6 +7464,32 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-i18next": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz", + "integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-icons": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz", @@ -7597,11 +7692,6 @@ "decimal.js-light": "^2.4.1" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, "node_modules/regexparam": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz", @@ -8719,7 +8809,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -9385,6 +9475,15 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", diff --git a/package.json b/package.json index 47f9576..ec420f8 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,9 @@ "express": "^4.21.2", "express-session": "^1.18.1", "framer-motion": "^11.13.1", + "i18next": "^25.2.1", + "i18next-browser-languagedetector": "^8.1.0", + "i18next-http-backend": "^3.0.2", "input-otp": "^1.4.2", "lucide-react": "^0.453.0", "memorystore": "^1.6.7", @@ -68,6 +71,7 @@ "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.55.0", + "react-i18next": "^15.5.2", "react-icons": "^5.4.0", "react-resizable-panels": "^2.1.7", "recharts": "^2.15.2",