diff options
author | Colin Reeder <colin@vpzom.click> | 2020-07-21 17:29:37 -0600 |
---|---|---|
committer | Colin Reeder <colin@vpzom.click> | 2020-07-21 17:29:47 -0600 |
commit | 207d06ae4118bf42828db2300cee74c0a42237ba (patch) | |
tree | 02ab7a29b77914a93356e3142673c11697f7791f | |
parent | ff37f13ba8d0cccd8c06db9b52001cd6388e029e (diff) |
Initial work on translation support
-rw-r--r-- | Cargo.lock | 170 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | res/lang/en.flt | 66 | ||||
-rw-r--r-- | res/lang/eo.flt | 66 | ||||
-rw-r--r-- | src/components/mod.rs | 52 | ||||
-rw-r--r-- | src/main.rs | 85 | ||||
-rw-r--r-- | src/routes/communities.rs | 140 | ||||
-rw-r--r-- | src/routes/mod.rs | 257 | ||||
-rw-r--r-- | src/routes/posts.rs | 57 |
9 files changed, 713 insertions, 184 deletions
@@ -34,6 +34,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] name = "bytes" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -100,6 +106,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] +name = "fluent" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3b6132d1377d8776409a337c6851d342aee4e85277c96ecd2755c4e0efde1d" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a094d494ab2ed06077e9a95f4e47f446c376de95f6c93045dd88c499bfcd70" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rental", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac0f7e83d14cccbf26e165d8881dcac5891af0d85a88543c09dd72ebd31d91ba" + +[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -192,6 +238,15 @@ dependencies = [ ] [[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] name = "getrandom" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -237,10 +292,13 @@ dependencies = [ "ammonia", "chrono", "fallible-iterator", + "fluent", + "fluent-langneg", "ginger", "http", "hyper", "hyper-tls", + "lazy_static", "render", "serde", "serde_derive", @@ -249,6 +307,7 @@ dependencies = [ "timeago", "tokio", "trout", + "unic-langid", "urlencoding", ] @@ -351,6 +410,26 @@ dependencies = [ ] [[package]] +name = "intl-memoizer" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0ed58ba6089d49f8a9a7d5e16fc9b9e2019cdf40ef270f3d465fa244d9630b" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c271cdb1f12a9feb3a017619c3ee681f971f270f6757341d6abe1f9f7a98bc3" +dependencies = [ + "tinystr", + "unic-langid", +] + +[[package]] name = "iovec" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -736,6 +815,12 @@ dependencies = [ ] [[package]] +name = "proc-macro-hack" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" + +[[package]] name = "proc-macro2" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -954,6 +1039,27 @@ dependencies = [ ] [[package]] +name = "rental" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8545debe98b2b139fb04cad8618b530e9b07c152d99a5de83c860b877d67847f" +dependencies = [ + "rental-impl", + "stable_deref_trait", +] + +[[package]] +name = "rental-impl" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1069,6 +1175,12 @@ dependencies = [ ] [[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] name = "string_cache" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1181,6 +1293,12 @@ dependencies = [ ] [[package]] +name = "tinystr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bac79c4b51eda1b090b1edebfb667821bbb51f713855164dc7cec2cb8ac2ba3" + +[[package]] name = "tokio" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1256,6 +1374,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" [[package]] +name = "type-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2741b1474c327d95c1f1e3b0a2c3977c8e128409c572a33af2914e7d636717" +dependencies = [ + "fxhash", +] + +[[package]] +name = "unic-langid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" +dependencies = [ + "unic-langid-impl", + "unic-langid-macros", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" +dependencies = [ + "tinystr", +] + +[[package]] +name = "unic-langid-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f980d6d87e8805f2836d64b4138cc95aa7986fa63b1f51f67d5fbff64dd6e5" +dependencies = [ + "proc-macro-hack", + "tinystr", + "unic-langid-impl", + "unic-langid-macros-impl", +] + +[[package]] +name = "unic-langid-macros-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29396ffd97e27574c3e01368b1a64267d3064969e4848e2e130ff668be9daa9f" +dependencies = [ + "proc-macro-hack", + "quote", + "syn", + "unic-langid-impl", +] + +[[package]] name = "unicode-bidi" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -24,3 +24,7 @@ urlencoding = "1.1.1" http = "0.2.1" chrono = "0.4.13" timeago = "0.2.1" +fluent-langneg = "0.13.0" +fluent = "0.12.0" +lazy_static = "1.4.0" +unic-langid = { version = "0.9.0", features = ["macros"] } diff --git a/res/lang/en.flt b/res/lang/en.flt new file mode 100644 index 0000000..51b9d78 --- /dev/null +++ b/res/lang/en.flt @@ -0,0 +1,66 @@ +about = About +about_title = About this instance +about_what_is = What is lotide? +about_text1 = lotide is an attempt to build a federated forum. Users can create communities to share links and text posts and discuss them with other users, include those registered on other servers through +about_text2 = For more information or to view the source code, check out the +about_sourcehut = SourceHut page +about_versions = This instance is running hitide { $hitide_version } on { $backend_name } { $backend_version }. +add_by_remote_id = Add by ID: +all = All +all_title = The Whole Known Network +by = by +comment = Comment +comment_delete_title = Delete Comment +comment_delete_question = Delete this comment? +comment_submit = Post Comment +communities = Communities +community_create = Create Community +community_create_submit = Create +community_edit = Customize Community +community_edit_link = Customize +community_remote_note = This is a remote community, information on this page may be incomplete. +delete = delete +delete_yes = Yes, delete +description = Description +edit = Edit +fetch = Fetch +follow = Follow +follow_request_sent = Follow request sent! +follow_undo = Unfollow +home_follow_prompt1 = Why not +home_follow_prompt2 = follow some communities? +like = Like +like_undo = Unlike +local = Local +login = Login +login_signup_link = create a new account +lookup_nothing = Nothing found. +lookup_title = Lookup +name_prompt = Name: +no_cancel = No, cancel +nothing = Looks like there's nothing here. +nothing_yet = Looks like there's nothing here (yet!). +on = on +or_start = Or +password_prompt = Password: +post_delete_question = Delete this post? +post_delete_title = Delete Post +post_new = New Post +register = Register +remote = Remote +reply = reply +reply_submit = Reply +submit = Submit +submitted = Submitted +text_with_markdown = Text (markdown supported) +title = Title +to = to +url = URL +user_edit_description_prompt = Profile Description: +user_edit_not_you = You can only edit your own profile. +user_edit_submit = Save +user_edit_title = Edit Profile +user_remote_note = This is a remote user, information on this page may be incomplete. +username_prompt = Username: +view_at_source = View at Source +view_more_comments = View More Comments diff --git a/res/lang/eo.flt b/res/lang/eo.flt new file mode 100644 index 0000000..fe01c53 --- /dev/null +++ b/res/lang/eo.flt @@ -0,0 +1,66 @@ +about = Pri +about_title = Pri ĉi tiu servilo +about_what_is = Kio estas lotide? +about_text1 = lotide estas provo konstrui federacian forumon. Uzantoj povas krei komunumojn por disdoni ligilojn kaj tekstpoŝtojn kaj diskuti ilin kun aliaj uzantoj, inkluzive de tiuj en aliaj serviloj per +about_text2 = Por pli da informo aŭ vidi la fontkodon, kontrolu la +about_sourcehut = SourceHut paĝon +about_versions = Ĉi tiu servilo uzas hitide { $hitide_version } kun { $backend_name } { $backend_version }. +add_by_remote_id = Aldoni per ID: +all = Ĉiuj +all_title = La Tuta Konata Reto +by = de +comment = Komento +comment_delete_title = Forigi Komenton +comment_delete_question = Ĉu vi volas forigi ĉi tiun komenton? +comment_submit = Afiŝi Komenton +communities = Komunumoj +community_create = Krei Komunumon +community_create_submit = Krei +community_edit = Agordi Komunumon +community_edit_link = Agordi +community_remote_note = Ĉi tiu estas fora komunumo, informo en ĉi tiu paĝo eble neplenas. +delete = forigi +delete_yes = Jes, forigi +description = Priskribo +edit = Redakti +fetch = Alporti +follow = Aboni +follow_request_sent = Abonado peto senditas! +follow_undo = Ne plu aboni +home_follow_prompt1 = Kial ne +home_follow_prompt2 = aboni iujn komunumojn? +like = Ŝati +like_undo = Ne plu ŝati +local = Loka +login = Ensaluti +login_signup_link = krei novan konton +lookup_nothing = Nenio troveblas. +lookup_title = Serĉi +name_prompt = Nomo: +no_cancel = Ne, nuligi +nothing = Ŝajnas, ke estas nenio ĉi tie. +nothing_yet = Ŝajnas, ke estas nenio ĉi tie (ĝis nun!). +on = sur +or_start = Aŭ +password_prompt = Pasvorto: +post_delete_question = Ĉu vi volas forigi ĉi tiun poŝton? +post_delete_title = Forigi Poŝton +post_new = Nova Poŝto +register = Registriĝi +remote = Fora +reply = respondi +reply_submit = Respondi +submit = Sendi +submitted = Afiŝita +text_with_markdown = Teksto (markdown estas permesita) +title = Titolo +to = al +url = URL +user_edit_description_prompt = Priskribo de Profilo +user_edit_not_you = Vi nur rajtas redakti vian propran profilon. +user_edit_submit = Konservi +user_edit_title = Redakti Profilon +user_remote_note = Ĉi tiu estas fora uzanto, informo en ĉi tiu paĝo eble neplenas. +username_prompt = Uzantnomo: +view_at_source = Vidi ĉe Fonto +view_more_comments = Vidi Pli da Komentoj diff --git a/src/components/mod.rs b/src/components/mod.rs index f6b5785..b43b43b 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -9,9 +9,10 @@ use crate::util::{abbreviate_link, author_is_me}; use crate::PageBaseData; #[render::component] -pub fn Comment<'comment, 'base_data>( - comment: &'comment RespPostCommentInfo<'comment>, - base_data: &'base_data PageBaseData, +pub fn Comment<'a>( + comment: &'a RespPostCommentInfo<'a>, + base_data: &'a PageBaseData, + lang: &'a crate::Translator, ) { render::rsx! { <li> @@ -30,18 +31,18 @@ pub fn Comment<'comment, 'base_data>( if comment.your_vote.is_some() { render::rsx! { <form method={"POST"} action={format!("/comments/{}/unlike", comment.id)}> - <button type={"submit"}>{"Unlike"}</button> + <button type={"submit"}>{lang.tr("like_undo", None)}</button> </form> } } else { render::rsx! { <form method={"POST"} action={format!("/comments/{}/like", comment.id)}> - <button type={"submit"}>{"Like"}</button> + <button type={"submit"}>{lang.tr("like", None)}</button> </form> } } } - <a href={format!("/comments/{}", comment.id)}>{"reply"}</a> + <a href={format!("/comments/{}", comment.id)}>{lang.tr("reply", None)}</a> </> }) } else { @@ -51,7 +52,7 @@ pub fn Comment<'comment, 'base_data>( { if author_is_me(&comment.author, &base_data.login) { Some(render::rsx! { - <a href={format!("/comments/{}/delete", comment.id)}>{"delete"}</a> + <a href={format!("/comments/{}/delete", comment.id)}>{lang.tr("delete", None)}</a> }) } else { None @@ -66,7 +67,7 @@ pub fn Comment<'comment, 'base_data>( { replies.iter().map(|reply| { render::rsx! { - <Comment comment={reply} base_data /> + <Comment comment={reply} base_data lang /> } }) .collect::<Vec<_>>() @@ -80,7 +81,7 @@ pub fn Comment<'comment, 'base_data>( { if comment.replies.is_none() && comment.has_replies { Some(render::rsx! { - <ul><li><a href={format!("/comments/{}", comment.id)}>{"-> View More Comments"}</a></li></ul> + <ul><li><a href={format!("/comments/{}", comment.id)}>{"-> "}{lang.tr("view_more_comments", None)}</a></li></ul> }) } else { None @@ -174,6 +175,7 @@ impl<'a, T: HavingContent + 'a> render::Render for Content<'a, T> { #[render::component] pub fn HTPage<'a, Children: render::Render>( base_data: &'a PageBaseData, + lang: &'a crate::Translator, title: &'a str, children: Children, ) { @@ -190,19 +192,19 @@ pub fn HTPage<'a, Children: render::Render>( <header class={"mainHeader"}> <div class={"left actionList"}> <a href={"/"} class={"siteName"}>{"lotide"}</a> - <a href={"/all"}>{"All"}</a> - <a href={"/communities"}>{"Communities"}</a> - <a href={"/about"}>{"About"}</a> + <a href={"/all"}>{lang.tr("all", None)}</a> + <a href={"/communities"}>{lang.tr("communities", None)}</a> + <a href={"/about"}>{lang.tr("about", None)}</a> </div> <div class={"right actionList"}> { match &base_data.login { Some(login) => Some(render::rsx! { - <a href={format!("/users/{}", login.user.id)}>{"👤︎"}</a> + <a href={format!("/users/{}", login.user.id)}>{Cow::Borrowed("👤︎")}</a> }), None => { Some(render::rsx! { - <a href={"/login"}>{"Login"}</a> + <a href={"/login"}>{lang.tr("login", None)}</a> }) } } @@ -217,7 +219,12 @@ pub fn HTPage<'a, Children: render::Render>( } #[render::component] -pub fn PostItem<'post>(post: &'post RespPostListPost<'post>, in_community: bool, no_user: bool) { +pub fn PostItem<'a>( + post: &'a RespPostListPost<'a>, + in_community: bool, + no_user: bool, + lang: &'a crate::Translator, +) { render::rsx! { <li> <a href={format!("/posts/{}", post.as_ref().id)}> @@ -236,14 +243,14 @@ pub fn PostItem<'post>(post: &'post RespPostListPost<'post>, in_community: bool, } } <br /> - {"Submitted"} + {lang.tr("submitted", None)} { if no_user { None } else { Some(render::rsx! { <> - {" by "}<UserLink user={post.author.as_ref()} /> + {" "}{lang.tr("by", None)}{" "}<UserLink user={post.author.as_ref()} /> </> }) } @@ -251,7 +258,7 @@ pub fn PostItem<'post>(post: &'post RespPostListPost<'post>, in_community: bool, { if !in_community { Some(render::rsx! { - <>{" to "}<CommunityLink community={&post.community} /></> + <>{" "}{lang.tr("to", None)}{" "}<CommunityLink community={&post.community} /></> }) } else { None @@ -262,21 +269,24 @@ pub fn PostItem<'post>(post: &'post RespPostListPost<'post>, in_community: bool, } pub struct ThingItem<'a> { + pub lang: &'a crate::Translator, pub thing: &'a RespThingInfo<'a>, } impl<'a> render::Render for ThingItem<'a> { fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result { + let lang = self.lang; + match self.thing { RespThingInfo::Post(post) => { - (PostItem { post, in_community: false, no_user: true }).render_into(writer) + (PostItem { post, in_community: false, no_user: true, lang: self.lang }).render_into(writer) }, RespThingInfo::Comment(comment) => { (render::rsx! { <li> <small> - <a href={format!("/comments/{}", comment.id)}>{"Comment"}</a> - {" on "}<a href={format!("/posts/{}", comment.post.id)}>{comment.post.title.as_ref()}</a>{":"} + <a href={format!("/comments/{}", comment.id)}>{lang.tr("comment", None)}</a> + {" "}{lang.tr("on", None)}{" "}<a href={format!("/posts/{}", comment.post.id)}>{comment.post.title.as_ref()}</a>{":"} </small> <Content src={comment} /> </li> diff --git a/src/main.rs b/src/main.rs index 2e23682..d513f20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ #![allow(unused_braces)] use crate::resp_types::RespLoginInfo; +use std::borrow::Cow; +use std::collections::HashMap; use std::sync::Arc; use trout::hyper::RoutingFailureExtHyper; @@ -54,6 +56,89 @@ pub fn simple_response( res } +lazy_static::lazy_static! { + static ref LANG_MAP: HashMap<unic_langid::LanguageIdentifier, fluent::FluentResource> = { + let mut result = HashMap::new(); + + result.insert(unic_langid::langid!("en"), fluent::FluentResource::try_new(include_str!("../res/lang/en.flt").to_owned()).expect("Failed to parse translation")); + result.insert(unic_langid::langid!("eo"), fluent::FluentResource::try_new(include_str!("../res/lang/eo.flt").to_owned()).expect("Failed to parse translation")); + + result + }; + + static ref LANGS: Vec<unic_langid::LanguageIdentifier> = { + LANG_MAP.keys().cloned().collect() + }; +} + +pub struct Translator { + bundle: fluent::concurrent::FluentBundle<&'static fluent::FluentResource>, +} +impl Translator { + pub fn tr<'a>(&'a self, key: &str, args: Option<&'a fluent::FluentArgs>) -> Cow<'a, str> { + let mut errors = Vec::with_capacity(0); + let out = self.bundle.format_pattern( + self.bundle + .get_message(key) + .expect("Missing message in translation") + .value + .expect("Missing value for translation key"), + args, + &mut errors, + ); + if !errors.is_empty() { + eprintln!("Errors in translation: {:?}", errors); + } + + out + } +} +impl std::fmt::Debug for Translator { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Translator") + } +} + +pub fn get_lang_for_headers(headers: &hyper::header::HeaderMap) -> Translator { + let default = unic_langid::langid!("en"); + let languages = match headers + .get(hyper::header::ACCEPT_LANGUAGE) + .and_then(|x| x.to_str().ok()) + { + Some(accept_language) => { + let requested = fluent_langneg::accepted_languages::parse(accept_language); + fluent_langneg::negotiate_languages( + &requested, + &LANGS, + Some(&default), + fluent_langneg::NegotiationStrategy::Filtering, + ) + } + None => vec![&default], + }; + + let mut bundle = fluent::concurrent::FluentBundle::new(languages.iter().map(|x| *x)); + for lang in languages { + if let Err(errors) = bundle.add_resource(&LANG_MAP[lang]) { + for err in errors { + match err { + fluent::FluentError::Overriding { .. } => {} + _ => { + eprintln!("Failed to add language resource: {:?}", err); + break; + } + } + } + } + } + + Translator { bundle } +} + +pub fn get_lang_for_req(req: &hyper::Request<hyper::Body>) -> Translator { + get_lang_for_headers(req.headers()) +} + #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let backend_host = std::env::var("BACKEND_HOST").expect("Missing BACKEND_HOST"); diff --git a/src/routes/communities.rs b/src/routes/communities.rs index e4b1f13..442b958 100644 --- a/src/routes/communities.rs +++ b/src/routes/communities.rs @@ -3,8 +3,8 @@ use crate::resp_types::{ RespCommunityInfoMaybeYour, RespMinimalCommunityInfo, RespPostListPost, RespYourFollow, }; use crate::routes::{ - fetch_base_data, get_cookie_map, get_cookie_map_for_headers, get_cookie_map_for_req, - html_response, res_to_error, with_auth, CookieMap, + fetch_base_data, for_client, get_cookie_map_for_headers, get_cookie_map_for_req, html_response, + res_to_error, CookieMap, }; use serde_derive::Deserialize; use std::collections::HashMap; @@ -15,8 +15,10 @@ async fn page_communities( ctx: Arc<crate::RouteContext>, req: hyper::Request<hyper::Body>, ) -> Result<hyper::Response<hyper::Body>, crate::Error> { + let lang = crate::get_lang_for_req(&req); let cookies = get_cookie_map_for_req(&req)?; - let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, &cookies).await?; + let base_data = + fetch_base_data(&ctx.backend_host, &ctx.http_client, req.headers(), &cookies).await?; let api_res = res_to_error( ctx.http_client @@ -30,14 +32,16 @@ async fn page_communities( let api_res = hyper::body::to_bytes(api_res.into_body()).await?; let communities: Vec<RespMinimalCommunityInfo> = serde_json::from_slice(&api_res)?; + let title = lang.tr("communities", None); + Ok(html_response(render::html! { - <HTPage base_data={&base_data} title={"Communities"}> - <h1>{"Communities"}</h1> + <HTPage base_data={&base_data} lang={&lang} title={&title}> + <h1>{title.as_ref()}</h1> <div> - <h2>{"Local"}</h2> + <h2>{lang.tr("local", None)}</h2> { if base_data.login.is_some() { - Some(render::rsx! { <a href={"/new_community"}>{"Create Community"}</a> }) + Some(render::rsx! { <a href={"/new_community"}>{lang.tr("community_create", None)}</a> }) } else { None } @@ -56,14 +60,14 @@ async fn page_communities( </ul> </div> <div> - <h2>{"Remote"}</h2> + <h2>{lang.tr("remote", None)}</h2> <form method={"GET"} action={"/lookup"}> <label> - {"Add by ID: "} + {lang.tr("add_by_remote_id", None)}{" "} <input r#type={"text"} name={"query"} placeholder={"group@example.com"} /> </label> {" "} - <button r#type={"submit"}>{"Fetch"}</button> + <button r#type={"submit"}>{lang.tr("fetch", None)}</button> </form> <ul> { @@ -89,15 +93,17 @@ async fn page_community( ) -> Result<hyper::Response<hyper::Body>, crate::Error> { let (community_id,) = params; + let lang = crate::get_lang_for_req(&req); let cookies = get_cookie_map_for_req(&req)?; // TODO parallelize requests - let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, &cookies).await?; + let base_data = + fetch_base_data(&ctx.backend_host, &ctx.http_client, req.headers(), &cookies).await?; let community_info_api_res = res_to_error( ctx.http_client - .request(with_auth( + .request(for_client( hyper::Request::get(format!( "{}/api/unstable/communities/{}{}", ctx.backend_host, @@ -109,6 +115,7 @@ async fn page_community( }, )) .body(Default::default())?, + req.headers(), &cookies, )?) .await?, @@ -121,12 +128,13 @@ async fn page_community( let posts_api_res = res_to_error( ctx.http_client - .request(with_auth( + .request(for_client( hyper::Request::get(format!( "{}/api/unstable/communities/{}/posts", ctx.backend_host, community_id )) .body(Default::default())?, + req.headers(), &cookies, )?) .await?, @@ -141,7 +149,7 @@ async fn page_community( let title = community_info.as_ref().name.as_ref(); Ok(html_response(render::html! { - <HTPage base_data={&base_data} title> + <HTPage base_data={&base_data} lang={&lang} title> <div class={"communitySidebar"}> <h2>{title}</h2> <div><em>{format!("@{}@{}", community_info.as_ref().name, community_info.as_ref().host)}</em></div> @@ -151,8 +159,9 @@ async fn page_community( } else if let Some(remote_url) = &community_info.as_ref().remote_url { Some(render::rsx! { <div class={"infoBox"}> - {"This is a remote community, information on this page may be incomplete. "} - <a href={remote_url.as_ref()}>{"View at Source ↗"}</a> + {lang.tr("community_remote_note", None)} + {" "} + <a href={remote_url.as_ref()}>{lang.tr("view_at_source", None)}{" ↗"}</a> </div> }) } else { @@ -166,21 +175,21 @@ async fn page_community( Some(RespYourFollow { accepted: true }) => { render::rsx! { <form method={"POST"} action={format!("/communities/{}/unfollow", community_id)}> - <button type={"submit"}>{"Unfollow"}</button> + <button type={"submit"}>{lang.tr("follow_undo", None)}</button> </form> } }, Some(RespYourFollow { accepted: false }) => { render::rsx! { <form> - <button disabled={""}>{"Follow request sent!"}</button> + <button disabled={""}>{lang.tr("follow_request_sent", None)}</button> </form> } }, None => { render::rsx! { <form method={"POST"} action={format!("/communities/{}/follow", community_id)}> - <button type={"submit"}>{"Follow"}</button> + <button type={"submit"}>{lang.tr("follow", None)}</button> </form> } } @@ -191,13 +200,13 @@ async fn page_community( } </p> <p> - <a href={&new_post_url}>{"New Post"}</a> + <a href={&new_post_url}>{lang.tr("post_new", None)}</a> </p> { if community_info.you_are_moderator == Some(true) { Some(render::rsx! { <p> - <a href={format!("/communities/{}/edit", community_id)}>{"Customize"}</a> + <a href={format!("/communities/{}/edit", community_id)}>{lang.tr("community_edit_link", None)}</a> </p> }) } else { @@ -208,14 +217,14 @@ async fn page_community( </div> { if posts.is_empty() { - Some(render::rsx! { <p>{"Looks like there's nothing here."}</p> }) + Some(render::rsx! { <p>{lang.tr("nothing", None)}</p> }) } else { None } } <ul> {posts.iter().map(|post| { - PostItem { post, in_community: true, no_user: false } + PostItem { post, in_community: true, no_user: false, lang: &lang } }).collect::<Vec<_>>()} </ul> </HTPage> @@ -231,26 +240,29 @@ async fn page_community_edit( let cookies = get_cookie_map_for_req(&req)?; - page_community_edit_inner(community_id, &cookies, ctx, None, None).await + page_community_edit_inner(community_id, req.headers(), &cookies, ctx, None, None).await } async fn page_community_edit_inner( community_id: i64, + headers: &hyper::header::HeaderMap, cookies: &CookieMap<'_>, ctx: Arc<crate::RouteContext>, display_error: Option<String>, prev_values: Option<&HashMap<&str, serde_json::Value>>, ) -> Result<hyper::Response<hyper::Body>, crate::Error> { - let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, &cookies).await?; + let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, headers, &cookies).await?; + let lang = crate::get_lang_for_headers(headers); let community_info_api_res = res_to_error( ctx.http_client - .request(with_auth( + .request(for_client( hyper::Request::get(format!( "{}/api/unstable/communities/{}", ctx.backend_host, community_id, )) .body(Default::default())?, + he |