i18n+filtering fork - fluent-templates v2
at main 201 lines 6.3 kB view raw
1use anyhow::Result; 2use axum::{extract::State, response::IntoResponse}; 3use axum_extra::extract::{Cached, Form}; 4use axum_htmx::HxBoosted; 5use http::StatusCode; 6use minijinja::context as template_context; 7use serde::Deserialize; 8use std::borrow::Cow; 9use unic_langid::LanguageIdentifier; 10 11use crate::{ 12 contextual_error, create_renderer, 13 http::{ 14 context::WebContext, errors::WebError, middleware_auth::Auth, middleware_i18n::Language, 15 timezones::supported_timezones, 16 }, 17 storage::handle::{handle_for_did, handle_update_field, HandleField}, 18}; 19 20#[derive(Deserialize, Clone, Debug)] 21pub struct TimezoneForm { 22 timezone: String, 23} 24 25#[derive(Deserialize, Clone, Debug)] 26pub struct LanguageForm { 27 language: String, 28} 29 30pub async fn handle_settings( 31 State(web_context): State<WebContext>, 32 Language(language): Language, 33 Cached(auth): Cached<Auth>, 34 HxBoosted(hx_boosted): HxBoosted, 35) -> Result<impl IntoResponse, WebError> { 36 // Require authentication 37 let current_handle = auth.require(&web_context.config.destination_key, "/settings")?; 38 39 // Create the template renderer with enhanced context 40 let renderer = create_renderer!(web_context.clone(), Language(language), hx_boosted, false); 41 42 let canonical_url = format!("https://{}/settings", web_context.config.external_base); 43 44 // Get available timezones 45 let (_, timezones) = supported_timezones(Some(&current_handle)); 46 47 // Get the list of supported languages 48 let supported_languages = web_context 49 .i18n_context 50 .supported_languages 51 .iter() 52 .map(|lang| lang.to_string()) 53 .collect::<Vec<String>>(); 54 55 // Render the form 56 Ok(( 57 StatusCode::OK, 58 renderer.render_template( 59 "settings", 60 template_context! { 61 timezones => timezones, 62 languages => supported_languages, 63 }, 64 Some(&current_handle), 65 &canonical_url, 66 ), 67 ) 68 .into_response()) 69} 70 71#[tracing::instrument(skip_all, err)] 72pub async fn handle_timezone_update( 73 State(web_context): State<WebContext>, 74 Language(language): Language, 75 Cached(auth): Cached<Auth>, 76 Form(timezone_form): Form<TimezoneForm>, 77) -> Result<impl IntoResponse, WebError> { 78 let current_handle = auth.require_flat()?; 79 80 // Create the template renderer for HTMX content replacement 81 let renderer = create_renderer!(web_context.clone(), Language(language), false, false); 82 83 let canonical_url = format!("https://{}/settings", web_context.config.external_base); 84 85 let (_, timezones) = supported_timezones(Some(&current_handle)); 86 87 if timezone_form.timezone.is_empty() 88 || timezone_form.timezone == current_handle.tz 89 || !timezones.contains(&timezone_form.timezone.as_str()) 90 { 91 return contextual_error!(renderer: renderer, "Invalid timezone", template_context!{}); 92 } 93 94 if let Err(err) = handle_update_field( 95 &web_context.pool, 96 &current_handle.did, 97 HandleField::Timezone(Cow::Owned(timezone_form.timezone)), 98 ) 99 .await 100 { 101 return contextual_error!(renderer: renderer, err, template_context!{}); 102 } 103 104 let current_handle = match handle_for_did(&web_context.pool, &current_handle.did).await { 105 Ok(value) => value, 106 Err(err) => { 107 return contextual_error!(renderer: renderer, err, template_context!{}); 108 } 109 }; 110 111 Ok(( 112 StatusCode::OK, 113 renderer.render_template( 114 "settings.tz", 115 template_context! { 116 timezone_updated => true, 117 timezones, 118 }, 119 Some(&current_handle), 120 &canonical_url, 121 ), 122 ) 123 .into_response()) 124} 125 126#[tracing::instrument(skip_all, err)] 127pub async fn handle_language_update( 128 State(web_context): State<WebContext>, 129 Language(language): Language, 130 Cached(auth): Cached<Auth>, 131 Form(language_form): Form<LanguageForm>, 132) -> Result<impl IntoResponse, WebError> { 133 let current_handle = auth.require_flat()?; 134 135 // Create the template renderer for HTMX content replacement 136 let renderer = create_renderer!(web_context.clone(), Language(language), false, false); 137 138 let canonical_url = format!("https://{}/settings", web_context.config.external_base); 139 140 // Get the list of supported languages 141 let supported_languages = web_context 142 .i18n_context 143 .supported_languages 144 .iter() 145 .map(|lang| lang.to_string()) 146 .collect::<Vec<String>>(); 147 148 if language_form.language.is_empty() || language_form.language == current_handle.language { 149 return contextual_error!(renderer: renderer, "Invalid language", template_context!{}); 150 } 151 152 let lang_id = match language_form.language.parse::<LanguageIdentifier>() { 153 Ok(value) => value, 154 Err(err) => { 155 return contextual_error!(renderer: renderer, err, template_context!{}); 156 } 157 }; 158 159 if !web_context 160 .i18n_context 161 .supported_languages 162 .iter() 163 .any(|l| l == &lang_id) 164 { 165 return contextual_error!(renderer: renderer, "Invalid language", template_context!{}); 166 } 167 168 if let Err(err) = handle_update_field( 169 &web_context.pool, 170 &current_handle.did, 171 HandleField::Language(Cow::Owned(language_form.language)), 172 ) 173 .await 174 { 175 return contextual_error!(renderer: renderer, err, template_context!{}); 176 } 177 178 let current_handle = match handle_for_did(&web_context.pool, &current_handle.did).await { 179 Ok(value) => value, 180 Err(err) => { 181 return contextual_error!(renderer: renderer, err, template_context!{}); 182 } 183 }; 184 185 // Create a new renderer with the updated language for proper template selection 186 let new_renderer = create_renderer!(web_context.clone(), Language(lang_id), false, false); 187 188 Ok(( 189 StatusCode::OK, 190 new_renderer.render_template( 191 "settings.language", 192 template_context! { 193 language_updated => true, 194 languages => supported_languages, 195 }, 196 Some(&current_handle), 197 &canonical_url, 198 ), 199 ) 200 .into_response()) 201}