diff options
author | metamuffin <metamuffin@disroot.org> | 2025-04-20 16:51:44 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-04-20 16:51:44 +0200 |
commit | 646b7663a98a64556fd179137c4e8f55f037b0ca (patch) | |
tree | b53ea3a312f677b57096d8cda0c94e6ad8ad47fe /server | |
parent | 08f067aa1d0c1c1cec072dc73d4b4c04ce135b01 (diff) | |
download | jellything-646b7663a98a64556fd179137c4e8f55f037b0ca.tar jellything-646b7663a98a64556fd179137c4e8f55f037b0ca.tar.bz2 jellything-646b7663a98a64556fd179137c4e8f55f037b0ca.tar.zst |
more translation
Diffstat (limited to 'server')
-rw-r--r-- | server/src/routes/ui/browser.rs | 20 | ||||
-rw-r--r-- | server/src/routes/ui/home.rs | 4 | ||||
-rw-r--r-- | server/src/routes/ui/layout.rs | 16 | ||||
-rw-r--r-- | server/src/routes/ui/node.rs | 81 | ||||
-rw-r--r-- | server/src/routes/ui/player.rs | 29 | ||||
-rw-r--r-- | server/src/routes/ui/search.rs | 6 | ||||
-rw-r--r-- | server/src/routes/ui/sort.rs | 72 | ||||
-rw-r--r-- | server/src/routes/ui/stats.rs | 30 |
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:?}") } |