analytics.rs
97 lignes · 3378 octets
//! Template context for the audience-measurement opt-out component. //! //! The framework side of audience measurement lives in //! [`rustwarden_core::config::analytics`]: configuration, snippet rendering //! and the CSP allowance. This module only carries what the reference UI //! needs to render `components/analytics_opt_out.html`. use rustwarden_core::config::analytics::AnalyticsContext; use rustwarden_core::i18n::I18nService; /// Everything `components/analytics_opt_out.html` needs. /// /// Built from the [`AnalyticsContext`] extractor plus the translated labels, /// following the convention of this crate: templates receive ready-made /// `String`s rather than calling into i18n themselves. /// /// # Example /// /// ```rust,ignore /// let analytics_opt_out = /// AnalyticsOptOutContext::build(&analytics, &i18n_service, locale.as_str()).await; /// ``` /// /// Then, in the template: `{% include "components/analytics_opt_out.html" %}`. #[derive(Debug, Clone, Default)] pub struct AnalyticsOptOutContext { /// `localStorage` key toggled by the component. Empty when the configured /// vendor exposes no opt-out, in which case nothing is rendered. pub storage_key: String, /// Sentence explaining the choice and when it takes effect. pub hint: String, /// Label of the button while measurement is active. pub opt_out_label: String, /// Label of the button once the visitor has opted out. pub opt_in_label: String, /// Badge shown next to the button while opted out. pub opted_out_badge: String, } impl AnalyticsOptOutContext { /// Build the context, translating the labels into `locale`. /// /// Returns an empty context — rendering nothing — when analytics is not /// configured or the vendor exposes no opt-out key, so a page can include /// the component unconditionally. pub async fn build( analytics: &AnalyticsContext, i18n_service: &I18nService, locale: &str, ) -> Self { if !analytics.has_opt_out() { return Self::default(); } Self { storage_key: analytics.opt_out_storage_key.clone(), hint: i18n_service .translate("analytics.opt_out.hint", locale) .await, opt_out_label: i18n_service .translate("analytics.opt_out.opt_out_label", locale) .await, opt_in_label: i18n_service .translate("analytics.opt_out.opt_in_label", locale) .await, opted_out_badge: i18n_service .translate("analytics.opt_out.opted_out_badge", locale) .await, } } /// Whether the component has an opt-out key to work with. /// /// Called from the template guard; an empty key means the whole component /// is skipped. pub fn has_key(&self) -> bool { !self.storage_key.is_empty() } } #[cfg(test)] mod tests { use super::*; #[test] fn default_context_renders_nothing() { assert!(!AnalyticsOptOutContext::default().has_key()); } #[test] fn a_configured_key_enables_the_component() { let ctx = AnalyticsOptOutContext { storage_key: "disable-rybbit".to_string(), ..AnalyticsOptOutContext::default() }; assert!(ctx.has_key()); } }
GitRust