From d207c3d60d14b28f30d5f018d286e573fff22149 Mon Sep 17 00:00:00 2001 From: Jean Date: Sun, 19 Jul 2026 23:22:56 +0000 Subject: [PATCH] Migrate interpolated service messages to the t! macro (8 modules) --- api/src/lib.rs | 14 ++++--- modules/botserv/src/assign.rs | 14 +++---- modules/botserv/src/autoassign.rs | 8 ++-- modules/botserv/src/badwords.rs | 20 +++++----- modules/botserv/src/bot.rs | 24 ++++++------ modules/botserv/src/botlist.rs | 6 +-- modules/botserv/src/copy.rs | 4 +- modules/botserv/src/info.rs | 18 ++++----- modules/botserv/src/kick.rs | 34 ++++++++--------- modules/botserv/src/lib.rs | 8 ++-- modules/botserv/src/say.rs | 12 +++--- modules/botserv/src/set.rs | 30 +++++++-------- modules/botserv/src/trigger.rs | 26 ++++++------- modules/chanserv/src/access.rs | 19 +++++----- modules/chanserv/src/akick.rs | 27 ++++++------- modules/chanserv/src/ban.rs | 3 +- modules/chanserv/src/clone.rs | 3 +- modules/chanserv/src/enforce.rs | 3 +- modules/chanserv/src/entrymsg.rs | 11 +++--- modules/chanserv/src/flags.rs | 29 +++++++------- modules/chanserv/src/getkey.rs | 5 ++- modules/chanserv/src/invite.rs | 5 ++- modules/chanserv/src/kick.rs | 3 +- modules/chanserv/src/levels.rs | 13 ++++--- modules/chanserv/src/lib.rs | 61 +++++++++++++++--------------- modules/chanserv/src/list.rs | 5 ++- modules/chanserv/src/mode.rs | 11 +++--- modules/chanserv/src/noexpire.rs | 9 +++-- modules/chanserv/src/op.rs | 3 +- modules/chanserv/src/seen.rs | 17 +++++---- modules/chanserv/src/set.rs | 29 +++++++------- modules/chanserv/src/status.rs | 5 ++- modules/chanserv/src/suspend.rs | 9 +++-- modules/chanserv/src/topic.rs | 3 +- modules/chanserv/src/unban.rs | 3 +- modules/chanserv/src/updown.rs | 11 +++--- modules/chanserv/src/xop.rs | 21 +++++----- modules/gameserv/src/lib.rs | 2 +- modules/groupserv/src/add.rs | 10 ++--- modules/groupserv/src/del.rs | 10 ++--- modules/groupserv/src/drop.rs | 8 ++-- modules/groupserv/src/flags.rs | 22 +++++------ modules/groupserv/src/info.rs | 10 ++--- modules/groupserv/src/lib.rs | 4 +- modules/groupserv/src/list.rs | 6 +-- modules/groupserv/src/register.rs | 8 ++-- modules/helpserv/src/cancel.rs | 4 +- modules/helpserv/src/close.rs | 6 +-- modules/helpserv/src/extbans.rs | 15 ++++++-- modules/helpserv/src/lib.rs | 10 ++--- modules/helpserv/src/list.rs | 10 ++--- modules/helpserv/src/request.rs | 4 +- modules/helpserv/src/view.rs | 14 +++---- modules/hostserv/src/approve.rs | 16 ++++---- modules/hostserv/src/default.rs | 6 +-- modules/hostserv/src/del.rs | 8 ++-- modules/hostserv/src/forbid.rs | 14 +++---- modules/hostserv/src/lib.rs | 10 ++--- modules/hostserv/src/list.rs | 8 ++-- modules/hostserv/src/offer.rs | 14 +++---- modules/hostserv/src/on.rs | 4 +- modules/hostserv/src/request.rs | 10 ++--- modules/hostserv/src/set.rs | 10 ++--- modules/hostserv/src/take.rs | 10 ++--- modules/hostserv/src/template.rs | 6 +-- modules/hostserv/src/waiting.rs | 6 +-- modules/infoserv/src/del.rs | 8 ++-- modules/infoserv/src/lib.rs | 4 +- modules/infoserv/src/list.rs | 8 ++-- modules/infoserv/src/post.rs | 6 +-- modules/memoserv/src/cancel.rs | 8 ++-- modules/memoserv/src/check.rs | 10 ++--- modules/memoserv/src/del.rs | 8 ++-- modules/memoserv/src/ignore.rs | 14 +++---- modules/memoserv/src/info.rs | 4 +- modules/memoserv/src/lib.rs | 4 +- modules/memoserv/src/list.rs | 6 +-- modules/memoserv/src/read.rs | 8 ++-- modules/memoserv/src/send.rs | 12 +++--- modules/memoserv/src/sendall.rs | 4 +- modules/memoserv/src/set.rs | 10 ++--- modules/memoserv/src/staff.rs | 4 +- modules/nickserv/src/ajoin.rs | 19 +++++----- modules/nickserv/src/alist.rs | 5 ++- modules/nickserv/src/cert.rs | 9 +++-- modules/nickserv/src/confirm.rs | 5 ++- modules/nickserv/src/drop.rs | 9 +++-- modules/nickserv/src/getemail.rs | 5 ++- modules/nickserv/src/ghost.rs | 15 ++++---- modules/nickserv/src/glist.rs | 7 ++-- modules/nickserv/src/group.rs | 11 +++--- modules/nickserv/src/identify.rs | 15 ++++---- modules/nickserv/src/info.rs | 21 +++++----- modules/nickserv/src/lib.rs | 2 +- modules/nickserv/src/list.rs | 9 +++-- modules/nickserv/src/logout.rs | 3 +- modules/nickserv/src/noexpire.rs | 9 +++-- modules/nickserv/src/resetpass.rs | 9 +++-- modules/nickserv/src/saset.rs | 11 +++--- modules/nickserv/src/set.rs | 14 +++---- modules/nickserv/src/suspend.rs | 11 +++--- modules/nickserv/src/ungroup.rs | 7 ++-- modules/nickserv/src/update.rs | 3 +- modules/operserv/src/chankill.rs | 6 +-- modules/operserv/src/defcon.rs | 24 +++++++----- modules/operserv/src/forbid.rs | 12 +++--- modules/operserv/src/ignore.rs | 24 +++++++----- modules/operserv/src/info.rs | 12 +++--- modules/operserv/src/jupe.rs | 12 +++--- modules/operserv/src/kick.rs | 6 +-- modules/operserv/src/kill.rs | 6 +-- modules/operserv/src/lib.rs | 4 +- modules/operserv/src/logsearch.rs | 6 +-- modules/operserv/src/mode.rs | 4 +- modules/operserv/src/notify.rs | 37 +++++++++--------- modules/operserv/src/oper.rs | 32 ++++++++++------ modules/operserv/src/redact.rs | 4 +- modules/operserv/src/session.rs | 28 ++++++++------ modules/operserv/src/set.rs | 4 +- modules/operserv/src/shutdown.rs | 6 +-- modules/operserv/src/spamfilter.rs | 31 ++++++++------- modules/operserv/src/stats.rs | 4 +- modules/operserv/src/svs.rs | 16 ++++---- modules/operserv/src/xline.rs | 40 +++++++++++--------- modules/reportserv/src/close.rs | 6 +-- modules/reportserv/src/del.rs | 6 +-- modules/reportserv/src/lib.rs | 4 +- modules/reportserv/src/list.rs | 6 +-- modules/reportserv/src/report.rs | 4 +- modules/reportserv/src/view.rs | 12 +++--- modules/statserv/src/channel.rs | 8 ++-- modules/statserv/src/global.rs | 4 +- modules/statserv/src/lib.rs | 8 ++-- 133 files changed, 782 insertions(+), 702 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index fac36bd..1d8dd42 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -566,11 +566,12 @@ impl ServiceCtx { } pub fn notice(&mut self, from: &str, to: &str, text: impl Into) { - self.actions.push(NetAction::Notice { - from: from.to_string(), - to: to.to_string(), - text: text.into(), - }); + // Auto-localize: a static English reply is looked up in the catalog for this + // command's language and swapped for its translation (English/no-catalog = + // unchanged). Interpolated messages must use the `t!` macro so their template + // — not the already-filled-in text — is the lookup key. + let text = render(self.lang(), &text.into(), &[]); + self.actions.push(NetAction::Notice { from: from.to_string(), to: to.to_string(), text }); } // IRCv3 standard replies from a service (`from` uid) to a client (`to` uid). @@ -591,13 +592,14 @@ impl ServiceCtx { } fn standard_reply(&mut self, kind: ReplyKind, from: &str, to: &str, command: &str, code: &str, text: impl Into) { + let text = render(self.lang(), &text.into(), &[]); // auto-localize static replies (see `notice`) self.actions.push(NetAction::StandardReply { kind, from: from.to_string(), to: to.to_string(), command: command.to_string(), code: code.to_string(), - text: text.into(), + text, }); } diff --git a/modules/botserv/src/assign.rs b/modules/botserv/src/assign.rs index b32a525..03c9391 100644 --- a/modules/botserv/src/assign.rs +++ b/modules/botserv/src/assign.rs @@ -1,4 +1,4 @@ -use echo_api::{Priv, Sender, ServiceCtx, Store}; +use echo_api::{t, Priv, Sender, ServiceCtx, Store}; // ASSIGN <#channel> / UNASSIGN <#channel>: put a bot in a channel (or take // it out). Channel founder only (or a services admin). @@ -14,13 +14,13 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: // NOBOT reserves (un)assignment for services operators. let is_admin = from.privs.has(Priv::Admin); if !is_admin && db.channel(chan).is_some_and(|c| c.nobot) { - ctx.notice(me, from.uid, format!("\x02{chan}\x02 is set \x02NOBOT\x02 — only a services operator can change its bot.")); + ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 is set \x02NOBOT\x02 — only a services operator can change its bot.", chan = chan)); return; } if !assigning { match db.unassign_bot(chan) { - Ok(true) => ctx.notice(me, from.uid, format!("The bot has left \x02{chan}\x02.")), - Ok(false) => ctx.notice(me, from.uid, format!("\x02{chan}\x02 has no bot assigned.")), + Ok(true) => ctx.notice(me, from.uid, t!(ctx, "The bot has left \x02{chan}\x02.", chan = chan)), + Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no bot assigned.", chan = chan)), Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."), } return; @@ -30,16 +30,16 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: return; }; let Some(target) = db.bots().into_iter().find(|b| b.nick.eq_ignore_ascii_case(bot)) else { - ctx.notice(me, from.uid, format!("There's no bot named \x02{bot}\x02. See \x02BOT LIST\x02.")); + ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02. See \x02BOT LIST\x02.", nick = bot)); return; }; if target.private && !is_admin { - ctx.notice(me, from.uid, format!("Bot \x02{}\x02 is private — only a services operator can assign it.", target.nick)); + ctx.notice(me, from.uid, t!(ctx, "Bot \x02{nick}\x02 is private — only a services operator can assign it.", nick = target.nick)); return; } let botnick = target.nick; match db.assign_bot(chan, &botnick) { - Ok(()) => ctx.notice(me, from.uid, format!("Bot \x02{botnick}\x02 is now assigned to \x02{chan}\x02.")), + Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Bot \x02{botnick}\x02 is now assigned to \x02{chan}\x02.", botnick = botnick, chan = chan)), Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."), } } diff --git a/modules/botserv/src/autoassign.rs b/modules/botserv/src/autoassign.rs index 313e923..1734f66 100644 --- a/modules/botserv/src/autoassign.rs +++ b/modules/botserv/src/autoassign.rs @@ -1,4 +1,4 @@ -use echo_api::{Priv, Sender, ServiceCtx, Store}; +use echo_api::{t, Priv, Sender, ServiceCtx, Store}; // AUTOASSIGN | OFF: set the bot automatically assigned to every newly // registered channel, or turn auto-assignment off. Oper-only (Priv::Admin) — @@ -10,7 +10,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: } let Some(&arg) = args.get(1) else { match db.default_bot() { - Some(bot) => ctx.notice(me, from.uid, format!("New channels are auto-assigned \x02{bot}\x02. Use \x02AUTOASSIGN OFF\x02 to stop.")), + Some(bot) => ctx.notice(me, from.uid, t!(ctx, "New channels are auto-assigned \x02{bot}\x02. Use \x02AUTOASSIGN OFF\x02 to stop.", bot = bot)), None => ctx.notice(me, from.uid, "No bot is auto-assigned to new channels. Set one with \x02AUTOASSIGN \x02."), } return; @@ -24,11 +24,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: } // Store the bot's canonical nick, so the setting survives a re-cased lookup. let Some(bot) = db.bots().into_iter().find(|b| b.nick.eq_ignore_ascii_case(arg)) else { - ctx.notice(me, from.uid, format!("There's no bot named \x02{arg}\x02. See \x02BOTLIST\x02.")); + ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02. See \x02BOTLIST\x02.", nick = arg)); return; }; match db.set_default_bot(Some(&bot.nick)) { - Ok(()) => ctx.notice(me, from.uid, format!("New channels will be auto-assigned \x02{}\x02.", bot.nick)), + Ok(()) => ctx.notice(me, from.uid, t!(ctx, "New channels will be auto-assigned \x02{nick}\x02.", nick = bot.nick)), Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."), } } diff --git a/modules/botserv/src/badwords.rs b/modules/botserv/src/badwords.rs index c715702..a634cc0 100644 --- a/modules/botserv/src/badwords.rs +++ b/modules/botserv/src/badwords.rs @@ -1,4 +1,4 @@ -use echo_api::{ChanError, Sender, ServiceCtx, Store}; +use echo_api::{t, ChanError, Sender, ServiceCtx, Store}; // BADWORDS <#channel> ADD | DEL | LIST | CLEAR: manage the // channel's badword patterns. Each entry is a regular expression, so a channel @@ -21,9 +21,9 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: } let pattern = args[3..].join(" "); match db.badword_add(chan, &pattern) { - Ok(true) => ctx.notice(me, from.uid, format!("Added badword pattern to \x02{chan}\x02: {pattern}")), + Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Added badword pattern to \x02{chan}\x02: {pattern}", chan = chan, pattern = pattern)), Ok(false) => ctx.notice(me, from.uid, "That pattern is already on the list."), - Err(ChanError::InvalidPattern) => ctx.notice(me, from.uid, format!("\x02{pattern}\x02 isn't a valid regular expression.")), + Err(ChanError::InvalidPattern) => ctx.notice(me, from.uid, t!(ctx, "\x02{pattern}\x02 isn't a valid regular expression.", pattern = pattern)), Err(_) => reg_error(me, from, chan, ctx), } } @@ -34,30 +34,30 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: } let pattern = args[3..].join(" "); match db.badword_del(chan, &pattern) { - Ok(true) => ctx.notice(me, from.uid, format!("Removed badword pattern from \x02{chan}\x02.")), + Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Removed badword pattern from \x02{chan}\x02.", chan = chan)), Ok(false) => ctx.notice(me, from.uid, "That pattern isn't on the list."), Err(_) => reg_error(me, from, chan, ctx), } } Some("CLEAR") => match db.badword_clear(chan) { - Ok(n) => ctx.notice(me, from.uid, format!("Cleared \x02{n}\x02 badword pattern(s) from \x02{chan}\x02.")), + Ok(n) => ctx.notice(me, from.uid, t!(ctx, "Cleared \x02{n}\x02 badword pattern(s) from \x02{chan}\x02.", n = n, chan = chan)), Err(_) => reg_error(me, from, chan, ctx), }, None | Some("LIST") => { let words = db.badwords(chan); if words.is_empty() { - ctx.notice(me, from.uid, format!("\x02{chan}\x02 has no badword patterns.")); + ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no badword patterns.", chan = chan)); return; } - ctx.notice(me, from.uid, format!("Badword patterns for \x02{chan}\x02 ({}):", words.len())); + ctx.notice(me, from.uid, t!(ctx, "Badword patterns for \x02{chan}\x02 ({count}):", chan = chan, count = words.len())); for (i, w) in words.iter().enumerate() { - ctx.notice(me, from.uid, format!(" {}. {w}", i + 1)); + ctx.notice(me, from.uid, t!(ctx, " {num}. {word}", num = i + 1, word = w)); } } - Some(other) => ctx.notice(me, from.uid, format!("Unknown BADWORDS command \x02{other}\x02. Use ADD, DEL, LIST or CLEAR.")), + Some(other) => ctx.notice(me, from.uid, t!(ctx, "Unknown BADWORDS command \x02{other}\x02. Use ADD, DEL, LIST or CLEAR.", other = other)), } } fn reg_error(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx) { - ctx.notice(me, from.uid, format!("Couldn't update \x02{chan}\x02 — please try again in a moment.")); + ctx.notice(me, from.uid, t!(ctx, "Couldn't update \x02{chan}\x02 — please try again in a moment.", chan = chan)); } diff --git a/modules/botserv/src/bot.rs b/modules/botserv/src/bot.rs index 70b3269..5fa0043 100644 --- a/modules/botserv/src/bot.rs +++ b/modules/botserv/src/bot.rs @@ -1,4 +1,4 @@ -use echo_api::{ChanError, Priv, Sender, ServiceCtx, Store}; +use echo_api::{t, ChanError, Priv, Sender, ServiceCtx, Store}; // BOT ADD [gecos] | BOT DEL | BOT LIST — manage the // bot registry. Administering bots is oper-only (Priv::Admin). @@ -15,8 +15,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: }; let gecos = if args.len() > 5 { args[5..].join(" ") } else { "Service Bot".to_string() }; match db.bot_add(nick, user, host, &gecos) { - Ok(()) => ctx.notice(me, from.uid, format!("Bot \x02{nick}\x02 (\x02{user}@{host}\x02) added.")), - Err(_) => ctx.notice(me, from.uid, format!("A bot named \x02{nick}\x02 already exists, or that didn't work.")), + Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Bot \x02{nick}\x02 (\x02{user}@{host}\x02) added.", nick = nick, user = user, host = host)), + Err(_) => ctx.notice(me, from.uid, t!(ctx, "A bot named \x02{nick}\x02 already exists, or that didn't work.", nick = nick)), } } Some("CHANGE") => { @@ -26,15 +26,15 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: }; // Omitted fields keep the bot's current values. let Some(cur) = db.bots().into_iter().find(|b| b.nick.eq_ignore_ascii_case(old)) else { - ctx.notice(me, from.uid, format!("There's no bot named \x02{old}\x02.")); + ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02.", nick = old)); return; }; let user = args.get(4).map(|s| s.to_string()).unwrap_or(cur.user); let host = args.get(5).map(|s| s.to_string()).unwrap_or(cur.host); let gecos = if args.len() > 6 { args[6..].join(" ") } else { cur.gecos }; match db.bot_change(old, newnick, &user, &host, &gecos) { - Ok(()) => ctx.notice(me, from.uid, format!("Bot \x02{old}\x02 is now \x02{newnick}\x02 (\x02{user}@{host}\x02).")), - Err(ChanError::Exists) => ctx.notice(me, from.uid, format!("A bot named \x02{newnick}\x02 already exists.")), + Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Bot \x02{old}\x02 is now \x02{newnick}\x02 (\x02{user}@{host}\x02).", old = old, newnick = newnick, user = user, host = host)), + Err(ChanError::Exists) => ctx.notice(me, from.uid, t!(ctx, "A bot named \x02{newnick}\x02 already exists.", newnick = newnick)), Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."), } } @@ -45,14 +45,14 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: }; if nick == "*" { match db.bot_del_all() { - Ok(n) => ctx.notice(me, from.uid, format!("Removed all \x02{n}\x02 bot(s).")), + Ok(n) => ctx.notice(me, from.uid, t!(ctx, "Removed all \x02{n}\x02 bot(s).", n = n)), Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."), } return; } match db.bot_del(nick) { - Ok(true) => ctx.notice(me, from.uid, format!("Bot \x02{nick}\x02 deleted.")), - Ok(false) => ctx.notice(me, from.uid, format!("There's no bot named \x02{nick}\x02.")), + Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Bot \x02{nick}\x02 deleted.", nick = nick)), + Ok(false) => ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02.", nick = nick)), Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."), } } @@ -62,12 +62,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: ctx.notice(me, from.uid, "No bots have been added yet. Add one with \x02BOT ADD\x02."); return; } - ctx.notice(me, from.uid, format!("Bots ({}):", bots.len())); + ctx.notice(me, from.uid, t!(ctx, "Bots ({count}):", count = bots.len())); for b in &bots { let flag = if b.private { " \x02[private]\x02" } else { "" }; - ctx.notice(me, from.uid, format!(" \x02{}\x02 ({}@{}) — {}{flag}", b.nick, b.user, b.host, b.gecos)); + ctx.notice(me, from.uid, t!(ctx, " \x02{nick}\x02 ({user}@{host}) — {gecos}{flag}", nick = b.nick, user = b.user, host = b.host, gecos = b.gecos, flag = flag)); } } - Some(other) => ctx.notice(me, from.uid, format!("Unknown BOT command \x02{other}\x02. Use \x02ADD\x02, \x02CHANGE\x02, \x02DEL\x02 or \x02LIST\x02.")), + Some(other) => ctx.notice(me, from.uid, t!(ctx, "Unknown BOT command \x02{other}\x02. Use \x02ADD\x02, \x02CHANGE\x02, \x02DEL\x02 or \x02LIST\x02.", other = other)), } } diff --git a/modules/botserv/src/botlist.rs b/modules/botserv/src/botlist.rs index 7216afd..04b574f 100644 --- a/modules/botserv/src/botlist.rs +++ b/modules/botserv/src/botlist.rs @@ -1,4 +1,4 @@ -use echo_api::{Priv, Sender, ServiceCtx, Store}; +use echo_api::{t, Priv, Sender, ServiceCtx, Store}; // BOTLIST: show the bots a channel founder can assign. Available to everyone, // unlike \x02BOT LIST\x02 (operator bot administration). Private bots are hidden @@ -10,10 +10,10 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) { ctx.notice(me, from.uid, "No bots are available."); return; } - ctx.notice(me, from.uid, format!("Available bots ({}):", bots.len())); + ctx.notice(me, from.uid, t!(ctx, "Available bots ({count}):", count = bots.len())); for b in &bots { let flag = if b.private { " \x02[private]\x02" } else { "" }; - ctx.notice(me, from.uid, format!(" \x02{}\x02 ({}@{}){flag}", b.nick, b.user, b.host)); + ctx.notice(me, from.uid, t!(ctx, " \x02{nick}\x02 ({user}@{host}){flag}", nick = b.nick, user = b.user, host = b.host, flag = flag)); } ctx.notice(me, from.uid, "Assign one with \x02ASSIGN <#channel> \x02."); } diff --git a/modules/botserv/src/copy.rs b/modules/botserv/src/copy.rs index 226ac76..28e5c91 100644 --- a/modules/botserv/src/copy.rs +++ b/modules/botserv/src/copy.rs @@ -1,4 +1,4 @@ -use echo_api::{Sender, ServiceCtx, Store}; +use echo_api::{t, Sender, ServiceCtx, Store}; // COPY <#source> <#dest>: copy a channel's bot configuration — kickers, // badwords, greet and nobot — onto another. Requires founder-or-admin on both. @@ -11,7 +11,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: return; } match db.copy_bot_config(src, dst) { - Ok(()) => ctx.notice(me, from.uid, format!("Copied \x02{src}\x02's bot settings (kickers, badwords, greet, nobot) to \x02{dst}\x02.")), + Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Copied \x02{src}\x02's bot settings (kickers, badwords, greet, nobot) to \x02{dst}\x02.", src = src, dst = dst)), Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."), } } diff --git a/modules/botserv/src/info.rs b/modules/botserv/src/info.rs index 524c275..f892b03 100644 --- a/modules/botserv/src/info.rs +++ b/modules/botserv/src/info.rs @@ -1,4 +1,4 @@ -use echo_api::{Sender, ServiceCtx, Store}; +use echo_api::{t, Sender, ServiceCtx, Store}; // INFO — describe a bot and list the channels it serves. // INFO <#channel> — show which bot (if any) is assigned to a channel. @@ -10,12 +10,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: if target.starts_with('#') { let Some(chan) = db.channel(target) else { - ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered.")); + ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = target)); return; }; match &chan.assigned_bot { - Some(bot) => ctx.notice(me, from.uid, format!("\x02{target}\x02 is served by bot \x02{bot}\x02.")), - None => ctx.notice(me, from.uid, format!("\x02{target}\x02 has no bot assigned. Assign one with \x02ASSIGN\x02 {target} .")), + Some(bot) => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 is served by bot \x02{bot}\x02.", chan = target, bot = bot)), + None => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no bot assigned. Assign one with \x02ASSIGN\x02 {chan} .", chan = target)), } // The BotServ options in effect on this channel. let mut opts = Vec::new(); @@ -29,12 +29,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: opts.push("nobot"); } let summary = if opts.is_empty() { "none".to_string() } else { opts.join(", ") }; - ctx.notice(me, from.uid, format!(" Options: {summary}")); + ctx.notice(me, from.uid, t!(ctx, " Options: {summary}", summary = summary)); return; } let Some(bot) = db.bots().into_iter().find(|b| b.nick.eq_ignore_ascii_case(target)) else { - ctx.notice(me, from.uid, format!("There's no bot named \x02{target}\x02. See \x02BOT LIST\x02.")); + ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02. See \x02BOT LIST\x02.", nick = target)); return; }; let channels: Vec = db @@ -45,11 +45,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: .collect(); let privacy = if bot.private { " (private)" } else { "" }; - ctx.notice(me, from.uid, format!("Bot \x02{}\x02 — {}@{}{privacy}", bot.nick, bot.user, bot.host)); - ctx.notice(me, from.uid, format!(" Real name: {}", bot.gecos)); + ctx.notice(me, from.uid, t!(ctx, "Bot \x02{nick}\x02 — {user}@{host}{privacy}", nick = bot.nick, user = bot.user, host = bot.host, privacy = privacy)); + ctx.notice(me, from.uid, t!(ctx, " Real name: {gecos}", gecos = bot.gecos)); if channels.is_empty() { ctx.notice(me, from.uid, " Not assigned to any channel."); } else { - ctx.notice(me, from.uid, format!(" Serving {} channel(s): {}", channels.len(), channels.join(", "))); + ctx.notice(me, from.uid, t!(ctx, " Serving {count} channel(s): {list}", count = channels.len(), list = channels.join(", "))); } } diff --git a/modules/botserv/src/kick.rs b/modules/botserv/src/kick.rs index f6201dc..70afedb 100644 --- a/modules/botserv/src/kick.rs +++ b/modules/botserv/src/kick.rs @@ -1,4 +1,4 @@ -use echo_api::{Kicker, Sender, ServiceCtx, Store}; +use echo_api::{t, Kicker, Sender, ServiceCtx, Store}; // KICK <#channel> {ON|OFF} [params]: configure the bot's kickers. // Types: CAPS [min [percent]], BOLDS, COLORS, UNDERLINES, REVERSES, ITALICS, @@ -16,8 +16,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: if kind.eq_ignore_ascii_case("TTB") { match args.get(3).and_then(|s| s.parse::().ok()) { Some(n) => match db.set_ttb(chan, n) { - Ok(()) if n == 0 => ctx.notice(me, from.uid, format!("The bot will only kick (not ban) in \x02{chan}\x02.")), - Ok(()) => ctx.notice(me, from.uid, format!("The bot will ban a user after \x02{n}\x02 kick(s) in \x02{chan}\x02.")), + Ok(()) if n == 0 => ctx.notice(me, from.uid, t!(ctx, "The bot will only kick (not ban) in \x02{chan}\x02.", chan = chan)), + Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The bot will ban a user after \x02{n}\x02 kick(s) in \x02{chan}\x02.", n = n, chan = chan)), Err(_) => reg_error(me, from, chan, ctx), }, None => ctx.notice(me, from.uid, "Syntax: KICK <#channel> TTB "), @@ -34,7 +34,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: } let text = args[3..].join(" "); match db.kicker_test(chan, &text) { - Some(reason) => ctx.notice(me, from.uid, format!("That line \x02would be kicked\x02: {reason}")), + Some(reason) => ctx.notice(me, from.uid, t!(ctx, "That line \x02would be kicked\x02: {reason}", reason = reason)), None => ctx.notice(me, from.uid, "That line trips no content kicker (flood/repeat depend on timing and repetition, so they aren't tested here)."), } return; @@ -55,7 +55,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: let min = args.get(4).and_then(|s| s.parse::().ok()).unwrap_or(0); let percent = args.get(5).and_then(|s| s.parse::().ok()).filter(|p| (1..=100).contains(p)).unwrap_or(0); match db.set_caps_kicker(chan, min, percent) { - Ok(()) => ctx.notice(me, from.uid, format!("The bot will now kick for \x02caps\x02 in \x02{chan}\x02.")), + Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The bot will now kick for \x02caps\x02 in \x02{chan}\x02.", chan = chan)), Err(_) => reg_error(me, from, chan, ctx), } } else { @@ -68,7 +68,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: let lines = args.get(4).and_then(|s| s.parse::().ok()).unwrap_or(0); let secs = args.get(5).and_then(|s| s.parse::().ok()).unwrap_or(0); match db.set_flood_kicker(chan, lines, secs) { - Ok(()) => ctx.notice(me, from.uid, format!("The bot will now kick for \x02flooding\x02 in \x02{chan}\x02.")), + Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The bot will now kick for \x02flooding\x02 in \x02{chan}\x02.", chan = chan)), Err(_) => reg_error(me, from, chan, ctx), } } else { @@ -80,7 +80,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: if on { let times = args.get(4).and_then(|s| s.parse::().ok()).unwrap_or(0); match db.set_repeat_kicker(chan, times) { - Ok(()) => ctx.notice(me, from.uid, format!("The bot will now kick for \x02repeating\x02 in \x02{chan}\x02.")), + Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The bot will now kick for \x02repeating\x02 in \x02{chan}\x02.", chan = chan)), Err(_) => reg_error(me, from, chan, ctx), } } else { @@ -100,7 +100,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: "DONTKICKOPS" => Kicker::DontKickOps, "DONTKICKVOICES" => Kicker::DontKickVoices, other => { - ctx.notice(me, from.uid, format!("Unknown kicker \x02{other}\x02. Try CAPS, FLOOD, REPEAT, BADWORDS, BOLDS, COLORS, UNDERLINES, REVERSES, ITALICS, WARN, DONTKICKOPS or DONTKICKVOICES.")); + ctx.notice(me, from.uid, t!(ctx, "Unknown kicker \x02{other}\x02. Try CAPS, FLOOD, REPEAT, BADWORDS, BOLDS, COLORS, UNDERLINES, REVERSES, ITALICS, WARN, DONTKICKOPS or DONTKICKVOICES.", other = other)); return; } }; @@ -126,18 +126,18 @@ fn label(kicker: Kicker) -> &'static str { fn toggle(me: &str, from: &Sender, chan: &str, kicker: Kicker, on: bool, ctx: &mut ServiceCtx, db: &mut dyn Store) { match db.set_kicker(chan, kicker, on) { - Ok(()) if kicker == Kicker::DontKickOps && on => ctx.notice(me, from.uid, format!("The bot will no longer kick channel operators in \x02{chan}\x02.")), - Ok(()) if kicker == Kicker::DontKickOps => ctx.notice(me, from.uid, format!("The bot may again kick channel operators in \x02{chan}\x02.")), - Ok(()) if kicker == Kicker::DontKickVoices && on => ctx.notice(me, from.uid, format!("The bot will no longer kick voiced users in \x02{chan}\x02.")), - Ok(()) if kicker == Kicker::DontKickVoices => ctx.notice(me, from.uid, format!("The bot may again kick voiced users in \x02{chan}\x02.")), - Ok(()) if kicker == Kicker::Warn && on => ctx.notice(me, from.uid, format!("The bot will warn once before the first kick in \x02{chan}\x02.")), - Ok(()) if kicker == Kicker::Warn => ctx.notice(me, from.uid, format!("The bot will kick without warning in \x02{chan}\x02.")), - Ok(()) if on => ctx.notice(me, from.uid, format!("The \x02{}\x02 kicker is now on in \x02{chan}\x02.", label(kicker))), - Ok(()) => ctx.notice(me, from.uid, format!("The \x02{}\x02 kicker is now off in \x02{chan}\x02.", label(kicker))), + Ok(()) if kicker == Kicker::DontKickOps && on => ctx.notice(me, from.uid, t!(ctx, "The bot will no longer kick channel operators in \x02{chan}\x02.", chan = chan)), + Ok(()) if kicker == Kicker::DontKickOps => ctx.notice(me, from.uid, t!(ctx, "The bot may again kick channel operators in \x02{chan}\x02.", chan = chan)), + Ok(()) if kicker == Kicker::DontKickVoices && on => ctx.notice(me, from.uid, t!(ctx, "The bot will no longer kick voiced users in \x02{chan}\x02.", chan = chan)), + Ok(()) if kicker == Kicker::DontKickVoices => ctx.notice(me, from.uid, t!(ctx, "The bot may again kick voiced users in \x02{chan}\x02.", chan = chan)), + Ok(()) if kicker == Kicker::Warn && on => ctx.notice(me, from.uid, t!(ctx, "The bot will warn once before the first kick in \x02{chan}\x02.", chan = chan)), + Ok(()) if kicker == Kicker::Warn => ctx.notice(me, from.uid, t!(ctx, "The bot will kick without warning in \x02{chan}\x02.", chan = chan)), + Ok(()) if on => ctx.notice(me, from.uid, t!(ctx, "The \x02{kicker}\x02 kicker is now on in \x02{chan}\x02.", kicker = label(kicker), chan = chan)), + Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The \x02{kicker}\x02 kicker is now off in \x02{chan}\x02.", kicker = label(kicker), chan = chan)), Err(_) => reg_error(me, from, chan, ctx), } } fn reg_error(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx) { - ctx.notice(me, from.uid, format!("Couldn't update \x02{chan}\x02 — please try again in a moment.")); + ctx.notice(me, from.uid, t!(ctx, "Couldn't update \x02{chan}\x02 — please try again in a moment.", chan = chan)); } diff --git a/modules/botserv/src/lib.rs b/modules/botserv/src/lib.rs index 6273ef5..98b7551 100644 --- a/modules/botserv/src/lib.rs +++ b/modules/botserv/src/lib.rs @@ -2,7 +2,7 @@ //! and (in later slices) run fantasy commands. `lib.rs` holds the dispatcher; //! each command lives in its own file, matching NickServ/ChanServ. -use echo_api::{HelpEntry, NetView, Priv, Sender, Service, ServiceCtx, Store}; +use echo_api::{t, HelpEntry, NetView, Priv, Sender, Service, ServiceCtx, Store}; #[path = "bot.rs"] mod bot; @@ -31,11 +31,11 @@ mod trigger; // founder or a services admin. Notices and returns false on failure. fn require_channel_admin(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx, db: &dyn Store) -> bool { let Some(founder) = db.channel(chan).map(|c| c.founder) else { - ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered.")); + ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan)); return false; }; if from.account != Some(founder.as_str()) && !from.privs.has(Priv::Admin) { - ctx.notice(me, from.uid, format!("Only \x02{chan}\x02's founder can change that.")); + ctx.notice(me, from.uid, t!(ctx, "Only \x02{chan}\x02's founder can change that.", chan = chan)); return false; } true @@ -95,7 +95,7 @@ impl Service for BotServ { Some("COPY") => copy::handle(me, from, args, ctx, db), Some("TRIGGER") => trigger::handle(me, from, args, ctx, db), Some("HELP") | None => echo_api::help(me, from, ctx, BLURB, TOPICS, args.get(1).copied()), - Some(other) => ctx.notice(me, from.uid, format!("I don't know the command \x02{other}\x02. Try \x02HELP\x02.")), + Some(other) => ctx.notice(me, from.uid, t!(ctx, "I don't know the command \x02{other}\x02. Try \x02HELP\x02.", other = other)), } } } diff --git a/modules/botserv/src/say.rs b/modules/botserv/src/say.rs index 646b3c4..ae0ecf7 100644 --- a/modules/botserv/src/say.rs +++ b/modules/botserv/src/say.rs @@ -1,4 +1,4 @@ -use echo_api::{NetView, Priv, Sender, ServiceCtx, Store}; +use echo_api::{t, NetView, Priv, Sender, ServiceCtx, Store}; // SAY <#channel> — make the channel's assigned bot say something. // ACT <#channel> — the same, as a CTCP ACTION (/me). @@ -6,27 +6,27 @@ use echo_api::{NetView, Priv, Sender, ServiceCtx, Store}; pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store, action: bool) { let verb = if action { "ACT" } else { "SAY" }; if args.len() < 3 { - ctx.notice(me, from.uid, format!("Syntax: {verb} <#channel> ")); + ctx.notice(me, from.uid, t!(ctx, "Syntax: {verb} <#channel> ", verb = verb)); return; } let chan = args[1]; let text = args[2..].join(" "); let Some(info) = db.channel(chan) else { - ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered.")); + ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan)); return; }; let allowed = from.privs.has(Priv::Admin) || from.account.is_some_and(|a| info.is_op(a)); if !allowed { - ctx.notice(me, from.uid, format!("Access denied — you need operator access in \x02{chan}\x02.")); + ctx.notice(me, from.uid, t!(ctx, "Access denied — you need operator access in \x02{chan}\x02.", chan = chan)); return; } let Some(bot) = info.assigned_bot else { - ctx.notice(me, from.uid, format!("\x02{chan}\x02 has no bot assigned. Assign one with \x02ASSIGN\x02 {chan} .")); + ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no bot assigned. Assign one with \x02ASSIGN\x02 {chan} .", chan = chan)); return; }; let Some(botuid) = net.uid_by_nick(&bot) else { - ctx.notice(me, from.uid, format!("Bot \x02{bot}\x02 isn't on the network right now.")); + ctx.notice(me, from.uid, t!(ctx, "Bot \x02{bot}\x02 isn't on the network right now.", bot = bot)); return; }; let botuid = botuid.to_string(); diff --git a/modules/botserv/src/set.rs b/modules/botserv/src/set.rs index 7713b5e..1a51b65 100644 --- a/modules/botserv/src/set.rs +++ b/modules/botserv/src/set.rs @@ -1,4 +1,4 @@ -use echo_api::{parse_duration, ChanSetting, Priv, Sender, ServiceCtx, Store}; +use echo_api::{parse_duration, t, ChanSetting, Priv, Sender, ServiceCtx, Store}; // SET <#channel>