Skip to content

Commit

Permalink
Allow email localization (fixes #500)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nutomic authored and dessalines committed Mar 24, 2022
1 parent 01cdac0 commit bf7d2ca
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 77 deletions.
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 3 additions & 9 deletions crates/api/src/local_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,17 +189,11 @@ impl Perform for SaveUserSettings {
let email = diesel_option_overwrite(&email_deref);

if let Some(Some(email)) = &email {
let previous_email = local_user_view.local_user.email.unwrap_or_default();
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
// Only send the verification email if there was an email change
if previous_email.ne(email) {
send_verification_email(
local_user_view.local_user.id,
email,
&local_user_view.person.name,
context.pool(),
&context.settings(),
)
.await?;
send_verification_email(&local_user_view, email, context.pool(), &context.settings())
.await?;
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/api_common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ serde_json = { version = "1.0.72", features = ["preserve_order"] }
tracing = "0.1.29"
url = "2.2.2"
itertools = "0.10.3"
rosetta-i18n = "0.1"
93 changes: 41 additions & 52 deletions crates/api_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ use lemmy_db_views_actor::{
};
use lemmy_utils::{
claims::Claims,
email::send_email,
email::{send_email, translations::Lang},
settings::structs::{FederationConfig, Settings},
utils::generate_random_string,
LemmyError,
Sensitive,
};
use rosetta_i18n::{Language, LanguageId};
use tracing::warn;
use url::Url;

pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
Expand Down Expand Up @@ -363,42 +365,30 @@ pub fn honeypot_check(honeypot: &Option<String>) -> Result<(), LemmyError> {

pub fn send_email_to_user(
local_user_view: &LocalUserView,
subject_text: &str,
body_text: &str,
comment_content: &str,
subject: &str,
body: &str,
settings: &Settings,
) {
if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email {
return;
}

if let Some(user_email) = &local_user_view.local_user.email {
let subject = &format!(
"{} - {} {}",
subject_text, settings.hostname, local_user_view.person.name,
);
let html = &format!(
"<h1>{}</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
body_text,
local_user_view.person.name,
comment_content,
settings.get_protocol_and_hostname()
);
match send_email(
subject,
user_email,
&local_user_view.person.name,
html,
body,
settings,
) {
Ok(_o) => _o,
Err(e) => tracing::error!("{}", e),
Err(e) => warn!("{}", e),
};
}
}

pub async fn send_password_reset_email(
local_user_view: &LocalUserView,
user: &LocalUserView,
pool: &DbPool,
settings: &Settings,
) -> Result<(), LemmyError> {
Expand All @@ -407,29 +397,30 @@ pub async fn send_password_reset_email(

// Insert the row
let token2 = token.clone();
let local_user_id = local_user_view.local_user.id;
let local_user_id = user.local_user.id;
blocking(pool, move |conn| {
PasswordResetRequest::create_token(conn, local_user_id, &token2)
})
.await??;

let email = &local_user_view.local_user.email.to_owned().expect("email");
let subject = &format!("Password reset for {}", local_user_view.person.name);
let email = &user.local_user.email.to_owned().expect("email");
let lang = get_user_lang(user);
let subject = &lang.password_reset_subject(&user.person.name);
let protocol_and_hostname = settings.get_protocol_and_hostname();
let html = &format!("<h1>Password Reset Request for {}</h1><br><a href={}/password_change/{}>Click here to reset your password</a>", local_user_view.person.name, protocol_and_hostname, &token);
send_email(subject, email, &local_user_view.person.name, html, settings)
let reset_link = format!("{}/password_change/{}", protocol_and_hostname, &token);
let body = &lang.password_reset_body(&user.person.name, reset_link);
send_email(subject, email, &user.person.name, body, settings)
}

/// Send a verification email
pub async fn send_verification_email(
local_user_id: LocalUserId,
user: &LocalUserView,
new_email: &str,
username: &str,
pool: &DbPool,
settings: &Settings,
) -> Result<(), LemmyError> {
let form = EmailVerificationForm {
local_user_id,
local_user_id: user.local_user.id,
email: new_email.to_string(),
verification_token: generate_random_string(),
};
Expand All @@ -440,44 +431,42 @@ pub async fn send_verification_email(
);
blocking(pool, move |conn| EmailVerification::create(conn, &form)).await??;

let subject = format!("Verify your email address for {}", settings.hostname);
let body = format!(
concat!(
"Please click the link below to verify your email address ",
"for the account @{}@{}. Ignore this email if the account isn't yours.<br><br>",
"<a href=\"{}\">Verify your email</a>"
),
username, settings.hostname, verify_link
);
send_email(&subject, new_email, username, &body, settings)?;
let lang = get_user_lang(user);
let subject = lang.verify_email_subject(&settings.hostname);
let body = lang.verify_email_body(&user.person.name, &settings.hostname, verify_link);
send_email(&subject, new_email, &user.person.name, &body, settings)?;

Ok(())
}

pub fn send_email_verification_success(
local_user_view: &LocalUserView,
user: &LocalUserView,
settings: &Settings,
) -> Result<(), LemmyError> {
let email = &local_user_view.local_user.email.to_owned().expect("email");
let subject = &format!("Email verified for {}", local_user_view.person.actor_id);
let html = "Your email has been verified.";
send_email(subject, email, &local_user_view.person.name, html, settings)
let email = &user.local_user.email.to_owned().expect("email");
let lang = get_user_lang(user);
let subject = &lang.email_verified_subject(&user.person.actor_id);
let body = &lang.email_verified_body();
send_email(subject, email, &user.person.name, body, settings)
}

pub fn get_user_lang(user: &LocalUserView) -> Lang {
let user_lang = LanguageId::new(user.local_user.lang.clone());
Lang::from_language_id(&user_lang).unwrap_or_else(|| {
let en = LanguageId::new("en");
Lang::from_language_id(&en).expect("default language")
})
}

pub fn send_application_approved_email(
local_user_view: &LocalUserView,
user: &LocalUserView,
settings: &Settings,
) -> Result<(), LemmyError> {
let email = &local_user_view.local_user.email.to_owned().expect("email");
let subject = &format!(
"Registration approved for {}",
local_user_view.person.actor_id
);
let html = &format!(
"Your registration application has been approved. Welcome to {}!",
settings.hostname
);
send_email(subject, email, &local_user_view.person.name, html, settings)
let email = &user.local_user.email.to_owned().expect("email");
let lang = get_user_lang(user);
let subject = lang.registration_approved_subject(&user.person.actor_id);
let body = lang.registration_approved_body(&settings.hostname);
send_email(&subject, email, &user.person.name, &body, settings)
}

pub async fn check_registration_application(
Expand Down
12 changes: 9 additions & 3 deletions crates/api_crud/src/private_message/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use lemmy_api_common::{
blocking,
check_person_block,
get_local_user_view_from_jwt,
get_user_lang,
person::{CreatePrivateMessage, PrivateMessageResponse},
send_email_to_user,
};
Expand Down Expand Up @@ -106,11 +107,16 @@ impl PerformCrud for CreatePrivateMessage {
LocalUserView::read_person(conn, recipient_id)
})
.await??;
let lang = get_user_lang(&local_recipient);
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
send_email_to_user(
&local_recipient,
"Private Message from",
"Private Message",
&content_slurs_removed,
&lang.notification_mentioned_by_subject(&local_recipient.person.name),
&lang.notification_mentioned_by_body(
&local_recipient.person.name,
&content_slurs_removed,
&inbox_link,
),
&context.settings(),
);
}
Expand Down
26 changes: 22 additions & 4 deletions crates/api_crud/src/user/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use lemmy_apub::{
EndpointType,
};
use lemmy_db_schema::{
aggregates::person_aggregates::PersonAggregates,
newtypes::CommunityId,
source::{
community::{
Expand All @@ -32,6 +33,7 @@ use lemmy_db_schema::{
},
traits::{Crud, Followable, Joinable},
};
use lemmy_db_views::local_user_view::LocalUserView;
use lemmy_db_views_actor::person_view::PersonViewSafe;
use lemmy_utils::{
apub::generate_actor_keypair,
Expand Down Expand Up @@ -272,11 +274,27 @@ impl PerformCrud for Register {
);
} else {
if email_verification {
let local_user_view = LocalUserView {
local_user: inserted_local_user,
person: inserted_person,
counts: PersonAggregates {
id: 0,
person_id: Default::default(),
post_count: 0,
post_score: 0,
comment_count: 0,
comment_score: 0,
},
};
// we check at the beginning of this method that email is set
let email = local_user_view
.local_user
.email
.clone()
.expect("email was provided");
send_verification_email(
inserted_local_user.id,
// we check at the beginning of this method that email is set
&inserted_local_user.email.expect("email was provided"),
&inserted_person.name,
&local_user_view,
&email,
context.pool(),
&context.settings(),
)
Expand Down
4 changes: 4 additions & 0 deletions crates/utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@ doku = "0.10.2"
uuid = { version = "0.8.2", features = ["serde", "v4"] }
encoding = "0.2.33"
html2text = "0.2.1"
rosetta-i18n = "0.1"

[build-dependencies]
rosetta-build = "0.1"
8 changes: 8 additions & 0 deletions crates/utils/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
rosetta_build::config()
.source("en", "translations/en.json")
.fallback("en")
.generate()?;

Ok(())
}
4 changes: 4 additions & 0 deletions crates/utils/src/email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ use lettre::{
use std::str::FromStr;
use uuid::Uuid;

pub mod translations {
rosetta_i18n::include_translations!();
}

pub fn send_email(
subject: &str,
to_email: &str,
Expand Down
18 changes: 18 additions & 0 deletions crates/utils/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"registration_approved_subject": "Registration approved for {username}",
"registration_approved_body": "Your registration application has been approved. Welcome to {hostname}!",
"password_reset_subject": "Password reset for {username}",
"password_reset_body": "<h1>Password Reset Request for {username}</h1><br><a href={reset_link}>Click here to reset your password</a>",
"verify_email_subject": "Verify your email address for {hostname}",
"verify_email_body": "Please click the link below to verify your email address for the account @{username}@{hostname}. Ignore this email if the account isn't yours.<br><br>, <a href=\"{verify_link}\">Verify your email</a>",
"email_verified_subject": "Email verified for {username}",
"email_verified_body": "Your email has been verified.",
"notification_post_reply_subject": "Reply from {username}",
"notification_post_reply_body": "<h1>Post reply</h1><br><div>{username} - {comment_text}</div><br><a href={inbox_link}>inbox</a>",
"notification_comment_reply_subject": "Reply from {username}",
"notification_comment_reply_body": "<h1>Comment reply</h1><br><div>{username} - {comment_text}</div><br><a href={inbox_link}>inbox</a>",
"notification_mentioned_by_subject": "Mentioned by {username}",
"notification_mentioned_by_body": "<h1>Person Mention</h1><br><div>{username} - {comment_text}</div><br><a href={inbox_link}>inbox</a>",
"notification_private_message_subject": "Private message from {username}",
"notification_private_message_body": "<h1>Private message</h1><br><div>{username} - {message_text}</div><br><a href={inbox_link}>inbox</a>"
}
Loading

0 comments on commit bf7d2ca

Please sign in to comment.