i18n: localize the snotice/announce path — translate at deliver_server_notice chokepoint; rehash, connect/exit, oper-up, login, channel & ojoin notices via trf (fr, es)

This commit is contained in:
Jean Chevronnet 2026-08-25 13:04:31 +00:00
parent 6f8ff39f99
commit ef96bcbd06
No known key found for this signature in database
GPG key ID: 439666D63A9477E4
9 changed files with 93 additions and 31 deletions

View file

@ -43,7 +43,8 @@ impl Server {
// connected. A SASL-at-connect login runs before registration completes, so
// it's already reflected in the "Client connecting: … account: …" notice.
if registered {
self.snotice_c('c', &format!("Client {nick} is now logged in as {account}"));
let m = self.trf("Client {0} is now logged in as {1}", &[nick.as_str(), account]);
self.snotice_c('c', &m);
}
}

View file

@ -941,7 +941,8 @@ impl Server {
.get(&uid)
.map(|u| u.nick.clone())
.unwrap_or_default();
self.snotice_c('v', &format!("{nick} used oper override to join {name}"));
let m = self.trf("{0} used oper override to join {1}", &[nick.as_str(), name]);
self.snotice_c('v', &m);
}
let is_new = !self.channels.contains_key(&key);
let ch = self
@ -971,7 +972,8 @@ impl Server {
.get(&uid)
.map(|u| u.nick.clone())
.unwrap_or_default();
self.snotice_c('j', &format!("{who} created channel {name}"));
let m = self.trf("{0} created channel {1}", &[who.as_str(), name]);
self.snotice_c('j', &m);
}
// JOIN broadcast — extended-join clients also get the account + realname

View file

@ -48,27 +48,23 @@ impl Command for Rehash {
match Config::try_load(&path) {
Some(fresh) => {
// announce to everyone connected, not just opers
s.announce(&format!(
"admin {who} has changed the configuration of the server."
));
s.announce(&format!("{who} is rehashing the server config file."));
let m = s.trf("admin {0} has changed the configuration of the server.", &[who.as_str()]);
s.announce(&m);
let m = s.trf("{0} is rehashing the server config file.", &[who.as_str()]);
s.announce(&m);
s.apply_config(fresh);
s.announce("Server configuration reloaded.");
s.numeric(uid, RPL_REHASHING, &format!("{path} :Rehashing"));
}
None => {
// Unreadable config — keep what's running (do NOT reset to defaults).
s.announce(&format!(
"{who} tried to reload the server configuration, but the config file \
could not be read no changes were made."
));
s.send(
uid,
format!(
":{} NOTICE {who} :*** Could not read {path} — the running configuration was kept.",
s.name
),
let m = s.trf(
"{0} tried to reload the server configuration, but the config file could not be read — no changes were made.",
&[who.as_str()],
);
s.announce(&m);
let m = s.trf("Could not read {0} — the running configuration was kept.", &[path.as_str()]);
s.send(uid, format!(":{} NOTICE {who} :*** {m}", s.name));
}
}
CmdResult::Ok

View file

@ -49,7 +49,8 @@ impl Command for OjoinCmd {
if s.conf_bool("ojoin_op", true) {
crate::coremods::core_mode::svs_set_chan_modes(s, &chan, "+o", &[nick.clone()]);
}
s.snotice_c('v', &format!("{nick} used OJOIN to enter {chan}"));
let m = s.trf("{0} used OJOIN to enter {1}", &[nick.as_str(), chan.as_str()]);
s.snotice_c('v', &m);
CmdResult::Ok
}
}

View file

@ -14,22 +14,36 @@ impl Module for Snoop {
fn on_user_connect(&mut self, srv: &mut Server, uid: Uid) {
// `conf_bool`/`snotice_c` are `&self`, so we can hold the `&User` borrow and
// reference its fields directly instead of cloning them out.
let Some(u) = srv.users.get(&uid) else {
return;
let (nick, ident, host, port, sni, account) = {
let Some(u) = srv.users.get(&uid) else {
return;
};
(
u.nick.clone(),
u.ident.clone(),
u.host.clone(),
u.port,
u.sni.clone(),
u.account.clone(),
)
};
if srv.conf_bool("snoop_stderr", false) {
eprintln!("[snoop] connect {} ({}@{})", u.nick, u.ident, u.host);
eprintln!("[snoop] connect {nick} ({ident}@{host})");
}
// port is always shown; sni/account only when present, so plaintext or
// anonymous connects don't carry empty fields.
let mut extra = format!(", port: {}", u.port);
if let Some(sni) = &u.sni {
extra.push_str(&format!(", sni: {sni}"));
// port is always shown; sni/account only when present. Prose + field labels come
// from the locale catalog so a translated build reads naturally.
let mut msg = srv.trf(
"Client connecting: {0} ({1}@{2})",
&[nick.as_str(), ident.as_str(), host.as_str()],
);
let port_s = port.to_string();
msg.push_str(&srv.trf(", port: {0}", &[port_s.as_str()]));
if let Some(sni) = &sni {
msg.push_str(&srv.trf(", sni: {0}", &[sni.as_str()]));
}
if let Some(acct) = &u.account {
extra.push_str(&format!(", account: {acct}"));
if let Some(acct) = &account {
msg.push_str(&srv.trf(", account: {0}", &[acct.as_str()]));
}
let msg = format!("Client connecting: {} ({}@{}){extra}", u.nick, u.ident, u.host);
srv.snotice_c('c', &msg);
}
fn on_join(&mut self, srv: &mut Server, uid: Uid, chan: &str) {
@ -56,6 +70,7 @@ impl Module for Snoop {
if srv.conf_bool("snoop_stderr", false) {
eprintln!("[snoop] quit uid={uid} ({reason})");
}
srv.snotice_c('q', &format!("Client exiting: {nick} ({reason})"));
let m = srv.trf("Client exiting: {0} ({1})", &[nick.as_str(), reason]);
srv.snotice_c('q', &m);
}
}

View file

@ -1070,6 +1070,10 @@ impl Server {
return;
};
let nick = u.nick.clone();
// Localize the prose for the recipient; the log/chanlog/syslog tees keep the
// English source. Static snotice/announce text auto-translates here (like the
// numeric chokepoint); already-templated dynamic text passes through unchanged.
let msg = self.catalog.tr(msg);
let mut tags: Vec<String> = Vec::new();
if u.caps.server_time {
tags.push(format!("time={}", iso_time(now())));

View file

@ -331,7 +331,8 @@ impl Server {
.unwrap_or_default();
self.numeric(uid, RPL_YOUREOPER, ":You are now an IRC operator");
self.send(uid, format!(":{} MODE {nick} :+os", self.name));
self.snotice_c('o', &format!("{nick} is now an IRC operator"));
let m = self.trf("{0} is now an IRC operator", &[nick.as_str()]);
self.snotice_c('o', &m);
// operprefix: give this oper the ! prefix in every channel they're already in
crate::modules::operprefix::grant_all(self, uid);
// opermodes: extra umodes on oper-up