1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-07 17:03:01 +03:00

templates: translate a lot more stuff

This commit is contained in:
Quentin Gliech
2023-10-05 17:20:02 +02:00
parent 6ff549f5df
commit b2cd8d83f7
32 changed files with 385 additions and 97 deletions

View File

@@ -18,7 +18,7 @@ use lettre::{
message::{Mailbox, MessageBuilder, MultiPart},
AsyncTransport, Message,
};
use mas_templates::{EmailVerificationContext, Templates};
use mas_templates::{EmailVerificationContext, Templates, WithLanguage};
use thiserror::Error;
use crate::MailTransport;
@@ -66,7 +66,7 @@ impl Mailer {
fn prepare_verification_email(
&self,
to: Mailbox,
context: &EmailVerificationContext,
context: &WithLanguage<EmailVerificationContext>,
) -> Result<Message, Error> {
let plain = self.templates.render_email_verification_txt(context)?;
@@ -95,6 +95,7 @@ impl Mailer {
skip_all,
fields(
email.to = %to,
email.language = %context.language(),
user.id = %context.user().id,
user_email_verification.id = %context.verification().id,
user_email_verification.code = context.verification().code,
@@ -104,7 +105,7 @@ impl Mailer {
pub async fn send_verification_email(
&self,
to: Mailbox,
context: &EmailVerificationContext,
context: &WithLanguage<EmailVerificationContext>,
) -> Result<(), Error> {
let message = self.prepare_verification_email(to, context)?;
self.transport.send(message).await?;

View File

@@ -449,6 +449,7 @@ impl UserEmailMutations {
.mark_as_verified(&state.clock(), user_email)
.await?;
} else {
// TODO: figure out the locale
repo.job()
.schedule_job(VerifyEmailJob::new(&user_email))
.await?;
@@ -490,6 +491,7 @@ impl UserEmailMutations {
// Schedule a job to verify the email address if needed
let needs_verification = user_email.confirmed_at.is_none();
if needs_verification {
// TODO: figure out the locale
repo.job()
.schedule_job(VerifyEmailJob::new(&user_email))
.await?;

View File

@@ -105,7 +105,8 @@ pub async fn get(
if clock.now() > login.created_at + Duration::minutes(30) {
let ctx = ErrorContext::new()
.with_code("compat_sso_login_expired")
.with_description("This login session expired.".to_owned());
.with_description("This login session expired.".to_owned())
.with_language(&locale);
let content = templates.render_error(&ctx)?;
return Ok((cookie_jar, Html(content)).into_response());
@@ -131,6 +132,7 @@ pub async fn post(
mut rng: BoxRng,
clock: BoxClock,
mut repo: BoxRepository,
PreferredLanguage(locale): PreferredLanguage,
State(templates): State<Templates>,
cookie_jar: CookieJar,
Path(id): Path<Ulid>,
@@ -173,7 +175,8 @@ pub async fn post(
if clock.now() > login.created_at + Duration::minutes(30) {
let ctx = ErrorContext::new()
.with_code("compat_sso_login_expired")
.with_description("This login session expired.".to_owned());
.with_description("This login session expired.".to_owned())
.with_language(&locale);
let content = templates.render_error(&ctx)?;
return Ok((cookie_jar, Html(content)).into_response());

View File

@@ -77,6 +77,7 @@ pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
mut repo: BoxRepository,
PreferredLanguage(locale): PreferredLanguage,
mut policy: Policy,
cookie_jar: CookieJar,
activity_tracker: BoundActivityTracker,
@@ -124,7 +125,7 @@ pub(crate) async fn post(
// verify page
let next = if user_email.confirmed_at.is_none() {
repo.job()
.schedule_job(VerifyEmailJob::new(&user_email))
.schedule_job(VerifyEmailJob::new(&user_email).with_language(locale.to_string()))
.await?;
let next = mas_router::AccountVerifyEmail::new(user_email.id);

View File

@@ -34,7 +34,6 @@ pub async fn get(
cookie_jar: CookieJar,
PreferredLanguage(locale): PreferredLanguage,
) -> Result<impl IntoResponse, FancyError> {
tracing::info!("{locale}");
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let (session_info, cookie_jar) = cookie_jar.session_info();
let session = session_info.load_session(&mut repo).await?;
@@ -52,7 +51,5 @@ pub async fn get(
let content = templates.render_index(&ctx)?;
tracing::info!("rendered index page");
Ok((cookie_jar, Html(content)))
}

View File

@@ -225,7 +225,7 @@ pub(crate) async fn post(
.await?;
repo.job()
.schedule_job(VerifyEmailJob::new(&user_email))
.schedule_job(VerifyEmailJob::new(&user_email).with_language(locale.to_string()))
.await?;
repo.job()

View File

@@ -19,6 +19,7 @@ pub mod sprintf;
pub mod translations;
mod translator;
pub use icu_locid::locale;
pub use icu_provider::DataLocale;
pub use self::{

View File

@@ -18,7 +18,7 @@ use ulid::Ulid;
pub use crate::traits::*;
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "snake_case", tag = "next")]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum PostAuthAction {
ContinueAuthorizationGrant {
id: Ulid,

View File

@@ -239,6 +239,7 @@ mod jobs {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct VerifyEmailJob {
user_email_id: Ulid,
language: Option<String>,
}
impl VerifyEmailJob {
@@ -247,9 +248,23 @@ mod jobs {
pub fn new(user_email: &UserEmail) -> Self {
Self {
user_email_id: user_email.id,
language: None,
}
}
/// Set the language to use for the email.
#[must_use]
pub fn with_language(mut self, language: String) -> Self {
self.language = Some(language);
self
}
/// The language to use for the email.
#[must_use]
pub fn language(&self) -> Option<&str> {
self.language.as_deref()
}
/// The ID of the email address to verify.
#[must_use]
pub fn user_email_id(&self) -> Ulid {

View File

@@ -32,7 +32,9 @@ serde_json.workspace = true
mas-data-model = { path = "../data-model" }
mas-email = { path = "../email" }
mas-i18n = { path = "../i18n" }
mas-matrix = { path = "../matrix" }
mas-storage = { path = "../storage" }
mas-storage-pg = { path = "../storage-pg" }
mas-templates = { path = "../templates" }
mas-tower = { path = "../tower" }

View File

@@ -15,8 +15,10 @@
use anyhow::Context;
use apalis_core::{context::JobContext, executor::TokioExecutor, monitor::Monitor};
use chrono::Duration;
use mas_email::{Address, EmailVerificationContext, Mailbox};
use mas_email::{Address, Mailbox};
use mas_i18n::locale;
use mas_storage::job::{JobWithSpanContext, VerifyEmailJob};
use mas_templates::{EmailVerificationContext, TemplateContext};
use rand::{distributions::Uniform, Rng};
use tracing::info;
@@ -38,6 +40,11 @@ async fn verify_email(
let mailer = state.mailer();
let clock = state.clock();
let language = job
.language()
.and_then(|l| l.parse().ok())
.unwrap_or(locale!("en").into());
// Lookup the user email
let user_email = repo
.user_email()
@@ -68,7 +75,8 @@ async fn verify_email(
// And send the verification email
let mailbox = Mailbox::new(Some(user.username.clone()), address);
let context = EmailVerificationContext::new(user.clone(), verification.clone());
let context =
EmailVerificationContext::new(user.clone(), verification.clone()).with_language(language);
mailer.send_verification_email(mailbox, &context).await?;

View File

@@ -109,6 +109,21 @@ pub struct WithLanguage<T> {
inner: T,
}
impl<T> WithLanguage<T> {
/// Get the language of this context
pub fn language(&self) -> &str {
&self.lang
}
}
impl<T> std::ops::Deref for WithLanguage<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T: TemplateContext> TemplateContext for WithLanguage<T> {
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
where
@@ -984,6 +999,7 @@ pub struct ErrorContext {
code: Option<&'static str>,
description: Option<String>,
details: Option<String>,
lang: Option<String>,
}
impl std::fmt::Display for ErrorContext {
@@ -1047,6 +1063,13 @@ impl ErrorContext {
self
}
/// Add the language to the context
#[must_use]
pub fn with_language(mut self, lang: &DataLocale) -> Self {
self.lang = Some(lang.to_string());
self
}
/// Get the error code, if any
#[must_use]
pub fn code(&self) -> Option<&'static str> {

View File

@@ -29,7 +29,7 @@ use std::{collections::HashSet, sync::Arc};
use anyhow::Context as _;
use arc_swap::ArcSwap;
use camino::{Utf8Path, Utf8PathBuf};
use mas_i18n::{Translator};
use mas_i18n::Translator;
use mas_router::UrlBuilder;
use mas_spa::ViteManifest;
use rand::Rng;
@@ -346,13 +346,13 @@ register_templates! {
pub fn render_error(ErrorContext) { "pages/error.html" }
/// Render the email verification email (plain text variant)
pub fn render_email_verification_txt(EmailVerificationContext) { "emails/verification.txt" }
pub fn render_email_verification_txt(WithLanguage<EmailVerificationContext>) { "emails/verification.txt" }
/// Render the email verification email (HTML text variant)
pub fn render_email_verification_html(EmailVerificationContext) { "emails/verification.html" }
pub fn render_email_verification_html(WithLanguage<EmailVerificationContext>) { "emails/verification.html" }
/// Render the email verification subject
pub fn render_email_verification_subject(EmailVerificationContext) { "emails/verification.subject" }
pub fn render_email_verification_subject(WithLanguage<EmailVerificationContext>) { "emails/verification.subject" }
/// Render the upstream link mismatch message
pub fn render_upstream_oauth2_link_mismatch(WithLanguage<WithCsrf<WithSession<UpstreamExistingLinkContext>>>) { "pages/upstream_oauth2/link_mismatch.html" }