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)
}