aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-04-20 16:51:44 +0200
committermetamuffin <metamuffin@disroot.org>2025-04-20 16:51:44 +0200
commit646b7663a98a64556fd179137c4e8f55f037b0ca (patch)
treeb53ea3a312f677b57096d8cda0c94e6ad8ad47fe /server
parent08f067aa1d0c1c1cec072dc73d4b4c04ce135b01 (diff)
downloadjellything-646b7663a98a64556fd179137c4e8f55f037b0ca.tar
jellything-646b7663a98a64556fd179137c4e8f55f037b0ca.tar.bz2
jellything-646b7663a98a64556fd179137c4e8f55f037b0ca.tar.zst
more translation
Diffstat (limited to 'server')
-rw-r--r--server/src/routes/ui/browser.rs20
-rw-r--r--server/src/routes/ui/home.rs4
-rw-r--r--server/src/routes/ui/layout.rs16
-rw-r--r--server/src/routes/ui/node.rs81
-rw-r--r--server/src/routes/ui/player.rs29
-rw-r--r--server/src/routes/ui/search.rs6
-rw-r--r--server/src/routes/ui/sort.rs72
-rw-r--r--server/src/routes/ui/stats.rs30
8 files changed, 163 insertions, 95 deletions
diff --git a/server/src/routes/ui/browser.rs b/server/src/routes/ui/browser.rs
index 13f30c8..9cc9d88 100644
--- a/server/src/routes/ui/browser.rs
+++ b/server/src/routes/ui/browser.rs
@@ -6,11 +6,15 @@
use super::{
account::session::Session,
error::MyError,
- layout::{DynLayoutPage, LayoutPage},
+ layout::{trs, trsa, DynLayoutPage, LayoutPage},
node::NodeCard,
sort::{filter_and_sort_nodes, NodeFilterSort, NodeFilterSortForm, SortOrder, SortProperty},
};
-use crate::{database::Database, routes::api::AcceptJson, uri};
+use crate::{
+ database::Database,
+ routes::{api::AcceptJson, locale::AcceptLanguage},
+ uri,
+};
use jellycommon::{api::ApiItemsResponse, Visibility};
use rocket::{get, serde::json::Json, Either, State};
@@ -25,7 +29,9 @@ pub fn r_all_items_filter(
aj: AcceptJson,
page: Option<usize>,
filter: NodeFilterSort,
+ lang: AcceptLanguage,
) -> Result<Either<DynLayoutPage<'_>, Json<ApiItemsResponse>>, MyError> {
+ let AcceptLanguage(lang) = lang;
let mut items = db.list_nodes_with_udata(sess.user.name.as_str())?;
items.retain(|(n, _)| matches!(n.visibility, Visibility::Visible));
@@ -55,17 +61,17 @@ pub fn r_all_items_filter(
content: markup::new! {
.page.dir {
h1 { "All Items" }
- @NodeFilterSortForm { f: &filter }
+ @NodeFilterSortForm { f: &filter, lang: &lang }
ul.children { @for (node, udata) in &items[from..to] {
- li {@NodeCard { node, udata }}
+ li {@NodeCard { node, udata, lang: &lang }}
}}
p.pagecontrols {
- span.current { "Page " @{page + 1} " of " @max_page " " }
+ span.current { @trsa(&lang, "page.curr", &[("cur", &(page + 1).to_string()), ("max", &max_page.to_string())]) " " }
@if page > 0 {
- a.prev[href=uri!(r_all_items_filter(Some(page - 1), filter.clone()))] { "Previous page" } " "
+ a.prev[href=uri!(r_all_items_filter(Some(page - 1), filter.clone()))] { @trs(&lang, "page.prev") } " "
}
@if page + 1 < max_page {
- a.next[href=uri!(r_all_items_filter(Some(page + 1), filter.clone()))] { "Next page" }
+ a.next[href=uri!(r_all_items_filter(Some(page + 1), filter.clone()))] { @trs(&lang, "page.next") }
}
}
}
diff --git a/server/src/routes/ui/home.rs b/server/src/routes/ui/home.rs
index ec28c7a..9e4035b 100644
--- a/server/src/routes/ui/home.rs
+++ b/server/src/routes/ui/home.rs
@@ -152,13 +152,13 @@ pub fn r_home(
content: markup::new! {
h2 { "Explore " @CONF.brand }
ul.children.hlist {@for (node, udata) in &toplevel {
- li { @NodeCard { node, udata } }
+ li { @NodeCard { node, udata, lang: &lang } }
}}
@for (name, nodes) in &categories {
@if !nodes.is_empty() {
h2 { @name }
ul.children.hlist {@for (node, udata) in nodes {
- li { @NodeCard { node, udata } }
+ li { @NodeCard { node, udata, lang: &lang } }
}}
}
}
diff --git a/server/src/routes/ui/layout.rs b/server/src/routes/ui/layout.rs
index aea5b4a..d0bb780 100644
--- a/server/src/routes/ui/layout.rs
+++ b/server/src/routes/ui/layout.rs
@@ -29,7 +29,7 @@ use jellybase::{
use jellycommon::user::Theme;
use jellycommon::NodeID;
use jellyimport::is_importing;
-use markup::{DynRender, Render};
+use markup::{DynRender, Render, RenderAttributeValue};
use rocket::{
http::ContentType,
response::{self, Responder},
@@ -45,6 +45,20 @@ impl Render for TrString<'_> {
self.0.as_str().render(writer)
}
}
+impl RenderAttributeValue for TrString<'_> {
+ fn is_none(&self) -> bool {
+ false
+ }
+ fn is_true(&self) -> bool {
+ false
+ }
+ fn is_false(&self) -> bool {
+ false
+ }
+}
+pub fn trsa<'a>(lang: &Language, key: &str, args: &[(&str, &str)]) -> TrString<'a> {
+ TrString(tr(*lang, key, args))
+}
pub fn trs<'a>(lang: &Language, key: &str) -> TrString<'a> {
TrString(tr(*lang, key, &[]))
}
diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs
index 57e8562..e94547c 100644
--- a/server/src/routes/ui/node.rs
+++ b/server/src/routes/ui/node.rs
@@ -9,12 +9,14 @@ use super::{
rocket_uri_macro_r_node_thumbnail,
},
error::MyResult,
+ layout::{trs, trsa},
sort::{filter_and_sort_nodes, NodeFilterSort, NodeFilterSortForm, SortOrder, SortProperty},
};
use crate::{
database::Database,
routes::{
api::AcceptJson,
+ locale::AcceptLanguage,
ui::{
account::session::Session,
assets::rocket_uri_macro_r_person_asset,
@@ -30,6 +32,7 @@ use crate::{
};
use anyhow::{anyhow, Result};
use chrono::DateTime;
+use jellybase::locale::Language;
use jellycommon::{
api::ApiNodeResponse,
user::{NodeUserData, WatchedState},
@@ -51,9 +54,11 @@ pub async fn r_library_node_filter<'a>(
db: &'a State<Database>,
aj: AcceptJson,
filter: NodeFilterSort,
+ lang: AcceptLanguage,
parents: bool,
children: bool,
) -> MyResult<Either<DynLayoutPage<'a>, Json<ApiNodeResponse>>> {
+ let AcceptLanguage(lang) = lang;
let (node, udata) = db.get_node_with_userdata(id, &session)?;
let mut children = if !*aj || children {
@@ -100,9 +105,16 @@ pub async fn r_library_node_filter<'a>(
} else {
Either::Left(LayoutPage {
title: node.title.clone().unwrap_or_default(),
- content: markup::new! {
- @NodePage { node: &node, udata: &udata, children: &children, parents: &parents, filter: &filter, player: false, similar: &similar }
- },
+ content: markup::new!(@NodePage {
+ node: &node,
+ udata: &udata,
+ children: &children,
+ parents: &parents,
+ filter: &filter,
+ player: false,
+ similar: &similar,
+ lang: &lang,
+ }),
..Default::default()
})
})
@@ -134,7 +146,7 @@ pub fn get_similar_media(
}
markup::define! {
- NodeCard<'a>(node: &'a Node, udata: &'a NodeUserData) {
+ NodeCard<'a>(node: &'a Node, udata: &'a NodeUserData, lang: &'a Language) {
@let cls = format!("node card poster {}", aspect_class(node.kind));
div[class=cls] {
.poster {
@@ -145,7 +157,7 @@ markup::define! {
@if node.media.is_some() {
a.play.icon[href=&uri!(r_player(&node.slug, PlayerConfig::default()))] { "play_arrow" }
}
- @Props { node, udata, full: false }
+ @Props { node, udata, full: false, lang }
}
}
div.title {
@@ -160,7 +172,7 @@ markup::define! {
}
}
}
- NodeCardWide<'a>(node: &'a Node, udata: &'a NodeUserData) {
+ NodeCardWide<'a>(node: &'a Node, udata: &'a NodeUserData, lang: &'a Language) {
div[class="node card widecard poster"] {
div[class=&format!("poster {}", aspect_class(node.kind))] {
a[href=uri!(r_library_node(&node.slug))] {
@@ -174,12 +186,21 @@ markup::define! {
}
div.details {
a.title[href=uri!(r_library_node(&node.slug))] { @node.title }
- @Props { node, udata, full: false }
+ @Props { node, udata, full: false, lang }
span.overview { @node.description }
}
}
}
- NodePage<'a>(node: &'a Node, udata: &'a NodeUserData, children: &'a [(Arc<Node>, NodeUserData)], parents: &'a [(Arc<Node>, NodeUserData)], similar: &'a [(Arc<Node>, NodeUserData)], filter: &'a NodeFilterSort, player: bool) {
+ NodePage<'a>(
+ node: &'a Node,
+ udata: &'a NodeUserData,
+ children: &'a [(Arc<Node>, NodeUserData)],
+ parents: &'a [(Arc<Node>, NodeUserData)],
+ similar: &'a [(Arc<Node>, NodeUserData)],
+ filter: &'a NodeFilterSort,
+ lang: &'a Language,
+ player: bool,
+ ) {
@if !matches!(node.kind, NodeKind::Collection) && !player {
img.backdrop[src=uri!(r_item_backdrop(&node.slug, Some(2048))), loading="lazy"];
}
@@ -193,26 +214,28 @@ markup::define! {
ul.parents { @for (node, _) in *parents { li {
a.component[href=uri!(r_library_node(&node.slug))] { @node.title }
}}}
- @if node.media.is_some() { a.play[href=&uri!(r_player(&node.slug, PlayerConfig::default()))] { "Watch now" }}
+ @if node.media.is_some() {
+ a.play[href=&uri!(r_player(&node.slug, PlayerConfig::default()))] { @trs(lang, "node.player_link") }
+ }
@if !matches!(node.kind, NodeKind::Collection | NodeKind::Channel) {
@if matches!(udata.watched, WatchedState::None | WatchedState::Pending | WatchedState::Progress(_)) {
form.mark_watched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::Watched))] {
- input[type="submit", value="Mark Watched"];
+ input[type="submit", value=trs(lang, "node.watched.set")];
}
}
@if matches!(udata.watched, WatchedState::Watched) {
form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::None))] {
- input[type="submit", value="Mark Unwatched"];
+ input[type="submit", value=trs(lang, "node.watched.unset")];
}
}
@if matches!(udata.watched, WatchedState::None) {
form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::Pending))] {
- input[type="submit", value="Add to Watchlist"];
+ input[type="submit", value=trs(lang, "node.watchlist.set")];
}
}
@if matches!(udata.watched, WatchedState::Pending) {
form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::None))] {
- input[type="submit", value="Remove from Watchlist"];
+ input[type="submit", value=trs(lang, "node.watchlist.unset")];
}
}
form.rating[method="POST", action=uri!(r_node_userdata_rating(&node.slug))] {
@@ -222,14 +245,14 @@ markup::define! {
}
}
.details {
- @Props { node, udata, full: true }
+ @Props { node, udata, full: true, lang }
h3 { @node.tagline }
@if let Some(description) = &node.description {
p { @for line in description.lines() { @line br; } }
}
@if let Some(media) = &node.media {
@if !media.chapters.is_empty() {
- h2 { "Chapters" }
+ h2 { @trs(lang, "node.chapters") }
ul.children.hlist { @for chap in &media.chapters {
@let (inl, sub) = format_chapter(chap);
li { .card."aspect-thumb" {
@@ -244,7 +267,7 @@ markup::define! {
}}
}
@if !node.people.is_empty() {
- h2 { "Cast & Crew" }
+ h2 { @trs(lang, "node.people") }
@for (group, people) in &node.people {
details[open=group==&PeopleGroup::Cast] {
summary { h3 { @format!("{}", group) } }
@@ -270,7 +293,7 @@ markup::define! {
}
}
details {
- summary { "Tracks" }
+ summary { @trs(lang, "media.tracks") }
ol { @for track in &media.tracks {
li { @format!("{track}") }
}}
@@ -278,7 +301,7 @@ markup::define! {
}
@if !node.tags.is_empty() {
details {
- summary { "Tags" }
+ summary { @trs(lang, "node.tags") }
ol { @for tag in &node.tags {
li { @tag }
}}
@@ -286,30 +309,30 @@ markup::define! {
}
}
@if matches!(node.kind, NodeKind::Collection | NodeKind::Channel) {
- @NodeFilterSortForm { f: filter }
+ @NodeFilterSortForm { f: filter, lang }
}
@if !similar.is_empty() {
- h2 { "Similar Media" }
+ h2 { @trs(lang, "node.similar") }
ul.children.hlist {@for (node, udata) in similar.iter() {
- li { @NodeCard { node, udata } }
+ li { @NodeCard { node, udata, lang } }
}}
}
@match node.kind {
NodeKind::Show | NodeKind::Series | NodeKind::Season => {
ol { @for (node, udata) in children.iter() {
- li { @NodeCardWide { node, udata } }
+ li { @NodeCardWide { node, udata, lang } }
}}
}
NodeKind::Collection | NodeKind::Channel | _ => {
ul.children {@for (node, udata) in children.iter() {
- li { @NodeCard { node, udata } }
+ li { @NodeCard { node, udata, lang } }
}}
}
}
}
}
- Props<'a>(node: &'a Node, udata: &'a NodeUserData, full: bool) {
+ Props<'a>(node: &'a Node, udata: &'a NodeUserData, full: bool, lang: &'a Language) {
.props {
@if let Some(m) = &node.media {
p { @format_duration(m.duration) }
@@ -324,8 +347,8 @@ markup::define! {
}
@match node.visibility {
Visibility::Visible => {}
- Visibility::Reduced => {p.visibility{"Reduced visibility"}}
- Visibility::Hidden => {p.visibility{"Hidden"}}
+ Visibility::Reduced => {p.visibility{@trs(lang, "prop.vis.reduced")}}
+ Visibility::Hidden => {p.visibility{@trs(lang, "prop.vis.hidden")}}
}
// TODO
// @if !node.children.is_empty() {
@@ -349,9 +372,9 @@ markup::define! {
}
@match udata.watched {
WatchedState::None => {}
- WatchedState::Pending => { p.pending { "Watchlisted" } }
- WatchedState::Progress(x) => { p.progress { "Watched up to " @format_duration(x) } }
- WatchedState::Watched => { p.watched { "Watched" } }
+ WatchedState::Pending => { p.pending { @trs(lang, "prop.watched.pending") } }
+ WatchedState::Progress(x) => { p.progress { @trsa(lang, "prop.watched.progress", &[("time", &format_duration(x))]) } }
+ WatchedState::Watched => { p.watched { @trs(lang, "prop.watched.watched") } }
}
}
}
diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs
index 5222573..2bb439b 100644
--- a/server/src/routes/ui/player.rs
+++ b/server/src/routes/ui/player.rs
@@ -11,7 +11,10 @@ use super::{
};
use crate::{
database::Database,
- routes::ui::{error::MyResult, layout::DynLayoutPage},
+ routes::{
+ locale::AcceptLanguage,
+ ui::{error::MyResult, layout::DynLayoutPage},
+ },
};
use anyhow::anyhow;
use jellybase::CONF;
@@ -59,10 +62,12 @@ fn jellynative_url(action: &str, seek: f64, secret: &str, node: &str, session: &
#[get("/n/<id>/player?<conf..>", rank = 4)]
pub fn r_player(
session: Session,
+ lang: AcceptLanguage,
db: &State<Database>,
id: NodeID,
conf: PlayerConfig,
) -> MyResult<Either<DynLayoutPage<'_>, Redirect>> {
+ let AcceptLanguage(lang) = lang;
let (node, udata) = db.get_node_with_userdata(id, &session)?;
let mut parents = node
@@ -112,19 +117,27 @@ pub fn r_player(
// webm: Some(true),
// ..Default::default()
// };
+ // let playing = false; // !spec.track.is_empty();
+ // let conf = player_conf(node.clone(), playing)?;
- let playing = false; // !spec.track.is_empty();
-
- let conf = player_conf(node.clone(), playing)?;
Ok(Either::Left(LayoutPage {
title: node.title.to_owned().unwrap_or_default(),
class: Some("player"),
content: markup::new! {
- @if playing {
- // video[src=uri!(r_stream(&node.slug, &spec)), controls, preload="auto"]{}
+ // @if playing {
+ // // video[src=uri!(r_stream(&node.slug, &spec)), controls, preload="auto"]{}
+ // }
+ // @conf
+ @NodePage {
+ children: &[],
+ parents: &parents,
+ filter: &NodeFilterSort::default(),
+ node: &node,
+ udata: &udata,
+ player: true,
+ similar: &similar,
+ lang: &lang
}
- @NodePage { children: &[], parents: &parents, filter: &NodeFilterSort::default(), node: &node, udata: &udata, player: true, similar: &similar }
- @conf
},
}))
}
diff --git a/server/src/routes/ui/search.rs b/server/src/routes/ui/search.rs
index c5944ec..051dd24 100644
--- a/server/src/routes/ui/search.rs
+++ b/server/src/routes/ui/search.rs
@@ -9,7 +9,7 @@ use super::{
layout::{DynLayoutPage, LayoutPage},
node::{DatabaseNodeUserDataExt, NodeCard},
};
-use crate::routes::api::AcceptJson;
+use crate::routes::{api::AcceptJson, locale::AcceptLanguage};
use anyhow::anyhow;
use jellybase::database::Database;
use jellycommon::{api::ApiSearchResponse, Visibility};
@@ -23,7 +23,9 @@ pub async fn r_search<'a>(
aj: AcceptJson,
query: Option<&str>,
page: Option<usize>,
+ lang: AcceptLanguage
) -> MyResult<Either<DynLayoutPage<'a>, Json<ApiSearchResponse>>> {
+ let AcceptLanguage(lang)=lang;
let results = if let Some(query) = query {
let timing = Instant::now();
let (count, ids) = db.search(query, 32, page.unwrap_or_default() * 32)?;
@@ -58,7 +60,7 @@ pub async fn r_search<'a>(
h2 { "Results" }
p.stats { @format!("Found {count} nodes in {search_dur:?}.") }
ul.children {@for (node, udata) in results.iter() {
- li { @NodeCard { node, udata } }
+ li { @NodeCard { node, udata, lang: &lang } }
}}
// TODO pagination
}
diff --git a/server/src/routes/ui/sort.rs b/server/src/routes/ui/sort.rs
index 6bee2ef..4906c43 100644
--- a/server/src/routes/ui/sort.rs
+++ b/server/src/routes/ui/sort.rs
@@ -1,3 +1,4 @@
+use jellybase::locale::Language;
use jellycommon::{helpers::SortAnyway, user::NodeUserData, Node, NodeKind, Rating};
use markup::RenderAttributeValue;
use rocket::{
@@ -6,6 +7,8 @@ use rocket::{
};
use std::sync::Arc;
+use crate::routes::ui::layout::trs;
+
#[derive(FromForm, UriDisplayQuery, Default, Clone)]
pub struct NodeFilterSort {
pub sort_by: Option<SortProperty>,
@@ -64,22 +67,25 @@ impl SortProperty {
use SortProperty::*;
&[
(
- "General",
- &[(Title, "Title"), (ReleaseDate, "Release Date")],
+ "filter_sort.sort.general",
+ &[(Title, "node.title"), (ReleaseDate, "node.release_date")],
),
- ("Media", &[(Duration, "Runtime")]),
+ ("filter_sort.sort.media", &[(Duration, "media.runtime")]),
(
- "By Rating",
+ "filter_sort.sort.rating",
&[
- (RatingImdb, "IMDb Rating"),
- (RatingTmdb, "TMDB Rating"),
- (RatingMetacritic, "Metacritic Rating"),
- (RatingRottenTomatoes, "Rotten Tomatoes"),
- (RatingYoutubeFollowers, "Youtube Subscribers"),
- (RatingYoutubeLikes, "Youtube Likes"),
- (RatingYoutubeViews, "Youtube Views"),
- (RatingUser, "Your Rating"),
- (RatingLikesDivViews, "Likes per view"),
+ (RatingImdb, "rating.imdb"),
+ (RatingTmdb, "rating.tmdb"),
+ (RatingMetacritic, "rating.metacritic"),
+ (RatingRottenTomatoes, "rating.rotten_tomatoes"),
+ (RatingYoutubeFollowers, "rating.youtube_followers"),
+ (RatingYoutubeLikes, "rating.youtube_likes"),
+ (RatingYoutubeViews, "rating.youtube_views"),
+ (RatingUser, "filter_sort.sort.rating.user"),
+ (
+ RatingLikesDivViews,
+ "filter_sort.sort.rating.likes_div_views",
+ ),
],
),
]
@@ -90,30 +96,30 @@ impl FilterProperty {
use FilterProperty::*;
&[
(
- "By Kind",
+ "filter_sort.filter.kind",
&[
- (KindMovie, "Movie"),
- (KindVideo, "Video"),
- (KindShortFormVideo, "Short Form Video"),
- (KindMusic, "Music"),
- (KindCollection, "Collection"),
- (KindChannel, "Channel"),
- (KindShow, "Show"),
- (KindSeries, "Series"),
- (KindSeason, "Season"),
- (KindEpisode, "Episode"),
+ (KindMovie, "kind.movie"),
+ (KindVideo, "kind.video"),
+ (KindShortFormVideo, "kind.short_form_video"),
+ (KindMusic, "kind.music"),
+ (KindCollection, "kind.collection"),
+ (KindChannel, "kind.channel"),
+ (KindShow, "kind.show"),
+ (KindSeries, "kind.series"),
+ (KindSeason, "kind.season"),
+ (KindEpisode, "kind.episode"),
],
),
(
- "By Federation",
+ "filter_sort.filter.federation",
&[(FederationLocal, "Local"), (FederationRemote, "Remote")],
),
(
- "By Watched",
+ "filter_sort.filter.watched",
&[
- (Watched, "Watched"),
- (Unwatched, "Unwatched"),
- (WatchProgress, "Partially Watched"),
+ (Watched, "watched.watched"),
+ (Unwatched, "watched.none"),
+ (WatchProgress, "watched.progress"),
],
),
]
@@ -221,7 +227,7 @@ pub fn filter_and_sort_nodes(
}
markup::define! {
- NodeFilterSortForm<'a>(f: &'a NodeFilterSort) {
+ NodeFilterSortForm<'a>(f: &'a NodeFilterSort, lang: &'a Language) {
details.filtersort[open=f.is_open()] {
summary { "Filter and Sort" }
form[method="GET", action=""] {
@@ -232,7 +238,7 @@ markup::define! {
.category {
h3 { @cname }
@for (value, label) in *cat {
- label { input[type="checkbox", name="filter_kind", value=value, checked=f.filter_kind.as_ref().map(|k|k.contains(value)).unwrap_or(true)]; @label } br;
+ label { input[type="checkbox", name="filter_kind", value=value, checked=f.filter_kind.as_ref().map(|k|k.contains(value)).unwrap_or(true)]; @trs(lang, label) } br;
}
}
}
@@ -245,7 +251,7 @@ markup::define! {
.category {
h3 { @cname }
@for (value, label) in *cat {
- label { input[type="radio", name="sort_by", value=value, checked=Some(value)==f.sort_by.as_ref()]; @label } br;
+ label { input[type="radio", name="sort_by", value=value, checked=Some(value)==f.sort_by.as_ref()]; @trs(lang, label) } br;
}
}
}
@@ -255,7 +261,7 @@ markup::define! {
legend { "Sort Order" }
@use SortOrder::*;
@for (value, label) in [(Ascending, "Ascending"), (Descending, "Descending")] {
- label { input[type="radio", name="sort_order", value=value, checked=Some(value)==f.sort_order]; @label } br;
+ label { input[type="radio", name="sort_order", value=value, checked=Some(value)==f.sort_order]; @trs(lang, label) } br;
}
}
input[type="submit", value="Apply"]; a[href="?"] { "Clear" }
diff --git a/server/src/routes/ui/stats.rs b/server/src/routes/ui/stats.rs
index 927d85c..07da1d4 100644
--- a/server/src/routes/ui/stats.rs
+++ b/server/src/routes/ui/stats.rs
@@ -12,12 +12,14 @@ use crate::{
database::Database,
routes::{
api::AcceptJson,
- ui::node::{
+ locale::AcceptLanguage,
+ ui::{layout::trs, node::{
format_duration, format_duration_long, format_size, rocket_uri_macro_r_library_node,
- },
+ }},
},
uri,
};
+use jellybase::locale::tr;
use jellycommon::{Node, NodeID, NodeKind, Visibility};
use rocket::{get, serde::json::Json, Either, State};
use serde::Serialize;
@@ -29,7 +31,9 @@ pub fn r_stats(
sess: Session,
db: &State<Database>,
aj: AcceptJson,
+ lang: AcceptLanguage,
) -> Result<Either<DynLayoutPage<'_>, Json<Value>>, MyError> {
+ let AcceptLanguage(lang) = lang;
let mut items = db.list_nodes_with_udata(sess.user.name.as_str())?;
items.retain(|(n, _)| matches!(n.visibility, Visibility::Visible));
@@ -77,25 +81,25 @@ pub fn r_stats(
})))
} else {
Either::Left(LayoutPage {
- title: "Library Statistics".to_owned(),
+ title: tr(lang, "stats.title", &[]).to_string(),
content: markup::new! {
.page.stats {
- h1 { "Library Statistics" }
+ h1 { @trs(&lang, "stats.title") }
p { "There is a total of " b{@all.count} " nodes in the library." }
p { "The total runtime of the library is " b{@format_duration_long(all.runtime)} ", taking up " b{@format_size(all.size)} " of disk space." }
p { "An average node has a runtime of " b{@format_duration(all.average_runtime())} " and file size of " b{@format_size(all.average_size() as u64)} "." }
- h2 { "Grouped by Kind" }
+ h2 { @trs(&lang, "stats.by_kind.title") }
table.striped {
tr {
- th { "Kind" }
- th { "Count" }
- th { "Storage Size" }
- th { "Media Runtime" }
- th { "Average Size" }
- th { "Average Runtime" }
- th { "Largest File" }
- th { "Longest Runtime" }
+ th { @trs(&lang, "stats.by_kind.kind") }
+ th { @trs(&lang, "stats.by_kind.count") }
+ th { @trs(&lang, "stats.by_kind.total_size") }
+ th { @trs(&lang, "stats.by_kind.total_runtime") }
+ th { @trs(&lang, "stats.by_kind.average_size") }
+ th { @trs(&lang, "stats.by_kind.average_runtime") }
+ th { @trs(&lang, "stats.by_kind.max_size") }
+ th { @trs(&lang, "stats.by_kind.max_runtime") }
}
@for (k,b) in &kinds { tr {
td { @format!("{k:?}") }