i18n: sweep all remaining dynamic prose (oper commands, XLINE/FILTER/DNSBL notices, whois/vhost/knock/dcc/title/reputation/tline/rmode, hash, rename) through trf; +\xNN catalog escape; fr+es now 236 entries each

This commit is contained in:
Jean Chevronnet 2026-08-25 14:30:29 +00:00
parent ef96bcbd06
commit 9b51db07f5
No known key found for this signature in database
GPG key ID: 439666D63A9477E4
32 changed files with 295 additions and 128 deletions

View file

@ -31,6 +31,63 @@
"{0} created channel {1}" = "{0} creó el canal {1}"
"{0} used OJOIN to enter {1}" = "{0} usó OJOIN para entrar a {1}"
# --- comandos de operador: SA*/CHG/XLINE/FILTER/RLINE/etc. (prosa dinámica) ---
"GLOBOPS from {0}: {1}" = "GLOBOPS de {0}: {1}"
"{0}-line not found: {1}" = "línea {0} no encontrada: {1}"
"R-line not found: {0}" = "línea R no encontrada: {0}"
"Invalid RLINE regex: {0}" = "expresión regular RLINE no válida: {0}"
"{0} used NICKLOCK on {1}" = "{0} usó NICKLOCK en {1}"
"{0} used NICKUNLOCK on {1}" = "{0} usó NICKUNLOCK en {1}"
"{0} used SQUIT on {1}" = "{0} usó SQUIT en {1}"
"{0} used SAMODE: {1}" = "{0} usó SAMODE: {1}"
"{0} used SATOPIC on {1}" = "{0} usó SATOPIC en {1}"
"{0} used SAKICK on {1} in {2}" = "{0} usó SAKICK en {1} en {2}"
"{0} used SAQUIT on {1}: {2}" = "{0} usó SAQUIT en {1}: {2}"
"{0} used CHGNAME on {1}: {2}" = "{0} usó CHGNAME en {1}: {2}"
"{0} used CLEARCHAN on {1}" = "{0} usó CLEARCHAN en {1}"
"{0} used SWHOIS on {1}: {2}" = "{0} usó SWHOIS en {1}: {2}"
"{0} is already banned on {1}" = "{0} ya está vetado en {1}"
"[Knock] {0} is knocking: {1}" = "[Knock] {0} está llamando: {1}"
"Channel invite list is full" = "La lista de invitaciones del canal está llena"
"Your invite to {0} was revoked" = "Tu invitación a {0} fue revocada"
"{0} renamed to {1} by {2}" = "{0} renombrado a {1} por {2}"
"SSLINFO {0}: TLS={1} certfp={2}" = "SSLINFO {0}: TLS={1} certfp={2}"
"{0} did a /WHOIS on you" = "{0} te hizo un /WHOIS"
"Cannot message {0}: a TLS connection is required (+z)" = "No se puede enviar mensaje a {0}: se requiere una conexión TLS (+z)"
"{0} IRC network" = "Red IRC {0}"
"Your vhost is now {0}" = "Tu vhost ahora es {0}"
"Your SIGNORE list is full ({0} max)." = "Tu lista SIGNORE está llena ({0} máx.)."
"SIGNORE \x02{0}\x02 added — you and they can no longer see each other's messages." = "SIGNORE \x02{0}\x02 añadido — tú y esa persona ya no podéis ver los mensajes del otro."
"SIGNORE \x02{0}\x02 removed." = "SIGNORE \x02{0}\x02 eliminado."
"{0} (Flagged by the spam filter; network operators have been notified.)" = "{0} (Detectado por el filtro antispam; se ha notificado a los operadores de la red.)"
"User {0} had an /amsg or /ame blocked" = "Al usuario {0} se le bloqueó un /amsg o /ame"
"Cloudflare Challenge: {0}" = "Cloudflare Challenge: {0}"
"connect class {0} is full ({1})" = "la clase de conexión {0} está llena ({1})"
"connect class {0} refused {1}: {2}" = "la clase de conexión {0} rechazó {1}: {2}"
"TITLE: you are now known as \"{0}\"." = "TITLE: ahora se le conoce como \"{0}\"."
"Your DCC to {0} was blocked; they must /DCCALLOW +{1} first." = "Su DCC a {0} fue bloqueado; primero debe hacer /DCCALLOW +{1}."
"{0} tried to send you {1} — blocked. /DCCALLOW +{2} to allow it, then ask them to resend." = "{0} intentó enviarle {1} — bloqueado. Haga /DCCALLOW +{2} para permitirlo, y luego pídale que lo reenvíe."
"oper type '{0}' is not defined — granting full access" = "el tipo de operador '{0}' no está definido — otorgando acceso completo"
"{0} hashed password: {1}" = "contraseña cifrada con {0}: {1}"
"Unknown hash '{0}' (try md5, sha1, sha256, sha512, pbkdf2, bcrypt)" = "hash desconocido '{0}' (pruebe md5, sha1, sha256, sha512, pbkdf2, bcrypt)"
"REPUTATION {0} ({1}) set to {2}" = "REPUTATION {0} ({1}) establecido en {2}"
"RMODE: removed {0} +{1} entr{2} from {3}" = "RMODE: eliminó {0} entr{2} +{1} de {3}"
"{0} is in security groups: {1}" = "{0} está en los grupos de seguridad: {1}"
"{0}: file not available." = "{0}: archivo no disponible."
"To cut spam, answer this to send your message — what is {0} {1} {2} ?" = "Para reducir el spam, responda esto para enviar su mensaje — ¿cuánto es {0} {1} {2} ?"
"TLINE: {0} matches {1} of {2} local users ({3}%)" = "TLINE: {0} coincide con {1} de {2} usuarios locales ({3}%)"
"RPC initiated a link to {0}" = "RPC inició un enlace hacia {0}"
"slow event: a command took {0}ms on the core thread" = "evento lento: un comando tardó {0}ms en el hilo principal"
"Could not hash with '{0}'" = "No se pudo cifrar con '{0}'"
"Link denied: {0}" = "Enlace denegado: {0}"
"End of channel {0}" = "Fin de {0} del canal"
"{0} is now known as {1}" = "{0} ahora se conoce como {1}"
"FILTER rejected ({0})" = "FILTER rechazado ({0})"
"{0} added FILTER {1} (engine={2} action={3})" = "{0} añadió FILTER {1} (engine={2} action={3})"
"XLINE: {0} added a {1}: {2}" = "XLINE: {0} añadió un {1}: {2}"
"XLINE: {0} removed a {1}" = "XLINE: {0} eliminó un {1}"
"XLINE: {0}-line on {1} expired" = "XLINE: la {0}-line sobre {1} ha expirado"
# --- avisos / desafíos ---
"*** Cloudflare Challenge: verification successful — you may continue." = "*** Desafío de Cloudflare: verificación exitosa — puedes continuar."
"*** Correct — you may now message freely; please resend your message." = "*** Correcto — ya puedes enviar mensajes libremente; por favor reenvía tu mensaje."

View file

@ -31,6 +31,63 @@
"{0} created channel {1}" = "{0} a créé le salon {1}"
"{0} used OJOIN to enter {1}" = "{0} a utilisé OJOIN pour entrer dans {1}"
# --- commandes oper : SA*/CHG/XLINE/FILTER/RLINE/etc. (prose dynamique) ---
"GLOBOPS from {0}: {1}" = "GLOBOPS de {0} : {1}"
"{0}-line not found: {1}" = "ligne {0} introuvable : {1}"
"R-line not found: {0}" = "ligne R introuvable : {0}"
"Invalid RLINE regex: {0}" = "expression régulière RLINE invalide : {0}"
"{0} used NICKLOCK on {1}" = "{0} a utilisé NICKLOCK sur {1}"
"{0} used NICKUNLOCK on {1}" = "{0} a utilisé NICKUNLOCK sur {1}"
"{0} used SQUIT on {1}" = "{0} a utilisé SQUIT sur {1}"
"{0} used SAMODE: {1}" = "{0} a utilisé SAMODE : {1}"
"{0} used SATOPIC on {1}" = "{0} a utilisé SATOPIC sur {1}"
"{0} used SAKICK on {1} in {2}" = "{0} a utilisé SAKICK sur {1} dans {2}"
"{0} used SAQUIT on {1}: {2}" = "{0} a utilisé SAQUIT sur {1} : {2}"
"{0} used CHGNAME on {1}: {2}" = "{0} a utilisé CHGNAME sur {1} : {2}"
"{0} used CLEARCHAN on {1}" = "{0} a utilisé CLEARCHAN sur {1}"
"{0} used SWHOIS on {1}: {2}" = "{0} a utilisé SWHOIS sur {1} : {2}"
"{0} is already banned on {1}" = "{0} est déjà banni sur {1}"
"[Knock] {0} is knocking: {1}" = "[Knock] {0} frappe à la porte : {1}"
"Channel invite list is full" = "La liste d'invitations du salon est pleine"
"Your invite to {0} was revoked" = "Votre invitation à {0} a été révoquée"
"{0} renamed to {1} by {2}" = "{0} renommé en {1} par {2}"
"SSLINFO {0}: TLS={1} certfp={2}" = "SSLINFO {0} : TLS={1} certfp={2}"
"{0} did a /WHOIS on you" = "{0} a fait un /WHOIS sur vous"
"Cannot message {0}: a TLS connection is required (+z)" = "Impossible d'envoyer un message à {0} : une connexion TLS est requise (+z)"
"{0} IRC network" = "Réseau IRC {0}"
"Your vhost is now {0}" = "Votre vhost est maintenant {0}"
"Your SIGNORE list is full ({0} max)." = "Votre liste SIGNORE est pleine ({0} max)."
"SIGNORE \x02{0}\x02 added — you and they can no longer see each other's messages." = "SIGNORE \x02{0}\x02 ajouté — vous et cette personne ne pouvez plus voir vos messages respectifs."
"SIGNORE \x02{0}\x02 removed." = "SIGNORE \x02{0}\x02 retiré."
"{0} (Flagged by the spam filter; network operators have been notified.)" = "{0} (Signalé par le filtre anti-spam ; les opérateurs du réseau ont été avertis.)"
"User {0} had an /amsg or /ame blocked" = "L'utilisateur {0} a eu un /amsg ou /ame bloqué"
"Cloudflare Challenge: {0}" = "Cloudflare Challenge : {0}"
"connect class {0} is full ({1})" = "la classe de connexion {0} est pleine ({1})"
"connect class {0} refused {1}: {2}" = "la classe de connexion {0} a refusé {1} : {2}"
"TITLE: you are now known as \"{0}\"." = "TITLE : vous êtes désormais connu sous le nom de « {0} »."
"Your DCC to {0} was blocked; they must /DCCALLOW +{1} first." = "Votre DCC vers {0} a été bloqué ; il doit d'abord faire /DCCALLOW +{1}."
"{0} tried to send you {1} — blocked. /DCCALLOW +{2} to allow it, then ask them to resend." = "{0} a tenté de vous envoyer {1} — bloqué. Faites /DCCALLOW +{2} pour l'autoriser, puis demandez-lui de renvoyer."
"oper type '{0}' is not defined — granting full access" = "le type d'opérateur '{0}' n'est pas défini — octroi d'un accès complet"
"{0} hashed password: {1}" = "mot de passe haché en {0} : {1}"
"Unknown hash '{0}' (try md5, sha1, sha256, sha512, pbkdf2, bcrypt)" = "hachage inconnu '{0}' (essayez md5, sha1, sha256, sha512, pbkdf2, bcrypt)"
"REPUTATION {0} ({1}) set to {2}" = "REPUTATION {0} ({1}) réglé sur {2}"
"RMODE: removed {0} +{1} entr{2} from {3}" = "RMODE : a supprimé {0} entr{2} +{1} de {3}"
"{0} is in security groups: {1}" = "{0} est dans les groupes de sécurité : {1}"
"{0}: file not available." = "{0} : fichier non disponible."
"To cut spam, answer this to send your message — what is {0} {1} {2} ?" = "Pour limiter le spam, répondez à ceci pour envoyer votre message — combien font {0} {1} {2} ?"
"TLINE: {0} matches {1} of {2} local users ({3}%)" = "TLINE : {0} correspond à {1} des {2} utilisateurs locaux ({3}%)"
"RPC initiated a link to {0}" = "RPC a initié une liaison vers {0}"
"slow event: a command took {0}ms on the core thread" = "événement lent : une commande a pris {0}ms sur le thread principal"
"Could not hash with '{0}'" = "Impossible de hacher avec '{0}'"
"Link denied: {0}" = "Lien refusé : {0}"
"End of channel {0}" = "Fin de {0} du salon"
"{0} is now known as {1}" = "{0} est maintenant connu sous le nom de {1}"
"FILTER rejected ({0})" = "FILTER rejeté ({0})"
"{0} added FILTER {1} (engine={2} action={3})" = "{0} a ajouté FILTER {1} (engine={2} action={3})"
"XLINE: {0} added a {1}: {2}" = "XLINE : {0} a ajouté un {1} : {2}"
"XLINE: {0} removed a {1}" = "XLINE : {0} a supprimé un {1}"
"XLINE: {0}-line on {1} expired" = "XLINE : la {0}-line sur {1} a expiré"
# --- notices / défis ---
"*** Cloudflare Challenge: verification successful — you may continue." = "*** Défi Cloudflare : vérification réussie — vous pouvez continuer."
"*** Correct — you may now message freely; please resend your message." = "*** Correct — vous pouvez maintenant envoyer des messages librement ; veuillez renvoyer votre message."

View file

@ -66,12 +66,10 @@ impl Command for Tban {
(u.nick.clone(), u.prefix())
};
if s.channels[&key].bans.iter().any(|b| b.mask == mask) {
let m = s.trf("{0} is already banned on {1}", &[mask.as_str(), chan.as_str()]);
s.send(
uid,
format!(
":{} NOTICE {nick} :{mask} is already banned on {chan}",
s.name
),
format!(":{} NOTICE {nick} :{m}", s.name),
);
return CmdResult::Fail;
}
@ -127,12 +125,10 @@ impl Command for Knock {
.cloned()
.unwrap_or_else(|| "requesting an invite".to_string());
let who = s.users[&uid].prefix();
let m = s.trf("[Knock] {0} is knocking: {1}", &[who.as_str(), reason.as_str()]);
s.to_channel(
&key,
&format!(
":{} NOTICE {chan} :[Knock] {who} is knocking: {reason}",
s.name
),
&format!(":{} NOTICE {chan} :{m}", s.name),
None,
);
s.numeric(
@ -314,9 +310,10 @@ impl Command for Invite {
let maxinv = s.conf_num("maxinvites", 100usize);
if s.channels[&key].invites.len() >= maxinv && !s.channels[&key].invites.contains(&tuid) {
let nick = s.users.get(&uid).map(|u| u.nick.clone()).unwrap_or_default();
let m = s.trf("Channel invite list is full", &[]);
s.send(
uid,
format!(":{} NOTICE {nick} :{chan} :Channel invite list is full", s.name),
format!(":{} NOTICE {nick} :{chan} :{m}", s.name),
);
return CmdResult::Fail;
}
@ -408,12 +405,10 @@ impl Command for Uninvite {
format!(":{} NOTICE {nick} :{who} {word} {chan}", s.name),
);
if removed {
let m = s.trf("Your invite to {0} was revoked", &[chan.as_str()]);
s.send(
tuid,
format!(
":{} NOTICE {who} :Your invite to {chan} was revoked",
s.name
),
format!(":{} NOTICE {who} :{m}", s.name),
);
}
CmdResult::Ok
@ -563,7 +558,8 @@ impl Command for Rename {
s.fail(uid, "RENAME", "CANNOT_RENAME", "The channel cannot be renamed.");
return CmdResult::Fail;
}
s.snotice_c('a', &format!("{oldname} renamed to {new} by {prefix}"));
let m = s.trf("{0} renamed to {1} by {2}", &[oldname.as_str(), new.as_str(), prefix.as_str()]);
s.snotice_c('a', &m);
s.propagate_rename(&uuid, &oldname, new, &reason, None);
CmdResult::Ok
}

View file

@ -245,7 +245,9 @@ impl Command for Admin {
RPL_ADMINME,
&format!("{} :Administrative info", s.name),
);
s.numeric(uid, RPL_ADMINLOC1, &format!(":{} IRC network", s.network));
let network = s.network.clone();
let m = s.trf("{0} IRC network", &[network.as_str()]);
s.numeric(uid, RPL_ADMINLOC1, &format!(":{m}"));
s.numeric(
uid,
RPL_ADMINLOC2,

View file

@ -53,12 +53,10 @@ impl Command for SslInfo {
};
let tls = if secure { "yes" } else { "no" };
let fp = certfp.unwrap_or_else(|| "none".to_string());
let m = s.trf("SSLINFO {0}: TLS={1} certfp={2}", &[nick.as_str(), tls, fp.as_str()]);
s.send(
uid,
format!(
":{} NOTICE {asker} :SSLINFO {nick}: TLS={tls} certfp={fp}",
s.name
),
format!(":{} NOTICE {asker} :{m}", s.name),
);
CmdResult::Ok
}
@ -437,9 +435,10 @@ impl Command for Whois {
// +W showwhois — tell the target that someone looked them up
if showwhois && !is_self {
let by = s.users.get(&uid).map(|u| u.prefix()).unwrap_or_default();
let m = s.trf("{0} did a /WHOIS on you", &[by.as_str()]);
s.send(
tuid,
format!(":{} NOTICE {nick} :*** {by} did a /WHOIS on you", s.name),
format!(":{} NOTICE {nick} :*** {m}", s.name),
);
}
s.numeric(uid, RPL_ENDOFWHOIS, &format!("{nick} :End of /WHOIS list"));

View file

@ -543,12 +543,10 @@ pub(crate) fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool)
.map(|u| u.nick.clone())
.unwrap_or_default(),
);
let m = s.trf("Cannot message {0}: a TLS connection is required (+z)", &[tn.as_str()]);
s.send(
uid,
format!(
":{} NOTICE {sn} :Cannot message {tn}: a TLS connection is required (+z)",
s.name
),
format!(":{} NOTICE {sn} :{m}", s.name),
);
}
return CmdResult::Fail;

View file

@ -398,14 +398,12 @@ impl Command for GlobOps {
.filter(|(_, u)| u.flags.oper)
.map(|(&u, _)| u)
.collect();
let m = s.trf("GLOBOPS from {0}: {1}", &[from.as_str(), params[0].as_str()]);
for o in opers {
let nick = s.users.get(&o).map(|u| u.nick.clone()).unwrap_or_default();
s.send(
o,
format!(
":{} NOTICE {nick} :*** GLOBOPS from {from}: {}",
s.name, params[0]
),
format!(":{} NOTICE {nick} :*** {m}", s.name),
);
}
CmdResult::Ok
@ -731,9 +729,10 @@ fn do_xline(s: &mut Server, uid: Uid, params: &[String], kind: XKind) -> CmdResu
if s.remove_xline(kind, &mask, &nick) {
s.propagate_delline(kind.tag(), &mask);
} else {
let m = s.trf("{0}-line not found: {1}", &[kind.tag(), mask.as_str()]);
s.send(
uid,
format!(":{} NOTICE {nick} :{}-line not found: {mask}", s.name, kind.tag()),
format!(":{} NOTICE {nick} :{m}", s.name),
);
}
return CmdResult::Ok;
@ -878,17 +877,20 @@ impl Command for Rline {
if s.remove_xline(XKind::Rline, &pattern, &nick) {
s.propagate_delline("R", &pattern);
} else {
let m = s.trf("R-line not found: {0}", &[pattern.as_str()]);
s.send(
uid,
format!(":{} NOTICE {nick} :R-line not found: {pattern}", s.name),
format!(":{} NOTICE {nick} :{m}", s.name),
);
}
return CmdResult::Ok;
}
if let Err(e) = crate::regex::Regex::new(&pattern) {
let e = e.to_string();
let m = s.trf("Invalid RLINE regex: {0}", &[e.as_str()]);
s.send(
uid,
format!(":{} NOTICE {nick} :Invalid RLINE regex: {e}", s.name),
format!(":{} NOTICE {nick} :{m}", s.name),
);
return CmdResult::Fail;
}
@ -952,7 +954,8 @@ impl Command for NickLock {
u.flags.nick_locked = true;
}
let by = oper_nick(s, uid);
s.snotice_c('v', &format!("{by} used NICKLOCK on {newnick}"));
let m = s.trf("{0} used NICKLOCK on {1}", &[by.as_str(), newnick.as_str()]);
s.snotice_c('v', &m);
CmdResult::Ok
}
}
@ -977,7 +980,8 @@ impl Command for NickUnlock {
u.flags.nick_locked = false;
}
let by = oper_nick(s, uid);
s.snotice_c('v', &format!("{by} used NICKUNLOCK on {}", params[0]));
let m = s.trf("{0} used NICKUNLOCK on {1}", &[by.as_str(), params[0].as_str()]);
s.snotice_c('v', &m);
CmdResult::Ok
}
}
@ -1049,7 +1053,8 @@ impl Command for Squit {
return CmdResult::Fail;
};
let by = oper_nick(s, uid);
s.snotice_c('l', &format!("{by} used SQUIT on {name}"));
let m = s.trf("{0} used SQUIT on {1}", &[by.as_str(), name.as_str()]);
s.snotice_c('l', &m);
s.close_link(via, &format!("SQUIT from {by}"));
CmdResult::Ok
}
@ -1209,7 +1214,9 @@ impl Command for SaMode {
let r = apply_mode(s, uid, params);
s.mode_sudo = false;
let by = oper_nick(s, uid);
s.snotice_c('v', &format!("{by} used SAMODE: {}", params.join(" ")));
let joined = params.join(" ");
let m = s.trf("{0} used SAMODE: {1}", &[by.as_str(), joined.as_str()]);
s.snotice_c('v', &m);
r
}
}
@ -1248,7 +1255,8 @@ impl Command for SaTopic {
s.to_channel(&key, &format!(":{prefix} TOPIC {chan} :{text}"), None);
s.propagate_topic(uid, chan, &text);
let by = oper_nick(s, uid);
s.snotice_c('v', &format!("{by} used SATOPIC on {chan}"));
let m = s.trf("{0} used SATOPIC on {1}", &[by.as_str(), chan.as_str()]);
s.snotice_c('v', &m);
CmdResult::Ok
}
}
@ -1313,7 +1321,8 @@ impl Command for SaKick {
s.events
.push_back(Hook::Part(tuid, key, "kicked".to_string()));
let by = oper_nick(s, uid);
s.snotice_c('v', &format!("{by} used SAKICK on {victim} in {chan}"));
let m = s.trf("{0} used SAKICK on {1} in {2}", &[by.as_str(), victim.as_str(), chan.as_str()]);
s.snotice_c('v', &m);
CmdResult::Ok
}
}
@ -1343,7 +1352,8 @@ impl Command for SaQuit {
s.send(tuid, format!("ERROR :Closing link: (SAQUIT: {reason})"));
s.remove_user(tuid, &format!("Quit: {reason}"));
let by = oper_nick(s, uid);
s.snotice_c('v', &format!("{by} used SAQUIT on {}: {reason}", params[0]));
let m = s.trf("{0} used SAQUIT on {1}: {2}", &[by.as_str(), params[0].as_str(), reason.as_str()]);
s.snotice_c('v', &m);
CmdResult::Ok
}
}
@ -1379,7 +1389,8 @@ impl Command for ChgName {
}
s.notify_peers(t, &line, |c| c.setname);
let by = oper_nick(s, uid);
s.snotice_c('v', &format!("{by} used CHGNAME on {}: {realname}", params[0]));
let m = s.trf("{0} used CHGNAME on {1}: {2}", &[by.as_str(), params[0].as_str(), realname.as_str()]);
s.snotice_c('v', &m);
CmdResult::Ok
}
}
@ -1436,7 +1447,8 @@ impl Command for ClearChan {
}
s.channels.retain(|_, c| c.keep_alive());
let by = oper_nick(s, uid);
s.snotice_c('v', &format!("{by} used CLEARCHAN on {chan}"));
let m = s.trf("{0} used CLEARCHAN on {1}", &[by.as_str(), chan.as_str()]);
s.snotice_c('v', &m);
CmdResult::Ok
}
}
@ -1553,7 +1565,8 @@ impl Command for SwhoisCmd {
}
}
let by = oper_nick(s, uid);
s.snotice_c('v', &format!("{by} used SWHOIS on {}: {text}", params[0]));
let m = s.trf("{0} used SWHOIS on {1}: {2}", &[by.as_str(), params[0].as_str(), text.as_str()]);
s.snotice_c('v', &m);
CmdResult::Ok
}
}

View file

@ -60,9 +60,10 @@ impl Command for Vhost {
match host {
Some(h) => {
s.change_host_ident(uid, None, Some(&h));
let m = s.trf("Your vhost is now {0}", &[h.as_str()]);
s.send(
uid,
format!(":{} NOTICE {nick} :Your vhost is now {h}", s.name),
format!(":{} NOTICE {nick} :{m}", s.name),
);
}
None => {

View file

@ -351,7 +351,9 @@ impl Command for Signore {
.map(|u| u.signore.len() >= max && !u.signore.contains(&mask))
.unwrap_or(true);
if full {
s.send(uid, format!(":{sn} NOTICE {nick} :Your SIGNORE list is full ({max} max)."));
let maxs = max.to_string();
let m = s.trf("Your SIGNORE list is full ({0} max).", &[maxs.as_str()]);
s.send(uid, format!(":{sn} NOTICE {nick} :{m}"));
return CmdResult::Fail;
}
if let Some(u) = s.users.get_mut(&uid) {
@ -359,12 +361,14 @@ impl Command for Signore {
u.signore.push(mask.clone());
}
}
s.send(uid, format!(":{sn} NOTICE {nick} :SIGNORE \x02{mask}\x02 added — you and they can no longer see each other's messages."));
let m = s.trf("SIGNORE \x02{0}\x02 added — you and they can no longer see each other's messages.", &[mask.as_str()]);
s.send(uid, format!(":{sn} NOTICE {nick} :{m}"));
} else {
if let Some(u) = s.users.get_mut(&uid) {
u.signore.retain(|x| x != &mask);
}
s.send(uid, format!(":{sn} NOTICE {nick} :SIGNORE \x02{mask}\x02 removed."));
let m = s.trf("SIGNORE \x02{0}\x02 removed.", &[mask.as_str()]);
s.send(uid, format!(":{sn} NOTICE {nick} :{m}"));
}
// Persist the change to the user's services account (no-op if not logged in).
s.push_signore_to_services(uid);

View file

@ -231,6 +231,20 @@ fn read_quoted(s: &str) -> Option<(String, &str)> {
't' => out.push('\t'),
'"' => out.push('"'),
'\\' => out.push('\\'),
// \xNN — a raw byte, so IRC formatting controls (e.g. \x02 bold) in a
// msgid match the literal byte the source `"\x02"` compiles to.
'x' => {
let (h1, h2) = (chars.next()?.1, chars.next()?.1);
match u8::from_str_radix(&format!("{h1}{h2}"), 16) {
Ok(b) => out.push(b as char),
Err(_) => {
out.push('\\');
out.push('x');
out.push(h1);
out.push(h2);
}
}
}
other => {
out.push('\\');
out.push(other);

View file

@ -175,8 +175,11 @@ impl Ircd {
busy.store(0, Ordering::Relaxed);
let ms = start.elapsed().as_millis() as u64;
if slow_ms != 0 && ms >= slow_ms {
self.server
.snotice(&format!("slow event: a command took {ms}ms on the core thread"));
let ms_s = ms.to_string();
let m = self
.server
.trf("slow event: a command took {0}ms on the core thread", &[ms_s.as_str()]);
self.server.snotice(&m);
}
}
}
@ -260,11 +263,16 @@ impl Ircd {
.map(|u| u.nick.clone())
.unwrap_or_default();
let line = match hash {
Some(h) => format!(
":{} NOTICE {nick} :{algo} hashed password: {h}",
self.server.name
),
None => format!(":{} NOTICE {nick} :Could not hash with '{algo}'", self.server.name),
Some(h) => {
let m = self
.server
.trf("{0} hashed password: {1}", &[algo.as_str(), h.as_str()]);
format!(":{} NOTICE {nick} :{m}", self.server.name)
}
None => {
let m = self.server.trf("Could not hash with '{0}'", &[algo.as_str()]);
format!(":{} NOTICE {nick} :{m}", self.server.name)
}
};
self.server.send(uid, line);
}

View file

@ -340,7 +340,8 @@ impl Server {
}
fn reject_link(&mut self, uid: Uid, why: &str) {
self.link_out(uid, format!("ERROR :Link denied: {why}"));
let m = self.trf("Link denied: {0}", &[why]);
self.link_out(uid, format!("ERROR :{m}"));
eprintln!("[link] rejected {uid}: {why}");
self.close_link(uid, why);
}

View file

@ -726,7 +726,8 @@ impl ChanMode for ListMode {
for (m, setter, ts) in rows {
s.numeric(uid, entry_num, &format!("{chan} {m} {setter} {ts}"));
}
s.numeric(uid, end_num, &format!("{chan} :End of channel {noun}"));
let m = s.trf("End of channel {0}", &[noun]);
s.numeric(uid, end_num, &format!("{chan} :{m}"));
return Applied::No;
};
// autoop (+w) embeds a status prefix to grant on join, applied under server

View file

@ -285,12 +285,14 @@ impl Module for AntiMixedUtf8 {
// Notify the sender even for punitive actions: the writer flushes queued
// lines before a disconnect.
let block_msg = srv.amu.block_msg.clone();
let m = srv.trf(
"{0} (Flagged by the spam filter; network operators have been notified.)",
&[block_msg.as_str()],
);
srv.send(
uid,
format!(
":{} NOTICE {nick} :*** {} (Flagged by the spam filter; network operators have been notified.)",
srv.name, srv.amu.block_msg
),
format!(":{} NOTICE {nick} :*** {m}", srv.name),
);
let action = srv.amu.action.to_ascii_lowercase();

View file

@ -90,7 +90,8 @@ impl Module for BlockAmsg {
let notify_opers = matches!(action.as_str(), "killopers" | "noticeopers" | "zlineopers");
if notify_opers {
let mask = srv.users.get(&uid).map(|u| u.prefix()).unwrap_or_default();
srv.snotice(&format!("User {mask} had an /amsg or /ame blocked"));
let m = srv.trf("User {0} had an /amsg or /ame blocked", &[mask.as_str()]);
srv.snotice(&m);
}
let reason = "Attempted to global message (/amsg or /ame)";
match action.as_str() {

View file

@ -158,9 +158,10 @@ impl Command for VerifyChallenge {
.unwrap_or_default();
let fail = |s: &mut Server, why: &str| {
let m = s.trf("Cloudflare Challenge: {0}", &[why]);
s.send(
uid,
format!(":{} NOTICE {nick} :*** Cloudflare Challenge: {why}", s.name),
format!(":{} NOTICE {nick} :*** {m}", s.name),
);
CmdResult::Fail
};

View file

@ -289,7 +289,9 @@ fn pick(
if let Some(max) = c.limit {
if class_count(s, &c.name, uid) >= max {
if c.maxconnwarn {
s.snotice_c('c', &format!("connect class {} is full ({max})", c.name));
let max_s = max.to_string();
let m = s.trf("connect class {0} is full ({1})", &[c.name.as_str(), max_s.as_str()]);
s.snotice_c('c', &m);
}
continue; // full — try the next class
}
@ -355,7 +357,11 @@ pub fn assign(s: &mut Server, uid: Uid) -> Option<String> {
};
let warn = |s: &Server, why: &str| {
if class.maxconnwarn {
s.snotice_c('c', &format!("connect class {} refused {ip}: {why}", class.name));
let m = s.trf(
"connect class {0} refused {1}: {2}",
&[class.name.as_str(), ip.as_str(), why],
);
s.snotice_c('c', &m);
}
};
if let Some(max) = class.localmax {

View file

@ -45,9 +45,10 @@ pub fn grant(s: &mut Server, uid: Uid, title: &str, vhost: &str) {
s.change_host_ident(uid, None, Some(vhost));
}
let nick = nick(s, uid);
let m = s.trf("TITLE: you are now known as \"{0}\".", &[title]);
s.send(
uid,
format!(":{} NOTICE {nick} :*** TITLE: you are now known as \"{title}\".", s.name),
format!(":{} NOTICE {nick} :*** {m}", s.name),
);
}

View file

@ -117,19 +117,21 @@ impl Module for DccAllow {
return ModResult::Passthru;
}
let tnick = s.users.get(&tuid).map(|u| u.nick.clone()).unwrap_or_default();
let m1 = s.trf(
"Your DCC to {0} was blocked; they must /DCCALLOW +{1} first.",
&[tnick.as_str(), sender.as_str()],
);
s.send(
uid,
format!(
":{} NOTICE {sender} :*** Your DCC to {tnick} was blocked; they must /DCCALLOW +{sender} first.",
s.name
),
format!(":{} NOTICE {sender} :*** {m1}", s.name),
);
let m2 = s.trf(
"{0} tried to send you {1} — blocked. /DCCALLOW +{2} to allow it, then ask them to resend.",
&[sender.as_str(), what.as_str(), sender.as_str()],
);
s.send(
tuid,
format!(
":{} NOTICE {tnick} :*** {sender} tried to send you {what} — blocked. /DCCALLOW +{sender} to allow it, then ask them to resend.",
s.name
),
format!(":{} NOTICE {tnick} :*** {m2}", s.name),
);
ModResult::Deny
}

View file

@ -185,14 +185,20 @@ impl Command for FilterCmd {
let filter = match SpamFilter::new(pattern.clone(), engine.clone(), action.clone(), duration, reason) {
Ok(f) => f,
Err(e) => {
s.send(uid, format!(":{} NOTICE {nick} :FILTER rejected ({e})", s.name));
let e_s = e.to_string();
let m = s.trf("FILTER rejected ({0})", &[e_s.as_str()]);
s.send(uid, format!(":{} NOTICE {nick} :{m}", s.name));
return CmdResult::Fail;
}
};
let f = s.ext.get_or_insert_with::<Filters>(Filters::default);
f.0.retain(|r| r.pattern != pattern);
f.0.push(filter);
s.snotice_c('f', &format!("{nick} added FILTER {pattern} (engine={engine} action={action})"));
let m = s.trf(
"{0} added FILTER {1} (engine={2} action={3})",
&[nick.as_str(), pattern.as_str(), engine.as_str(), action.as_str()],
);
s.snotice_c('f', &m);
}
}
}

View file

@ -28,7 +28,9 @@ impl Command for GlobopsCmd {
return CmdResult::Fail;
}
let nick = s.users.get(&uid).map(|u| u.nick.clone()).unwrap_or_default();
s.snotice_c('g', &format!("GLOBOPS from {nick}: {}", params.join(" ")));
let body = params.join(" ");
let m = s.trf("GLOBOPS from {0}: {1}", &[nick.as_str(), body.as_str()]);
s.snotice_c('g', &m);
CmdResult::Ok
}
}

View file

@ -102,7 +102,8 @@ pub fn apply(s: &mut Server, uid: Uid, type_id: Option<&str>) {
return;
};
let Some(r) = with_resolved(s, |m| m.get(&id).cloned()) else {
s.snotice_c('o', &format!("oper type '{id}' is not defined — granting full access"));
let m = s.trf("oper type '{0}' is not defined — granting full access", &[id.as_str()]);
s.snotice_c('o', &m);
return;
};
if !r.modes.is_empty() {

View file

@ -199,23 +199,16 @@ impl Command for MkPasswd {
}
match make(&algo, &pass) {
Some(hashed) => {
s.send(
uid,
format!(
":{} NOTICE {nick} :{algo} hashed password: {hashed}",
s.name
),
);
let m = s.trf("{0} hashed password: {1}", &[algo.as_str(), hashed.as_str()]);
s.send(uid, format!(":{} NOTICE {nick} :{m}", s.name));
CmdResult::Ok
}
None => {
s.send(
uid,
format!(
":{} NOTICE {nick} :Unknown hash '{algo}' (try md5, sha1, sha256, sha512, pbkdf2, bcrypt)",
s.name
),
let m = s.trf(
"Unknown hash '{0}' (try md5, sha1, sha256, sha512, pbkdf2, bcrypt)",
&[algo.as_str()],
);
s.send(uid, format!(":{} NOTICE {nick} :{m}", s.name));
CmdResult::Fail
}
}

View file

@ -289,13 +289,13 @@ impl Command for ReputationCmd {
e.score = val.min(cap);
e.last_seen = n;
save(s);
s.send(
uid,
format!(
":{} NOTICE {anick} :REPUTATION {nick} ({k}) set to {val}",
s.name
),
let ks = k.to_string();
let vals = val.to_string();
let m = s.trf(
"REPUTATION {0} ({1}) set to {2}",
&[nick.as_str(), ks.as_str(), vals.as_str()],
);
s.send(uid, format!(":{} NOTICE {anick} :{m}", s.name));
} else {
let score = score_of(s, tuid);
s.send(

View file

@ -99,13 +99,13 @@ impl Command for RMode {
.map(|u| u.nick.clone())
.unwrap_or_default();
let plural = if removed == 1 { "y" } else { "ies" };
s.send(
uid,
format!(
":{} NOTICE {nick} :RMODE: removed {removed} +{mode} entr{plural} from {chan}",
s.name
),
let removeds = removed.to_string();
let modes = mode.to_string();
let m = s.trf(
"RMODE: removed {0} +{1} entr{2} from {3}",
&[removeds.as_str(), modes.as_str(), plural, chan.as_str()],
);
s.send(uid, format!(":{} NOTICE {nick} :{m}", s.name));
CmdResult::Ok
}
}

View file

@ -61,7 +61,8 @@ pub fn handle(s: &mut Server, action: &str, params: &str) -> Result<String, RpcE
std::thread::spawn(move || {
crate::socketengine::connect_link(&addr, tx, counter, max_line)
});
s.snotice_c('l', &format!("RPC initiated a link to {}", b.name));
let m = s.trf("RPC initiated a link to {0}", &[b.name.as_str()]);
s.snotice_c('l', &m);
Ok(obj(&[("result", "true".into())]))
}
"disconnect" => {

View file

@ -221,13 +221,8 @@ impl Command for SecGroupsCmd {
.map(|u| u.nick.clone())
.unwrap_or_default(),
);
s.send(
uid,
format!(
":{} NOTICE {anick} :{tnick} is in security groups: {list}",
s.name
),
);
let m = s.trf("{0} is in security groups: {1}", &[tnick.as_str(), list.as_str()]);
s.send(uid, format!(":{} NOTICE {anick} :{m}", s.name));
CmdResult::Ok
}
}

View file

@ -36,10 +36,10 @@ pub fn maybe_show(s: &mut Server, uid: Uid, cmd: &str) -> bool {
s.send(uid, format!(":{} NOTICE {nick} :{line}", s.name));
}
}
None => s.send(
uid,
format!(":{} NOTICE {nick} :*** {cmd}: file not available.", s.name),
),
None => {
let m = s.trf("{0}: file not available.", &[cmd]);
s.send(uid, format!(":{} NOTICE {nick} :*** {m}", s.name));
}
}
true
}

View file

@ -97,13 +97,13 @@ impl Module for SolveMsg {
answer: Some(ans),
});
}
s.send(
uid,
format!(
":{} NOTICE {nick} :*** To cut spam, answer this to send your message — what is {a} {sym} {b} ?",
s.name
),
let as_ = a.to_string();
let bs = b.to_string();
let m = s.trf(
"To cut spam, answer this to send your message — what is {0} {1} {2} ?",
&[as_.as_str(), sym, bs.as_str()],
);
s.send(uid, format!(":{} NOTICE {nick} :*** {m}", s.name));
ModResult::Deny
}
}

View file

@ -48,13 +48,14 @@ impl Command for TLine {
.get(&uid)
.map(|u| u.nick.clone())
.unwrap_or_default();
s.send(
uid,
format!(
":{} NOTICE {nick} :*** TLINE: {mask} matches {matched} of {total} local users ({pct}%)",
s.name
),
let matcheds = matched.to_string();
let totals = total.to_string();
let pcts = pct.to_string();
let m = s.trf(
"TLINE: {0} matches {1} of {2} local users ({3}%)",
&[mask.as_str(), matcheds.as_str(), totals.as_str(), pcts.as_str()],
);
s.send(uid, format!(":{} NOTICE {nick} :*** {m}", s.name));
CmdResult::Ok
}
}

View file

@ -438,7 +438,8 @@ impl Server {
self.send(t, line.clone());
}
if self.conf_bool("seenicks", false) {
self.snotice_c('n', &format!("{old} is now known as {newnick}"));
let m = self.trf("{0} is now known as {1}", &[old.as_str(), newnick]);
self.snotice_c('n', &m);
}
// WATCH/MONITOR: the old nick is now gone, the new one is here
self.watch_notify_offline(&old);

View file

@ -304,7 +304,8 @@ impl Server {
crate::server::long_date(expires)
)
};
self.snotice_c('x', &format!("XLINE: {setter} added a {detail}: {reason}"));
let m = self.trf("XLINE: {0} added a {1}: {2}", &[setter, detail.as_str(), reason]);
self.snotice_c('x', &m);
self.save_xlines();
self.enforce_xlines();
}
@ -335,7 +336,8 @@ impl Server {
Some(e) if e != 0 => format!("timed {tag}-line on {mask} (already expired)"),
_ => format!("permanent {tag}-line on {mask}"),
};
self.snotice_c('x', &format!("XLINE: {remover} removed a {detail}"));
let m = self.trf("XLINE: {0} removed a {1}", &[remover, detail.as_str()]);
self.snotice_c('x', &m);
self.save_xlines();
}
removed
@ -383,7 +385,8 @@ impl Server {
}
self.xlines.retain(|x| x.expires == 0 || x.expires > n);
for (kind, mask) in &expired {
self.snotice_c('x', &format!("XLINE: {}-line on {mask} expired", kind.tag()));
let m = self.trf("XLINE: {0}-line on {1} expired", &[kind.tag(), mask.as_str()]);
self.snotice_c('x', &m);
}
self.save_xlines(); // an expiry changed the set — persist it
}