From 2b8623fde242d379c66e4c532f7cad0dbb2198aa Mon Sep 17 00:00:00 2001 From: cel 🌸 Date: Mon, 12 Feb 2024 01:39:00 +0000 Subject: add language select widget --- src/i18n.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/i18n.rs (limited to 'src/i18n.rs') 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(next: E, mut req: Request) -> poem::Result { + if let Ok(params) = req.params::() { + let session = req + .extensions() + .get::() + .expect("To use the `set_language` middleware, the `Session` data is required."); + session.set("lang", params.lang); + println!("{:?}", session.get::("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, + args: impl Into>, + ) -> Result { + self.0.text_with_args(id, args) + } + + /// Gets the text. + /// + /// See also: [`I18NBundle::text`](I18NBundle::text) + pub fn text(&self, id: impl AsRef) -> Result { + 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 { + let session = req + .extensions() + .get::() + .expect("To use the `Locale` extractor, the `Session` data is required."); + let resources = req + .extensions() + .get::() + .expect("To use the `Locale` extractor, the `I18NResources` data is required."); + + let mut lang_id = None; + if let Some(lang) = session.get::("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 { + 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 { + let mut parts = value.split('='); + let name = parts.next()?.trim(); + if name != "q" { + return None; + } + let q = parts.next()?.trim().parse::().ok()?; + Some((q.clamp(0.0, 1.0) * 1000.0) as u16) +} -- cgit