aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-04-19 21:40:47 +0200
committermetamuffin <metamuffin@disroot.org>2025-04-19 21:40:47 +0200
commitea22b4ce7a2a089eb3824870561e555c65a2eb1b (patch)
treea690409637c98737d898f75a2428a89dbf51857d
parente4d60fc1a59f1c747c81871118512ef543e48e05 (diff)
downloadjellything-ea22b4ce7a2a089eb3824870561e555c65a2eb1b.tar
jellything-ea22b4ce7a2a089eb3824870561e555c65a2eb1b.tar.bz2
jellything-ea22b4ce7a2a089eb3824870561e555c65a2eb1b.tar.zst
start on localization
-rw-r--r--base/src/lib.rs1
-rw-r--r--base/src/locale.rs22
-rw-r--r--locale/en.ini73
-rw-r--r--server/src/routes/locale.rs54
-rw-r--r--server/src/routes/mod.rs1
-rw-r--r--server/src/routes/ui/home.rs25
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 {