diff options
| author | 2024-02-12 01:39:00 +0000 | |
|---|---|---|
| committer | 2024-02-12 01:39:00 +0000 | |
| commit | 2b8623fde242d379c66e4c532f7cad0dbb2198aa (patch) | |
| tree | d875588258b57998a1e089a17124e21e9632059b /src/i18n.rs | |
| parent | 82ef62654f58eb6212fad1beb3adb2c1c979fb56 (diff) | |
| download | blossom-2b8623fde242d379c66e4c532f7cad0dbb2198aa.tar.gz blossom-2b8623fde242d379c66e4c532f7cad0dbb2198aa.tar.bz2 blossom-2b8623fde242d379c66e4c532f7cad0dbb2198aa.zip | |
add language select widget
Diffstat (limited to 'src/i18n.rs')
| -rw-r--r-- | src/i18n.rs | 120 | 
1 files changed, 120 insertions, 0 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) +} | 
