mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-21 20:01:18 +00:00
* Allow email localization (fixes #500) * add PersonAggregates::default() * add lemmy-translations submodule * fix gitmodules
This commit is contained in:
parent
ecd157d4a7
commit
cb44b14717
14 changed files with 133 additions and 78 deletions
|
@ -14,6 +14,8 @@ steps:
|
|||
commands:
|
||||
- chown 1000:1000 . -R
|
||||
- git fetch --tags
|
||||
- git submodule init
|
||||
- git submodule update --recursive --remote
|
||||
|
||||
- name: check formatting
|
||||
image: rustdocker/rust:nightly
|
||||
|
|
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
[submodule "crates/utils/translations"]
|
||||
path = crates/utils/translations
|
||||
url = https://github.com/LemmyNet/lemmy-translations.git
|
||||
branch = main
|
29
Cargo.lock
generated
29
Cargo.lock
generated
|
@ -1884,6 +1884,7 @@ dependencies = [
|
|||
"lemmy_db_views_actor",
|
||||
"lemmy_db_views_moderator",
|
||||
"lemmy_utils",
|
||||
"rosetta-i18n",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
|
@ -2170,6 +2171,8 @@ dependencies = [
|
|||
"regex",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"rosetta-build",
|
||||
"rosetta-i18n",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smart-default",
|
||||
|
@ -3368,6 +3371,26 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosetta-build"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f697b8b3f19bee20f30dc87213d05ce091c43bc733ab1bfc98b0e5cdd9943f3"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"lazy_static",
|
||||
"proc-macro2 1.0.33",
|
||||
"quote 1.0.10",
|
||||
"regex",
|
||||
"tinyjson",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosetta-i18n"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5298de832602aecc9458398f435d9bff0be57da7aac11221b6ff3d4ef9503de"
|
||||
|
||||
[[package]]
|
||||
name = "rss"
|
||||
version = "2.0.0"
|
||||
|
@ -3902,6 +3925,12 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
|
||||
|
||||
[[package]]
|
||||
name = "tinyjson"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a8304da9f9370f6a6f9020b7903b044aa9ce3470f300a1fba5bc77c78145a16"
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.5.1"
|
||||
|
|
|
@ -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?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
@ -363,9 +365,8 @@ 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 {
|
||||
|
@ -373,32 +374,21 @@ pub fn send_email_to_user(
|
|||
}
|
||||
|
||||
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> {
|
||||
|
@ -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(),
|
||||
};
|
||||
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ use lemmy_apub::{
|
|||
EndpointType,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
aggregates::person_aggregates::PersonAggregates,
|
||||
newtypes::CommunityId,
|
||||
source::{
|
||||
community::{
|
||||
|
@ -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,
|
||||
|
@ -272,11 +274,20 @@ impl PerformCrud for Register {
|
|||
);
|
||||
} else {
|
||||
if email_verification {
|
||||
let local_user_view = LocalUserView {
|
||||
local_user: inserted_local_user,
|
||||
person: inserted_person,
|
||||
counts: PersonAggregates::default(),
|
||||
};
|
||||
// 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(),
|
||||
)
|
||||
|
|
|
@ -3,7 +3,7 @@ use diesel::{result::Error, *};
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(
|
||||
Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone,
|
||||
Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone, Default,
|
||||
)]
|
||||
#[table_name = "person_aggregates"]
|
||||
pub struct PersonAggregates {
|
||||
|
|
|
@ -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
crates/utils/build.rs
Normal file
8
crates/utils/build.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
rosetta_build::config()
|
||||
.source("en", "translations/email/en.json")
|
||||
.fallback("en")
|
||||
.generate()?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -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,
|
||||
|
|
1
crates/utils/translations
Submodule
1
crates/utils/translations
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 1314f10fbc0db9c16ff4209a2885431024a14ed8
|
|
@ -8,6 +8,7 @@ use lemmy_api_common::{
|
|||
check_person_block,
|
||||
comment::CommentResponse,
|
||||
community::CommunityResponse,
|
||||
get_user_lang,
|
||||
person::PrivateMessageResponse,
|
||||
post::PostResponse,
|
||||
send_email_to_user,
|
||||
|
@ -183,6 +184,7 @@ pub async fn send_local_notifs(
|
|||
context: &LemmyContext,
|
||||
) -> Result<Vec<LocalUserId>, LemmyError> {
|
||||
let mut recipient_ids = Vec::new();
|
||||
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
||||
|
||||
// Send the local mentions
|
||||
for mention in mentions
|
||||
|
@ -217,11 +219,11 @@ pub async fn send_local_notifs(
|
|||
|
||||
// Send an email to those local users that have notifications on
|
||||
if do_send_email {
|
||||
let lang = get_user_lang(&mention_user_view);
|
||||
send_email_to_user(
|
||||
&mention_user_view,
|
||||
"Mentioned by",
|
||||
"Person Mention",
|
||||
&comment.content,
|
||||
&lang.notification_mentioned_by_subject(&person.name),
|
||||
&lang.notification_mentioned_by_body(&person.name, &comment.content, &inbox_link),
|
||||
&context.settings(),
|
||||
)
|
||||
}
|
||||
|
@ -252,11 +254,11 @@ pub async fn send_local_notifs(
|
|||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
|
||||
if do_send_email {
|
||||
let lang = get_user_lang(&parent_user_view);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
"Reply from",
|
||||
"Comment Reply",
|
||||
&comment.content,
|
||||
&lang.notification_post_reply_subject(&person.name),
|
||||
&lang.notification_post_reply_body(&person.name, &comment.content, &inbox_link),
|
||||
&context.settings(),
|
||||
)
|
||||
}
|
||||
|
@ -282,11 +284,11 @@ pub async fn send_local_notifs(
|
|||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
|
||||
if do_send_email {
|
||||
let lang = get_user_lang(&parent_user_view);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
"Reply from",
|
||||
"Post Reply",
|
||||
&comment.content,
|
||||
&lang.notification_post_reply_subject(&person.name),
|
||||
&lang.notification_post_reply_body(&person.name, &comment.content, &inbox_link),
|
||||
&context.settings(),
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue