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 --- Cargo.lock | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 +- TODO.md | 9 +-- src/i18n.rs | 120 +++++++++++++++++++++++++++++++++ src/main.rs | 17 ++++- src/templates.rs | 2 +- static/style.css | 7 ++ templates/base.html | 17 ++++- 8 files changed, 346 insertions(+), 15 deletions(-) create mode 100644 src/i18n.rs diff --git a/Cargo.lock b/Cargo.lock index 2c56a4a..3d2dd4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -302,6 +337,34 @@ dependencies = [ "chrono", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "aes-gcm", + "base64", + "hkdf", + "hmac", + "percent-encoding", + "rand", + "sha2", + "subtle", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -343,9 +406,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "darling" version = "0.14.4" @@ -452,6 +525,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -723,6 +797,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.1" @@ -747,7 +831,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.11", - "indexmap", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -766,13 +850,19 @@ dependencies = [ "futures-sink", "futures-util", "http 1.0.0", - "indexmap", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.3" @@ -824,6 +914,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.11" @@ -1014,6 +1122,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.1.0" @@ -1021,7 +1139,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", ] [[package]] @@ -1320,6 +1447,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.63" @@ -1454,7 +1587,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38a712ff53e257d60d3d22936c51cafa606552129d55539c8a400de44eff676d" dependencies = [ "async-trait", + "base64", "bytes", + "chrono", + "cookie", "fluent", "fluent-langneg", "fluent-syntax", @@ -1473,6 +1609,8 @@ dependencies = [ "percent-encoding", "pin-project-lite", "poem-derive", + "priority-queue", + "rand", "regex", "rfc7239", "rust-embed", @@ -1482,6 +1620,7 @@ dependencies = [ "smallvec", "sync_wrapper", "thiserror", + "time", "tokio", "tokio-util", "tracing", @@ -1501,6 +1640,18 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1513,6 +1664,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "priority-queue" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bda9164fe05bc9225752d54aae413343c36f684380005398a6a8fde95fe785" +dependencies = [ + "autocfg", + "indexmap 1.9.3", +] + [[package]] name = "proc-macro-crate" version = "2.0.1" @@ -1987,6 +2148,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "sval" version = "2.11.0" @@ -2331,7 +2498,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", @@ -2344,7 +2511,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap", + "indexmap 2.1.0", "toml_datetime", "winnow", ] @@ -2533,6 +2700,16 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "url" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 7be8c9d..83814b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ toml = "0.7.4" tokio-stream = { version = "0.1.14", features = ["fs"] } atom_syndication = "0.12.1" fluent = "0.16.0" -poem = { version = "2.0.0", features = ["embed", "i18n"] } +poem = { version = "2.0.0", features = ["embed", "i18n", "session"] } askama = { git = "https://bunny.garden/forks/askama.git", features = ["with-poem"] } askama_poem = { git = "https://bunny.garden/forks/askama.git" } rust-embed = "8.2.0" diff --git a/TODO.md b/TODO.md index 2b1d327..18b793d 100644 --- a/TODO.md +++ b/TODO.md @@ -9,8 +9,8 @@ [x] make font smaller [x] fix blos.sm badge [x] fix update font -[ ] localisation - [ ] landing page for blos.sm +[x] localisation +[x] site translations [ ] opengraph [ ] more badges [ ] harbor valorant @@ -19,7 +19,8 @@ [ ] links i like [ ] badges i created [ ] skinnymoji url -[ ] site translations +[ ] clean up css +[ ] move routes to own file [ ] credits [ ] latest video from garbageh channel widget [ ] homepage revamp @@ -36,5 +37,5 @@ badges to steal: [x] keith [x] webb +[ ] bunny ring [ ] puff place -[ ] shibao blog 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) +} 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::::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}; diff --git a/static/style.css b/static/style.css index df6921d..b777549 100644 --- a/static/style.css +++ b/static/style.css @@ -554,6 +554,13 @@ pre code { border: none; } +#lang-select { + background: #311f20; + float: right; + padding: 0.5vw 1vw; + margin-left: 4vw; +} + /* branches */ .branch { diff --git a/templates/base.html b/templates/base.html index 428ce40..366d42a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -29,8 +29,19 @@
- {% block header %} - {% endblock header %} +
+
+ de + en + fr + ja + pt + zh +
+ + {% block header %} + {% endblock header %} +
- \ No newline at end of file + -- cgit