diff options
Diffstat (limited to '')
-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) +} |