mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-03 04:25:24 +02:00
feat: Internationalize sent emails (#3818)
This commit is contained in:
parent
c205dff523
commit
60c33b499c
7 changed files with 78 additions and 31 deletions
|
@ -7,7 +7,7 @@
|
|||
"recipe-defaults": {
|
||||
"ingredient-note": "1 Cup Flour",
|
||||
"step-text": "Recipe steps as well as other fields in the recipe page support markdown syntax.\n\n**Add a link**\n\n[My Link](https://demo.mealie.io)\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mealplan": {
|
||||
"no-recipes-match-your-rules": "No recipes match your rules"
|
||||
|
@ -44,5 +44,28 @@
|
|||
"second": "second|seconds",
|
||||
"millisecond": "millisecond|milliseconds",
|
||||
"microsecond": "microsecond|microseconds"
|
||||
},
|
||||
"emails": {
|
||||
"password": {
|
||||
"subject": "Mealie Forgot Password",
|
||||
"header_text": "Forgot Password",
|
||||
"message_top": "You have requested to reset your password.",
|
||||
"message_bottom": "Please click the button above to reset your password.",
|
||||
"button_text": "Reset Password"
|
||||
},
|
||||
"invitation": {
|
||||
"subject": "Invitation to join Mealie",
|
||||
"header_text": "You're Invited!",
|
||||
"message_top": "You have been invited to join Mealie.",
|
||||
"message_bottom": "Please click the button above to accept the invitation.",
|
||||
"button_text": "Accept Invitation"
|
||||
},
|
||||
"test": {
|
||||
"subject": "Mealie Test Email",
|
||||
"header_text": "Test Email",
|
||||
"message_top": "This is a test email.",
|
||||
"message_bottom": "Please click the button above to test the email.",
|
||||
"button_text": "Open Mealie"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from fastapi import APIRouter
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Header
|
||||
|
||||
from mealie.routes._base import BaseAdminController, controller
|
||||
from mealie.schema.admin.email import EmailReady, EmailSuccess, EmailTest
|
||||
|
@ -15,8 +17,12 @@ class AdminEmailController(BaseAdminController):
|
|||
return EmailReady(ready=self.settings.SMTP_ENABLE)
|
||||
|
||||
@router.post("", response_model=EmailSuccess)
|
||||
async def send_test_email(self, data: EmailTest):
|
||||
service = EmailService()
|
||||
async def send_test_email(
|
||||
self,
|
||||
data: EmailTest,
|
||||
accept_language: Annotated[str | None, Header()] = None,
|
||||
):
|
||||
service = EmailService(locale=accept_language)
|
||||
status = False
|
||||
error = None
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from fastapi import APIRouter, HTTPException, status
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Header, HTTPException, status
|
||||
|
||||
from mealie.core.security import url_safe_token
|
||||
from mealie.routes._base import BaseUserController, controller
|
||||
|
@ -23,14 +25,21 @@ class GroupInvitationsController(BaseUserController):
|
|||
@router.post("", response_model=ReadInviteToken, status_code=status.HTTP_201_CREATED)
|
||||
def create_invite_token(self, uses: CreateInviteToken):
|
||||
if not self.user.can_invite:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="User is not allowed to create invite tokens")
|
||||
raise HTTPException(
|
||||
status.HTTP_403_FORBIDDEN,
|
||||
detail="User is not allowed to create invite tokens",
|
||||
)
|
||||
|
||||
token = SaveInviteToken(uses_left=uses.uses, group_id=self.group_id, token=url_safe_token())
|
||||
return self.repos.group_invite_tokens.create(token)
|
||||
|
||||
@router.post("/email", response_model=EmailInitationResponse)
|
||||
def email_invitation(self, invite: EmailInvitation):
|
||||
email_service = EmailService()
|
||||
def email_invitation(
|
||||
self,
|
||||
invite: EmailInvitation,
|
||||
accept_language: Annotated[str | None, Header()] = None,
|
||||
):
|
||||
email_service = EmailService(locale=accept_language)
|
||||
url = f"{self.settings.BASE_URL}/register?token={invite.token}"
|
||||
|
||||
success = False
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from fastapi import APIRouter, Depends
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Header
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.db_setup import generate_session
|
||||
|
@ -9,10 +11,14 @@ router = APIRouter(prefix="")
|
|||
|
||||
|
||||
@router.post("/forgot-password")
|
||||
def forgot_password(email: ForgotPassword, session: Session = Depends(generate_session)):
|
||||
def forgot_password(
|
||||
email: ForgotPassword,
|
||||
session: Session = Depends(generate_session),
|
||||
accept_language: Annotated[str | None, Header()] = None,
|
||||
):
|
||||
"""Sends an email with a reset link to the user"""
|
||||
f_service = PasswordResetService(session)
|
||||
return f_service.send_reset_email(email.email)
|
||||
return f_service.send_reset_email(email.email, accept_language)
|
||||
|
||||
|
||||
@router.post("/reset-password")
|
||||
|
|
|
@ -4,6 +4,8 @@ from jinja2 import Template
|
|||
from pydantic import BaseModel
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.lang import local_provider
|
||||
from mealie.lang.providers import Translator
|
||||
from mealie.services._base_service import BaseService
|
||||
|
||||
from .email_senders import ABCEmailSender, DefaultEmailSender
|
||||
|
@ -28,10 +30,11 @@ class EmailTemplate(BaseModel):
|
|||
|
||||
|
||||
class EmailService(BaseService):
|
||||
def __init__(self, sender: ABCEmailSender | None = None) -> None:
|
||||
def __init__(self, sender: ABCEmailSender | None = None, locale: str | None = None) -> None:
|
||||
self.templates_dir = CWD / "templates"
|
||||
self.default_template = self.templates_dir / "default.html"
|
||||
self.sender: ABCEmailSender = sender or DefaultEmailSender()
|
||||
self.translator: Translator = local_provider(locale)
|
||||
|
||||
super().__init__()
|
||||
|
||||
|
@ -43,33 +46,33 @@ class EmailService(BaseService):
|
|||
|
||||
def send_forgot_password(self, address: str, reset_password_url: str) -> bool:
|
||||
forgot_password = EmailTemplate(
|
||||
subject="Mealie Forgot Password",
|
||||
header_text="Forgot Password",
|
||||
message_top="You have requested to reset your password.",
|
||||
message_bottom="Please click the button above to reset your password.",
|
||||
subject=self.translator.t("emails.password.subject"),
|
||||
header_text=self.translator.t("emails.password.header_text"),
|
||||
message_top=self.translator.t("emails.password.message_top"),
|
||||
message_bottom=self.translator.t("emails.password.message_bottom"),
|
||||
button_link=reset_password_url,
|
||||
button_text="Reset Password",
|
||||
button_text=self.translator.t("emails.password.button_text"),
|
||||
)
|
||||
return self.send_email(address, forgot_password)
|
||||
|
||||
def send_invitation(self, address: str, invitation_url: str) -> bool:
|
||||
invitation = EmailTemplate(
|
||||
subject="Invitation to join Mealie",
|
||||
header_text="You're Invited!",
|
||||
message_top="You have been invited to join Mealie.",
|
||||
message_bottom="Please click the button above to accept the invitation.",
|
||||
subject=self.translator.t("emails.invitation.subject"),
|
||||
header_text=self.translator.t("emails.invitation.header_text"),
|
||||
message_top=self.translator.t("emails.invitation.message_top"),
|
||||
message_bottom=self.translator.t("emails.invitation.message_bottom"),
|
||||
button_link=invitation_url,
|
||||
button_text="Accept Invitation",
|
||||
button_text=self.translator.t("emails.invitation.button_text"),
|
||||
)
|
||||
return self.send_email(address, invitation)
|
||||
|
||||
def send_test_email(self, address: str) -> bool:
|
||||
test_email = EmailTemplate(
|
||||
subject="Test Email",
|
||||
header_text="Test Email",
|
||||
message_top="This is a test email.",
|
||||
message_bottom="Please click the button above to test the email.",
|
||||
button_link="https://www.google.com",
|
||||
button_text="Test Email",
|
||||
subject=self.translator.t("emails.test.subject"),
|
||||
header_text=self.translator.t("emails.test.header_text"),
|
||||
message_top=self.translator.t("emails.test.message_top"),
|
||||
message_bottom=self.translator.t("emails.test.message_bottom"),
|
||||
button_link=self.settings.BASE_URL,
|
||||
button_text=self.translator.t("emails.test.button_text"),
|
||||
)
|
||||
return self.send_email(address, test_email)
|
||||
|
|
|
@ -32,14 +32,14 @@ class PasswordResetService(BaseService):
|
|||
|
||||
return self.db.tokens_pw_reset.create(save_token)
|
||||
|
||||
def send_reset_email(self, email: str):
|
||||
def send_reset_email(self, email: str, accept_language: str | None = None):
|
||||
token_entry = self.generate_reset_token(email)
|
||||
|
||||
if token_entry is None:
|
||||
return None
|
||||
|
||||
# Send Email
|
||||
email_servive = EmailService()
|
||||
email_servive = EmailService(locale=accept_language)
|
||||
reset_url = f"{self.settings.BASE_URL}/reset-password/?token={token_entry.token}"
|
||||
|
||||
try:
|
||||
|
|
|
@ -6,7 +6,7 @@ from mealie.services.email.email_senders import ABCEmailSender
|
|||
|
||||
FAKE_ADDRESS = "my_secret_email@example.com"
|
||||
|
||||
SUBJECTS = {"Mealie Forgot Password", "Invitation to join Mealie", "Test Email"}
|
||||
SUBJECTS = {"Mealie Forgot Password", "Invitation to join Mealie", "Mealie Test Email"}
|
||||
|
||||
|
||||
class TestEmailSender(ABCEmailSender):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue