diff options
author | metamuffin <metamuffin@disroot.org> | 2025-04-19 21:40:47 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-04-19 21:40:47 +0200 |
commit | ea22b4ce7a2a089eb3824870561e555c65a2eb1b (patch) | |
tree | a690409637c98737d898f75a2428a89dbf51857d | |
parent | e4d60fc1a59f1c747c81871118512ef543e48e05 (diff) | |
download | jellything-ea22b4ce7a2a089eb3824870561e555c65a2eb1b.tar jellything-ea22b4ce7a2a089eb3824870561e555c65a2eb1b.tar.bz2 jellything-ea22b4ce7a2a089eb3824870561e555c65a2eb1b.tar.zst |
start on localization
-rw-r--r-- | base/src/lib.rs | 1 | ||||
-rw-r--r-- | base/src/locale.rs | 22 | ||||
-rw-r--r-- | locale/en.ini | 73 | ||||
-rw-r--r-- | server/src/routes/locale.rs | 54 | ||||
-rw-r--r-- | server/src/routes/mod.rs | 1 | ||||
-rw-r--r-- | server/src/routes/ui/home.rs | 25 |
6 files changed, 165 insertions, 11 deletions
diff --git a/base/src/lib.rs b/base/src/lib.rs index cb5bae7..14926ef 100644 --- a/base/src/lib.rs +++ b/base/src/lib.rs @@ -8,6 +8,7 @@ pub mod cache; pub mod database; pub mod federation; pub mod permission; +pub mod locale; pub use jellycommon as common; diff --git a/base/src/locale.rs b/base/src/locale.rs new file mode 100644 index 0000000..6df7221 --- /dev/null +++ b/base/src/locale.rs @@ -0,0 +1,22 @@ +use std::{borrow::Cow, collections::HashMap}; + +#[derive(Debug, Clone, Copy)] +pub enum Language { + English, +} + +pub fn tr<'a>(lang: Language, key: &str, args: &[(&str, &str)]) -> Cow<'a, str> { + let source_str = include_str!("../../locale/en.ini"); + let tr_map = source_str + .lines() + .filter_map(|line| { + let (key, value) = line.split_once("=")?; + Some((key.trim(), value.trim())) + }) + .collect::<HashMap<&'static str, &'static str>>(); + + match tr_map.get(key) { + Some(value) => Cow::Borrowed(value), + None => Cow::Owned(format!("TR[{key}]")), + } +} diff --git a/locale/en.ini b/locale/en.ini new file mode 100644 index 0000000..5970f67 --- /dev/null +++ b/locale/en.ini @@ -0,0 +1,73 @@ +[jellything] +nav.root=My Library +nav.all=All items +nav.search=Search +nav.stats=Stats +nav.username=Logged in as {name} +nav.admin=Administration +nav.settings=Settings +nav.log_out=Log out + +stats.title=Library Statistics +stats.count=There is a total of {count} nodes in the library +stats.runtime=The total runtime of the library is {dur}, taking up {size} of disk space. +stats.average=An average node has a runtime of {dur} and file size of {size}. +stats.by_kind.title=Grouped by Kind +stats.by_kind.kind=Kind +stats.by_kind.count=Count +stats.by_kind.total_size=Storage Size +stats.by_kind.total_runtime=Media Runtime +stats.by_kind.average_size=Average Size +stats.by_kind.average_runtime=Average Runtime +stats.by_kind.max_size=Largest File +stats.by_kind.max_runtime=Longest Runtime + +home=Home +home.bin.root=Explore {title} +home.bin.continue_watching=Continue Watching +home.bin.watchlist=Your Watchlist +home.bin.latest_video=Latest in Videos +home.bin.latest_music=Latest in Music +home.bin.latest_short_form=Latest in Short form +home.bin.max_rating=Top Rated +home.bin.daily_random=Today´s Picks +home.bin.watch_again=Watch again +home.bin.daily_random_music=Discover Music + +node.player_link=Watch now +node.watched.set=Mark Watched +node.watched.unset=Mark Unwatched +node.watchlist.set=Add to Watchlist +node.watchlist.unset=Remove from Watchlist +node.chapters=Chapters +node.people=Cast & Crew + +filter_sort=Filter and Sort +filter_sort.filter.kind=By Kind +filter_sort.filter.kind=By Federation +filter_sort.filter.kind=By Watched +filter_sort.sort.general=General +filter_sort.sort.media=Media +filter_sort.sort.rating=By Rating +filter_sort.order.asc=Ascending +filter_sort.order.desc=Descending + +kind.movie=Movie +kind.video=Video +kind.short_form=Short Form Video +kind.music=Music +kind.collection=collection +kind.channel=Channel +kind.show=Show +kind.series=Series +kind.season=Season +kind.episode=Episode + +media.tracks=Tracks + +prop.watched=Watched up to {time} + +admin.dashboard.title=Admin Panel +admin.dashboard.import.inc=Start incremental import +admin.dashboard.import.full=Start full import + diff --git a/server/src/routes/locale.rs b/server/src/routes/locale.rs new file mode 100644 index 0000000..cdf25d5 --- /dev/null +++ b/server/src/routes/locale.rs @@ -0,0 +1,54 @@ +use jellybase::locale::Language; +use rocket::{ + outcome::Outcome, + request::{self, FromRequest}, + Request, +}; +use std::ops::Deref; + +pub struct AcceptLanguage(pub Language); +impl Deref for AcceptLanguage { + type Target = Language; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl<'r> FromRequest<'r> for AcceptLanguage { + type Error = (); + + fn from_request<'life0, 'async_trait>( + request: &'r Request<'life0>, + ) -> ::core::pin::Pin< + Box< + dyn ::core::future::Future<Output = request::Outcome<Self, Self::Error>> + + ::core::marker::Send + + 'async_trait, + >, + > + where + 'r: 'async_trait, + 'life0: 'async_trait, + Self: 'async_trait, + { + Box::pin(async move { + Outcome::Success(AcceptLanguage( + request + .headers() + .get_one("accept-language") + .and_then(|h| { + h.split(",") + .filter_map(|e| { + let code = e.split(";").next()?; + let code = code.split_once("-").unwrap_or((code, "")).0; + match code { + "en" => Some(Language::English), + _ => None, + } + }) + .next() + }) + .unwrap_or(Language::English), + )) + }) + } +} diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index e909e15..90fa16d 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -68,6 +68,7 @@ pub mod playersync; pub mod stream; pub mod ui; pub mod userdata; +pub mod locale; #[macro_export] macro_rules! uri { diff --git a/server/src/routes/ui/home.rs b/server/src/routes/ui/home.rs index 3e002ee..ec28c7a 100644 --- a/server/src/routes/ui/home.rs +++ b/server/src/routes/ui/home.rs @@ -12,12 +12,13 @@ use crate::{ database::Database, routes::{ api::AcceptJson, + locale::AcceptLanguage, ui::{error::MyResult, layout::DynLayoutPage}, }, }; use anyhow::Context; use chrono::{Datelike, Utc}; -use jellybase::CONF; +use jellybase::{locale::tr, CONF}; use jellycommon::{api::ApiHomeResponse, user::WatchedState, NodeID, NodeKind, Rating, Visibility}; use rocket::{get, serde::json::Json, Either, State}; @@ -26,7 +27,9 @@ pub fn r_home( sess: Session, db: &State<Database>, aj: AcceptJson, + lang: AcceptLanguage, ) -> MyResult<Either<DynLayoutPage, Json<ApiHomeResponse>>> { + let AcceptLanguage(lang) = lang; let mut items = db.list_nodes_with_udata(&sess.user.name)?; let mut toplevel = db @@ -40,7 +43,7 @@ pub fn r_home( let mut categories = Vec::<(String, Vec<_>)>::new(); categories.push(( - "Continue Watching".to_string(), + tr(lang, "home.bin.continue_watching", &[]).to_string(), items .iter() .filter(|(_, u)| matches!(u.watched, WatchedState::Progress(_))) @@ -48,7 +51,7 @@ pub fn r_home( .collect(), )); categories.push(( - "Your Watchlist".to_string(), + tr(lang, "home.bin.watchlist", &[]).to_string(), items .iter() .filter(|(_, u)| matches!(u.watched, WatchedState::Pending)) @@ -61,7 +64,7 @@ pub fn r_home( items.sort_by_key(|(n, _)| n.release_date.map(|d| -d).unwrap_or(i64::MAX)); categories.push(( - "Latest in Videos".to_string(), + tr(lang, "home.bin.latest_video", &[]).to_string(), items .iter() .filter(|(n, _)| matches!(n.kind, NodeKind::Video)) @@ -70,7 +73,7 @@ pub fn r_home( .collect(), )); categories.push(( - "Latest in Music".to_string(), + tr(lang, "home.bin.latest_music", &[]).to_string(), items .iter() .filter(|(n, _)| matches!(n.kind, NodeKind::Music)) @@ -79,7 +82,7 @@ pub fn r_home( .collect(), )); categories.push(( - "Latest in Short form".to_string(), + tr(lang, "home.bin.latest_short_form", &[]).to_string(), items .iter() .filter(|(n, _)| matches!(n.kind, NodeKind::ShortFormVideo)) @@ -96,7 +99,7 @@ pub fn r_home( }); categories.push(( - "Top Rated".to_string(), + tr(lang, "home.bin.max_rating", &[]).to_string(), items .iter() .take(16) @@ -113,7 +116,7 @@ pub fn r_home( }); categories.push(( - "Today's Picks".to_string(), + tr(lang, "home.bin.daily_random", &[]).to_string(), (0..16) .flat_map(|i| Some(items[cheap_daily_random(i).checked_rem(items.len())?].clone())) .collect(), @@ -123,7 +126,7 @@ pub fn r_home( let mut items = items.clone(); items.retain(|(_, u)| matches!(u.watched, WatchedState::Watched)); categories.push(( - "Watch again".to_string(), + tr(lang, "home.bin.watch_again", &[]).to_string(), (0..16) .flat_map(|i| Some(items[cheap_daily_random(i).checked_rem(items.len())?].clone())) .collect(), @@ -132,7 +135,7 @@ pub fn r_home( items.retain(|(n, _)| matches!(n.kind, NodeKind::Music)); categories.push(( - "Discover Music".to_string(), + tr(lang, "home.bin.daily_random_music", &[]).to_string(), (0..16) .flat_map(|i| Some(items[cheap_daily_random(i).checked_rem(items.len())?].clone())) .collect(), @@ -145,7 +148,7 @@ pub fn r_home( })) } else { Either::Left(LayoutPage { - title: "Home".to_string(), + title: tr(lang, "home", &[]).to_string(), content: markup::new! { h2 { "Explore " @CONF.brand } ul.children.hlist {@for (node, udata) in &toplevel { |