diff options
| author | 2024-02-12 01:39:00 +0000 | |
|---|---|---|
| committer | 2024-02-12 01:39:00 +0000 | |
| commit | 2b8623fde242d379c66e4c532f7cad0dbb2198aa (patch) | |
| tree | d875588258b57998a1e089a17124e21e9632059b /src | |
| parent | 82ef62654f58eb6212fad1beb3adb2c1c979fb56 (diff) | |
| download | blossom-2b8623fde242d379c66e4c532f7cad0dbb2198aa.tar.gz blossom-2b8623fde242d379c66e4c532f7cad0dbb2198aa.tar.bz2 blossom-2b8623fde242d379c66e4c532f7cad0dbb2198aa.zip | |
add language select widget
Diffstat (limited to '')
| -rw-r--r-- | src/i18n.rs | 120 | ||||
| -rw-r--r-- | src/main.rs | 17 | ||||
| -rw-r--r-- | src/templates.rs | 2 | 
3 files changed, 137 insertions, 2 deletions
| diff --git a/src/i18n.rs b/src/i18n.rs new file mode 100644 index 0000000..964aaa3 --- /dev/null +++ b/src/i18n.rs @@ -0,0 +1,120 @@ +use std::str::FromStr; + +use poem::{ +    error::I18NError, +    http::header, +    i18n::{unic_langid::LanguageIdentifier, I18NArgs, I18NResources}, +    session::Session, +    Endpoint, FromRequest, Middleware, Request, RequestBody, +}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Params { +    lang: String, +} + +pub async fn set_language<E: Endpoint>(next: E, mut req: Request) -> poem::Result<E::Output> { +    if let Ok(params) = req.params::<Params>() { +        let session = req +            .extensions() +            .get::<Session>() +            .expect("To use the `set_language` middleware, the `Session` data is required."); +        session.set("lang", params.lang); +        println!("{:?}", session.get::<String>("lang")) +    } +    next.call(req).await +} + +pub struct Locale(poem::i18n::I18NBundle); + +impl Locale { +    /// Gets the text with arguments. +    /// +    /// See also: [`I18NBundle::text_with_args`](I18NBundle::text_with_args) +    pub fn text_with_args<'a>( +        &self, +        id: impl AsRef<str>, +        args: impl Into<I18NArgs<'a>>, +    ) -> Result<String, I18NError> { +        self.0.text_with_args(id, args) +    } + +    /// Gets the text. +    /// +    /// See also: [`I18NBundle::text`](I18NBundle::text) +    pub fn text(&self, id: impl AsRef<str>) -> Result<String, I18NError> { +        self.0.text(id) +    } +} + +#[poem::async_trait] +impl<'a> FromRequest<'a> for Locale { +    async fn from_request(req: &'a Request, body: &mut RequestBody) -> poem::Result<Self> { +        let session = req +            .extensions() +            .get::<Session>() +            .expect("To use the `Locale` extractor, the `Session` data is required."); +        let resources = req +            .extensions() +            .get::<I18NResources>() +            .expect("To use the `Locale` extractor, the `I18NResources` data is required."); + +        let mut lang_id = None; +        if let Some(lang) = session.get::<String>("lang") { +            lang_id = Some(lang); +        }; + +        if let Some(lang_id) = lang_id { +            if let Ok(lang_id) = LanguageIdentifier::from_str(&lang_id) { +                return Ok(Self(resources.negotiate_languages(&[&lang_id]))); +            } +        }; + +        let accept_languages = req +            .headers() +            .get(header::ACCEPT_LANGUAGE) +            .and_then(|value| value.to_str().ok()) +            .map(parse_accept_languages) +            .unwrap_or_default(); + +        Ok(Self(resources.negotiate_languages(&accept_languages))) +    } +} + +fn parse_accept_languages(value: &str) -> Vec<LanguageIdentifier> { +    let mut languages = Vec::new(); + +    for s in value.split(',').map(str::trim) { +        if let Some(res) = parse_language(s) { +            languages.push(res); +        } +    } + +    languages.sort_by(|(_, a), (_, b)| b.cmp(a)); +    languages +        .into_iter() +        .map(|(language, _)| language) +        .collect() +} + +fn parse_language(value: &str) -> Option<(LanguageIdentifier, u16)> { +    let mut parts = value.split(';'); +    let name = parts.next()?.trim(); +    let quality = match parts.next() { +        Some(quality) => parse_quality(quality).unwrap_or_default(), +        None => 1000, +    }; +    let language = LanguageIdentifier::from_str(name).ok()?; +    Some((language, quality)) +} + +fn parse_quality(value: &str) -> Option<u16> { +    let mut parts = value.split('='); +    let name = parts.next()?.trim(); +    if name != "q" { +        return None; +    } +    let q = parts.next()?.trim().parse::<f32>().ok()?; +    Some((q.clamp(0.0, 1.0) * 1000.0) as u16) +} diff --git a/src/main.rs b/src/main.rs index d810159..8e31fee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod article;  mod atom;  mod blog;  mod error; +mod i18n;  mod live;  mod poetry;  mod posts; @@ -14,9 +15,12 @@ mod utils;  use std::{collections::HashSet, time::Duration}; +use i18n::set_language;  use poem::http::StatusCode;  use poem::i18n::unic_langid::langid; -use poem::i18n::{I18NResources, Locale}; +use poem::i18n::I18NResources; +use poem::session::{CookieConfig, CookieSession, ServerSession}; +use poem::web::cookie::{CookieKey, SameSite};  use poem::{      endpoint::EmbeddedFilesEndpoint,      get, handler, @@ -36,6 +40,7 @@ use tracing_subscriber::FmtSubscriber;  use crate::article::Article;  use crate::blog::Blogpost; +use crate::i18n::Locale;  use crate::poetry::Poem;  use crate::posts::Post; @@ -208,6 +213,14 @@ async fn main() -> std::result::Result<(), std::io::Error> {          .build()          .unwrap(); +    let session = CookieSession::new( +        // CookieConfig::private(CookieKey::generate()) +        //     .name("blossom") +        //     .domain("blos.sm") +        //     .same_site(SameSite::Strict), +        CookieConfig::default(), +    ); +      let blossom = Route::new()          .at("/", get(home))          .at("/blog", get(get_blog)) @@ -220,7 +233,9 @@ async fn main() -> std::result::Result<(), std::io::Error> {          .nest("/static/", EmbeddedFilesEndpoint::<Static>::new())          .catch_all_error(custom_error)          .data(resources) +        .around(set_language)          .with(Tracing) +        .with(session)          .with(AddData::new(              reqwest::Client::builder()                  .connect_timeout(Duration::from_secs(1)) diff --git a/src/templates.rs b/src/templates.rs index 2ab9c41..66462eb 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -2,9 +2,9 @@ use std::collections::HashSet;  use askama::Template;  use poem::http::StatusCode; -use poem::i18n::Locale;  use rand::{thread_rng, Rng}; +use crate::i18n::Locale;  use crate::poetry;  use crate::posts::Post;  use crate::{blog, scrobbles::NowPlayingData}; | 
