~vpzom/hitide

ff300d93e907a1fddefc8f86e7a4bbcb40045a22 — Colin Reeder 1 year, 1 month ago fcedb0f
Initial work on translation placeholders for elements
6 files changed, 150 insertions(+), 26 deletions(-)

M Cargo.lock
M Cargo.toml
M res/lang/en.ftl
M src/components/mod.rs
M src/lang.rs
M src/routes/mod.rs
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 {