diff --git a/django_email_learning/personalised/api/views.py b/django_email_learning/personalised/api/views.py
index ec036738..d757eae7 100644
--- a/django_email_learning/personalised/api/views.py
+++ b/django_email_learning/personalised/api/views.py
@@ -5,6 +5,7 @@
QuestionResponse,
)
from django_email_learning.services import jwt_service
+from django.utils.translation import gettext as _
from django_email_learning.models import (
ContentDelivery,
QuizSubmission,
@@ -79,7 +80,7 @@ def post(self, request, *args, **kwargs): # type: ignore[no-untyped-def]
delivery.update_hash()
if passed:
- message = "Congratulations! You have passed the quiz."
+ message = _("Congratulations! You have passed the quiz.")
delivery = delivery.schedule_next_delivery()
if not delivery:
enrolment.graduate()
@@ -91,14 +92,18 @@ def post(self, request, *args, **kwargs): # type: ignore[no-untyped-def]
).count()
if failed_submissions_count > 1:
- message = "You have failed the quiz twice. Unfortunatly you can not continue the course on this enrollment. But you can enroll again to retake the course."
+ message = _(
+ "You have failed the quiz twice. Unfortunatly you can not continue the course on this enrollment. But you can enroll again to retake the course."
+ )
logger.info(
f"Learner ID {enrolment.learner.id} has failed the quiz twice for Course {enrolment.course.title}. "
f"Marking enrollment as failed."
)
enrolment.fail()
else:
- message = "You have failed the quiz. You will receive another chance to retake it tomorrow."
+ message = _(
+ "You have failed the quiz. You will receive another chance to retake it tomorrow."
+ )
logger.info(
f"Learner ID {enrolment.learner.id} has failed the quiz for Course {enrolment.course.title}. "
f"Scheduling a retry for the next day."
diff --git a/django_email_learning/personalised/views.py b/django_email_learning/personalised/views.py
index 019cc50d..5b727a9d 100644
--- a/django_email_learning/personalised/views.py
+++ b/django_email_learning/personalised/views.py
@@ -1,6 +1,7 @@
from django.views import View
from django.views.generic.base import TemplateResponseMixin
from django.http import HttpResponse
+from django.utils.translation import gettext as _
from django.urls import reverse
from django_email_learning.models import ContentDelivery, EnrollmentStatus
from django_email_learning.services import jwt_service
@@ -14,7 +15,11 @@
class ErrrorLoggingMixin(TemplateResponseMixin):
def errr_response(
- self, message: str, exception: Exception | None, status_code: int = 500
+ self,
+ message: str,
+ exception: Exception | None,
+ status_code: int = 500,
+ title: str = _("Error"),
) -> HttpResponse:
error_ref = uuid.uuid4().hex
if exception:
@@ -26,7 +31,8 @@ def errr_response(
f"{message} - Ref: {error_ref}", extra={"error_ref": error_ref}
)
return self.render_to_response(
- context={"ref": error_ref, "error_message": message}, status=status_code
+ context={"ref": error_ref, "error_message": message, "page_title": title},
+ status=status_code,
)
@@ -44,18 +50,22 @@ def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-unty
enrolment = delivery.enrollment
if enrolment.status != EnrollmentStatus.ACTIVE:
return self.errr_response(
- message="Quiz is not valid anymore",
+ message=_("Quiz is not valid anymore"),
exception=ValueError("Enrolment is not active"),
+ title=_("Invalid Quiz"),
)
quiz = delivery.course_content.quiz
if not quiz:
return self.errr_response(
- message="No quiz associated with this link", exception=None
+ message=_("No quiz associated with this link"),
+ exception=None,
+ title=_("Invalid Quiz"),
)
if not delivery.course_content.is_published:
return self.errr_response(
- message="No valid quiz associated with this link",
+ message=_("No valid quiz associated with this link"),
exception=ValueError("Quiz is not published"),
+ title=_("Invalid Quiz"),
)
quiz_data = PublicQuizSerializer.model_validate(quiz).model_dump()
if question_ids:
@@ -75,19 +85,30 @@ def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-unty
except ContentDelivery.DoesNotExist as e:
return self.errr_response(
- message="An error occurred while retrieving the quiz", exception=e
+ message=_("An error occurred while retrieving the quiz"),
+ exception=e,
+ title=_("Error"),
)
except KeyError as e:
return self.errr_response(
- message="The link is not valid", exception=e, status_code=400
+ message=_("The link is not valid"),
+ exception=e,
+ status_code=400,
+ title=_("Invalid Link"),
)
except jwt_service.InvalidTokenException as e:
return self.errr_response(
- message="The link is not valid", exception=e, status_code=400
+ message=_("The link is not valid"),
+ exception=e,
+ status_code=400,
+ title=_("Invalid Link"),
)
except jwt_service.ExpiredTokenException as e:
return self.errr_response(
- message="The link has expired", exception=e, status_code=410
+ message=_("The link has expired"),
+ exception=e,
+ status_code=410,
+ title=_("Expired Link"),
)
@@ -99,23 +120,26 @@ def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-unty
token = request.GET["token"]
except KeyError as e:
return self.errr_response(
- message="The verification link is not valid.",
+ message=_("The verification link is not valid."),
exception=e,
status_code=400,
+ title=_("Invalid Link"),
)
try:
decoded = jwt_service.decode_jwt(token=token)
except jwt_service.InvalidTokenException as e:
return self.errr_response(
- message="The verification link is not valid.",
+ message=_("The verification link is not valid."),
exception=e,
status_code=400,
+ title=_("Invalid Link"),
)
except jwt_service.ExpiredTokenException as e:
return self.errr_response(
- message="The verification link has expired.",
+ message=_("The verification link has expired."),
exception=e,
status_code=410,
+ title=_("Expired Link"),
)
enrollment_id = decoded["enrollment_id"]
@@ -130,7 +154,9 @@ def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-unty
command.execute()
except Exception as e:
return self.errr_response(
- message="An error occurred during enrollment verification.", exception=e
+ message=_("An error occurred during enrollment verification."),
+ exception=e,
+ title=_("Verification Error"),
)
- return self.render_to_response(context={"page_title": "Enrollment Verified"})
+ return self.render_to_response(context={"page_title": _("Enrollment Verified")})
diff --git a/django_email_learning/platform/views.py b/django_email_learning/platform/views.py
index 53d3b1ea..573617e1 100644
--- a/django_email_learning/platform/views.py
+++ b/django_email_learning/platform/views.py
@@ -2,6 +2,7 @@
from django.views.generic import TemplateView
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
+from django.utils.translation import gettext as _
from django.urls import reverse
from django_email_learning.models import Organization, OrganizationUser, Course
from django_email_learning.decorators import (
@@ -70,7 +71,7 @@ class Courses(BasePlatformView):
def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
context = super().get_context_data(**kwargs)
- context["page_title"] = "Courses"
+ context["page_title"] = _("Courses")
return context
@@ -83,7 +84,7 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
context = super().get_context_data(**kwargs)
course = Course.objects.get(pk=self.kwargs["course_id"])
context["course"] = course
- context["page_title"] = f"Course: {course.title}"
+ context["page_title"] = _("Course: %(title)s") % {"title": course.title}
return context
@@ -94,7 +95,7 @@ class Organizations(BasePlatformView):
def get_context_data(self, **kwargs): # type: ignore[no-untyped-def]
context = super().get_context_data(**kwargs)
- context["page_title"] = "Organizations"
+ context["page_title"] = _("Organizations")
return context
@@ -105,5 +106,5 @@ class Learners(BasePlatformView):
def get_context_data(self, **kwargs): # type: ignore[no-untyped-def]
context = super().get_context_data(**kwargs)
- context["page_title"] = "Learners"
+ context["page_title"] = _("Learners")
return context
diff --git a/django_email_learning/public/views.py b/django_email_learning/public/views.py
index 1ad1943f..868688c6 100644
--- a/django_email_learning/public/views.py
+++ b/django_email_learning/public/views.py
@@ -3,6 +3,7 @@
from django_email_learning.models import Organization, Course
from django.http import Http404
from django.urls import reverse
+from django.utils.translation import gettext as _
from django.conf import settings
from django_email_learning.public.serializers import (
OrganizationSerializer,
@@ -29,7 +30,7 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
if organization_details.exists():
organization = organization_details.first()
if not organization:
- raise Http404("Organization does not exist")
+ raise Http404(_("Organization does not exist"))
courses = []
for course in organization.courses:
course_data = PublicCourseSerializer(
@@ -59,4 +60,4 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
return context
# If organization not found, raise 404
- raise Http404("Organization does not exist")
+ raise Http404(_("Organization does not exist"))
diff --git a/django_email_learning/services/command_models/enroll_command.py b/django_email_learning/services/command_models/enroll_command.py
index de858c04..bb2951c0 100644
--- a/django_email_learning/services/command_models/enroll_command.py
+++ b/django_email_learning/services/command_models/enroll_command.py
@@ -16,6 +16,7 @@
from django_email_learning.services import jwt_service
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
+from django.utils.translation import gettext as _
from django.conf import settings
from django.urls import reverse
from typing import Literal
@@ -106,7 +107,7 @@ def execute(self) -> None:
else None,
}
email_service = EmailSenderService()
- subject = "Verify your enrollment"
+ subject = _("Verify your enrollment")
body = render_to_string(
"emails/enrolment_verification.txt",
template_context,
diff --git a/django_email_learning/services/command_models/verify_enrollment_command.py b/django_email_learning/services/command_models/verify_enrollment_command.py
index 5f19d060..45141cbb 100644
--- a/django_email_learning/services/command_models/verify_enrollment_command.py
+++ b/django_email_learning/services/command_models/verify_enrollment_command.py
@@ -13,6 +13,7 @@
from django_email_learning.services.email_sender_service import EmailSenderService
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
+from django.utils.translation import gettext as _
from typing import Literal
@@ -59,7 +60,7 @@ def execute(self) -> None:
# Send confirmation email
email_service = EmailSenderService()
- subject = "Enrollment Verified"
+ subject = _("Enrollment Verified")
body = render_to_string(
"emails/enrollment_verified.txt",
{
diff --git a/django_email_learning/templates/emails/base.html b/django_email_learning/templates/emails/base.html
index d978baa0..d9ee5520 100644
--- a/django_email_learning/templates/emails/base.html
+++ b/django_email_learning/templates/emails/base.html
@@ -1,3 +1,4 @@
+{% load i18n %}
{% with brand_color="#7c86ff" %}
@@ -24,6 +25,16 @@
background-color: #f9f9f9;
}
+ .locale {
+ {% get_current_language_bidi as IS_RTL %}
+ {% if IS_RTL %}
+ direction: rtl;
+ text-align: right;
+ {% else %}
+ direction: ltr;
+ text-align: left;
+ {% endif %}
+ }
@media screen and (max-width: 480px) {
.mobile-content {
@@ -56,7 +67,7 @@
-
+
{% block content %}{% endblock %}
diff --git a/django_email_learning/templates/emails/enrollment_verified.html b/django_email_learning/templates/emails/enrollment_verified.html
index 60116882..5ffcd4fe 100644
--- a/django_email_learning/templates/emails/enrollment_verified.html
+++ b/django_email_learning/templates/emails/enrollment_verified.html
@@ -1,22 +1,23 @@
{% extends "emails/base.html" %}
+{% load i18n %}
{% block content %}
-
Hello there,
+
{% translate "Hello there," %}
-
Congratulations! Your enrollment for "{{ course_title }} " has been successfully verified and confirmed.
+
{% blocktranslate %}Congratulations! Your enrollment for {{ course_title }} has been successfully verified and confirmed.{% endblocktranslate %}
-
We're excited to have you join our learning community. Here's what you can expect:
+
{% translate "We're excited to have you join our learning community. Here's what you can expect:" %}
- Course materials will be delivered directly to this email address
- You'll receive structured lessons and assignments on a regular schedule
- Interactive content and assessments will help track your progress
+ {% translate "Course materials will be delivered directly to this email address" %}
+ {% translate "You'll receive structured lessons and assignments on a regular schedule" %}
+ {% translate "Interactive content and assessments will help track your progress" %}
-
Your learning adventure begins soon! Keep an eye on your inbox for the first lesson and welcome materials.
+
{% translate "Your learning adventure begins soon! Keep an eye on your inbox for the first lesson and welcome materials." %}
-
Welcome aboard!
+
{% translate "Welcome aboard!" %}
-
Best regards,
-The {{ organization_name }} Team
+
{% translate "Best regards," %}
+{% blocktranslate %}The {{ organization_name }} Team{% endblocktranslate %}
{% endblock %}
diff --git a/django_email_learning/templates/emails/enrollment_verified.txt b/django_email_learning/templates/emails/enrollment_verified.txt
index 9edcbb51..764cdad0 100644
--- a/django_email_learning/templates/emails/enrollment_verified.txt
+++ b/django_email_learning/templates/emails/enrollment_verified.txt
@@ -1,16 +1,17 @@
-Hello there,
+{% load i18n %}
+{% translate "Hello there" %},
-Congratulations! Your enrollment for "{{ course_title }}" has been successfully verified and confirmed.
+{% blocktranslate %}Congratulations! Your enrollment for "{{ course_title }}" has been successfully verified and confirmed.{% endblocktranslate %}
-We're excited to have you join our learning community. Here's what you can expect:
+{% translate "We're excited to have you join our learning community. Here's what you can expect:" %}
-• Course materials will be delivered directly to this email address
-• You'll receive structured lessons and assignments on a regular schedule
-• Interactive content and assessments will help track your progress
+• {% translate "Course materials will be delivered directly to this email address" %}
+• {% translate "You'll receive structured lessons and assignments on a regular schedule" %}
+• {% translate "Interactive content and assessments will help track your progress" %}
-Your learning adventure begins soon! Keep an eye on your inbox for the first lesson and welcome materials.
+{% translate "Your learning adventure begins soon! Keep an eye on your inbox for the first lesson and welcome materials." %}
-Welcome aboard!
+{% translate "Welcome aboard!" %}
-Best regards,
-The {{ organization_name }} Team
+{% translate "Best regards," %}
+{% blocktranslate %}The {{ organization_name }} Team{% endblocktranslate %}
diff --git a/django_email_learning/templates/emails/enrolment_verification.html b/django_email_learning/templates/emails/enrolment_verification.html
index 578719a9..651da9ec 100644
--- a/django_email_learning/templates/emails/enrolment_verification.html
+++ b/django_email_learning/templates/emails/enrolment_verification.html
@@ -1,48 +1,48 @@
{% extends "emails/base.html" %}
+{% load i18n %}
{% block content %}
-
Welcome!
+
{% translate "Welcome!" %}
-
Hello there,
+
{% translate "Hello there," %}
- Congratulations on enrolling in {{ course_title }} !
- We're excited to have you join our learning community.
+ {% blocktranslate %}Congratulations on enrolling in {{ course_title }} !{% endblocktranslate %}
+ {% translate "We're excited to have you join our learning community." %}
- To get started, please verify your email address by clicking the button below:
+ {% translate "To get started, please verify your email address by clicking the button below:" %}
{% if support_imap_interface %}
-
Alternative Verification Method
+
{% translate "Alternative Verification Method" %}
- You can also verify your email by sending a message to
- {{ imap_email_address }}
- with your verification code in the subject line or email body.
- Simply forward this email to complete the verification process.
+ {% blocktranslate %}You can also verify your email by sending a message to {{ imap_email_address }} {% endblocktranslate %}
+ {% translate "with your verification code in the subject line or email body." %}
+ {% translate "Simply forward this email to complete the verification process." %}
- Verification Code:
+ {% translate "Verification Code:" %}
{{ verification_code }}
{% endif %}
-
If you didn't sign up for this course, you can safely ignore this email.
+
{% translate "If you didn't sign up for this course, you can safely ignore this email." %}
-
Best regards,
+
{% translate "Best regards," %}
{{ organization_name }}
{% endblock %}
diff --git a/django_email_learning/templates/emails/enrolment_verification.txt b/django_email_learning/templates/emails/enrolment_verification.txt
index 13be1435..0aba3ec8 100644
--- a/django_email_learning/templates/emails/enrolment_verification.txt
+++ b/django_email_learning/templates/emails/enrolment_verification.txt
@@ -1,16 +1,18 @@
-Hello there,
+{% load i18n %}
-Congratulations on enrolling in {{ course_title }}!
-We're excited to have you join our learning community.
+{% translate "Hello there," %}
-To get started, please verify your email address by visiting the link below:
+{% blocktranslate %}Congratulations on enrolling in {{ course_title }}!{% endblocktranslate %}
+{% translate "We're excited to have you join our learning community." %}
+
+{% translate "To get started, please verify your email address by visiting the link below:" %}
{{ verification_link }}
{% if support_imap_interface %}
-Alternatively, you can also verify your email by sending the verification code {{ verification_code }} via an email to {{ imap_email_address }}.
+{% blocktranslate %}Alternatively, you can also verify your email by sending the verification code {{ verification_code }} via an email to {{ imap_email_address }}.{% endblocktranslate %}
{% endif %}
-If you didn't sign up for this course, you can safely ignore this email.
+{% translate "If you didn't sign up for this course, you can safely ignore this email." %}
-Best Regards,
+{% translate "Best Regards," %}
{{ organization_name }}
diff --git a/django_email_learning/templates/emails/quiz.html b/django_email_learning/templates/emails/quiz.html
index 2644402d..c3cb4d89 100644
--- a/django_email_learning/templates/emails/quiz.html
+++ b/django_email_learning/templates/emails/quiz.html
@@ -1,21 +1,20 @@
{% extends "emails/base.html" %}
-
+{% load i18n %}
{% block content %}
{{ quiz.title }}
-
Dear Learner,
-
-
Your quiz is now available for completion. You have {{ quiz.deadline_days }} days from today to submit your responses.
+
{% translate "Dear Learner," %}
-
To successfully pass this quiz, you must achieve a minimum score of {{ quiz.required_score }} .
+
{% blocktranslate with deadline_days=quiz.deadline_days %}Your quiz is now available for completion. You have {{ deadline_days }} days from today to submit your responses.{% endblocktranslate %}
-
Please click the link below to access and submit your quiz:
+
{% blocktranslate with score=quiz.required_score %}To successfully pass this quiz, you must achieve a minimum score of {{ score }} .{% endblocktranslate %}
+
{% translate "Please click the link below to access and submit your quiz:" %}
-
Best of luck!
+
{% translate "Best of luck!" %}
{% endblock %}
diff --git a/django_email_learning/templates/emails/quiz.txt b/django_email_learning/templates/emails/quiz.txt
index 07620bdd..932df18d 100644
--- a/django_email_learning/templates/emails/quiz.txt
+++ b/django_email_learning/templates/emails/quiz.txt
@@ -1,10 +1,11 @@
+{% load i18n %}
{{ quiz.title }}
-Dear Learner,
+{% translate "Dear Learner," %}
-Your quiz is now available for completion. You have {{ quiz.deadline_days }} days from today to submit your responses.
+{% blocktranslate with with deadline_days=quiz.deadline_days %}Your quiz is now available for completion. You have {{ deadline_days }} days from today to submit your responses.{% endblocktranslate %}
-To successfully pass this quiz, you must achieve a minimum score of {{ quiz.required_score }}.
+{% blocktranslate with score=quiz.required_score %}To successfully pass this quiz, you must achieve a minimum score of {{ score }}.{% endblocktranslate %}
-Please click the link below to access and submit your quiz:
+{% translate "Please click the link below to access and submit your quiz:" %}
{{ link }}
diff --git a/django_email_learning/templates/personalised/base.html b/django_email_learning/templates/personalised/base.html
index c8417840..164de682 100644
--- a/django_email_learning/templates/personalised/base.html
+++ b/django_email_learning/templates/personalised/base.html
@@ -1,5 +1,6 @@
{% load django_vite %}
{% load static %}
+{% load i18n %}
@@ -15,7 +16,8 @@
{% block title %}{{ page_title }}{% endblock %}
-
+ {% get_current_language_bidi as IS_RTL %}
+
diff --git a/django_email_learning/templates/personalised/quiz_public.html b/django_email_learning/templates/personalised/quiz_public.html
index 763fdebd..95e0ba4f 100644
--- a/django_email_learning/templates/personalised/quiz_public.html
+++ b/django_email_learning/templates/personalised/quiz_public.html
@@ -1,4 +1,5 @@
{% extends "personalised/base.html" %}
+{% load i18n %}
{% load django_vite %}
{% block head_script %}
{% vite_asset 'personalised/quiz_public/Quiz.jsx' %}
{% endblock %}
diff --git a/django_email_learning/templates/personalised/verify_enrollment.html b/django_email_learning/templates/personalised/verify_enrollment.html
index 12d09c19..fdc1133d 100644
--- a/django_email_learning/templates/personalised/verify_enrollment.html
+++ b/django_email_learning/templates/personalised/verify_enrollment.html
@@ -1,5 +1,11 @@
{% extends "personalised/base.html" %}
+{% load i18n %}
{% load django_vite %}
{% block head_script %}
+
{% vite_asset 'personalised/verify_enrollment/Verification.jsx' %}
{% endblock %}
diff --git a/django_email_learning/templates/platform/base.html b/django_email_learning/templates/platform/base.html
index 4283a822..813d2cd3 100644
--- a/django_email_learning/templates/platform/base.html
+++ b/django_email_learning/templates/platform/base.html
@@ -1,23 +1,32 @@
{% load django_vite %}
{% load static %}
+{% load i18n %}
+{% get_current_language_bidi as IS_RTL %}
-
+
{% vite_hmr_client %}
{% vite_react_refresh %}
- {% block extra_head %}{% endblock %}
+ {% block extra_head %}{% endblock %}
{% block title %}{{ page_title }}{% endblock %}
+
diff --git a/django_email_learning/templates/platform/course.html b/django_email_learning/templates/platform/course.html
index 29d57be3..e065b0e5 100644
--- a/django_email_learning/templates/platform/course.html
+++ b/django_email_learning/templates/platform/course.html
@@ -1,9 +1,65 @@
{% extends "platform/base.html" %}
{% load django_vite %}
{% block extra_head %}
+{% load i18n %}
{% vite_asset 'platform/course/Course.jsx' %}
{% endblock %}
diff --git a/django_email_learning/templates/platform/courses.html b/django_email_learning/templates/platform/courses.html
index 34be41b6..72d354de 100644
--- a/django_email_learning/templates/platform/courses.html
+++ b/django_email_learning/templates/platform/courses.html
@@ -1,5 +1,52 @@
{% extends "platform/base.html" %}
{% load django_vite %}
+{% load i18n %}
{% block extra_head %}
+
{% vite_asset 'platform/courses/Courses.jsx' %}
{% endblock %}
diff --git a/django_email_learning/templates/platform/learners.html b/django_email_learning/templates/platform/learners.html
index 7de9da61..b541cd7a 100644
--- a/django_email_learning/templates/platform/learners.html
+++ b/django_email_learning/templates/platform/learners.html
@@ -1,5 +1,37 @@
{% extends "platform/base.html" %}
{% load django_vite %}
+{% load i18n %}
{% block extra_head %}
+
{% vite_asset 'platform/learners/Learners.jsx' %}
{% endblock %}
diff --git a/django_email_learning/templates/platform/organizations.html b/django_email_learning/templates/platform/organizations.html
index 94f7f830..18615130 100644
--- a/django_email_learning/templates/platform/organizations.html
+++ b/django_email_learning/templates/platform/organizations.html
@@ -1,5 +1,25 @@
{% extends "platform/base.html" %}
{% load django_vite %}
+{% load i18n %}
{% block extra_head %}
+
{% vite_asset 'platform/organizations/Organizations.jsx' %}
{% endblock %}
diff --git a/django_email_learning/templates/public/base.html b/django_email_learning/templates/public/base.html
index c8417840..f1893181 100644
--- a/django_email_learning/templates/public/base.html
+++ b/django_email_learning/templates/public/base.html
@@ -1,11 +1,14 @@
{% load django_vite %}
+{% load i18n %}
{% load static %}
+{% get_current_language_bidi as IS_RTL %}
-
+
{% vite_hmr_client %}
{% vite_react_refresh %}
@@ -15,7 +18,8 @@
{% block title %}{{ page_title }}{% endblock %}
-
+ {% get_current_language_bidi as IS_RTL %}
+
diff --git a/django_email_learning/templates/public/organization.html b/django_email_learning/templates/public/organization.html
index 692bb9b1..a85efd1c 100644
--- a/django_email_learning/templates/public/organization.html
+++ b/django_email_learning/templates/public/organization.html
@@ -1,9 +1,23 @@
{% extends "public/base.html" %}
{% load django_vite %}
+{% load i18n %}
{% block head_script %}
{% vite_asset 'public/organization/Organization.jsx' %}
{% endblock %}
diff --git a/frontend/personalised/quiz_public/Quiz.jsx b/frontend/personalised/quiz_public/Quiz.jsx
index cf21d053..b81b1307 100644
--- a/frontend/personalised/quiz_public/Quiz.jsx
+++ b/frontend/personalised/quiz_public/Quiz.jsx
@@ -23,7 +23,7 @@ const Quiz = () => {
answerCounter += selectedAnswers[i].answers.length;
}
if (answerCounter === 0) {
- setWarning("You have not selected any answers. Are you sure you want to submit an empty quiz?");
+ setWarning(localeMessages['no_answer_warning']);
} else {
setWarning("");
}
@@ -61,8 +61,7 @@ const Quiz = () => {
{showQuestions ?
- Please select all correct answers for each question. Note that some questions may have multiple correct answers.
- This quiz uses negative marking for incorrect choices; if you are unsure, it is better to leave the question unanswered.
+ { localeMessages['quiz_intro'] }
@@ -85,21 +84,21 @@ const Quiz = () => {
))}
- Submit
+ {localeMessages['submit']}
:
- {isPassed !== null && (isPassed ? {message} Your score is {score}%. :
- {message} Your score is {score}%. )}
+ {isPassed !== null && (isPassed ? {message} {localeMessages['your_score']}: {score}%. :
+ {message} {localeMessages['your_score']}: {score}%. )}
}
- :
Error loading quiz: {error_message} {ref && `(Ref: ${ref})`} }
+ :
{localeMessages['error_loading_quiz']}: {error_message} {ref && `(Ref: ${ref})`} }
setDialogOpen(false)} fullWidth maxWidth="sm">
- Ready to submit?
+ {localeMessages['ready_to_submit']}
{warning ? {warning} :
- Please keep in mind that this quiz uses negative marking for incorrect answers. If you are unsure of an answer, it may be better to leave it blank. }
+ {localeMessages['submit_quiz_note']} }
- setDialogOpen(false)} sx={{ mr: 2 }}>Cancel
- Submit
+ setDialogOpen(false)} sx={{ mr: 2 }}>{localeMessages['cancel']}
+ {localeMessages['submit']}
diff --git a/frontend/personalised/verify_enrollment/Verification.jsx b/frontend/personalised/verify_enrollment/Verification.jsx
index 3254500b..913cd3e8 100644
--- a/frontend/personalised/verify_enrollment/Verification.jsx
+++ b/frontend/personalised/verify_enrollment/Verification.jsx
@@ -6,7 +6,7 @@ import Layout from '../../public/components/Layout.jsx';
const Verification = () => {
return
{ !error_message ?
- You have successfully verified your enrollment.
+ { localeMessages['verify_enrollment_success'] }
:
{error_message} (ref: {ref})
}
diff --git a/frontend/platform/course/Course.jsx b/frontend/platform/course/Course.jsx
index 67be86ae..90293fa6 100644
--- a/frontend/platform/course/Course.jsx
+++ b/frontend/platform/course/Course.jsx
@@ -79,7 +79,7 @@ function Course() {
console.log("Opening lesson editor for content:", content);
setDialogOpen(true);
setDialogContent(
- {userRole !== 'viewer' && <> } sx={{ marginBottom: 2 }} onClick={() => {
+ {userRole !== 'viewer' && <> } sx={{ marginBottom: 2 }} onClick={() => {
setDialogContent( setDialogOpen(false)}
successCallback={resetDialog}
courseId={course_id} />);
- setDialogOpen(true);}}>Add a Lesson
- } sx={{ marginBottom: 2, marginLeft: 1 }} onClick={() => {
+ setDialogOpen(true);}}>{localeMessages["add_lesson"]}
+ } sx={{ marginBottom: 2, marginLeft: 1, marginRight: 1 }} onClick={() => {
setDialogContent( setDialogOpen(false)}
successCallback={resetDialog}
courseId={course_id} />);
- setDialogOpen(true);}}>Add a Quiz> }
+ setDialogOpen(true);}}>{localeMessages["add_quiz"]}> }
tableEventHandler(event)} />
diff --git a/frontend/platform/course/components/ContentTable.jsx b/frontend/platform/course/components/ContentTable.jsx
index d3054dfa..fd383f60 100644
--- a/frontend/platform/course/components/ContentTable.jsx
+++ b/frontend/platform/course/components/ContentTable.jsx
@@ -116,11 +116,11 @@ const ContentTable = ({ courseId, eventHandler, loaded = false }) => {
{ userRole !== 'viewer' && }
- Title
- Waiting time
- type
- Published
- {userRole !== 'viewer' && Actions }
+ {localeMessages["title"]}
+ {localeMessages["waiting_time"]}
+ {localeMessages["type"]}
+ {localeMessages["published"]}
+ {userRole !== 'viewer' && {localeMessages["actions"]} }
@@ -140,17 +140,17 @@ const ContentTable = ({ courseId, eventHandler, loaded = false }) => {
eventHandler(event);
}
}}>
- { userRole !== 'viewer' && startDrag(content.id)}
/> }
- {let event = {type: 'content_clicked', content_id: content.id}; eventHandler(event);}}
color='primary.dark' sx={{ cursor: 'pointer'}}>{content.title}
- {formatPeriod(content.waiting_period)}
- {content.type.charAt(0).toUpperCase() + content.type.slice(1)}
- TogglePublishContent(content.id, !content.is_published)} disabled={userRole == 'viewer'} />
- {userRole !== 'viewer' &&
- deleteContent(content.id)}>
+ {formatPeriod(content.waiting_period)}
+ {localeMessages[content.type]}
+ TogglePublishContent(content.id, !content.is_published)} disabled={userRole == 'viewer'} />
+ {userRole !== 'viewer' &&
+ deleteContent(content.id)}>
}
diff --git a/frontend/platform/course/components/LessonForm.jsx b/frontend/platform/course/components/LessonForm.jsx
index 01371c70..792b21c5 100644
--- a/frontend/platform/course/components/LessonForm.jsx
+++ b/frontend/platform/course/components/LessonForm.jsx
@@ -19,7 +19,7 @@ function LessonForm({ header, initialTitle, initialContent, onContentChange, can
const addLesson = () =>{
if (!validateForm()) {
- setErrorMessage("Please fix the errors in the form before submitting.");
+ setErrorMessage(localeMessages["fix_errors"]);
return;
}
@@ -54,7 +54,7 @@ function LessonForm({ header, initialTitle, initialContent, onContentChange, can
const updateLesson = () => {
if (!validateForm()) {
- setErrorMessage("Please fix the errors in the form before submitting.");
+ setErrorMessage(localeMessages["fix_errors"]);
return;
}
@@ -105,13 +105,13 @@ function LessonForm({ header, initialTitle, initialContent, onContentChange, can
const validateForm = () => {
let isValid = true;
if (!title) {
- setTitleHelperText("Lesson title is required.");
+ setTitleHelperText(localeMessages["lesson_title_required"]);
isValid = false;
} else {
setTitleHelperText("");
}
if (!content) {
- setContentHelperText("Lesson content is required.");
+ setContentHelperText(localeMessages["lesson_content_required"]);
isValid = false;
} else {
setContentHelperText("");
@@ -127,7 +127,7 @@ function LessonForm({ header, initialTitle, initialContent, onContentChange, can
{errorMessage}
)}
- setTitle(e.target.value)} helperText={titleHelperText} disabled={userRole === 'viewer'} />
+ setTitle(e.target.value)} helperText={titleHelperText} disabled={userRole === 'viewer'} />
@@ -136,9 +136,9 @@ function LessonForm({ header, initialTitle, initialContent, onContentChange, can
+ title={localeMessages["lesson_waiting_tooltip"]}>
- setWaitingPeriodUnit(e.target.value)} name="waiting_period_unit" sx={{ width: '150px' }} disabled={userRole === 'viewer'}>
- Days
- Hours
+ setWaitingPeriodUnit(e.target.value)} name="waiting_period_unit" sx={{ width: '150px', mr: 2 }} disabled={userRole === 'viewer'}>
+ {localeMessages["days"]}
+ {localeMessages["hours"]}
- Back
+ {localeMessages["back"]}
- {userRole !== 'viewer' && {if(!lessonId) { addLesson(); } else { updateLesson(); }}}>
- Save Lesson
+ {userRole !== 'viewer' && {if(!lessonId) { addLesson(); } else { updateLesson(); }}}>
+ {localeMessages["save_lesson"]}
}
diff --git a/frontend/platform/course/components/QuestionForm.jsx b/frontend/platform/course/components/QuestionForm.jsx
index 59359ca0..e301f01e 100644
--- a/frontend/platform/course/components/QuestionForm.jsx
+++ b/frontend/platform/course/components/QuestionForm.jsx
@@ -6,7 +6,6 @@ import ClearIcon from '@mui/icons-material/Clear';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
-
const QuestionForm = ({question, index, eventHandler}) => {
const [questionText, setQuestionText] = useState(question.text);
const [options, setOptions] = useState(question.options || []);
@@ -80,10 +79,10 @@ const QuestionForm = ({question, index, eventHandler}) => {
{userRole !== 'viewer' && <> setAddingOption(true)} >
- Add Option
+ {localeMessages["add_option"]}
-
- Delete
+
+ {localeMessages["delete"]}
>}
{addingOption && (<>
@@ -112,10 +111,10 @@ const QuestionForm = ({question, index, eventHandler}) => {
}
}}>
- Add
+ { localeMessages["add"] }
setAddingOption(false)}>
- Cancel
+ { localeMessages["cancel"] }
>
@@ -125,9 +124,9 @@ const QuestionForm = ({question, index, eventHandler}) => {
- Options
- Correct Answer
- {userRole !== 'viewer' && Actions }
+ {localeMessages["options"]}
+ {localeMessages["correct_answer"]}
+ {userRole !== 'viewer' && {localeMessages["actions"]} }
diff --git a/frontend/platform/course/components/QuizForm.jsx b/frontend/platform/course/components/QuizForm.jsx
index aa59335d..e6fa74ac 100644
--- a/frontend/platform/course/components/QuizForm.jsx
+++ b/frontend/platform/course/components/QuizForm.jsx
@@ -68,11 +68,11 @@ const QuizForm = ({cancelCallback, successCallback, courseId, quizId, contentId,
const validateQuiz = () => {
if (title.trim() === "") {
- setErrorMessage("Quiz title cannot be empty.");
+ setErrorMessage(localeMessages["quiz_title_empty"]);
return false;
}
if (questions.length === 0) {
- setErrorMessage("Quiz must contain at least one question.");
+ setErrorMessage(localeMessages["at_least_one_question"]);
return false;
}
for (let i = 0; i < questions.length; i++) {
@@ -83,12 +83,12 @@ const QuizForm = ({cancelCallback, successCallback, courseId, quizId, contentId,
}
const options = question.options || [];
if (options.length < 2) {
- setErrorMessage(`Question ${i + 1} must have at least two answer options.`);
+ setErrorMessage(localeMessages["at_least_two_options"].replace("QUESTION_NUMBER", i + 1));
return false;
}
const hasCorrectOption = options.some(option => option.isCorrect);
if (!hasCorrectOption) {
- setErrorMessage(`Question ${i + 1} must have at least one correct answer.`);
+ setErrorMessage(localeMessages["at_least_one_correct"].replace("QUESTION_NUMBER", i + 1));
return false;
}
}
@@ -205,11 +205,11 @@ const QuizForm = ({cancelCallback, successCallback, courseId, quizId, contentId,
}
}} tabIndex={0} focusable="true">
- { quizId ? "Update Quiz" : "New Quiz" }
+ { quizId ? localeMessages["update_quiz"] : localeMessages["new_quiz"] }
{errorMessage && {errorMessage} }
- setTitle(e.target.value)} sx={{ mb: 2, width: '100%' }} disabled={userRole === 'viewer'} />
+ setTitle(e.target.value)} sx={{ mb: 2, width: '100%' }} disabled={userRole === 'viewer'} />
{userRole !== 'viewer' && setShowQuestionField(true)}>
- Add Question }
+ {localeMessages["add_question"]}}
{ showQuestionField && (
@@ -224,10 +224,10 @@ const QuizForm = ({cancelCallback, successCallback, courseId, quizId, contentId,
{
addToQuestions();
}}>
- Add Question
+ {localeMessages["add_question"]}
{ setShowQuestionField(false); setNewQuestion(""); }}>
- Cancel
+ {localeMessages["cancel"]}
@@ -241,17 +241,17 @@ const QuizForm = ({cancelCallback, successCallback, courseId, quizId, contentId,
{/* Quiz Settings Section */}
- Quiz Settings
+ {localeMessages["quiz_settings"]}
{/* Row 1: Required Score and Waiting Period */}
- Required Score to Pass
+ {localeMessages["required_score"]}
- Waiting Period
+ {localeMessages["waiting_period"]}
- Deadline to Complete Quiz
+ {localeMessages["quiz_deadline"]}
- Selection Strategy
+ {localeMessages["question_selection_strategy"]}
- All Questions
- Random Questions
+ {localeMessages["all_questions"]}
+ {localeMessages["random_questions"]}
@@ -350,10 +348,10 @@ const QuizForm = ({cancelCallback, successCallback, courseId, quizId, contentId,
- Back
+ {localeMessages["back"]}
- {userRole !== 'viewer' && {if(!quizId) { addQuiz(); } else { updateQuiz(); }}}>
- Save Quiz
+ {userRole !== 'viewer' && {if(!quizId) { addQuiz(); } else { updateQuiz(); }}}>
+ {localeMessages["save_quiz"]}
}
diff --git a/frontend/platform/courses/Courses.jsx b/frontend/platform/courses/Courses.jsx
index b99593ff..6da490f0 100644
--- a/frontend/platform/courses/Courses.jsx
+++ b/frontend/platform/courses/Courses.jsx
@@ -98,7 +98,7 @@ function Courses() {
return (
,
children: setQueryParameters(params)} />
@@ -107,7 +107,7 @@ function Courses() {
>
- {userRole !== 'viewer' && } sx={{ marginBottom: 2 }} onClick={() => {
+ {userRole !== 'viewer' && } sx={{ marginBottom: 2 }} onClick={() => {
setDialogContent( );
- setDialogOpen(true);}}>Add a Course}
+ setDialogOpen(true);}}>{localeMessages["add_course"]}}
-
+
- Title
- Slug
- Enabled
- {userRole !== 'viewer' && Actions }
+ {localeMessages["title"]}
+ {localeMessages["slug"]}
+ {localeMessages["enabled"]}
+ {userRole !== 'viewer' && {localeMessages["actions"]} }
@@ -132,11 +132,11 @@ function Courses() {
key={course.id}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
-
+
{course.title}
- {course.slug}
-
+ {course.slug}
+
- {userRole !== 'viewer' &&
+ {userRole !== 'viewer' &&
{
showEditCourseDialog(course);}}>
{
diff --git a/frontend/platform/courses/components/AddImapConnectionForm.jsx b/frontend/platform/courses/components/AddImapConnectionForm.jsx
index 6396e317..bf0d8c9a 100644
--- a/frontend/platform/courses/components/AddImapConnectionForm.jsx
+++ b/frontend/platform/courses/components/AddImapConnectionForm.jsx
@@ -54,11 +54,11 @@ function AddImapConnectionForm({onChangeCallback, activeOrganizationId, initialI
{ hasImapConnection && (
- IMAP Connection
+ {localeMessages["imap_connection"]}
handleImapConnectionChange(event.target.value)}
>
@@ -77,7 +77,7 @@ function AddImapConnectionForm({onChangeCallback, activeOrganizationId, initialI
aria-controls="panel1-content"
id="panel1-header"
>
- New IMAP connection
+ {localeMessages["new_imap_connection"]}
diff --git a/frontend/platform/courses/components/CourseForm.jsx b/frontend/platform/courses/components/CourseForm.jsx
index 756f2b16..89db978c 100644
--- a/frontend/platform/courses/components/CourseForm.jsx
+++ b/frontend/platform/courses/components/CourseForm.jsx
@@ -64,19 +64,19 @@ function CourseForm({successCallback, failureCallback, cancelCallback, activeOrg
const validateForm = () => {
let isValid = true
if (!courseTitle) {
- setTitleHelperText("Title is required");
+ setTitleHelperText(localeMessages["title_required_helper_text"]);
isValid = false;
} else {
setTitleHelperText("");
}
if (!courseSlug) {
- setSlugHelperText("Slug is required");
+ setSlugHelperText(localeMessages["slug_required_helper_text"]);
isValid = false;
} else {
setSlugHelperText("");
}
if (!courseDescription) {
- setDescriptionHelperText("Description is required");
+ setDescriptionHelperText(localeMessages["description_required_helper_text"]);
isValid = false;
} else {
setDescriptionHelperText("");
@@ -173,18 +173,14 @@ function CourseForm({successCallback, failureCallback, cancelCallback, activeOrg
return (
{ errorMessage && {errorMessage} }
- setCourseTitle(e.target.value)} />
- setCourseSlug(e.target.value)} {...(!createMode ? { disabled: true } : {})} />
+ setCourseTitle(e.target.value)} />
+ setCourseSlug(e.target.value)} {...(!createMode ? { disabled: true } : {})} />
- setCourseDescription(e.target.value)} />
+ setCourseDescription(e.target.value)} />
switchImapConnection()} checked={addImapConnection} />}
- label="Add IMAP connection" sx={{ m: 0 }} />
-
+ control={ switchImapConnection()} checked={addImapConnection} dir={direction} />}
+ label={localeMessages["add_imap_connection"]} sx={{ m: 0 }} />
+
@@ -200,8 +196,8 @@ function CourseForm({successCallback, failureCallback, cancelCallback, activeOrg
}
Cancel
- { createMode && handleCreateCourse()} sx={{ boxShadow: 'none' }}>Create Course }
- { !createMode && handleUpdateCourse()} sx={{ boxShadow: 'none' }}>Update Course }
+ { createMode && handleCreateCourse()} sx={{ boxShadow: 'none' }}>{localeMessages["create"]} }
+ { !createMode && handleUpdateCourse()} sx={{ boxShadow: 'none' }}>{localeMessages["update"]} }
);
}
diff --git a/frontend/platform/courses/components/CreateImapForm.jsx b/frontend/platform/courses/components/CreateImapForm.jsx
index 194ce894..ffee1ef5 100644
--- a/frontend/platform/courses/components/CreateImapForm.jsx
+++ b/frontend/platform/courses/components/CreateImapForm.jsx
@@ -68,31 +68,31 @@ const CreateImapForm = ({ onSuccess, activeOrganizationId }) => {
const validateForm = () => {
let isValid = true;
if (!email) {
- setEmailHelperText("Email is required");
+ setEmailHelperText(localeMessages["email_required_helper_text"]);
isValid = false;
} else if (isValidEmail(email) === false) {
- setEmailHelperText("Email is invalid");
+ setEmailHelperText(localeMessages["invalid_email_helper_text"]);
isValid = false;
} else {
setEmailHelperText("");
}
if (!password) {
- setPasswordHelperText("Password is required");
+ setPasswordHelperText(localeMessages["password_required_helper_text"]);
isValid = false;
} else {
setPasswordHelperText("");
}
if (!server) {
- setServerHelperText("Server is required");
+ setServerHelperText(localeMessages["server_required_helper_text"]);
isValid = false;
} else {
setServerHelperText("");
}
if (!port) {
- setPortHelperText("Port is required");
+ setPortHelperText(localeMessages["port_required_helper_text"]);
isValid = false;
} else if (isNaN(port) || parseInt(port) <= 0) {
- setPortHelperText("Port must be a positive number");
+ setPortHelperText(localeMessages["invalid_port_helper_text"]);
isValid = false;
} else {
setPortHelperText("");
@@ -102,13 +102,13 @@ const CreateImapForm = ({ onSuccess, activeOrganizationId }) => {
return (<>
{ errorMessage && {errorMessage} }
- setEmail(e.target.value)} />
- setPassword(e.target.value)} />
- setServer(e.target.value)} />
- setPort(e.target.value)} />
+ setEmail(e.target.value)} />
+ setPassword(e.target.value)} />
+ setServer(e.target.value)} />
+ setPort(e.target.value)} />
handleCreateImap()} sx={{ boxShadow: 'none' }}>
- Add
+ {localeMessages["add"]}
>)
diff --git a/frontend/platform/courses/components/DeleteCoursePopup.jsx b/frontend/platform/courses/components/DeleteCoursePopup.jsx
index ba3e9e31..b1bef925 100644
--- a/frontend/platform/courses/components/DeleteCoursePopup.jsx
+++ b/frontend/platform/courses/components/DeleteCoursePopup.jsx
@@ -47,19 +47,19 @@ const DeleteCoursePopup = ({ courseId, courseTitle, handleClose, handleSuccess})
- {`Delete ${courseTitle} Course`}
+ {localeMessages["delete_course"].replace("COURSE_NAME", courseTitle)}
- { `Are you sure you want to delete the course "${courseTitle}"?` }
+ { localeMessages["course_delete_confirmation"].replace("COURSE_NAME", courseTitle) }
- Cancel
+ {localeMessages["cancel"]}
- Delete
+ {localeMessages["delete"]}
>;
}
diff --git a/frontend/platform/courses/components/EnableCourseSwitchPopup.jsx b/frontend/platform/courses/components/EnableCourseSwitchPopup.jsx
index 9ae52623..64a7eebe 100644
--- a/frontend/platform/courses/components/EnableCourseSwitchPopup.jsx
+++ b/frontend/platform/courses/components/EnableCourseSwitchPopup.jsx
@@ -27,17 +27,17 @@ const EnableCourseSwitchPopup = ({ courseId, action, courseTitle, handleClose, h
}
return <>
- {`${action.charAt(0).toUpperCase() + action.slice(1)} ${courseTitle} Course`}
+ {localeMessages[`${action}_course`].replace('COURSE_NAME', courseTitle)}
- { `Are you sure you want to ${action} the course "${courseTitle}"?` }
+ { localeMessages[`course_${action}_confirmation`].replace('COURSE_NAME', courseTitle) }
- Cancel
+ {localeMessages["cancel"]}
- Continue
+ {localeMessages["continue"]}
>;
}
diff --git a/frontend/platform/courses/components/FilterForm.jsx b/frontend/platform/courses/components/FilterForm.jsx
index 82c4146d..a1ea7dab 100644
--- a/frontend/platform/courses/components/FilterForm.jsx
+++ b/frontend/platform/courses/components/FilterForm.jsx
@@ -4,10 +4,10 @@ const FilterForm = ({ onStatusChange }) => {
return (
<>
- Filter
+ {localeMessages["filter"]}
- Course Status:
+ {localeMessages["course_status"]}:
{
const value = event.target.value;
@@ -19,9 +19,9 @@ const FilterForm = ({ onStatusChange }) => {
onStatusChange("?enabled=false");
}
}}>
- } label="All" />
- } label="Enabled" />
- } label="Disabled" />
+ } label={localeMessages["all"]} />
+ } label={localeMessages["enabled"]} />
+ } label={localeMessages["disabled"]} />
>
);
diff --git a/frontend/platform/learners/Learners.jsx b/frontend/platform/learners/Learners.jsx
index 891a0789..7cac3130 100644
--- a/frontend/platform/learners/Learners.jsx
+++ b/frontend/platform/learners/Learners.jsx
@@ -13,7 +13,6 @@ import SchoolIcon from '@mui/icons-material/School';
import BackspaceIcon from '@mui/icons-material/Backspace';
import render from '../../src/render.jsx';
import { getCookie } from '../../src/utils.js';
-import { Title } from '@mui/icons-material';
const apiBaseUrl = localStorage.getItem('apiBaseUrl');
@@ -21,7 +20,7 @@ const apiBaseUrl = localStorage.getItem('apiBaseUrl');
function EnrolmentList({enrollments, selectHandler}) {
if (enrollments.length === 0) {
- return No enrollments found.
+ return {localeMessages["nor_enrollments_found"]}
}
return (
@@ -29,8 +28,8 @@ function EnrolmentList({enrollments, selectHandler}) {
- Course
- Status
+ {localeMessages["course"]}
+ {localeMessages["status"]}
@@ -38,8 +37,8 @@ function EnrolmentList({enrollments, selectHandler}) {
({':hover': {backgroundColor: theme.palette.background.dark, cursor: 'pointer', borderBottomColor: 'secondary.light', borderBottomWidth: 2, borderBottomStyle: 'solid'}})}
onClick={() => selectHandler(enrollment.id)}>
- {enrollment.course_title}
- {enrollment.status}
+ {enrollment.course_title}
+ {localeMessages[enrollment.status]}
))}
@@ -62,13 +61,13 @@ function Learners(initialQs="") {
const [currentPage, setCurrentPage] = useState(1);
const eventMap = {
- 'registered': {icon: , color: "#00bcd4", title: 'Learner Registered'},
- 'verified': {icon: , color: "#66bb6a", title: 'Learner Verified Email'},
- 'content_sent_lesson': {icon: , color: "#00acc1", title: 'Lesson Content Sent'},
- 'content_sent_quiz': {icon: , color: "#26a69a", title: 'Quiz Sent'},
- 'quiz_submitted': {icon: , color: "#26a69a", title: 'Quiz Submitted'},
- 'course_completed': {icon: , color: "#0097a7", title: 'Course Completed'},
- 'deactivated': {icon: , color: "#b71c1c", title: 'Learner Deactivated'},
+ 'registered': {icon: , color: "#00bcd4", title: localeMessages["learner_registered"]},
+ 'verified': {icon: , color: "#66bb6a", title: localeMessages["learner_verified"]},
+ 'content_sent_lesson': {icon: , color: "#00acc1", title: localeMessages["lesson_sent"]},
+ 'content_sent_quiz': {icon: , color: "#26a69a", title: localeMessages["quiz_sent"]},
+ 'quiz_submitted': {icon: , color: "#26a69a", title: localeMessages["quiz_submitted"]},
+ 'course_completed': {icon: , color: "#0097a7", title: localeMessages["course_completed"]},
+ 'deactivated': {icon: , color: "#b71c1c", title: localeMessages["learner_deactivated"]},
};
@@ -86,10 +85,10 @@ function Learners(initialQs="") {
.then(data => {
setDialogContent(
- Enrollment Details
+ {localeMessages["enrollment_details"]}
{data.learner.email}
{data.course.title}
- Enrollment id: {data.id}
+ {localeMessages["enrollment_id"]}: {data.id}
{data.events.map((event, index) => (
@@ -108,19 +107,19 @@ function Learners(initialQs="") {
-
+
{ event.type === "content_sent" ? eventMap["content_sent_"+event.event_data.course_content_type].title : eventMap[event.type].title }
{ event.type === "quiz_submitted" && <>
- Score: {event.event_data.score}
- Result: {event.event_data.is_passed ? "Passed" : "Failed"}
+ {localeMessages["score"]}: {event.event_data.score}
+ {localeMessages["result"]}: {event.event_data.is_passed ? localeMessages["passed"] : localeMessages["failed"]}
>}
{ event.type === "content_sent" && <>
{event.event_data.course_content_title}
>}
{ event.type === "deactivated" && <>
- Reason: {event.event_data.reason}
+ {localeMessages["reason"]}: {event.event_data.reason}
>}
@@ -203,7 +202,7 @@ function Learners(initialQs="") {
return (
@@ -213,8 +212,8 @@ function Learners(initialQs="") {
sx={{ mb: '10px', p: '2px 4px', display: 'flex', alignItems: 'center', width: 400 }}
>
{ if (e.key === 'Enter') { search(); } }}
/>
@@ -227,7 +226,7 @@ function Learners(initialQs="") {
- Learners List
+ {localeMessages["learners_list"]}
diff --git a/frontend/platform/organizations/Organizations.jsx b/frontend/platform/organizations/Organizations.jsx
index 831fab08..9fc1ddbf 100644
--- a/frontend/platform/organizations/Organizations.jsx
+++ b/frontend/platform/organizations/Organizations.jsx
@@ -69,17 +69,19 @@ function Organizations() {
});
}
+ const htmlTag = document.getElementsByTagName("html")[0];
+
const deleteConfirmationDialog = (organization) => {
setDialogContent(
- Confirm Deletion
- Are you sure you want to delete the organization "{organization.name}"? All the courses contents and users under this organization will also be deleted.
+ {localeMessages["confirm_deletion"]}
+ {localeMessages["are_you_sure_delete_org"].replace("ORGANIZATION_NAME", organization.name)}
- setDialogOpen(false)} sx={{ mr: 1 }}>Cancel
+ setDialogOpen(false)} sx={{ mr: 1 }}>{localeMessages["cancel"]}
{
deleteOrganization(organization.id);
setDialogOpen(false);
- }}>Delete
+ }}>{localeMessages["delete"]}
);
@@ -87,10 +89,10 @@ function Organizations() {
}
return (
-
+
- } sx={{ marginBottom: 2 }} onClick={() => {
+ } sx={{ marginBottom: 2 }} onClick={() => {
setDialogContent( );
setDialogOpen(true);
- }}>Add an Organization
+ }}>{localeMessages["add_organization"]}
{ organizations.length > 0 && (
- Name
- Actions
+ {localeMessages["name"]}
+ {localeMessages["actions"]}
{ organizations.map((org) => (
- {org.name}
+ {org.name}
goToUrl(org.public_url)}>
{
diff --git a/frontend/platform/organizations/components/OrganizationForm.jsx b/frontend/platform/organizations/components/OrganizationForm.jsx
index 6f1fb65c..f41da260 100644
--- a/frontend/platform/organizations/components/OrganizationForm.jsx
+++ b/frontend/platform/organizations/components/OrganizationForm.jsx
@@ -137,28 +137,30 @@ function OrganizationForm({ successCallback, failureCallback, cancelCallback, cr
return (
- setName(e.target.value)} />
- setDescription(e.target.value)} />
+ setName(e.target.value)} />
+ setDescription(e.target.value)} />
{ !logoUrl ? }
+ startIcon={ }
+ sx={{ textAlign: direction === 'rtl' ? 'right' : 'left', mt: 2, mb: 2}}
+ dir={direction}
>
- Logo
+ {localeMessages["logo"]}
setLogoFile(event.target.files[0])}
/>
- : (<>
- Remove Logo >
+ : (<>
+ {localeMessages["remove_logo"]} >
)}
- Cancel
+ {localeMessages["cancel"]}
- {createMode ? 'Create' : 'Update'} Organization
+ {createMode ? localeMessages["create"] : localeMessages["update"]}
diff --git a/frontend/public/components/EnrollmentForm.jsx b/frontend/public/components/EnrollmentForm.jsx
index 84f2c8d2..3d13a08b 100644
--- a/frontend/public/components/EnrollmentForm.jsx
+++ b/frontend/public/components/EnrollmentForm.jsx
@@ -13,13 +13,13 @@ const EnrollmentForm = ({course_title, course_slug, organization_id, endpoint, o
const validateForm = () => {
const email = emailRef.current.value;
if (!email) {
- setErrorMessage('Email is required');
+ setErrorMessage(localeMessages["email_required"]);
return false;
}
// Simple email regex for validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
- setErrorMessage('Please enter a valid email address');
+ setErrorMessage(localeMessages["email_invalid"]);
return false;
}
setErrorMessage('');
@@ -54,27 +54,27 @@ const EnrollmentForm = ({course_title, course_slug, organization_id, endpoint, o
})
.catch(error => {
setIsProcessing(false);
- setErrorMessage('Enrollment failed. Please try again.');
+ setErrorMessage(localeMessages['enrollment_failed']);
});
}
}
return (
- Enroll for {course_title}
+ {localeMessages['enrol_for_course'].replace('COURSE_NAME', course_title)}
{ isProcessing ? : <>
{errorMessage && {errorMessage} }
- {
+ {
if (e.key === 'Enter') {
enroll();
}
}} />
-
- Cancel
+
+ {localeMessages['cancel']}
- Submit
+ {localeMessages['submit']}
>}
diff --git a/frontend/public/organization/Organization.jsx b/frontend/public/organization/Organization.jsx
index 8fb38336..e6af29e6 100644
--- a/frontend/public/organization/Organization.jsx
+++ b/frontend/public/organization/Organization.jsx
@@ -45,7 +45,7 @@ function Organization() {
{ organization.name }
- Courses:
+ {localeMessages['courses']}:
{ organization.courses.length > 0 ? (
{ courses.map((course) => (
@@ -53,16 +53,16 @@ function Organization() {
{course.title}
showModalForCourse(course)} disabled={course.enrolled}>
- Enroll Now
+ {localeMessages['enroll_now']}
- { course.enrolled && You are enrolled in this course. }
+ { course.enrolled && {localeMessages['enrollment_success']} }
))}
) : (
- No courses available.
+ {localeMessages['no_courses_available']}
)}
diff --git a/frontend/src/components/Base.jsx b/frontend/src/components/Base.jsx
index 2e13f76a..31c37118 100644
--- a/frontend/src/components/Base.jsx
+++ b/frontend/src/components/Base.jsx
@@ -55,13 +55,12 @@ function Base({breadCrumbList, children, bottomDrawerParams, organizationIdRefre
}, [activeOrganizationId]);
-
return (
<>
({ body: { margin: 0, padding: 0, backgroundColor: theme.palette.background.dark, color: theme.palette.text.primary } })} />
+ sx={{ flexGrow: 1, padding: {sm: 3, xs: 1, md: 5}, width: { md: `calc(100% - ${drawerWidth + 100}px)` }, float: { md: direction === 'rtl' ? 'left' : 'right' } }}>
@@ -91,7 +90,8 @@ function Base({breadCrumbList, children, bottomDrawerParams, organizationIdRefre
sx={{
position: 'fixed',
bottom: 0,
- right: 16,
+ right: direction === 'rtl' ? 'auto' : 16,
+ left: direction === 'rtl' ? 16 : 'auto',
padding: 1,
zIndex: 1000,
backgroundColor: 'transparent'
diff --git a/frontend/src/components/MenuBar.jsx b/frontend/src/components/MenuBar.jsx
index 2895348c..8c8525b9 100644
--- a/frontend/src/components/MenuBar.jsx
+++ b/frontend/src/components/MenuBar.jsx
@@ -83,12 +83,12 @@ function MenuBar({activeOrganizationId, changeOrganizationCallback, showOrganiza
if (localStorage.getItem('isPlatformAdmin') == 'true') {
pages.push(
- { name: 'Organizations', icon: , href: platformBaseUrl + '/organizations/'},
+ { name: localeMessages["organizations"], icon: , href: platformBaseUrl + '/organizations/'},
);
}
- pages.push({ name: 'Course Management', icon: , href: platformBaseUrl + '/courses/' });
- pages.push({ name: 'Learners', icon: , href: platformBaseUrl + '/learners/' });
+ pages.push({ name: localeMessages["course_management"], icon: , href: platformBaseUrl + '/courses/' });
+ pages.push({ name: localeMessages["learners"], icon: , href: platformBaseUrl + '/learners/' });
// pages.push({ name: 'Analytics', icon: , href: platformBaseUrl + '/analytics/' });
@@ -102,7 +102,7 @@ function MenuBar({activeOrganizationId, changeOrganizationCallback, showOrganiza
-
+
@@ -111,7 +111,7 @@ function MenuBar({activeOrganizationId, changeOrganizationCallback, showOrganiza
-
diff --git a/frontend/src/components/RequiredTextField.jsx b/frontend/src/components/RequiredTextField.jsx
index 86f724a7..137ef56b 100644
--- a/frontend/src/components/RequiredTextField.jsx
+++ b/frontend/src/components/RequiredTextField.jsx
@@ -1,8 +1,9 @@
import { TextField } from '@mui/material';
import React from 'react';
-const RequiredTextField = React.forwardRef(({ label, value, onChange, error, helperText, ...props }, ref) => {
+const RequiredTextField = React.forwardRef(({ label, value, onChange, error, helperText, sx: passedSx = {}, ...props }, ref) => {
console.log('RequiredTextField rendered, ref:', ref);
+
return (
);
});