aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/i18n.rs120
-rw-r--r--src/main.rs17
-rw-r--r--src/templates.rs2
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};