M Cargo.lock => Cargo.lock +2 -2
@@ 1132,7 1132,7 @@ checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
[[package]]
name = "render"
version = "0.3.1"
-source = "git+https://github.com/vpzomtrrfrt/render.rs?rev=ec18ff5f#ec18ff5f013e4d448ff59c5dffb2c11fb6668bc2"
+source = "git+https://github.com/vpzomtrrfrt/render.rs?branch=for-hitide#19750310ed42eb978da944b0ee206731dad293c4"
dependencies = [
"render_macros",
]
@@ 1140,7 1140,7 @@ dependencies = [
[[package]]
name = "render_macros"
version = "0.3.1"
-source = "git+https://github.com/vpzomtrrfrt/render.rs?rev=ec18ff5f#ec18ff5f013e4d448ff59c5dffb2c11fb6668bc2"
+source = "git+https://github.com/vpzomtrrfrt/render.rs?branch=for-hitide#19750310ed42eb978da944b0ee206731dad293c4"
dependencies = [
"proc-macro-error",
"proc-macro2",
M Cargo.toml => Cargo.toml +1 -1
@@ 11,7 11,7 @@ license = "AGPL-3.0-or-later"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-render = { git = "https://github.com/vpzomtrrfrt/render.rs", rev = "ec18ff5f" }
+render = { git = "https://github.com/vpzomtrrfrt/render.rs", branch = "for-hitide" }
trout = "0.4.0"
hyper = "0.13.6"
hyper-tls = "0.4.1"
M res/lang/en.ftl => res/lang/en.ftl +7 -7
@@ 2,9 2,9 @@ a_comment = a comment
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_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, including those registered on other servers through { $part_activitypub }.
+about_text2 = For more information or to view the source code, check out the { $part_sourcehut }.
+about_text2_part_sourcehut = SourceHut page
about_versions = This instance is running hitide { $hitide_version } on { $backend_name } { $backend_version }.
action_flag = flag
add = Add
@@ 77,8 77,8 @@ forgot_password_complete = Password successfully reset.
forgot_password_email_prompt = Email Address:
forgot_password_info = If your account has an attached email address, you can reset your password here.
forgot_password_new_password_prompt = New Password:
-home_follow_prompt1 = Why not
-home_follow_prompt2 = follow some communities?
+home_follow_prompt = Why not { $part_follow }?
+home_follow_prompt_part_follow = follow some communities
invite_users = Invite Users
invitation_already_used = That invitation has already been used
liked_by = Liked by:
@@ 87,7 87,8 @@ local = Local
local_title = Posts from Local Communities
local_user_name_prompt = Local User Name:
login = Login
-login_signup_link = create a new account
+login_signup = Or { $part_signup }
+login_signup_part_signup = create a new account
logout = Log Out
lookup_nothing = Nothing found.
lookup_title = Lookup
@@ 122,7 123,6 @@ notification_post_mention = You were mentioned in a post:
on = on
on_your_post = on your post
open_menu = Open Menu
-or_start = Or
poll_new_closes_prompt = Closes in:
poll_new_multiple = Allow multiple choices
poll_new_options_prompt = Options:
M src/components/mod.rs => src/components/mod.rs +7 -7
@@ 178,7 178,7 @@ pub struct CommunityLink<'community> {
pub community: &'community RespMinimalCommunityInfo<'community>,
}
impl<'community> render::Render for CommunityLink<'community> {
- fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
+ fn render_into<W: std::fmt::Write + ?Sized>(self, writer: &mut W) -> std::fmt::Result {
let community = &self.community;
if community.deleted {
@@ 284,7 284,7 @@ pub struct ContentView<'a, T: HavingContent + 'a> {
}
impl<'a, T: HavingContent + 'a> render::Render for ContentView<'a, T> {
- fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
+ fn render_into<W: std::fmt::Write + ?Sized>(self, writer: &mut W) -> std::fmt::Result {
match self.src.content_html() {
Some(html) => {
(render::rsx! { <div class={"contentView"}>{render::raw!(html)}</div> })
@@ 525,7 525,7 @@ pub struct ThingItem<'a> {
}
impl<'a> render::Render for ThingItem<'a> {
- fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
+ fn render_into<W: std::fmt::Write + ?Sized>(self, writer: &mut W) -> std::fmt::Result {
let lang = self.lang;
match self.thing {
@@ 553,7 553,7 @@ pub struct UserLink<'a> {
}
impl<'user> render::Render for UserLink<'user> {
- fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
+ fn render_into<W: std::fmt::Write + ?Sized>(self, writer: &mut W) -> std::fmt::Result {
match self.user {
None => "[unknown]".render_into(writer),
Some(user) => {
@@ 756,7 756,7 @@ pub struct NotificationItem<'a> {
}
impl<'a> render::Render for NotificationItem<'a> {
- fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
+ fn render_into<W: std::fmt::Write + ?Sized>(self, writer: &mut W) -> std::fmt::Result {
let lang = self.lang;
write!(writer, "<li class=\"notification-item")?;
@@ 858,7 858,7 @@ pub struct SiteModlogEventItem<'a> {
}
impl<'a> render::Render for SiteModlogEventItem<'a> {
- fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
+ fn render_into<W: std::fmt::Write + ?Sized>(self, writer: &mut W) -> std::fmt::Result {
let lang = self.lang;
let event = &self.event;
@@ 934,7 934,7 @@ pub struct PollView<'a> {
pub lang: &'a crate::Translator,
}
impl<'a> render::Render for PollView<'a> {
- fn render_into<W: std::fmt::Write>(self, writer: &mut W) -> std::fmt::Result {
+ fn render_into<W: std::fmt::Write + ?Sized>(self, writer: &mut W) -> std::fmt::Result {
let PollView { poll, action, lang } = &self;
if poll.your_vote.is_some() || poll.is_closed {
M src/lang.rs => src/lang.rs +77 -0
@@ 1,4 1,5 @@
use std::borrow::Cow;
+use std::convert::TryFrom;
pub struct Translator {
bundle: fluent::bundle::FluentBundle<
@@ 56,6 57,82 @@ impl std::fmt::Debug for Translator {
pub struct LangKey<'a>(&'static str, Option<fluent::FluentArgs<'a>>);
+pub const PLACEHOLDER_BASE: u32 = '\u{fba00}' as u32;
+pub const PLACEHOLDER_MAX: u32 = PLACEHOLDER_BASE + (u8::MAX as u32);
+
+#[derive(Clone, Copy, PartialEq, Debug)]
+pub struct LangPlaceholder(pub u8);
+
+impl fluent::types::FluentType for LangPlaceholder {
+ fn duplicate(&self) -> Box<dyn fluent::types::FluentType + Send + 'static> {
+ Box::new(*self)
+ }
+
+ fn as_string(&self, _intls: &intl_memoizer::IntlLangMemoizer) -> Cow<'static, str> {
+ char::try_from(PLACEHOLDER_BASE + u32::from(self.0))
+ .unwrap()
+ .to_string()
+ .into()
+ }
+
+ fn as_string_threadsafe(
+ &self,
+ _intls: &intl_memoizer::concurrent::IntlLangMemoizer,
+ ) -> Cow<'static, str> {
+ char::try_from(PLACEHOLDER_BASE + u32::from(self.0))
+ .unwrap()
+ .to_string()
+ .into()
+ }
+}
+
+impl From<LangPlaceholder> for fluent::FluentValue<'_> {
+ fn from(src: LangPlaceholder) -> fluent::FluentValue<'static> {
+ fluent::FluentValue::Custom(Box::new(src))
+ }
+}
+
+pub struct TrElements<'a, F: (Fn(u8, &mut dyn std::fmt::Write) -> std::fmt::Result)> {
+ src: Cow<'a, str>,
+ render_placeholder: F,
+}
+
+impl<'a, F: (Fn(u8, &mut dyn std::fmt::Write) -> std::fmt::Result)> TrElements<'a, F> {
+ pub fn new(src: Cow<'a, str>, render_placeholder: F) -> Self {
+ Self {
+ src,
+ render_placeholder,
+ }
+ }
+}
+
+impl<'a, F: (Fn(u8, &mut dyn std::fmt::Write) -> std::fmt::Result)> render::Render
+ for TrElements<'a, F>
+{
+ fn render_into<W: std::fmt::Write + ?Sized>(self, mut writer: &mut W) -> std::fmt::Result {
+ let mut covered = 0;
+
+ for (idx, chr) in self.src.char_indices() {
+ let chr_value: u32 = chr.into();
+ if chr_value >= PLACEHOLDER_BASE && chr_value <= PLACEHOLDER_MAX {
+ if idx > covered {
+ self.src[covered..idx].render_into(writer)?;
+ }
+
+ (self.render_placeholder)((chr_value - PLACEHOLDER_BASE) as u8, &mut writer)?;
+
+ covered = idx + chr.len_utf8();
+ }
+ }
+
+ if covered < self.src.len() {
+ self.src[covered..].render_into(writer)?;
+ }
+
+ Ok(())
+ }
+}
+
#[allow(unused)]
pub mod keys {
use super::*;
M src/routes/mod.rs => src/routes/mod.rs +56 -9
@@ 1,3 1,4 @@
+use render::Render;
use serde_derive::{Deserialize, Serialize};
use std::borrow::Cow;
use std::collections::HashMap;
@@ 172,13 173,34 @@ async fn page_about(
</p>
<h2>{lang.tr(&lang::about_what_is())}</h2>
<p>
- {lang.tr(&lang::about_text1())}
- {" "}<a href={"https://activitypub.rocks"}>{"ActivityPub"}</a>{"."}
+ {
+ lang::TrElements::new(
+ lang.tr(&lang::about_text1(lang::LangPlaceholder(0))),
+ |id, w| {
+ match id {
+ 0 => render::rsx! {
+ <a href={"https://activitypub.rocks"}>{"ActivityPub"}</a>
+ }.render_into(w),
+ _ => unreachable!(),
+ }
+ },
+ )
+ }
</p>
<p>
- {lang.tr(&lang::about_text2())}
- {" "}
- <a href={"https://sr.ht/~vpzom/lotide/"}>{lang.tr(&lang::about_sourcehut())}</a>{"."}
+ {
+ lang::TrElements::new(
+ lang.tr(&lang::about_text2(lang::LangPlaceholder(0))),
+ |id, w| {
+ match id {
+ 0 => render::rsx! {
+ <a href={"https://sr.ht/~vpzom/lotide/"}>{lang.tr(&lang::about_text2_part_sourcehut())}</a>
+ }.render_into(w),
+ _ => unreachable!(),
+ }
+ }
+ )
+ }
</p>
</HTPage>
}))
@@ 239,7 261,19 @@ async fn page_login_inner(
</form>
<br />
<p>
- {lang.tr(&lang::or_start())}{" "}<a href={"/signup"}>{lang.tr(&lang::login_signup_link())}</a>
+ {
+ lang::TrElements::new(
+ lang.tr(&lang::login_signup(lang::LangPlaceholder(0))),
+ |id, w| {
+ match id {
+ 0 => render::rsx! {
+ <a href={"/signup"}>{lang.tr(&lang::login_signup_part_signup())}</a>
+ }.render_into(w),
+ _ => unreachable!(),
+ }
+ }
+ )
+ }
</p>
<p>
<a href={"/forgot_password"}>{lang.tr(&lang::forgot_password())}</a>
@@ 1555,6 1589,9 @@ async fn page_home(
let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
let api_res: RespList<RespPostListPost<'_>> = serde_json::from_slice(&api_res)?;
+ let home_follow_prompt_src = lang::home_follow_prompt(lang::LangPlaceholder(0));
+ let home_follow_prompt_src = lang.tr(&home_follow_prompt_src);
+
Ok(html_response(render::html! {
<HTPage base_data={&base_data} lang={&lang} title={"lotide"}>
{
@@ 1563,9 1600,19 @@ async fn page_home(
<p>
{lang.tr(&lang::NOTHING)}
{" "}
- {lang.tr(&lang::HOME_FOLLOW_PROMPT1)}
- {" "}
- <a href={"/communities"}>{lang.tr(&lang::HOME_FOLLOW_PROMPT2)}</a>
+ {
+ lang::TrElements::new(
+ home_follow_prompt_src,
+ |id, w| {
+ match id {
+ 0 => render::rsx! {
+ <a href={"/communities"}>{lang.tr(&lang::HOME_FOLLOW_PROMPT_PART_FOLLOW)}</a>
+ }.render_into(w),
+ _ => unreachable!(),
+ }
+ }
+ )
+ }
</p>
})
} else {