Skip to content

Commit 85b7e4d

Browse files
authored
Merge pull request #170 from AvaCodeSolutions/feat/151/internationalization
feat: #151 Add suuport for i18n translations and rtl
2 parents c9ee15c + d8139ba commit 85b7e4d

45 files changed

Lines changed: 527 additions & 278 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

django_email_learning/personalised/api/views.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
QuestionResponse,
66
)
77
from django_email_learning.services import jwt_service
8+
from django.utils.translation import gettext as _
89
from django_email_learning.models import (
910
ContentDelivery,
1011
QuizSubmission,
@@ -79,7 +80,7 @@ def post(self, request, *args, **kwargs): # type: ignore[no-untyped-def]
7980
delivery.update_hash()
8081

8182
if passed:
82-
message = "Congratulations! You have passed the quiz."
83+
message = _("Congratulations! You have passed the quiz.")
8384
delivery = delivery.schedule_next_delivery()
8485
if not delivery:
8586
enrolment.graduate()
@@ -91,14 +92,18 @@ def post(self, request, *args, **kwargs): # type: ignore[no-untyped-def]
9192
).count()
9293

9394
if failed_submissions_count > 1:
94-
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."
95+
message = _(
96+
"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."
97+
)
9598
logger.info(
9699
f"Learner ID {enrolment.learner.id} has failed the quiz twice for Course {enrolment.course.title}. "
97100
f"Marking enrollment as failed."
98101
)
99102
enrolment.fail()
100103
else:
101-
message = "You have failed the quiz. You will receive another chance to retake it tomorrow."
104+
message = _(
105+
"You have failed the quiz. You will receive another chance to retake it tomorrow."
106+
)
102107
logger.info(
103108
f"Learner ID {enrolment.learner.id} has failed the quiz for Course {enrolment.course.title}. "
104109
f"Scheduling a retry for the next day."

django_email_learning/personalised/views.py

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from django.views import View
22
from django.views.generic.base import TemplateResponseMixin
33
from django.http import HttpResponse
4+
from django.utils.translation import gettext as _
45
from django.urls import reverse
56
from django_email_learning.models import ContentDelivery, EnrollmentStatus
67
from django_email_learning.services import jwt_service
@@ -14,7 +15,11 @@
1415

1516
class ErrrorLoggingMixin(TemplateResponseMixin):
1617
def errr_response(
17-
self, message: str, exception: Exception | None, status_code: int = 500
18+
self,
19+
message: str,
20+
exception: Exception | None,
21+
status_code: int = 500,
22+
title: str = _("Error"),
1823
) -> HttpResponse:
1924
error_ref = uuid.uuid4().hex
2025
if exception:
@@ -26,7 +31,8 @@ def errr_response(
2631
f"{message} - Ref: {error_ref}", extra={"error_ref": error_ref}
2732
)
2833
return self.render_to_response(
29-
context={"ref": error_ref, "error_message": message}, status=status_code
34+
context={"ref": error_ref, "error_message": message, "page_title": title},
35+
status=status_code,
3036
)
3137

3238

@@ -44,18 +50,22 @@ def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-unty
4450
enrolment = delivery.enrollment
4551
if enrolment.status != EnrollmentStatus.ACTIVE:
4652
return self.errr_response(
47-
message="Quiz is not valid anymore",
53+
message=_("Quiz is not valid anymore"),
4854
exception=ValueError("Enrolment is not active"),
55+
title=_("Invalid Quiz"),
4956
)
5057
quiz = delivery.course_content.quiz
5158
if not quiz:
5259
return self.errr_response(
53-
message="No quiz associated with this link", exception=None
60+
message=_("No quiz associated with this link"),
61+
exception=None,
62+
title=_("Invalid Quiz"),
5463
)
5564
if not delivery.course_content.is_published:
5665
return self.errr_response(
57-
message="No valid quiz associated with this link",
66+
message=_("No valid quiz associated with this link"),
5867
exception=ValueError("Quiz is not published"),
68+
title=_("Invalid Quiz"),
5969
)
6070
quiz_data = PublicQuizSerializer.model_validate(quiz).model_dump()
6171
if question_ids:
@@ -75,19 +85,30 @@ def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-unty
7585

7686
except ContentDelivery.DoesNotExist as e:
7787
return self.errr_response(
78-
message="An error occurred while retrieving the quiz", exception=e
88+
message=_("An error occurred while retrieving the quiz"),
89+
exception=e,
90+
title=_("Error"),
7991
)
8092
except KeyError as e:
8193
return self.errr_response(
82-
message="The link is not valid", exception=e, status_code=400
94+
message=_("The link is not valid"),
95+
exception=e,
96+
status_code=400,
97+
title=_("Invalid Link"),
8398
)
8499
except jwt_service.InvalidTokenException as e:
85100
return self.errr_response(
86-
message="The link is not valid", exception=e, status_code=400
101+
message=_("The link is not valid"),
102+
exception=e,
103+
status_code=400,
104+
title=_("Invalid Link"),
87105
)
88106
except jwt_service.ExpiredTokenException as e:
89107
return self.errr_response(
90-
message="The link has expired", exception=e, status_code=410
108+
message=_("The link has expired"),
109+
exception=e,
110+
status_code=410,
111+
title=_("Expired Link"),
91112
)
92113

93114

@@ -99,23 +120,26 @@ def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-unty
99120
token = request.GET["token"]
100121
except KeyError as e:
101122
return self.errr_response(
102-
message="The verification link is not valid.",
123+
message=_("The verification link is not valid."),
103124
exception=e,
104125
status_code=400,
126+
title=_("Invalid Link"),
105127
)
106128
try:
107129
decoded = jwt_service.decode_jwt(token=token)
108130
except jwt_service.InvalidTokenException as e:
109131
return self.errr_response(
110-
message="The verification link is not valid.",
132+
message=_("The verification link is not valid."),
111133
exception=e,
112134
status_code=400,
135+
title=_("Invalid Link"),
113136
)
114137
except jwt_service.ExpiredTokenException as e:
115138
return self.errr_response(
116-
message="The verification link has expired.",
139+
message=_("The verification link has expired."),
117140
exception=e,
118141
status_code=410,
142+
title=_("Expired Link"),
119143
)
120144

121145
enrollment_id = decoded["enrollment_id"]
@@ -130,7 +154,9 @@ def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-unty
130154
command.execute()
131155
except Exception as e:
132156
return self.errr_response(
133-
message="An error occurred during enrollment verification.", exception=e
157+
message=_("An error occurred during enrollment verification."),
158+
exception=e,
159+
title=_("Verification Error"),
134160
)
135161

136-
return self.render_to_response(context={"page_title": "Enrollment Verified"})
162+
return self.render_to_response(context={"page_title": _("Enrollment Verified")})

django_email_learning/platform/views.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from django.views.generic import TemplateView
33
from django.contrib.auth.decorators import login_required
44
from django.utils.decorators import method_decorator
5+
from django.utils.translation import gettext as _
56
from django.urls import reverse
67
from django_email_learning.models import Organization, OrganizationUser, Course
78
from django_email_learning.decorators import (
@@ -70,7 +71,7 @@ class Courses(BasePlatformView):
7071

7172
def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
7273
context = super().get_context_data(**kwargs)
73-
context["page_title"] = "Courses"
74+
context["page_title"] = _("Courses")
7475
return context
7576

7677

@@ -83,7 +84,7 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
8384
context = super().get_context_data(**kwargs)
8485
course = Course.objects.get(pk=self.kwargs["course_id"])
8586
context["course"] = course
86-
context["page_title"] = f"Course: {course.title}"
87+
context["page_title"] = _("Course: %(title)s") % {"title": course.title}
8788
return context
8889

8990

@@ -94,7 +95,7 @@ class Organizations(BasePlatformView):
9495

9596
def get_context_data(self, **kwargs): # type: ignore[no-untyped-def]
9697
context = super().get_context_data(**kwargs)
97-
context["page_title"] = "Organizations"
98+
context["page_title"] = _("Organizations")
9899
return context
99100

100101

@@ -105,5 +106,5 @@ class Learners(BasePlatformView):
105106

106107
def get_context_data(self, **kwargs): # type: ignore[no-untyped-def]
107108
context = super().get_context_data(**kwargs)
108-
context["page_title"] = "Learners"
109+
context["page_title"] = _("Learners")
109110
return context

django_email_learning/public/views.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django_email_learning.models import Organization, Course
44
from django.http import Http404
55
from django.urls import reverse
6+
from django.utils.translation import gettext as _
67
from django.conf import settings
78
from django_email_learning.public.serializers import (
89
OrganizationSerializer,
@@ -29,7 +30,7 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
2930
if organization_details.exists():
3031
organization = organization_details.first()
3132
if not organization:
32-
raise Http404("Organization does not exist")
33+
raise Http404(_("Organization does not exist"))
3334
courses = []
3435
for course in organization.courses:
3536
course_data = PublicCourseSerializer(
@@ -59,4 +60,4 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
5960
return context
6061

6162
# If organization not found, raise 404
62-
raise Http404("Organization does not exist")
63+
raise Http404(_("Organization does not exist"))

django_email_learning/services/command_models/enroll_command.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from django_email_learning.services import jwt_service
1717
from django.core.mail import EmailMultiAlternatives
1818
from django.template.loader import render_to_string
19+
from django.utils.translation import gettext as _
1920
from django.conf import settings
2021
from django.urls import reverse
2122
from typing import Literal
@@ -106,7 +107,7 @@ def execute(self) -> None:
106107
else None,
107108
}
108109
email_service = EmailSenderService()
109-
subject = "Verify your enrollment"
110+
subject = _("Verify your enrollment")
110111
body = render_to_string(
111112
"emails/enrolment_verification.txt",
112113
template_context,

django_email_learning/services/command_models/verify_enrollment_command.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from django_email_learning.services.email_sender_service import EmailSenderService
1414
from django.core.mail import EmailMultiAlternatives
1515
from django.template.loader import render_to_string
16+
from django.utils.translation import gettext as _
1617
from typing import Literal
1718

1819

@@ -59,7 +60,7 @@ def execute(self) -> None:
5960

6061
# Send confirmation email
6162
email_service = EmailSenderService()
62-
subject = "Enrollment Verified"
63+
subject = _("Enrollment Verified")
6364
body = render_to_string(
6465
"emails/enrollment_verified.txt",
6566
{

django_email_learning/templates/emails/base.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{% load i18n %}
12
{% with brand_color="#7c86ff" %}
23
<html>
34
<head>
@@ -24,6 +25,16 @@
2425
background-color: #f9f9f9;
2526
}
2627

28+
.locale {
29+
{% get_current_language_bidi as IS_RTL %}
30+
{% if IS_RTL %}
31+
direction: rtl;
32+
text-align: right;
33+
{% else %}
34+
direction: ltr;
35+
text-align: left;
36+
{% endif %}
37+
}
2738

2839
@media screen and (max-width: 480px) {
2940
.mobile-content {
@@ -56,7 +67,7 @@
5667
</head>
5768
<body>
5869
<div class="background">
59-
<div class="mobile-content" style="background-color: #ffffff">
70+
<div class="mobile-content locale" style="background-color: #ffffff">
6071
{% block content %}{% endblock %}
6172
</div>
6273
</div>
Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
{% extends "emails/base.html" %}
2+
{% load i18n %}
23

34
{% block content %}
4-
<P>Hello there,<br /></P>
5+
<P>{% translate "Hello there," %}<br /></P>
56

6-
<p><strong>Congratulations!</strong> Your enrollment for "<strong style="color: {{ brand_color }};">{{ course_title }}</strong>" has been successfully verified and confirmed.</p>
7+
<p>{% blocktranslate %}Congratulations! Your enrollment for <strong style="color: {{ brand_color }}">{{ course_title }}</strong> has been successfully verified and confirmed.{% endblocktranslate %}</p>
78

8-
<p>We're excited to have you join our learning community. Here's what you can expect:</p>
9+
<p>{% translate "We're excited to have you join our learning community. Here's what you can expect:" %}</p>
910

1011
<ul>
11-
<li>Course materials will be delivered directly to this email address</li>
12-
<li>You'll receive structured lessons and assignments on a regular schedule</li>
13-
<li>Interactive content and assessments will help track your progress</li>
12+
<li>{% translate "Course materials will be delivered directly to this email address" %}</li>
13+
<li>{% translate "You'll receive structured lessons and assignments on a regular schedule" %}</li>
14+
<li>{% translate "Interactive content and assessments will help track your progress" %}</li>
1415
</ul>
1516

16-
<p>Your learning adventure begins soon! Keep an eye on your inbox for the first lesson and welcome materials.</p>
17+
<p>{% translate "Your learning adventure begins soon! Keep an eye on your inbox for the first lesson and welcome materials." %}</p>
1718

18-
<h3>Welcome aboard!</h3>
19+
<h3>{% translate "Welcome aboard!" %}</h3>
1920

20-
<p>Best regards,<br>
21-
The <strong>{{ organization_name }}</strong> Team</p>
21+
<p>{% translate "Best regards," %}<br>
22+
{% blocktranslate %}The {{ organization_name }} Team{% endblocktranslate %}</p>
2223
{% endblock %}
Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
Hello there,
1+
{% load i18n %}
2+
{% translate "Hello there" %},
23

3-
Congratulations! Your enrollment for "{{ course_title }}" has been successfully verified and confirmed.
4+
{% blocktranslate %}Congratulations! Your enrollment for "{{ course_title }}" has been successfully verified and confirmed.{% endblocktranslate %}
45

5-
We're excited to have you join our learning community. Here's what you can expect:
6+
{% translate "We're excited to have you join our learning community. Here's what you can expect:" %}
67

7-
• Course materials will be delivered directly to this email address
8-
• You'll receive structured lessons and assignments on a regular schedule
9-
• Interactive content and assessments will help track your progress
8+
{% translate "Course materials will be delivered directly to this email address" %}
9+
{% translate "You'll receive structured lessons and assignments on a regular schedule" %}
10+
{% translate "Interactive content and assessments will help track your progress" %}
1011

11-
Your learning adventure begins soon! Keep an eye on your inbox for the first lesson and welcome materials.
12+
{% translate "Your learning adventure begins soon! Keep an eye on your inbox for the first lesson and welcome materials." %}
1213

13-
Welcome aboard!
14+
{% translate "Welcome aboard!" %}
1415

15-
Best regards,
16-
The {{ organization_name }} Team
16+
{% translate "Best regards," %}
17+
{% blocktranslate %}The {{ organization_name }} Team{% endblocktranslate %}

0 commit comments

Comments
 (0)