feat(i18n,rtl): full Arabic localization + RTL sweep across all layouts
Frontend - i18n: install tailwindcss-rtl, Cairo font, RTL-aware direction in index.css. - Language toggle: localize aria-label / menu label, persist choice, update document dir synchronously. - Sidebar: add `side` prop so the drawer pins to the right in RTL; wire up AdminLmsLayout, RoleLayout (student/teacher) and AppSidebar to pass side = i18n.dir() === 'rtl' ? 'right' : 'left'. - AdminLmsLayout: convert every nav item from hard-coded title to titleKey, translate group labels (incl. the collapsible Training), breadcrumbs, user menu (Profile / Settings / Logout), help button and toggle aria labels; replace physical mr-/right- utilities with logical me-/end-. - AI components (AiTipBanner, AiInsightsPanel, AiAlertBanner, AiSearchBar, AiAssistantDrawer): apply dir="auto" at the container level, localize titles, loading / error / empty states. - Dashboards (admin / student / teacher): wrap numeric values in <bdi>, localize dates via ar-EG, fix flex direction for KPI and assignment cards. - UI primitives (breadcrumb, calendar, carousel, dropdown-menu, menubar, context-menu, pagination, sidebar): flip chevrons in RTL via a scoped CSS rule, swap pl-/pr-/ml-/mr- for ps-/pe-/ms-/me-. - Add logical-direction helpers and bidirectional isolation classes. Locales - Expand en.ts and ar.ts with full `nav`, `sidebarGroup`, `breadcrumb`, `userMenu`, `chrome`, `ai`, and dashboard key sets; keep key parity. API client - `api-client.ts` reads the active language from localStorage/i18n and sends `Accept-Language` on every request so the backend can localize AI output. Backend (encoach_ai) - openai_service: add _LANGUAGE_NAMES, normalize_language, language-aware system prompt injection for every OpenAI call. - coach_service + controllers (coach_controller, ai_controller): thread the requested language from headers / user locale down to OpenAIService. - ai_feedback: fix latent registry error by pointing course_id at op.course instead of the non-existent encoach.course. Other - .gitignore: ignore runtime odoo logs and local caches. Made-with: Cursor
This commit is contained in:
@@ -3,14 +3,33 @@
|
||||
* We deliberately annotate the literal with `Translations` (not `as const`)
|
||||
* so sibling locale files can widen their string values without fighting
|
||||
* literal-string type mismatches.
|
||||
*
|
||||
* Adding a new key: add it here FIRST (English is the source of truth), then
|
||||
* mirror it in `ar.ts`. The `Translations` interface is intentionally
|
||||
* permissive (`Record<string, string>` per group) so we don't have to update
|
||||
* a giant type every time we add a string.
|
||||
*/
|
||||
export interface Translations {
|
||||
common: Record<string, string>;
|
||||
auth: Record<string, string>;
|
||||
nav: Record<string, string>;
|
||||
sidebarGroup: Record<string, string>;
|
||||
breadcrumb: Record<string, string>;
|
||||
userMenu: Record<string, string>;
|
||||
chrome: Record<string, string>;
|
||||
notifications: Record<string, string>;
|
||||
theme: Record<string, string>;
|
||||
language: Record<string, string>;
|
||||
roles: Record<string, string>;
|
||||
dashboard: Record<string, string>;
|
||||
studentDash: Record<string, string>;
|
||||
teacherDash: Record<string, string>;
|
||||
adminDash: Record<string, string>;
|
||||
examPopup: Record<string, string>;
|
||||
errors: Record<string, string>;
|
||||
ai: Record<string, string>;
|
||||
privacy: Record<string, string>;
|
||||
feedback: Record<string, string>;
|
||||
theme: Record<string, string>;
|
||||
}
|
||||
|
||||
const en: Translations = {
|
||||
@@ -32,30 +51,293 @@ const en: Translations = {
|
||||
status: "Status",
|
||||
settings: "Settings",
|
||||
signOut: "Sign out",
|
||||
viewAll: "View All",
|
||||
dismiss: "Dismiss",
|
||||
pending: "Pending",
|
||||
active: "Active",
|
||||
inactive: "Inactive",
|
||||
available: "Available",
|
||||
unavailable: "Unavailable",
|
||||
name: "Name",
|
||||
email: "Email",
|
||||
user: "User",
|
||||
home: "Home",
|
||||
},
|
||||
auth: {
|
||||
signIn: "Sign in",
|
||||
signInTitle: "Sign in to EnCoach",
|
||||
welcomeBack: "Welcome back",
|
||||
signInDescription: "Sign in to your account to continue",
|
||||
email: "Email",
|
||||
emailPlaceholder: "you@example.com",
|
||||
password: "Password",
|
||||
rememberMe: "Remember me",
|
||||
forgotPassword: "Forgot password?",
|
||||
needAccount: "Don't have an account?",
|
||||
signUp: "Sign up",
|
||||
invalidCredentials: "Invalid email or password",
|
||||
missingCredentials: "Please enter email and password",
|
||||
loginFailedTitle: "Login Failed",
|
||||
errorTitle: "Error",
|
||||
},
|
||||
nav: {
|
||||
adminDashboard: "Admin Dashboard",
|
||||
platformDashboard: "Platform Dashboard",
|
||||
dashboard: "Dashboard",
|
||||
courses: "Courses",
|
||||
myCourses: "My Courses",
|
||||
students: "Students",
|
||||
teachers: "Teachers",
|
||||
exams: "Exams",
|
||||
batches: "Batches",
|
||||
timetable: "Timetable",
|
||||
reports: "Reports",
|
||||
settings: "Settings",
|
||||
profile: "Profile",
|
||||
privacy: "Privacy Center",
|
||||
assignments: "Assignments",
|
||||
examsList: "Exams List",
|
||||
examStructures: "Exam Structures",
|
||||
rubrics: "Rubrics",
|
||||
generation: "Generation",
|
||||
reviewQueue: "Review Queue",
|
||||
aiPrompts: "AI Prompts",
|
||||
aiFeedback: "AI Feedback",
|
||||
approvalWorkflows: "Approval Workflows",
|
||||
taxonomy: "Taxonomy",
|
||||
resources: "Resources",
|
||||
resourceLibrary: "Resource Library",
|
||||
academicYears: "Academic Years",
|
||||
departments: "Departments",
|
||||
admissions: "Admissions",
|
||||
admissionRegister: "Admission Register",
|
||||
examSessions: "Exam Sessions",
|
||||
marksheets: "Marksheets",
|
||||
studentLeave: "Student Leave",
|
||||
fees: "Fees & Payments",
|
||||
lessons: "Lessons",
|
||||
gradebook: "Gradebook",
|
||||
studentProgress: "Student Progress",
|
||||
library: "Library",
|
||||
activities: "Activities",
|
||||
facilities: "Facilities",
|
||||
users: "Users",
|
||||
entities: "Entities",
|
||||
classrooms: "Classrooms",
|
||||
userRoles: "User Roles",
|
||||
rolesPermissions: "Roles & Permissions",
|
||||
authorityMatrix: "Authority Matrix",
|
||||
studentPerformance: "Student Performance",
|
||||
statsCorporate: "Stats Corporate",
|
||||
record: "Record",
|
||||
vocabulary: "Vocabulary",
|
||||
grammar: "Grammar",
|
||||
faqManager: "FAQ Manager",
|
||||
notificationRules: "Notification Rules",
|
||||
approvalConfig: "Approval Config",
|
||||
paymentRecord: "Payment Record",
|
||||
tickets: "Tickets",
|
||||
settings: "Settings",
|
||||
profile: "Profile",
|
||||
privacy: "Privacy Center",
|
||||
subjectRegistration: "Subject Registration",
|
||||
mySubjects: "My Subjects",
|
||||
grades: "Grades",
|
||||
attendance: "Attendance",
|
||||
myJourney: "My Journey",
|
||||
discussions: "Discussions",
|
||||
messages: "Messages",
|
||||
announcements: "Announcements",
|
||||
},
|
||||
sidebarGroup: {
|
||||
overview: "Overview",
|
||||
lms: "LMS",
|
||||
adaptiveLearning: "Adaptive Learning",
|
||||
institutional: "Institutional",
|
||||
academic: "Academic",
|
||||
management: "Management",
|
||||
reports: "Reports",
|
||||
configuration: "Configuration",
|
||||
training: "Training",
|
||||
support: "Support",
|
||||
learning: "Learning",
|
||||
progress: "Progress",
|
||||
communication: "Communication",
|
||||
account: "Account",
|
||||
teaching: "Teaching",
|
||||
},
|
||||
breadcrumb: {
|
||||
home: "Home",
|
||||
admin: "Admin",
|
||||
dashboard: "Dashboard",
|
||||
platform: "Platform",
|
||||
exam: "Exam",
|
||||
review: "Review",
|
||||
ai: "AI",
|
||||
feedback: "Feedback",
|
||||
},
|
||||
userMenu: {
|
||||
profile: "Profile",
|
||||
settings: "Settings",
|
||||
logout: "Logout",
|
||||
userFallback: "User",
|
||||
},
|
||||
chrome: {
|
||||
needHelp: "Need help?",
|
||||
ticketsTooltip: "Support tickets",
|
||||
aiSearchPlaceholder: "Ask anything... e.g. 'Show students with low attendance'",
|
||||
aiSearching: "AI is searching…",
|
||||
aiRelatedQueries: "Related queries",
|
||||
aiNoResults: "No results for \"{{q}}\". Try a different question or keyword.",
|
||||
aiSearchFailedTitle: "Search failed",
|
||||
aiSearchFailedDesc: "Could not complete AI search.",
|
||||
portalSuffix: "Portal",
|
||||
adminAlt: "EnCoach",
|
||||
adminAltLong: "EnCoach — Unlock your potential with AI powered platform",
|
||||
toggleSidebar: "Toggle sidebar",
|
||||
},
|
||||
notifications: {
|
||||
title: "Notifications",
|
||||
empty: "You're all caught up.",
|
||||
ariaLabel: "Notifications",
|
||||
},
|
||||
theme: {
|
||||
title: "Theme",
|
||||
light: "Light",
|
||||
dark: "Dark",
|
||||
system: "System",
|
||||
toggleLabel: "Toggle theme",
|
||||
},
|
||||
language: {
|
||||
change: "Change language",
|
||||
label: "Language",
|
||||
},
|
||||
roles: {
|
||||
student: "student",
|
||||
teacher: "teacher",
|
||||
admin: "admin",
|
||||
developer: "developer",
|
||||
corporate: "corporate",
|
||||
mastercorporate: "master corporate",
|
||||
agent: "agent",
|
||||
},
|
||||
dashboard: {
|
||||
greetingFallback: "Student",
|
||||
},
|
||||
studentDash: {
|
||||
welcome: "Welcome back, {{name}}!",
|
||||
subtitle: "Here's an overview of your learning progress.",
|
||||
enrolledCourses: "Enrolled Courses",
|
||||
overallProgress: "Overall Progress",
|
||||
averageGrade: "Average Grade",
|
||||
totalChapters: "Total Chapters",
|
||||
myCourses: "My Courses",
|
||||
noEnrolledCourses: "No enrolled courses yet.",
|
||||
chaptersMaterials: "{{chapters}} chapters · {{materials}} materials",
|
||||
quickActions: "Quick Actions",
|
||||
enrollToStart: "Enroll in a course to get started.",
|
||||
continue: "Continue",
|
||||
start: "Start",
|
||||
chaptersDone: "{{done}}/{{total}} chapters done",
|
||||
recentGrades: "Recent Grades",
|
||||
noGradesYet: "No grades yet.",
|
||||
},
|
||||
teacherDash: {
|
||||
title: "Teacher Dashboard",
|
||||
subtitle: "Overview of your teaching activities.",
|
||||
activeCourses: "Active Courses",
|
||||
totalStudents: "Total Students",
|
||||
pendingGrading: "Pending Grading",
|
||||
avgPassRate: "Avg. Pass Rate",
|
||||
myCourses: "My Courses",
|
||||
colCourse: "Course",
|
||||
colStudents: "Students",
|
||||
colStatus: "Status",
|
||||
recentActivity: "Recent Activity",
|
||||
submitted: "Submitted {{when}}",
|
||||
pending: "Pending",
|
||||
},
|
||||
adminDash: {
|
||||
title: "Admin Dashboard",
|
||||
subtitle: "Platform overview and key metrics.",
|
||||
addStudent: "Add Student",
|
||||
newCourse: "New Course",
|
||||
totalStudents: "Total Students",
|
||||
activeCourses: "Active Courses",
|
||||
teachers: "Teachers",
|
||||
activeBatches: "Active Batches",
|
||||
openTickets: "Open Tickets",
|
||||
revenue: "Revenue",
|
||||
departments: "Departments",
|
||||
classrooms: "Classrooms",
|
||||
subjects: "Subjects",
|
||||
resources: "Resources",
|
||||
coursesOverview: "Courses Overview",
|
||||
batchCapacity: "Batch Capacity",
|
||||
noCourseData: "No course data available.",
|
||||
noBatchData: "No batch data available.",
|
||||
quickSummary: "Quick Summary",
|
||||
colModule: "Module",
|
||||
colCount: "Count",
|
||||
colStatus: "Status",
|
||||
exams: "Exams",
|
||||
examSessionsCount: "{{n}} sessions",
|
||||
assignments: "Assignments",
|
||||
acrossCourses: "across courses",
|
||||
supportTickets: "Support Tickets",
|
||||
ticketsOpenCount: "{{n}} open",
|
||||
payments: "Payments",
|
||||
revenueTotal: "{{amount}} total",
|
||||
chartCapacity: "Capacity",
|
||||
chartEnrolled: "Enrolled",
|
||||
},
|
||||
examPopup: {
|
||||
title: "Upcoming Exams ({{n}})",
|
||||
activeNow: "Active Now",
|
||||
from: "From:",
|
||||
to: "To:",
|
||||
startExam: "Start Exam",
|
||||
notAvailableYet: "Not Available Yet",
|
||||
willBeAvailable: "Exam will be available once it becomes active",
|
||||
notActive: "Exam is not currently active",
|
||||
},
|
||||
errors: {
|
||||
somethingWrong: "Something went wrong",
|
||||
unexpectedDescription: "An unexpected error occurred. Please try refreshing the page.",
|
||||
errorDetails: "Error details",
|
||||
refreshPage: "Refresh Page",
|
||||
notFoundCode: "404",
|
||||
notFoundMessage: "Oops! Page not found",
|
||||
returnHome: "Return to Home",
|
||||
},
|
||||
ai: {
|
||||
assistantLabel: "AI Assistant",
|
||||
assistantTitle: "EnCoach AI Assistant",
|
||||
placeholder: "Ask anything...",
|
||||
send: "Send",
|
||||
thinking: "Thinking…",
|
||||
emptyLine1: "Ask me anything about the platform,",
|
||||
emptyLine2: "or click a quick action above.",
|
||||
fallbackReply: "Sorry, I could not reach the assistant. Please try again in a moment.",
|
||||
replyErrorTitle: "Could not get a reply",
|
||||
replyErrorDesc: "Something went wrong. Try again.",
|
||||
quickHealth: "Platform health summary",
|
||||
quickDropouts: "Show dropout risks",
|
||||
quickCompare: "Compare course performance",
|
||||
quickBatch: "Suggest batch improvements",
|
||||
tipLabel: "AI Tip",
|
||||
insightLabel: "AI Insight",
|
||||
recommendationLabel: "AI Recommendation",
|
||||
categoryTipLabel: "AI {{category}} Tip",
|
||||
loadingTip: "Loading AI tip…",
|
||||
loadingInsights: "Loading insights…",
|
||||
loadingAlerts: "Loading alerts…",
|
||||
couldNotLoadTip: "Could not load tip.",
|
||||
couldNotLoadInsights: "Could not load insights.",
|
||||
couldNotLoadAlerts: "Could not load alerts.",
|
||||
noTipAvailable: "No tip available.",
|
||||
noTipForContext: "No tip for this context yet.",
|
||||
noInsightsAvailable: "No insights available for this view.",
|
||||
noAlertsRightNow: "No AI alerts right now.",
|
||||
alertsUnavailable: "Alerts unavailable",
|
||||
insightsUnavailable: "Insights unavailable",
|
||||
platformInsightsTitle: "AI Platform Insights",
|
||||
},
|
||||
privacy: {
|
||||
title: "Privacy Center",
|
||||
@@ -86,12 +368,6 @@ const en: Translations = {
|
||||
commentRequired: "Please tell us what was wrong.",
|
||||
submit: "Submit feedback",
|
||||
},
|
||||
theme: {
|
||||
title: "Theme",
|
||||
light: "Light",
|
||||
dark: "Dark",
|
||||
system: "System",
|
||||
},
|
||||
};
|
||||
|
||||
export default en;
|
||||
|
||||
Reference in New Issue
Block a user