From 488e028f14ff089a7c2ec00fae53ca5b6aa38439 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 5 May 2025 14:38:24 +0200 Subject: [PATCH] Check rendering of templates with all supported locales. --- crates/i18n/src/translator.rs | 4 +- crates/templates/src/context.rs | 144 ++++++++++++++++-------- crates/templates/src/context/captcha.rs | 4 +- crates/templates/src/macros.rs | 3 +- 4 files changed, 107 insertions(+), 48 deletions(-) diff --git a/crates/i18n/src/translator.rs b/crates/i18n/src/translator.rs index 07415ff26..68afb1793 100644 --- a/crates/i18n/src/translator.rs +++ b/crates/i18n/src/translator.rs @@ -345,8 +345,8 @@ impl Translator { /// Get a list of available locales. #[must_use] - pub fn available_locales(&self) -> Vec<&DataLocale> { - self.translations.keys().collect() + pub fn available_locales(&self) -> Vec { + self.translations.keys().cloned().collect() } /// Check if a locale is available. diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 345c8bf01..8c538a62a 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -105,13 +105,17 @@ pub trait TemplateContext: Serialize { /// /// This is then used to check for template validity in unit tests and in /// the CLI (`cargo run -- templates check`) - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, locales: &[DataLocale]) -> Vec where Self: Sized; } impl TemplateContext for () { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -144,15 +148,19 @@ impl std::ops::Deref for WithLanguage { } impl TemplateContext for WithLanguage { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, locales: &[DataLocale]) -> Vec where Self: Sized, { - T::sample(now, rng) - .into_iter() - .map(|inner| WithLanguage { - lang: "en".into(), - inner, + locales + .iter() + .flat_map(|locale| { + T::sample(now, rng, locales) + .into_iter() + .map(move |inner| WithLanguage { + lang: locale.to_string(), + inner, + }) }) .collect() } @@ -168,11 +176,11 @@ pub struct WithCsrf { } impl TemplateContext for WithCsrf { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, locales: &[DataLocale]) -> Vec where Self: Sized, { - T::sample(now, rng) + T::sample(now, rng, locales) .into_iter() .map(|inner| WithCsrf { csrf_token: "fake_csrf_token".into(), @@ -192,14 +200,14 @@ pub struct WithSession { } impl TemplateContext for WithSession { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, locales: &[DataLocale]) -> Vec where Self: Sized, { BrowserSession::samples(now, rng) .into_iter() .flat_map(|session| { - T::sample(now, rng) + T::sample(now, rng, locales) .into_iter() .map(move |inner| WithSession { current_session: session.clone(), @@ -220,7 +228,7 @@ pub struct WithOptionalSession { } impl TemplateContext for WithOptionalSession { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -229,7 +237,7 @@ impl TemplateContext for WithOptionalSession { .map(Some) // Wrap all samples in an Option .chain(std::iter::once(None)) // Add the "None" option .flat_map(|session| { - T::sample(now, rng) + T::sample(now, rng, locales) .into_iter() .map(move |inner| WithOptionalSession { current_session: session.clone(), @@ -257,7 +265,11 @@ impl Serialize for EmptyContext { } impl TemplateContext for EmptyContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -281,7 +293,11 @@ impl IndexContext { } impl TemplateContext for IndexContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -323,7 +339,11 @@ impl AppContext { } impl TemplateContext for AppContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -352,7 +372,11 @@ impl ApiDocContext { } impl TemplateContext for ApiDocContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -440,7 +464,11 @@ pub struct LoginContext { } impl TemplateContext for LoginContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -544,7 +572,11 @@ pub struct RegisterContext { } impl TemplateContext for RegisterContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -583,7 +615,11 @@ pub struct PasswordRegisterContext { } impl TemplateContext for PasswordRegisterContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -621,7 +657,7 @@ pub struct ConsentContext { } impl TemplateContext for ConsentContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -673,7 +709,7 @@ pub struct PolicyViolationContext { } impl TemplateContext for PolicyViolationContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -742,7 +778,7 @@ pub struct CompatSsoContext { } impl TemplateContext for CompatSsoContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -800,7 +836,7 @@ impl EmailRecoveryContext { } impl TemplateContext for EmailRecoveryContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -861,7 +897,7 @@ impl EmailVerificationContext { } impl TemplateContext for EmailVerificationContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -927,7 +963,7 @@ impl RegisterStepsVerifyEmailContext { } impl TemplateContext for RegisterStepsVerifyEmailContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -963,7 +999,11 @@ impl RegisterStepsEmailInUseContext { } impl TemplateContext for RegisterStepsEmailInUseContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -1014,7 +1054,11 @@ impl RegisterStepsDisplayNameContext { } impl TemplateContext for RegisterStepsDisplayNameContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -1061,7 +1105,11 @@ impl RecoveryStartContext { } impl TemplateContext for RecoveryStartContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -1099,7 +1147,7 @@ impl RecoveryProgressContext { } impl TemplateContext for RecoveryProgressContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -1141,7 +1189,7 @@ impl RecoveryExpiredContext { } impl TemplateContext for RecoveryExpiredContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -1202,7 +1250,7 @@ impl RecoveryFinishContext { } impl TemplateContext for RecoveryFinishContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -1245,7 +1293,7 @@ impl UpstreamExistingLinkContext { } impl TemplateContext for UpstreamExistingLinkContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -1277,7 +1325,7 @@ impl UpstreamSuggestLink { } impl TemplateContext for UpstreamSuggestLink { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -1402,7 +1450,7 @@ impl UpstreamRegister { } impl TemplateContext for UpstreamRegister { - fn sample(now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, _rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -1482,7 +1530,11 @@ impl DeviceLinkContext { } impl TemplateContext for DeviceLinkContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -1512,7 +1564,7 @@ impl DeviceConsentContext { } impl TemplateContext for DeviceConsentContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -1553,7 +1605,7 @@ impl AccountInactiveContext { } impl TemplateContext for AccountInactiveContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -1583,7 +1635,7 @@ impl DeviceNameContext { } impl TemplateContext for DeviceNameContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { @@ -1605,11 +1657,11 @@ pub struct FormPostContext { } impl TemplateContext for FormPostContext { - fn sample(now: chrono::DateTime, rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, rng: &mut impl Rng, locales: &[DataLocale]) -> Vec where Self: Sized, { - let sample_params = T::sample(now, rng); + let sample_params = T::sample(now, rng, locales); sample_params .into_iter() .map(|params| FormPostContext { @@ -1678,7 +1730,11 @@ impl std::fmt::Display for ErrorContext { } impl TemplateContext for ErrorContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample( + _now: chrono::DateTime, + _rng: &mut impl Rng, + _locales: &[DataLocale], + ) -> Vec where Self: Sized, { @@ -1768,7 +1824,7 @@ impl NotFoundContext { } impl TemplateContext for NotFoundContext { - fn sample(_now: DateTime, _rng: &mut impl Rng) -> Vec + fn sample(_now: DateTime, _rng: &mut impl Rng, _locales: &[DataLocale]) -> Vec where Self: Sized, { diff --git a/crates/templates/src/context/captcha.rs b/crates/templates/src/context/captcha.rs index 4e8c9f726..38d723ca0 100644 --- a/crates/templates/src/context/captcha.rs +++ b/crates/templates/src/context/captcha.rs @@ -6,6 +6,7 @@ use std::sync::Arc; +use mas_i18n::DataLocale; use minijinja::{ Value, value::{Enumerator, Object}, @@ -60,11 +61,12 @@ impl TemplateContext for WithCaptcha { fn sample( now: chrono::DateTime, rng: &mut impl rand::prelude::Rng, + locales: &[DataLocale], ) -> Vec where Self: Sized, { - let inner = T::sample(now, rng); + let inner = T::sample(now, rng, locales); inner .into_iter() .map(|inner| Self::new(None, inner)) diff --git a/crates/templates/src/macros.rs b/crates/templates/src/macros.rs index 8fae68237..a3166b2bb 100644 --- a/crates/templates/src/macros.rs +++ b/crates/templates/src/macros.rs @@ -79,7 +79,8 @@ macro_rules! register_templates { $(< $( $lt $( : $clt $(+ $dlt )* + TemplateContext )? ),+ >)? (templates: &Templates, now: chrono::DateTime, rng: &mut impl rand::Rng) -> anyhow::Result<()> { - let samples: Vec< $param > = TemplateContext::sample(now, rng); + let locales = templates.translator().available_locales(); + let samples: Vec< $param > = TemplateContext::sample(now, rng, &locales); let name = $template; for sample in samples {