diff --git a/locales/en.json b/locales/en.json index 5f0a3f7..f04253e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,18 +1,56 @@ { - "welcome_public": "@{user} Welcome to the group! The group user will now follow you back to complete the sign-up.\nTo share a post, @ the group user or use a group hashtag.\n\nUse /help for more info.", - "welcome_member_only": "@{user} Welcome to the group! This group has posting restricted to members.\nIf you'd like to join, please ask one of the group admins:\n{admins}", + "welcome_public": "Welcome to the group! The group user will now follow you back to complete the sign-up.\nTo share a post, @ the group user or use a group hashtag.\n\nUse /help for more info.", + "welcome_member_only": "Welcome to the group! This group has posting restricted to members.\nIf you'd like to join, please ask one of the group admins:\n{admins}", "welcome_join_cmd": "Welcome to the group! The group user will now follow you to complete the sign-up. Make sure you follow back to receive shared posts!\n\nUse /help for more info.", - "welcome_closed": "Sorry, this group is closed to new sign-ups.\nPlease ask one of the group admins to add you:", - "user_list_member": "- {user}", - "user_list_admin": "- {user} [admin]", - "help_admin_commands": "\n**Admin commands:**\n`/ping` - check the group works\n`/add user` - add a member (user@domain)\n`/remove user` - remove a member\n`/add #hashtag` - add a group hashtag\n`/remove #hashtag` - remove a group hashtag\n`/undo` - un-boost a replied-to post, delete an announcement\n`/ban x` - ban a user or server\n`/unban x` - lift a ban\n`/admin user` - grant admin rights\n`/deadmin user` - revoke admin rights\n`/closegroup` - make member-only\n`/opengroup` - make public-access\n`/announce x` - make a public announcement", - "cmd_leave_resp": "You're no longer a group member. Unfollow the group user to stop receiving group messages.", - "member_list_heading": "Group members:", - "admin_list_heading": "Group admins:", - "tag_list_heading": "Group tags:", - "tag_list_entry": "- {tag}", - "cmd_close_resp": "Group changed to member-only", - "cmd_close_resp_noaction": "No action, group is member-only already", - "cmd_open_resp": "Group changed to open-access", - "cmd_open_resp_noaction": "No action, group is open-access already", + "welcome_closed": "Sorry, this group is closed to new sign-ups.\nPlease ask one of the group admins to add you:\n", + "user_list_entry": "- {user}\n", + "user_list_entry_admin": "- {user} [admin]\n", + "help_group_info_closed": "This is a member-only group. {membership}\n", + "help_group_info_open": "This is a public-access group. {membership}\n", + "help_membership_admin": "*You are an admin.*", + "help_membership_member": "*You are a member.*", + "help_membership_guest_closed": "*You are not a member, ask one of the admins to add you.*", + "help_membership_guest_open": "*You are not a member, follow or use /join to join the group.*", + "help_admin_commands": "\n\n**Admin commands:**\n`/ping` - check the group works\n`/members - show group members / admins\n`/add user` - add a member (user@domain)\n`/remove user` - remove a member\n`/add #hashtag` - add a group hashtag\n`/remove #hashtag` - remove a group hashtag\n`/undo` - un-boost a replied-to post, delete an announcement\n`/ban x` - ban a user or server\n`/unban x` - lift a ban\n`/admin user` - grant admin rights\n`/deadmin user` - revoke admin rights\n`/closegroup` - make member-only\n`/opengroup` - make public-access\n`/announce x` - make a public announcement", + "help_basic_commands": "To share a post, @ the group user or use a group hashtag.\n\n**Supported commands:**\n`/boost`, `/b` - boost the replied-to post into the group\n`/ignore`, `/i` - make the group ignore the post\n`/tags` - show group hashtags\n`/join` - (re-)join the group\n`/leave` - leave the group\n`/optout` - forbid sharing of your posts", + "help_member_commands": "\n`/admins` - show group admins\n`/undo` - un-boost your post (use in a reply)", + "cmd_leave_ok": "You're no longer a group member. Unfollow the group user to stop receiving group messages.", + "member_list_heading": "Group members:\n", + "admin_list_heading": "Group admins:\n", + "tag_list_heading": "Group tags:\n", + "tag_list_entry": "- {tag}\n", + "cmd_error": "Command failed: {cause}", + "cmd_close_ok": "Group changed to member-only", + "cmd_close_fail_already": "No action, group is member-only already", + "cmd_open_ok": "Group changed to open-access", + "cmd_open_fail_already": "No action, group is open-access already", + "cmd_optout_fail_admin_cant": "Group admins can't opt-out.", + "cmd_optout_fail_member_cant": "Group members can't opt-out. You have to leave first.", + "cmd_optout_ok": "Your posts will no longer be shared to the group.", + "cmd_optin_fail_admin_cant": "Opt-in has no effect for admins.", + "cmd_optin_fail_member_cant": "Opt-in has no effect for members.", + "cmd_optin_ok": "Your posts can now be shared to the group.", + "cmd_ban_user_ok": "User {user} banned from group!", + "cmd_ban_user_fail_already": "No action, user {user} is already banned", + "cmd_unban_user_ok": "User {user} un-banned!", + "cmd_unban_user_fail_already": "No action, user {user} is not banned", + "cmd_ban_server_ok": "Server {server} banned from group!", + "cmd_ban_server_fail_already": "No action, server {server} already banned", + "cmd_unban_server_ok": "Server {server} un-banned!", + "cmd_unban_server_fail_already": "No action, server {server} is not banned", + "cmd_add_user_ok": "User {user} added to the group!", + "cmd_remove_user_ok": "User {user} removed from the group.", + "cmd_add_tag_ok": "Tag #{tag} added to the group!", + "cmd_add_tag_fail_already": "No action, #{tag} is already a group tag", + "cmd_remove_tag_ok": "Tag #{tag} removed from the group!", + "cmd_remove_tag_fail_already": "No action, #{tag} is not a group tag", + "cmd_admin_ok": "User {user} is now a group admin!", + "cmd_admin_fail_already": "No action, user {user} is a group admin already", + "cmd_unadmin_ok": "User {user} is no longer a group admin!", + "cmd_unadmin_fail_already": "No action, user {user} is not a group admin", + + + "mention_prefix": "@{user} ", + "group_announcement": "**📢Group announcement**\n{message}", + "ping_response": "pong, this is fedigroups service v{version}" } diff --git a/src/group_handler/handle_mention.rs b/src/group_handler/handle_mention.rs index b16e0a2..e5edfdc 100644 --- a/src/group_handler/handle_mention.rs +++ b/src/group_handler/handle_mention.rs @@ -27,8 +27,8 @@ pub struct ProcessMention<'a> { status_user_id: String, can_write: bool, is_admin: bool, - replies: Vec, - announcements: Vec, + replies: String, + announcements: String, do_boost_prev_post: bool, want_markdown: bool, } @@ -47,7 +47,7 @@ impl<'a> ProcessMention<'a> { self.client .search_v2(acct, !followed, Some(SearchType::Accounts), Some(1), followed), ) - .await + .await { Err(_) => { warn!("Account lookup timeout!"); @@ -78,9 +78,11 @@ impl<'a> ProcessMention<'a> { fn append_admin_list_to_reply(&mut self) { let mut admins = self.config.get_admins().collect::>(); admins.sort(); + let mut to_add = String::new(); for a in admins { - self.replies.push(format!("- {}", a)); + to_add.push_str(&crate::tr!(self, "user_list_entry", user = a)); } + self.add_reply(&to_add); } fn append_member_list_to_reply(&mut self) { @@ -89,13 +91,15 @@ impl<'a> ProcessMention<'a> { members.extend(admins.iter()); members.sort(); members.dedup(); + let mut to_add = String::new(); for m in members { - self.replies.push(if admins.contains(&m) { - crate::tr!(self, "user_list_admin", user=m) + to_add.push_str(&if admins.contains(&m) { + crate::tr!(self, "user_list_entry_admin", user=m) } else { - crate::tr!(self, "user_list_member", user=m) + crate::tr!(self, "user_list_entry", user=m) }); } + self.add_reply(&to_add); } async fn follow_user_by_id(&self, id: &str) -> Result<(), GroupError> { @@ -128,8 +132,8 @@ impl<'a> ProcessMention<'a> { cc: &gh.cc, can_write: gh.config.can_write(&status_acct), is_admin: gh.config.is_admin(&status_acct), - replies: vec![], - announcements: vec![], + replies: String::new(), + announcements: String::new(), do_boost_prev_post: false, want_markdown: false, group_acct, @@ -142,16 +146,17 @@ impl<'a> ProcessMention<'a> { } async fn reblog_status(&self) { - self.client.reblog(&self.status.id).await.log_error("Failed to reblog status"); + self.client.reblog(&self.status.id) + .await.log_error("Failed to reblog status"); self.delay_after_post().await; } fn add_reply(&mut self, line: impl AsRef) { - self.replies.push(line.as_ref().trim_matches(' ').to_string()) + self.replies.push_str(line.as_ref()) } fn add_announcement(&mut self, line: impl AsRef) { - self.announcements.push(line.as_ref().trim_matches(' ').to_string()) + self.announcements.push_str(line.as_ref()) } async fn handle(mut self) -> Result<(), GroupError> { @@ -166,6 +171,9 @@ impl<'a> ProcessMention<'a> { } for cmd in commands { + if !self.replies.is_empty() { + self.replies.push('\n'); // make sure there's a newline between batched commands. + } match cmd { StatusCommand::Undo => { self.cmd_undo().await.log_error("Error handling undo cmd"); @@ -213,7 +221,7 @@ impl<'a> ProcessMention<'a> { self.cmd_grant_admin(&u).await.log_error("Error handling grant-admin cmd"); } StatusCommand::RemoveAdmin(u) => { - self.cmd_revoke_member(&u).await.log_error("Error handling grant-admin cmd"); + self.cmd_revoke_admin(&u).await.log_error("Error handling grant-admin cmd"); } StatusCommand::OpenGroup => { self.cmd_open_group().await; @@ -261,26 +269,26 @@ impl<'a> ProcessMention<'a> { } if !self.replies.is_empty() { - let mut msg = self.replies.join("\n"); + let mut msg = std::mem::take(&mut self.replies); debug!("r={}", msg); if self.want_markdown { apply_trailing_hashtag_pleroma_bug_workaround(&mut msg); } - let mention = format!("@{user} ", user = self.status_acct); + let mention = crate::tr!(self, "mention_prefix", user = &self.status_acct); self.send_reply_multipart(mention, msg).await?; } if !self.announcements.is_empty() { - let mut msg = self.announcements.join("\n"); + let mut msg = std::mem::take(&mut self.announcements); debug!("a={}", msg); if self.want_markdown { apply_trailing_hashtag_pleroma_bug_workaround(&mut msg); } - let msg = format!("**📢 Group announcement**\n{msg}", msg = msg); + let msg = crate::tr!(self, "group_announcement", message = &msg); self.send_announcement_multipart(&msg).await?; } @@ -373,29 +381,29 @@ impl<'a> ProcessMention<'a> { async fn cmd_optout(&mut self) { if self.is_admin { - self.add_reply("Group admins can't opt-out."); + self.add_reply(crate::tr!(self, "cmd_optout_fail_admin_cant")); } else if self.config.is_member(&self.status_acct) { - self.add_reply("Group members can't opt-out. You have to leave first."); + self.add_reply(crate::tr!(self, "cmd_optout_fail_member_cant")); } else { self.config.set_optout(&self.status_acct, true); - self.add_reply("Your posts will no longer be shared to the group."); + self.add_reply(crate::tr!(self, "cmd_optout_ok")); } } async fn cmd_optin(&mut self) { if self.is_admin { - self.add_reply("Opt-in has no effect for admins."); + self.add_reply(crate::tr!(self, "cmd_optin_fail_admin_cant")); } else if self.config.is_member(&self.status_acct) { - self.add_reply("Opt-in has no effect for members."); + self.add_reply(crate::tr!(self, "cmd_optin_fail_member_cant")); } else { self.config.set_optout(&self.status_acct, false); - self.add_reply("Your posts can now be shared to the group."); + self.add_reply(crate::tr!(self, "cmd_optin_ok")); } } async fn cmd_undo(&mut self) -> Result<(), GroupError> { if let (Some(ref parent_account_id), Some(ref parent_status_id)) = - (&self.status.in_reply_to_account_id, &self.status.in_reply_to_id) + (&self.status.in_reply_to_account_id, &self.status.in_reply_to_id) { if parent_account_id == &self.group_account.id { // This is a post sent by the group user, likely an announcement. @@ -428,15 +436,15 @@ impl<'a> ProcessMention<'a> { if !self.config.is_banned(&u) { match self.config.ban_user(&u, true) { Ok(_) => { - self.add_reply(format!("User {} banned from group!", u)); + self.add_reply(crate::tr!(self, "cmd_ban_user_ok", user = &u)); self.unfollow_by_acct(&u).await.log_error("Failed to unfollow banned user"); } Err(e) => { - self.add_reply(format!("Failed to ban user {}: {}", u, e)); + self.add_reply(crate::tr!(self, "cmd_error", cause = &e.to_string())); } } } else { - self.add_reply(format!("No action, user {} is already banned", u)); + self.add_reply(crate::tr!(self, "cmd_ban_user_fail_already", user = &u)); } } else { warn!("Ignore cmd, user not admin"); @@ -450,15 +458,15 @@ impl<'a> ProcessMention<'a> { if self.config.is_banned(&u) { match self.config.ban_user(&u, false) { Ok(_) => { - self.add_reply(format!("User {} un-banned!", u)); + self.add_reply(crate::tr!(self, "cmd_unban_user_ok", user = &u)); // no announcement here } - Err(_) => { - unreachable!() + Err(e) => { + self.add_reply(crate::tr!(self, "cmd_error", cause = &e.to_string())); } } } else { - self.add_reply(format!("No action, user {} is not banned", u)); + self.add_reply(crate::tr!(self, "cmd_unban_user_fail_already", user = &u)); } } else { warn!("Ignore cmd, user not admin"); @@ -471,14 +479,14 @@ impl<'a> ProcessMention<'a> { if !self.config.is_server_banned(s) { match self.config.ban_server(s, true) { Ok(_) => { - self.add_reply(format!("Server {} banned from group!", s)); + self.add_reply(crate::tr!(self, "cmd_ban_server_ok", server = s)); } Err(e) => { - self.add_reply(format!("Failed to ban server {}: {}", s, e)); + self.add_reply(crate::tr!(self, "cmd_error", cause = &e.to_string())); } } } else { - self.add_reply(format!("No action, server {} already banned", s)); + self.add_reply(crate::tr!(self, "cmd_ban_server_fail_already", server = s)); } } else { warn!("Ignore cmd, user not admin"); @@ -490,14 +498,14 @@ impl<'a> ProcessMention<'a> { if self.config.is_server_banned(s) { match self.config.ban_server(s, false) { Ok(_) => { - self.add_reply(format!("Server {} un-banned!", s)); + self.add_reply(crate::tr!(self, "cmd_unban_server_ok", server = s)); } Err(e) => { - self.add_reply(format!("Unexpected error occured: {}", e)); + self.add_reply(crate::tr!(self, "cmd_error", cause = &e.to_string())); } } } else { - self.add_reply(format!("No action, server {} is not banned", s)); + self.add_reply(crate::tr!(self, "cmd_unban_server_fail_already", server = s)); } } else { warn!("Ignore cmd, user not admin"); @@ -510,11 +518,12 @@ impl<'a> ProcessMention<'a> { // Allow even if the user is already a member - that will trigger re-follow match self.config.set_member(&u, true) { Ok(_) => { - self.add_reply(format!("User {} added to the group!", u)); + self.add_reply(crate::tr!(self, "cmd_add_user_ok", user = &u)); + // marked as member, now also follow the user self.follow_by_acct(&u).await.log_error("Failed to follow"); } Err(e) => { - self.add_reply(format!("Failed to add user {} to group: {}", u, e)); + self.add_reply(crate::tr!(self, "cmd_error", cause = &e.to_string())); } } } else { @@ -528,11 +537,11 @@ impl<'a> ProcessMention<'a> { if self.is_admin { match self.config.set_member(&u, false) { Ok(_) => { - self.add_reply(format!("User {} removed from the group.", u)); + self.add_reply(crate::tr!(self, "cmd_remove_user_ok", user = &u)); self.unfollow_by_acct(&u).await.log_error("Failed to unfollow removed user"); } Err(e) => { - self.add_reply(format!("Unexpected error occured: {}", e)); + self.add_reply(crate::tr!(self, "cmd_error", cause = &e.to_string())); } } } else { @@ -545,9 +554,9 @@ impl<'a> ProcessMention<'a> { if self.is_admin { if !self.config.is_tag_followed(&tag) { self.config.add_tag(&tag); - self.add_reply(format!("Tag \"{}\" added to the group!", tag)); + self.add_reply(crate::tr!(self, "cmd_add_tag_ok", tag = &tag)); } else { - self.add_reply(format!("No action, \"{}\" is already a group tag", tag)); + self.add_reply(crate::tr!(self, "cmd_add_tag_fail_already", tag = &tag)); } } else { warn!("Ignore cmd, user not admin"); @@ -558,9 +567,9 @@ impl<'a> ProcessMention<'a> { if self.is_admin { if self.config.is_tag_followed(&tag) { self.config.remove_tag(&tag); - self.add_reply(format!("Tag \"{}\" removed from the group!", tag)); + self.add_reply(crate::tr!(self, "cmd_remove_tag_ok", tag = &tag)); } else { - self.add_reply(format!("No action, \"{}\" is not a group tag", tag)); + self.add_reply(crate::tr!(self, "cmd_remove_tag_fail_already", tag = &tag)); } } else { warn!("Ignore cmd, user not admin"); @@ -576,14 +585,14 @@ impl<'a> ProcessMention<'a> { // try to make the config a little more sane, admins should be members let _ = self.config.set_member(&u, true); - self.add_reply(format!("User {} is now a group admin!", u)); + self.add_reply(crate::tr!(self, "cmd_admin_ok", user = &u)); } Err(e) => { - self.add_reply(format!("Failed to make user {} a group admin: {}", u, e)); + self.add_reply(crate::tr!(self, "cmd_error", cause = &e.to_string())); } } } else { - self.add_reply(format!("No action, \"{}\" is admin already", u)); + self.add_reply(crate::tr!(self, "cmd_admin_fail_already", user = &u)); } } else { warn!("Ignore cmd, user not admin"); @@ -591,20 +600,20 @@ impl<'a> ProcessMention<'a> { Ok(()) } - async fn cmd_revoke_member(&mut self, user: &str) -> Result<(), GroupError> { + async fn cmd_revoke_admin(&mut self, user: &str) -> Result<(), GroupError> { let u = normalize_acct(user, &self.group_acct)?; if self.is_admin { if self.config.is_admin(&u) { match self.config.set_admin(&u, false) { Ok(_) => { - self.add_reply(format!("User {} is no longer a group admin!", u)); + self.add_reply(crate::tr!(self, "cmd_unadmin_ok", user = &u)); } Err(e) => { - self.add_reply(format!("Failed to revoke {}'s group admin: {}", u, e)); + self.add_reply(crate::tr!(self, "cmd_error", cause = &e.to_string())); } } } else { - self.add_reply(format!("No action, user {} is not admin", u)); + self.add_reply(crate::tr!(self, "cmd_unadmin_fail_already", user = &u)); } } else { warn!("Ignore cmd, user not admin"); @@ -618,7 +627,7 @@ impl<'a> ProcessMention<'a> { self.config.set_member_only(false); self.add_reply(crate::tr!(self, "cmd_open_resp")); } else { - self.add_reply(crate::tr!(self, "cmd_open_resp_noaction")); + self.add_reply(crate::tr!(self, "cmd_open_resp_already")); } } else { warn!("Ignore cmd, user not admin"); @@ -631,7 +640,7 @@ impl<'a> ProcessMention<'a> { self.config.set_member_only(true); self.add_reply(crate::tr!(self, "cmd_close_resp")); } else { - self.add_reply(crate::tr!(self, "cmd_close_resp_noaction")); + self.add_reply(crate::tr!(self, "cmd_close_resp_already")); } } else { warn!("Ignore cmd, user not admin"); @@ -642,43 +651,26 @@ impl<'a> ProcessMention<'a> { self.want_markdown = true; let membership_line = if self.is_admin { - "*You are an admin.*" + crate::tr!(self, "help_membership_admin") } else if self.config.is_member(&self.status_acct) { - "*You are a member.*" + crate::tr!(self, "help_membership_member") } else if self.config.is_member_only() { - "*You are not a member, ask one of the admins to add you.*" + crate::tr!(self, "help_membership_guest_closed") } else { - "*You are not a member, follow or use /join to join the group.*" + crate::tr!(self, "help_membership_guest_open") }; if self.config.is_member_only() { - self.add_reply(format!("This is a member-only group. {}", membership_line)); + self.add_reply(crate::tr!(self, "help_group_info_closed", membership = &membership_line)); } else { - self.add_reply(format!("This is a public-access group. {}", membership_line)); - } - - self.add_reply( - "\ - To share a post, @ the group user or use a group hashtag.\n\ - \n\ - **Supported commands:**\n\ - `/boost`, `/b` - boost the replied-to post into the group\n\ - `/ignore`, `/i` - make the group ignore the post\n\ - `/tags` - show group hashtags\n\ - `/join` - (re-)join the group\n\ - `/leave` - leave the group\n\ - `/optout` - forbid sharing of your posts", - ); - - if self.is_admin { - self.add_reply("`/members`, `/who` - show group members / admins"); - // undo is listed as an admin command - } else { - self.add_reply("`/admins` - show group admins"); - self.add_reply("`/undo` - un-boost your post (use in a reply)"); + self.add_reply(crate::tr!(self, "help_group_info_open", membership = &membership_line)); } - // XXX when used on instance with small character limit, this won't fit! + self.add_reply(crate::tr!(self, "help_basic_commands")); + + if !self.is_admin { + self.add_reply(crate::tr!(self, "help_member_commands")); + } if self.is_admin { self.add_reply(crate::tr!(self, "help_admin_commands")); @@ -701,9 +693,13 @@ impl<'a> ProcessMention<'a> { self.want_markdown = true; let mut tags = self.config.get_tags().collect::>(); tags.sort(); + + let mut to_add = String::new(); + for t in tags { - self.replies.push(crate::tr!(self, "tag_list_entry", tag=t)); + to_add.push_str(&crate::tr!(self, "tag_list_entry", tag=t)); } + self.add_reply(to_add); } async fn cmd_leave(&mut self) { @@ -742,10 +738,7 @@ impl<'a> ProcessMention<'a> { } async fn cmd_ping(&mut self) { - self.add_reply(format!( - "pong, this is fedigroups service v{}", - env!("CARGO_PKG_VERSION") - )); + self.add_reply(crate::tr!(self, "ping_response", version = env!("CARGO_PKG_VERSION"))); } async fn unfollow_by_acct(&self, acct: &str) -> Result<(), GroupError> { @@ -895,21 +888,21 @@ mod test { let to_split = "foo\nbar\nbaz"; let parts = super::smart_split(to_split, None, 1000); - assert_eq!(vec!["foo\nbar\nbaz".to_string(),], parts); + assert_eq!(vec!["foo\nbar\nbaz".to_string()], parts); } #[test] fn test_smart_split_nosplit_prefix() { let to_split = "foo\nbar\nbaz"; let parts = super::smart_split(to_split, Some("PREFIX".to_string()), 1000); - assert_eq!(vec!["PREFIXfoo\nbar\nbaz".to_string(),], parts); + assert_eq!(vec!["PREFIXfoo\nbar\nbaz".to_string()], parts); } #[test] fn test_smart_split_prefix_each() { let to_split = "1234\n56\n7"; let parts = super::smart_split(to_split, Some("PREFIX".to_string()), 10); - assert_eq!(vec!["PREFIX1234".to_string(), "PREFIX56\n7".to_string(),], parts); + assert_eq!(vec!["PREFIX1234".to_string(), "PREFIX56\n7".to_string()], parts); } #[test] @@ -951,7 +944,7 @@ mod test { let to_split = "one two threefourfive six"; let parts = super::smart_split(to_split, None, 10); assert_eq!( - vec!["one two".to_string(), "threefourf".to_string(), "ive six".to_string(),], + vec!["one two".to_string(), "threefourf".to_string(), "ive six".to_string()], parts ); } diff --git a/src/group_handler/mod.rs b/src/group_handler/mod.rs index c80469e..f9ea4e3 100644 --- a/src/group_handler/mod.rs +++ b/src/group_handler/mod.rs @@ -538,18 +538,15 @@ impl GroupHandle { let mut admins = self.config.get_admins().cloned().collect::>(); admins.sort(); - crate::tr!(self, "welcome_member_only", - user = notif_acct, - admins = &admins.join(", ") - ) + crate::tr!(self, "mention_prefix", user = notif_acct) + + &crate::tr!(self, "welcome_member_only", admins = &admins.join(", ")) } else { follow_back = true; self.config.set_member(notif_acct, true).log_error("Fail add a member"); - crate::tr!(self, "welcome_public", - user = notif_acct - ) + crate::tr!(self, "mention_prefix", user = notif_acct) + + &crate::tr!(self, "welcome_public") }; let post = StatusBuilder::new()