Localize engine notices, transactional emails, and operator-access denials in all six languages
All checks were successful
CI / check (push) Successful in 5m18s
All checks were successful
CI / check (push) Successful in 5m18s
This commit is contained in:
parent
ecdead8278
commit
e4a14b9813
13 changed files with 299 additions and 55 deletions
|
|
@ -33,38 +33,44 @@ fn render(brand: &str, accent: &str, logo: &str, title: &str, message: &str, cod
|
||||||
.replace("{{note}}", &escape(note))
|
.replace("{{note}}", &escape(note))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(brand: &str, accent: &str, logo: &str, account: &str, code: &str) -> Mail {
|
pub fn reset(brand: &str, accent: &str, logo: &str, account: &str, code: &str, lang: &str) -> Mail {
|
||||||
|
let acc = || vec![("account", account.to_string())];
|
||||||
Mail {
|
Mail {
|
||||||
subject: format!("Password reset for {account}"),
|
subject: crate::render(lang, "Password reset for {account}", &acc()),
|
||||||
text: format!(
|
text: crate::render(
|
||||||
"Your password reset code for {account} is: {code}\nIt expires in 15 minutes.\nReset with:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n"
|
lang,
|
||||||
|
"Your password reset code for {account} is: {code}\nIt expires in 15 minutes.\nReset with:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n",
|
||||||
|
&[("account", account.to_string()), ("code", code.to_string())],
|
||||||
),
|
),
|
||||||
html: render(
|
html: render(
|
||||||
brand,
|
brand,
|
||||||
accent,
|
accent,
|
||||||
logo,
|
logo,
|
||||||
"Password reset",
|
&crate::render(lang, "Password reset", &[]),
|
||||||
&format!("Use this code to reset the password for your account {account}."),
|
&crate::render(lang, "Use this code to reset the password for your account {account}.", &acc()),
|
||||||
code,
|
code,
|
||||||
"This code expires in 15 minutes. If you didn't ask to reset it, ignore this email.",
|
&crate::render(lang, "This code expires in 15 minutes. If you didn't ask to reset it, ignore this email.", &[]),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn confirm(brand: &str, accent: &str, logo: &str, account: &str, code: &str) -> Mail {
|
pub fn confirm(brand: &str, accent: &str, logo: &str, account: &str, code: &str, lang: &str) -> Mail {
|
||||||
|
let acc = || vec![("account", account.to_string())];
|
||||||
Mail {
|
Mail {
|
||||||
subject: format!("Confirm your {account} registration"),
|
subject: crate::render(lang, "Confirm your {account} registration", &acc()),
|
||||||
text: format!(
|
text: crate::render(
|
||||||
"Confirm your account {account} with:\n /msg NickServ CONFIRM {code}\nThe code expires in 15 minutes.\n"
|
lang,
|
||||||
|
"Confirm your account {account} with:\n /msg NickServ CONFIRM {code}\nThe code expires in 15 minutes.\n",
|
||||||
|
&[("account", account.to_string()), ("code", code.to_string())],
|
||||||
),
|
),
|
||||||
html: render(
|
html: render(
|
||||||
brand,
|
brand,
|
||||||
accent,
|
accent,
|
||||||
logo,
|
logo,
|
||||||
"Confirm your account",
|
&crate::render(lang, "Confirm your account", &[]),
|
||||||
&format!("Welcome! Confirm the email for your account {account} with the code below."),
|
&crate::render(lang, "Welcome! Confirm the email for your account {account} with the code below.", &acc()),
|
||||||
code,
|
code,
|
||||||
"This code expires in 15 minutes.",
|
&crate::render(lang, "This code expires in 15 minutes.", &[]),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -87,26 +93,39 @@ impl ExpiryTarget {
|
||||||
|
|
||||||
// Warn the owner of an account or channel that inactivity will soon expire it.
|
// Warn the owner of an account or channel that inactivity will soon expire it.
|
||||||
// `remaining` is a human span ("7 days") and takes the prominent code slot.
|
// `remaining` is a human span ("7 days") and takes the prominent code slot.
|
||||||
pub fn expiry_warning(brand: &str, accent: &str, logo: &str, kind: ExpiryTarget, name: &str, remaining: &str) -> Mail {
|
pub fn expiry_warning(brand: &str, accent: &str, logo: &str, kind: ExpiryTarget, name: &str, remaining: &str, lang: &str) -> Mail {
|
||||||
let word = kind.word();
|
let word = crate::render(lang, kind.word(), &[]);
|
||||||
let keep = if kind == ExpiryTarget::Channel {
|
let keep = crate::render(
|
||||||
|
lang,
|
||||||
|
if kind == ExpiryTarget::Channel {
|
||||||
"To keep it, have a member join the channel before then. Otherwise it will be removed."
|
"To keep it, have a member join the channel before then. Otherwise it will be removed."
|
||||||
} else {
|
} else {
|
||||||
"To keep it, just identify to it before then. Otherwise it will be removed."
|
"To keep it, just identify to it before then. Otherwise it will be removed."
|
||||||
|
},
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
let base = || {
|
||||||
|
vec![
|
||||||
|
("word", word.clone()),
|
||||||
|
("name", name.to_string()),
|
||||||
|
("remaining", remaining.to_string()),
|
||||||
|
]
|
||||||
};
|
};
|
||||||
Mail {
|
Mail {
|
||||||
subject: format!("Your {word} {name} is about to expire"),
|
subject: crate::render(lang, "Your {word} {name} is about to expire", &[("word", word.clone()), ("name", name.to_string())]),
|
||||||
text: format!(
|
text: crate::render(
|
||||||
"Your {word} {name} has been inactive and will expire in {remaining}.\n{keep}\n"
|
lang,
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.\n{keep}\n",
|
||||||
|
&[("word", word.clone()), ("name", name.to_string()), ("remaining", remaining.to_string()), ("keep", keep.clone())],
|
||||||
),
|
),
|
||||||
html: render(
|
html: render(
|
||||||
brand,
|
brand,
|
||||||
accent,
|
accent,
|
||||||
logo,
|
logo,
|
||||||
"About to expire",
|
&crate::render(lang, "About to expire", &[]),
|
||||||
&format!("Your {word} {name} has been inactive and will expire in {remaining}."),
|
&crate::render(lang, "Your {word} {name} has been inactive and will expire in {remaining}.", &base()),
|
||||||
remaining,
|
remaining,
|
||||||
keep,
|
&keep,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -545,19 +545,27 @@ pub fn plural_category(lang: &str, n: u64) -> Plural {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render the `one` or `other` English template for `n` under `lang`'s plural
|
||||||
|
/// rule, then substitute `{name}` args. The [`plural!`] macro wraps this for
|
||||||
|
/// service call sites; the engine and email layer (which have no `ctx`) call it
|
||||||
|
/// directly with an explicit language.
|
||||||
|
pub fn render_plural(lang: &str, n: u64, one: &str, other: &str, args: &[(&str, String)]) -> String {
|
||||||
|
let key = match plural_category(lang, n) {
|
||||||
|
Plural::One => one,
|
||||||
|
Plural::Other => other,
|
||||||
|
};
|
||||||
|
render(lang, key, args)
|
||||||
|
}
|
||||||
|
|
||||||
/// Localize a count-dependent reply. Selects the `one` or `other` English
|
/// Localize a count-dependent reply. Selects the `one` or `other` English
|
||||||
/// template by the target language's plural rule, then renders it like [`t!`].
|
/// template by the target language's plural rule, then renders it like [`t!`].
|
||||||
/// Both forms are message ids, so both need a catalog entry in each language.
|
/// Both forms are message ids, so both need a catalog entry in each language.
|
||||||
/// `plural!(ctx, n, one = "…{n} entry…", other = "…{n} entries…", n = n, …)`.
|
/// `plural!(ctx, n, one = "…{n} entry…", other = "…{n} entries…", n = n, …)`.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! plural {
|
macro_rules! plural {
|
||||||
($ctx:expr, $n:expr, one = $one:literal, other = $other:literal $(, $name:ident = $val:expr)* $(,)?) => {{
|
($ctx:expr, $n:expr, one = $one:literal, other = $other:literal $(, $name:ident = $val:expr)* $(,)?) => {
|
||||||
let __key = match $crate::plural_category($ctx.lang(), ($n) as u64) {
|
$crate::render_plural($ctx.lang(), ($n) as u64, $one, $other, &[$((stringify!($name), format!("{}", $val))),*])
|
||||||
$crate::Plural::One => $one,
|
|
||||||
$crate::Plural::Other => $other,
|
|
||||||
};
|
};
|
||||||
$crate::render($ctx.lang(), __key, &[$((stringify!($name), format!("{}", $val))),*])
|
|
||||||
}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The intent sink a service writes to. A service never mutates the network or
|
/// The intent sink a service writes to. A service never mutates the network or
|
||||||
|
|
@ -2594,7 +2602,9 @@ pub fn require_oper(me: &str, from: &Sender, ctx: &mut ServiceCtx, need: Option<
|
||||||
None => from.privs.any(),
|
None => from.privs.any(),
|
||||||
};
|
};
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.notice(me, from.uid, format!("Access denied — {what} is for services operators."));
|
// Translate the action fragment, then the sentence around it.
|
||||||
|
let what = render(ctx.lang(), what, &[]);
|
||||||
|
ctx.notice(me, from.uid, render(ctx.lang(), "Access denied — {what} is for services operators.", &[("what", what)]));
|
||||||
}
|
}
|
||||||
ok
|
ok
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
lang/de.json
27
lang/de.json
|
|
@ -1308,5 +1308,30 @@
|
||||||
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} Meldung. \u0002VIEW\u0002 <id> für Details.",
|
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} Meldung. \u0002VIEW\u0002 <id> für Details.",
|
||||||
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} Meldungen. \u0002VIEW\u0002 <id> für Details.",
|
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} Meldungen. \u0002VIEW\u0002 <id> für Details.",
|
||||||
"End of list ({count} group).": "Ende der Liste ({count} Gruppe).",
|
"End of list ({count} group).": "Ende der Liste ({count} Gruppe).",
|
||||||
"End of list ({count} groups).": "Ende der Liste ({count} Gruppen)."
|
"End of list ({count} groups).": "Ende der Liste ({count} Gruppen).",
|
||||||
|
"Password reset for {account}": "Passwort-Zurücksetzung für {account}",
|
||||||
|
"Your password reset code for {account} is: {code}\nIt expires in 15 minutes.\nReset with:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n": "Dein Code zum Zurücksetzen des Passworts für {account} lautet: {code}\nEr läuft in 15 Minuten ab.\nSetze es zurück mit:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n",
|
||||||
|
"Password reset": "Passwort zurücksetzen",
|
||||||
|
"Use this code to reset the password for your account {account}.": "Verwende diesen Code, um das Passwort für dein Konto {account} zurückzusetzen.",
|
||||||
|
"This code expires in 15 minutes. If you didn't ask to reset it, ignore this email.": "Dieser Code läuft in 15 Minuten ab. Wenn du das Zurücksetzen nicht angefordert hast, ignoriere diese E-Mail.",
|
||||||
|
"Confirm your {account} registration": "Bestätige deine Registrierung für {account}",
|
||||||
|
"Confirm your account {account} with:\n /msg NickServ CONFIRM {code}\nThe code expires in 15 minutes.\n": "Bestätige dein Konto {account} mit:\n /msg NickServ CONFIRM {code}\nDer Code läuft in 15 Minuten ab.\n",
|
||||||
|
"Confirm your account": "Bestätige dein Konto",
|
||||||
|
"Welcome! Confirm the email for your account {account} with the code below.": "Willkommen! Bestätige die E-Mail für dein Konto {account} mit dem folgenden Code.",
|
||||||
|
"This code expires in 15 minutes.": "Dieser Code läuft in 15 Minuten ab.",
|
||||||
|
"Your {word} {name} is about to expire": "Dein {word} {name} läuft bald ab",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.\n{keep}\n": "Dein {word} {name} war inaktiv und läuft in {remaining} ab.\n{keep}\n",
|
||||||
|
"About to expire": "Läuft bald ab",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.": "Dein {word} {name} war inaktiv und läuft in {remaining} ab.",
|
||||||
|
"To keep it, have a member join the channel before then. Otherwise it will be removed.": "Damit er erhalten bleibt, lass vorher ein Mitglied den Kanal betreten. Andernfalls wird er entfernt.",
|
||||||
|
"To keep it, just identify to it before then. Otherwise it will be removed.": "Damit er erhalten bleibt, identifiziere dich vorher einfach dafür. Andernfalls wird er entfernt.",
|
||||||
|
"account": "Konto",
|
||||||
|
"channel": "Kanal",
|
||||||
|
"You're now identified as \u0002{account}\u0002. Welcome back!": "Du bist jetzt als \u0002{account}\u0002 identifiziert. Willkommen zurück!",
|
||||||
|
"You have been logged out of \u0002{account}\u0002.": "Du wurdest von \u0002{account}\u0002 abgemeldet.",
|
||||||
|
"A confirmation code has been emailed to you. Confirm with \u0002CONFIRM <code>\u0002.": "Ein Bestätigungscode wurde dir per E-Mail geschickt. Bestätige mit \u0002CONFIRM <code>\u0002.",
|
||||||
|
"You are now the founder of \u0002{chan}\u0002 (inherited from \u0002{account}\u0002).": "Du bist jetzt der Gründer von \u0002{chan}\u0002 (übernommen von \u0002{account}\u0002).",
|
||||||
|
"You didn't identify in time; you've been renamed.": "Du hast dich nicht rechtzeitig identifiziert; du wurdest umbenannt.",
|
||||||
|
"Please mind the channel rules — {reason} Next time you'll be kicked.": "Bitte beachte die Kanalregeln — {reason} Beim nächsten Mal wirst du gekickt.",
|
||||||
|
"Access denied — {what} is for services operators.": "Zugriff verweigert — {what} ist für Services-Operatoren."
|
||||||
}
|
}
|
||||||
|
|
@ -1308,5 +1308,30 @@
|
||||||
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} reporte. \u0002VIEW\u0002 <id> para el detalle.",
|
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} reporte. \u0002VIEW\u0002 <id> para el detalle.",
|
||||||
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} reportes. \u0002VIEW\u0002 <id> para el detalle.",
|
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} reportes. \u0002VIEW\u0002 <id> para el detalle.",
|
||||||
"End of list ({count} group).": "Fin de la lista ({count} grupo).",
|
"End of list ({count} group).": "Fin de la lista ({count} grupo).",
|
||||||
"End of list ({count} groups).": "Fin de la lista ({count} grupos)."
|
"End of list ({count} groups).": "Fin de la lista ({count} grupos).",
|
||||||
|
"Password reset for {account}": "Restablecimiento de contraseña para {account}",
|
||||||
|
"Your password reset code for {account} is: {code}\nIt expires in 15 minutes.\nReset with:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n": "Tu código para restablecer la contraseña de {account} es: {code}\nVence en 15 minutos.\nRestablecela con:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n",
|
||||||
|
"Password reset": "Restablecimiento de contraseña",
|
||||||
|
"Use this code to reset the password for your account {account}.": "Usá este código para restablecer la contraseña de tu cuenta {account}.",
|
||||||
|
"This code expires in 15 minutes. If you didn't ask to reset it, ignore this email.": "Este código vence en 15 minutos. Si no pediste restablecerla, ignorá este correo.",
|
||||||
|
"Confirm your {account} registration": "Confirmá el registro de {account}",
|
||||||
|
"Confirm your account {account} with:\n /msg NickServ CONFIRM {code}\nThe code expires in 15 minutes.\n": "Confirmá tu cuenta {account} con:\n /msg NickServ CONFIRM {code}\nEl código vence en 15 minutos.\n",
|
||||||
|
"Confirm your account": "Confirmá tu cuenta",
|
||||||
|
"Welcome! Confirm the email for your account {account} with the code below.": "¡Bienvenido! Confirmá el correo de tu cuenta {account} con el código de abajo.",
|
||||||
|
"This code expires in 15 minutes.": "Este código vence en 15 minutos.",
|
||||||
|
"Your {word} {name} is about to expire": "Tu {word} {name} está por vencer",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.\n{keep}\n": "Tu {word} {name} estuvo inactivo y va a vencer en {remaining}.\n{keep}\n",
|
||||||
|
"About to expire": "Está por vencer",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.": "Tu {word} {name} estuvo inactivo y va a vencer en {remaining}.",
|
||||||
|
"To keep it, have a member join the channel before then. Otherwise it will be removed.": "Para conservarlo, hacé que un miembro entre al canal antes de eso. Si no, se va a eliminar.",
|
||||||
|
"To keep it, just identify to it before then. Otherwise it will be removed.": "Para conservarlo, identificate a él antes de eso. Si no, se va a eliminar.",
|
||||||
|
"account": "cuenta",
|
||||||
|
"channel": "canal",
|
||||||
|
"You're now identified as \u0002{account}\u0002. Welcome back!": "Ahora estás identificado como \u0002{account}\u0002. ¡Bienvenido de nuevo!",
|
||||||
|
"You have been logged out of \u0002{account}\u0002.": "Se cerró tu sesión de \u0002{account}\u0002.",
|
||||||
|
"A confirmation code has been emailed to you. Confirm with \u0002CONFIRM <code>\u0002.": "Se te envió un código de confirmación por correo. Confirmá con \u0002CONFIRM <code>\u0002.",
|
||||||
|
"You are now the founder of \u0002{chan}\u0002 (inherited from \u0002{account}\u0002).": "Ahora sos el fundador de \u0002{chan}\u0002 (heredado de \u0002{account}\u0002).",
|
||||||
|
"You didn't identify in time; you've been renamed.": "No te identificaste a tiempo; se te cambió el nombre.",
|
||||||
|
"Please mind the channel rules — {reason} Next time you'll be kicked.": "Prestá atención a las reglas del canal — {reason} La próxima vez te van a expulsar.",
|
||||||
|
"Access denied — {what} is for services operators.": "Acceso denegado — {what} es solo para operadores de los servicios."
|
||||||
}
|
}
|
||||||
27
lang/es.json
27
lang/es.json
|
|
@ -1308,5 +1308,30 @@
|
||||||
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} reporte. \u0002VIEW\u0002 <id> para ver el detalle.",
|
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} reporte. \u0002VIEW\u0002 <id> para ver el detalle.",
|
||||||
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} reportes. \u0002VIEW\u0002 <id> para ver el detalle.",
|
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} reportes. \u0002VIEW\u0002 <id> para ver el detalle.",
|
||||||
"End of list ({count} group).": "Fin de la lista ({count} grupo).",
|
"End of list ({count} group).": "Fin de la lista ({count} grupo).",
|
||||||
"End of list ({count} groups).": "Fin de la lista ({count} grupos)."
|
"End of list ({count} groups).": "Fin de la lista ({count} grupos).",
|
||||||
|
"Password reset for {account}": "Restablecimiento de contraseña para {account}",
|
||||||
|
"Your password reset code for {account} is: {code}\nIt expires in 15 minutes.\nReset with:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n": "Tu código para restablecer la contraseña de {account} es: {code}\nCaduca en 15 minutos.\nRestablécela con:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n",
|
||||||
|
"Password reset": "Restablecimiento de contraseña",
|
||||||
|
"Use this code to reset the password for your account {account}.": "Usa este código para restablecer la contraseña de tu cuenta {account}.",
|
||||||
|
"This code expires in 15 minutes. If you didn't ask to reset it, ignore this email.": "Este código caduca en 15 minutos. Si no pediste restablecerla, ignora este correo.",
|
||||||
|
"Confirm your {account} registration": "Confirma tu registro de {account}",
|
||||||
|
"Confirm your account {account} with:\n /msg NickServ CONFIRM {code}\nThe code expires in 15 minutes.\n": "Confirma tu cuenta {account} con:\n /msg NickServ CONFIRM {code}\nEl código caduca en 15 minutos.\n",
|
||||||
|
"Confirm your account": "Confirma tu cuenta",
|
||||||
|
"Welcome! Confirm the email for your account {account} with the code below.": "¡Bienvenido! Confirma el correo de tu cuenta {account} con el código de abajo.",
|
||||||
|
"This code expires in 15 minutes.": "Este código caduca en 15 minutos.",
|
||||||
|
"Your {word} {name} is about to expire": "Tu {word} {name} está a punto de caducar",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.\n{keep}\n": "Tu {word} {name} ha estado inactivo y caducará en {remaining}.\n{keep}\n",
|
||||||
|
"About to expire": "A punto de caducar",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.": "Tu {word} {name} ha estado inactivo y caducará en {remaining}.",
|
||||||
|
"To keep it, have a member join the channel before then. Otherwise it will be removed.": "Para conservarlo, haz que un miembro entre en el canal antes de esa fecha. De lo contrario se eliminará.",
|
||||||
|
"To keep it, just identify to it before then. Otherwise it will be removed.": "Para conservarla, solo identifícate con ella antes de esa fecha. De lo contrario se eliminará.",
|
||||||
|
"account": "cuenta",
|
||||||
|
"channel": "canal",
|
||||||
|
"You're now identified as \u0002{account}\u0002. Welcome back!": "Ahora estás identificado como \u0002{account}\u0002. ¡Bienvenido de nuevo!",
|
||||||
|
"You have been logged out of \u0002{account}\u0002.": "Se ha cerrado tu sesión de \u0002{account}\u0002.",
|
||||||
|
"A confirmation code has been emailed to you. Confirm with \u0002CONFIRM <code>\u0002.": "Se te ha enviado un código de confirmación por correo. Confírmalo con \u0002CONFIRM <code>\u0002.",
|
||||||
|
"You are now the founder of \u0002{chan}\u0002 (inherited from \u0002{account}\u0002).": "Ahora eres el fundador de \u0002{chan}\u0002 (heredado de \u0002{account}\u0002).",
|
||||||
|
"You didn't identify in time; you've been renamed.": "No te identificaste a tiempo; se te ha cambiado el nombre.",
|
||||||
|
"Please mind the channel rules — {reason} Next time you'll be kicked.": "Ten en cuenta las reglas del canal — {reason} La próxima vez se te expulsará.",
|
||||||
|
"Access denied — {what} is for services operators.": "Acceso denegado — {what} es solo para operadores de servicios."
|
||||||
}
|
}
|
||||||
27
lang/fr.json
27
lang/fr.json
|
|
@ -1308,5 +1308,30 @@
|
||||||
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} signalement. \u0002VIEW\u0002 <id> pour le détail.",
|
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} signalement. \u0002VIEW\u0002 <id> pour le détail.",
|
||||||
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} signalements. \u0002VIEW\u0002 <id> pour le détail.",
|
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} signalements. \u0002VIEW\u0002 <id> pour le détail.",
|
||||||
"End of list ({count} group).": "Fin de la liste ({count} groupe).",
|
"End of list ({count} group).": "Fin de la liste ({count} groupe).",
|
||||||
"End of list ({count} groups).": "Fin de la liste ({count} groupes)."
|
"End of list ({count} groups).": "Fin de la liste ({count} groupes).",
|
||||||
|
"Password reset for {account}": "Réinitialisation du mot de passe pour {account}",
|
||||||
|
"Your password reset code for {account} is: {code}\nIt expires in 15 minutes.\nReset with:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n": "Votre code de réinitialisation de mot de passe pour {account} est : {code}\nIl expire dans 15 minutes.\nRéinitialisez-le avec :\n /msg NickServ RESETPASS {account} {code} <newpassword>\n",
|
||||||
|
"Password reset": "Réinitialisation du mot de passe",
|
||||||
|
"Use this code to reset the password for your account {account}.": "Utilisez ce code pour réinitialiser le mot de passe de votre compte {account}.",
|
||||||
|
"This code expires in 15 minutes. If you didn't ask to reset it, ignore this email.": "Ce code expire dans 15 minutes. Si vous n'avez pas demandé cette réinitialisation, ignorez cet e-mail.",
|
||||||
|
"Confirm your {account} registration": "Confirmez l'enregistrement de {account}",
|
||||||
|
"Confirm your account {account} with:\n /msg NickServ CONFIRM {code}\nThe code expires in 15 minutes.\n": "Confirmez votre compte {account} avec :\n /msg NickServ CONFIRM {code}\nLe code expire dans 15 minutes.\n",
|
||||||
|
"Confirm your account": "Confirmez votre compte",
|
||||||
|
"Welcome! Confirm the email for your account {account} with the code below.": "Bienvenue ! Confirmez l'e-mail de votre compte {account} avec le code ci-dessous.",
|
||||||
|
"This code expires in 15 minutes.": "Ce code expire dans 15 minutes.",
|
||||||
|
"Your {word} {name} is about to expire": "Votre {word} {name} est sur le point d'expirer",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.\n{keep}\n": "Votre {word} {name} est resté inactif et expirera dans {remaining}.\n{keep}\n",
|
||||||
|
"About to expire": "Sur le point d'expirer",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.": "Votre {word} {name} est resté inactif et expirera dans {remaining}.",
|
||||||
|
"To keep it, have a member join the channel before then. Otherwise it will be removed.": "Pour le conserver, faites en sorte qu'un membre rejoigne le canal avant cette échéance. Sinon, il sera supprimé.",
|
||||||
|
"To keep it, just identify to it before then. Otherwise it will be removed.": "Pour le conserver, il vous suffit de vous y identifier avant cette échéance. Sinon, il sera supprimé.",
|
||||||
|
"account": "compte",
|
||||||
|
"channel": "canal",
|
||||||
|
"You're now identified as \u0002{account}\u0002. Welcome back!": "Vous êtes maintenant identifié en tant que \u0002{account}\u0002. Bon retour !",
|
||||||
|
"You have been logged out of \u0002{account}\u0002.": "Vous avez été déconnecté de \u0002{account}\u0002.",
|
||||||
|
"A confirmation code has been emailed to you. Confirm with \u0002CONFIRM <code>\u0002.": "Un code de confirmation vous a été envoyé par e-mail. Confirmez avec \u0002CONFIRM <code>\u0002.",
|
||||||
|
"You are now the founder of \u0002{chan}\u0002 (inherited from \u0002{account}\u0002).": "Vous êtes maintenant le fondateur de \u0002{chan}\u0002 (hérité de \u0002{account}\u0002).",
|
||||||
|
"You didn't identify in time; you've been renamed.": "Vous ne vous êtes pas identifié à temps ; vous avez été renommé.",
|
||||||
|
"Please mind the channel rules — {reason} Next time you'll be kicked.": "Veuillez respecter les règles du canal — {reason} La prochaine fois, vous serez expulsé.",
|
||||||
|
"Access denied — {what} is for services operators.": "Accès refusé — {what} est réservé aux opérateurs des services."
|
||||||
}
|
}
|
||||||
|
|
@ -1308,5 +1308,30 @@
|
||||||
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} denúncia. \u0002VIEW\u0002 <id> para detalhes.",
|
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} denúncia. \u0002VIEW\u0002 <id> para detalhes.",
|
||||||
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} denúncias. \u0002VIEW\u0002 <id> para detalhes.",
|
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} denúncias. \u0002VIEW\u0002 <id> para detalhes.",
|
||||||
"End of list ({count} group).": "Fim da lista ({count} grupo).",
|
"End of list ({count} group).": "Fim da lista ({count} grupo).",
|
||||||
"End of list ({count} groups).": "Fim da lista ({count} grupos)."
|
"End of list ({count} groups).": "Fim da lista ({count} grupos).",
|
||||||
|
"Password reset for {account}": "Redefinição de senha para {account}",
|
||||||
|
"Your password reset code for {account} is: {code}\nIt expires in 15 minutes.\nReset with:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n": "Seu código de redefinição de senha para {account} é: {code}\nEle expira em 15 minutos.\nRedefina com:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n",
|
||||||
|
"Password reset": "Redefinição de senha",
|
||||||
|
"Use this code to reset the password for your account {account}.": "Use este código para redefinir a senha da sua conta {account}.",
|
||||||
|
"This code expires in 15 minutes. If you didn't ask to reset it, ignore this email.": "Este código expira em 15 minutos. Se você não pediu para redefini-la, ignore este e-mail.",
|
||||||
|
"Confirm your {account} registration": "Confirme o registro de {account}",
|
||||||
|
"Confirm your account {account} with:\n /msg NickServ CONFIRM {code}\nThe code expires in 15 minutes.\n": "Confirme sua conta {account} com:\n /msg NickServ CONFIRM {code}\nO código expira em 15 minutos.\n",
|
||||||
|
"Confirm your account": "Confirme sua conta",
|
||||||
|
"Welcome! Confirm the email for your account {account} with the code below.": "Boas-vindas! Confirme o e-mail da sua conta {account} com o código abaixo.",
|
||||||
|
"This code expires in 15 minutes.": "Este código expira em 15 minutos.",
|
||||||
|
"Your {word} {name} is about to expire": "{word} {name} está prestes a expirar",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.\n{keep}\n": "{word} {name} está sem atividade e vai expirar em {remaining}.\n{keep}\n",
|
||||||
|
"About to expire": "Prestes a expirar",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.": "{word} {name} está sem atividade e vai expirar em {remaining}.",
|
||||||
|
"To keep it, have a member join the channel before then. Otherwise it will be removed.": "Para mantê-lo, peça que um membro entre no canal antes disso. Caso contrário, ele será removido.",
|
||||||
|
"To keep it, just identify to it before then. Otherwise it will be removed.": "Para mantê-la, basta identificar-se nela antes disso. Caso contrário, ela será removida.",
|
||||||
|
"account": "conta",
|
||||||
|
"channel": "canal",
|
||||||
|
"You're now identified as \u0002{account}\u0002. Welcome back!": "Você agora está identificado como \u0002{account}\u0002. Bem-vindo de volta!",
|
||||||
|
"You have been logged out of \u0002{account}\u0002.": "Você saiu de \u0002{account}\u0002.",
|
||||||
|
"A confirmation code has been emailed to you. Confirm with \u0002CONFIRM <code>\u0002.": "Um código de confirmação foi enviado para o seu e-mail. Confirme com \u0002CONFIRM <code>\u0002.",
|
||||||
|
"You are now the founder of \u0002{chan}\u0002 (inherited from \u0002{account}\u0002).": "Você agora é o fundador de \u0002{chan}\u0002 (herdado de \u0002{account}\u0002).",
|
||||||
|
"You didn't identify in time; you've been renamed.": "Você não se identificou a tempo; seu apelido foi alterado.",
|
||||||
|
"Please mind the channel rules — {reason} Next time you'll be kicked.": "Respeite as regras do canal — {reason} Da próxima vez você será expulso.",
|
||||||
|
"Access denied — {what} is for services operators.": "Acesso negado — {what} é para operadores de serviços."
|
||||||
}
|
}
|
||||||
27
lang/pt.json
27
lang/pt.json
|
|
@ -1308,5 +1308,30 @@
|
||||||
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} denúncia. \u0002VIEW\u0002 <id> para detalhes.",
|
"{n} report. \u0002VIEW\u0002 <id> for detail.": "{n} denúncia. \u0002VIEW\u0002 <id> para detalhes.",
|
||||||
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} denúncias. \u0002VIEW\u0002 <id> para detalhes.",
|
"{n} reports. \u0002VIEW\u0002 <id> for detail.": "{n} denúncias. \u0002VIEW\u0002 <id> para detalhes.",
|
||||||
"End of list ({count} group).": "Fim da lista ({count} grupo).",
|
"End of list ({count} group).": "Fim da lista ({count} grupo).",
|
||||||
"End of list ({count} groups).": "Fim da lista ({count} grupos)."
|
"End of list ({count} groups).": "Fim da lista ({count} grupos).",
|
||||||
|
"Password reset for {account}": "Reposição de palavra-passe para {account}",
|
||||||
|
"Your password reset code for {account} is: {code}\nIt expires in 15 minutes.\nReset with:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n": "O seu código de reposição de palavra-passe para {account} é: {code}\nExpira dentro de 15 minutos.\nReponha com:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n",
|
||||||
|
"Password reset": "Reposição de palavra-passe",
|
||||||
|
"Use this code to reset the password for your account {account}.": "Utilize este código para repor a palavra-passe da sua conta {account}.",
|
||||||
|
"This code expires in 15 minutes. If you didn't ask to reset it, ignore this email.": "Este código expira dentro de 15 minutos. Se não pediu a reposição, ignore este email.",
|
||||||
|
"Confirm your {account} registration": "Confirme o registo de {account}",
|
||||||
|
"Confirm your account {account} with:\n /msg NickServ CONFIRM {code}\nThe code expires in 15 minutes.\n": "Confirme a sua conta {account} com:\n /msg NickServ CONFIRM {code}\nO código expira dentro de 15 minutos.\n",
|
||||||
|
"Confirm your account": "Confirme a sua conta",
|
||||||
|
"Welcome! Confirm the email for your account {account} with the code below.": "Bem-vindo! Confirme o email da sua conta {account} com o código abaixo.",
|
||||||
|
"This code expires in 15 minutes.": "Este código expira dentro de 15 minutos.",
|
||||||
|
"Your {word} {name} is about to expire": "O seu {word} {name} está prestes a expirar",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.\n{keep}\n": "O seu {word} {name} tem estado inativo e vai expirar em {remaining}.\n{keep}\n",
|
||||||
|
"About to expire": "Prestes a expirar",
|
||||||
|
"Your {word} {name} has been inactive and will expire in {remaining}.": "O seu {word} {name} tem estado inativo e vai expirar em {remaining}.",
|
||||||
|
"To keep it, have a member join the channel before then. Otherwise it will be removed.": "Para o manter, peça a um membro que entre no canal antes disso. Caso contrário, será removido.",
|
||||||
|
"To keep it, just identify to it before then. Otherwise it will be removed.": "Para a manter, basta identificar-se nela antes disso. Caso contrário, será removida.",
|
||||||
|
"account": "conta",
|
||||||
|
"channel": "canal",
|
||||||
|
"You're now identified as \u0002{account}\u0002. Welcome back!": "Está agora identificado como \u0002{account}\u0002. Bem-vindo de volta!",
|
||||||
|
"You have been logged out of \u0002{account}\u0002.": "A sua sessão em \u0002{account}\u0002 foi terminada.",
|
||||||
|
"A confirmation code has been emailed to you. Confirm with \u0002CONFIRM <code>\u0002.": "Foi-lhe enviado um código de confirmação por email. Confirme com \u0002CONFIRM <code>\u0002.",
|
||||||
|
"You are now the founder of \u0002{chan}\u0002 (inherited from \u0002{account}\u0002).": "É agora o fundador de \u0002{chan}\u0002 (herdado de \u0002{account}\u0002).",
|
||||||
|
"You didn't identify in time; you've been renamed.": "Não se identificou a tempo; o seu nome foi alterado.",
|
||||||
|
"Please mind the channel rules — {reason} Next time you'll be kicked.": "Respeite as regras do canal — {reason} Da próxima vez será expulso.",
|
||||||
|
"Access denied — {what} is for services operators.": "Acesso negado — {what} é para operadores de serviços."
|
||||||
}
|
}
|
||||||
|
|
@ -25,7 +25,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let code = db.issue_code(&canonical, CodeKind::Reset);
|
let code = db.issue_code(&canonical, CodeKind::Reset);
|
||||||
let mail = echo_api::email::reset(db.email_brand(), db.email_accent(), db.email_logo(), &canonical, &code);
|
let lang = db.language_of(&canonical).unwrap_or_else(|| db.default_language());
|
||||||
|
let mail = echo_api::email::reset(db.email_brand(), db.email_accent(), db.email_logo(), &canonical, &code, &lang);
|
||||||
ctx.send_email(email, mail.subject, mail.text, Some(mail.html));
|
ctx.send_email(email, mail.subject, mail.text, Some(mail.html));
|
||||||
ctx.notice(me, from.uid, t!(ctx, "A reset code has been emailed to the address on file for \x02{canonical}\x02.", canonical = canonical));
|
ctx.notice(me, from.uid, t!(ctx, "A reset code has been emailed to the address on file for \x02{canonical}\x02.", canonical = canonical));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,8 @@ impl Engine {
|
||||||
};
|
};
|
||||||
if !already {
|
if !already {
|
||||||
self.bump("botserv.warn");
|
self.bump("botserv.warn");
|
||||||
return vec![NetAction::Notice { from: botuid, to: from.to_string(), text: format!("Please mind the channel rules — {reason} Next time you'll be kicked.") }];
|
let text = echo_api::render(&self.lang_for_uid(from), "Please mind the channel rules — {reason} Next time you'll be kicked.", &[("reason", reason.to_string())]);
|
||||||
|
return vec![NetAction::Notice { from: botuid, to: from.to_string(), text }];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -620,7 +620,7 @@ impl Engine {
|
||||||
out.push(NetAction::Notice {
|
out.push(NetAction::Notice {
|
||||||
from: ns.clone(),
|
from: ns.clone(),
|
||||||
to: uid.to_string(),
|
to: uid.to_string(),
|
||||||
text: format!("You have \x02{unread}\x02 new memo(s). Read them with \x02/msg MemoServ READ NEW\x02."),
|
text: echo_api::render_plural(&self.lang_for_account(account), unread as u64, "You have \x02{unread}\x02 new memo. Read it with \x02/msg MemoServ READ NEW\x02.", "You have \x02{unread}\x02 new memos. Read them with \x02/msg MemoServ READ NEW\x02.", &[("unread", unread.to_string())]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -718,7 +718,8 @@ impl Engine {
|
||||||
// Tell any online successor they inherited a channel.
|
// Tell any online successor they inherited a channel.
|
||||||
for (chan, s) in &inherited {
|
for (chan, s) in &inherited {
|
||||||
if let (Some(ns), Some(uid)) = (&ns, self.network.uids_logged_into(s).into_iter().next()) {
|
if let (Some(ns), Some(uid)) = (&ns, self.network.uids_logged_into(s).into_iter().next()) {
|
||||||
self.emit_irc(NetAction::Notice { from: ns.clone(), to: uid, text: format!("You are now the founder of \x02{chan}\x02 (inherited from \x02{account}\x02).") });
|
let text = echo_api::render(&self.lang_for_account(s), "You are now the founder of \x02{chan}\x02 (inherited from \x02{account}\x02).", &[("chan", chan.to_string()), ("account", account.to_string())]);
|
||||||
|
self.emit_irc(NetAction::Notice { from: ns.clone(), to: uid, text });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Log out and inform each local session that held the name.
|
// Log out and inform each local session that held the name.
|
||||||
|
|
@ -809,6 +810,21 @@ impl Engine {
|
||||||
// no per-record timer. Opers, accounts with a live session, and occupied
|
// no per-record timer. Opers, accounts with a live session, and occupied
|
||||||
// channels are spared; each expiry is announced to the audit channel. Emits
|
// channels are spared; each expiry is announced to the audit channel. Emits
|
||||||
// cleanup directly over the outbound path (like a takeover cleanup).
|
// cleanup directly over the outbound path (like a takeover cleanup).
|
||||||
|
// The language to address `account`'s owner in: their stored preference, or
|
||||||
|
// the network default.
|
||||||
|
fn lang_for_account(&self, account: &str) -> String {
|
||||||
|
self.db.language_of(account).unwrap_or_else(|| self.db.default_language())
|
||||||
|
}
|
||||||
|
|
||||||
|
// The language to address the user on `uid` in: their logged-in account's
|
||||||
|
// preference, or the network default (e.g. an unidentified user).
|
||||||
|
fn lang_for_uid(&self, uid: &str) -> String {
|
||||||
|
self.network
|
||||||
|
.account_of(uid)
|
||||||
|
.and_then(|a| self.db.language_of(a))
|
||||||
|
.unwrap_or_else(|| self.db.default_language())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn expire_sweep(&mut self) {
|
pub fn expire_sweep(&mut self) {
|
||||||
let now = self.now_secs();
|
let now = self.now_secs();
|
||||||
// First, warn owners whose account/channel is approaching expiry and for
|
// First, warn owners whose account/channel is approaching expiry and for
|
||||||
|
|
@ -816,14 +832,14 @@ impl Engine {
|
||||||
if let Some(lead) = self.expire_warn.filter(|_| self.db.email_enabled()) {
|
if let Some(lead) = self.expire_warn.filter(|_| self.db.email_enabled()) {
|
||||||
if let Some(ttl) = self.account_ttl {
|
if let Some(ttl) = self.account_ttl {
|
||||||
for (account, email, left) in self.db.accounts_to_warn(now, ttl, lead) {
|
for (account, email, left) in self.db.accounts_to_warn(now, ttl, lead) {
|
||||||
let mail = echo_api::email::expiry_warning(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), echo_api::email::ExpiryTarget::Account, &account, &human_duration(left));
|
let mail = echo_api::email::expiry_warning(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), echo_api::email::ExpiryTarget::Account, &account, &human_duration(left), &self.lang_for_account(&account));
|
||||||
self.emit_irc(NetAction::SendEmail { to: email, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
self.emit_irc(NetAction::SendEmail { to: email, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
||||||
self.db.mark_account_warned(&account);
|
self.db.mark_account_warned(&account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ttl) = self.channel_ttl {
|
if let Some(ttl) = self.channel_ttl {
|
||||||
for (channel, email, left) in self.db.channels_to_warn(now, ttl, lead) {
|
for (channel, email, left) in self.db.channels_to_warn(now, ttl, lead) {
|
||||||
let mail = echo_api::email::expiry_warning(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), echo_api::email::ExpiryTarget::Channel, &channel, &human_duration(left));
|
let mail = echo_api::email::expiry_warning(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), echo_api::email::ExpiryTarget::Channel, &channel, &human_duration(left), &self.db.default_language());
|
||||||
self.emit_irc(NetAction::SendEmail { to: email, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
self.emit_irc(NetAction::SendEmail { to: email, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
||||||
self.db.mark_channel_warned(&channel);
|
self.db.mark_channel_warned(&channel);
|
||||||
}
|
}
|
||||||
|
|
@ -1032,7 +1048,8 @@ impl Engine {
|
||||||
for uid in fire {
|
for uid in fire {
|
||||||
let guest = echo_api::next_guest_nick(&self.guest_nick, &mut self.enforce_seq, &self.network, &self.db);
|
let guest = echo_api::next_guest_nick(&self.guest_nick, &mut self.enforce_seq, &self.network, &self.db);
|
||||||
if let Some(ns) = self.nick_service.clone() {
|
if let Some(ns) = self.nick_service.clone() {
|
||||||
self.emit_irc(NetAction::Notice { from: ns, to: uid.clone(), text: "You didn't identify in time; you've been renamed.".to_string() });
|
let text = echo_api::render(&self.db.default_language(), "You didn't identify in time; you've been renamed.", &[]);
|
||||||
|
self.emit_irc(NetAction::Notice { from: ns, to: uid.clone(), text });
|
||||||
}
|
}
|
||||||
self.emit_irc(NetAction::ForceNick { uid, nick: guest });
|
self.emit_irc(NetAction::ForceNick { uid, nick: guest });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ impl Engine {
|
||||||
if status == AuthorityStatus::Ok && !self.db.is_verified(name) {
|
if status == AuthorityStatus::Ok && !self.db.is_verified(name) {
|
||||||
if let Some(addr) = addr {
|
if let Some(addr) = addr {
|
||||||
let code = self.db.issue_code(name, db::CodeKind::Confirm);
|
let code = self.db.issue_code(name, db::CodeKind::Confirm);
|
||||||
let mail = echo_api::email::confirm(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), name, &code);
|
let mail = echo_api::email::confirm(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), name, &code, &self.lang_for_account(name));
|
||||||
self.emit_irc(NetAction::SendEmail { to: addr, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
self.emit_irc(NetAction::SendEmail { to: addr, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +191,8 @@ impl Engine {
|
||||||
self.network.clear_account(&uid);
|
self.network.clear_account(&uid);
|
||||||
self.emit_irc(NetAction::Metadata { target: uid.clone(), key: "accountname".to_string(), value: String::new() });
|
self.emit_irc(NetAction::Metadata { target: uid.clone(), key: "accountname".to_string(), value: String::new() });
|
||||||
if let Some(ns) = &ns {
|
if let Some(ns) = &ns {
|
||||||
self.emit_irc(NetAction::Notice { from: ns.clone(), to: uid, text: format!("You have been logged out of \x02{account}\x02.") });
|
let text = echo_api::render(&self.lang_for_account(account), "You have been logged out of \x02{account}\x02.", &[("account", account.to_string())]);
|
||||||
|
self.emit_irc(NetAction::Notice { from: ns.clone(), to: uid, text });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n
|
n
|
||||||
|
|
@ -273,7 +274,7 @@ impl Engine {
|
||||||
}
|
}
|
||||||
Some((false, Some(addr))) => {
|
Some((false, Some(addr))) => {
|
||||||
let code = self.db.issue_code(&account, db::CodeKind::Confirm);
|
let code = self.db.issue_code(&account, db::CodeKind::Confirm);
|
||||||
let mail = echo_api::email::confirm(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), &account, &code);
|
let mail = echo_api::email::confirm(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), &account, &code, &self.lang_for_account(&account));
|
||||||
let mut out = resp("verification_required", "VERIFICATION_REQUIRED", "A new confirmation code has been emailed.");
|
let mut out = resp("verification_required", "VERIFICATION_REQUIRED", "A new confirmation code has been emailed.");
|
||||||
out.push(NetAction::SendEmail { to: addr, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
out.push(NetAction::SendEmail { to: addr, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
||||||
out
|
out
|
||||||
|
|
@ -338,10 +339,10 @@ impl Engine {
|
||||||
if needs_verify {
|
if needs_verify {
|
||||||
if let Some(addr) = addr {
|
if let Some(addr) = addr {
|
||||||
let code = self.db.issue_code(account, db::CodeKind::Confirm);
|
let code = self.db.issue_code(account, db::CodeKind::Confirm);
|
||||||
let mail = echo_api::email::confirm(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), account, &code);
|
let mail = echo_api::email::confirm(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), account, &code, &self.lang_for_account(account));
|
||||||
out.push(NetAction::SendEmail { to: addr, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
out.push(NetAction::SendEmail { to: addr, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
||||||
if let RegReply::NickServ { agent, uid, .. } = &reply {
|
if let RegReply::NickServ { agent, uid, .. } = &reply {
|
||||||
out.push(NetAction::Notice { from: agent.clone(), to: uid.clone(), text: "A confirmation code has been emailed to you. Confirm with \x02CONFIRM <code>\x02.".to_string() });
|
out.push(NetAction::Notice { from: agent.clone(), to: uid.clone(), text: echo_api::render(&self.lang_for_account(account), "A confirmation code has been emailed to you. Confirm with \x02CONFIRM <code>\x02.", &[]) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -366,14 +367,15 @@ impl Engine {
|
||||||
let actions = match then {
|
let actions = match then {
|
||||||
AuthThen::Identify { uid, agent, name, account } => {
|
AuthThen::Identify { uid, agent, name, account } => {
|
||||||
self.db.note_auth(&name, ok);
|
self.db.note_auth(&name, ok);
|
||||||
let mut ctx = ServiceCtx::default();
|
let lang = self.lang_for_account(&account);
|
||||||
|
let mut ctx = ServiceCtx { lang: lang.clone(), ..Default::default() };
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.count("nickserv.identify_fail");
|
ctx.count("nickserv.identify_fail");
|
||||||
ctx.fail(&agent, &uid, "IDENTIFY", "INVALID_CREDENTIALS", "Invalid password. Please try again.");
|
ctx.fail(&agent, &uid, "IDENTIFY", "INVALID_CREDENTIALS", "Invalid password. Please try again.");
|
||||||
} else {
|
} else {
|
||||||
ctx.login(&uid, &account);
|
ctx.login(&uid, &account);
|
||||||
ctx.count("nickserv.identify");
|
ctx.count("nickserv.identify");
|
||||||
ctx.notice(&agent, &uid, format!("You're now identified as \x02{account}\x02. Welcome back!"));
|
ctx.notice(&agent, &uid, echo_api::render(&lang, "You're now identified as \x02{account}\x02. Welcome back!", &[("account", account.clone())]));
|
||||||
for entry in self.db.ajoin_list(&account) {
|
for entry in self.db.ajoin_list(&account) {
|
||||||
ctx.force_join(&uid, &entry.channel, &entry.key);
|
ctx.force_join(&uid, &entry.channel, &entry.key);
|
||||||
}
|
}
|
||||||
|
|
@ -386,7 +388,7 @@ impl Engine {
|
||||||
}
|
}
|
||||||
let unread = self.db.unread_memos(&account);
|
let unread = self.db.unread_memos(&account);
|
||||||
if unread > 0 && self.db.memo_notify_on(&account) {
|
if unread > 0 && self.db.memo_notify_on(&account) {
|
||||||
ctx.notice(&agent, &uid, format!("You have \x02{unread}\x02 new memo(s). Read them with \x02/msg MemoServ READ NEW\x02."));
|
ctx.notice(&agent, &uid, echo_api::render_plural(&lang, unread as u64, "You have \x02{unread}\x02 new memo. Read it with \x02/msg MemoServ READ NEW\x02.", "You have \x02{unread}\x02 new memos. Read them with \x02/msg MemoServ READ NEW\x02.", &[("unread", unread.to_string())]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for key in std::mem::take(&mut ctx.stats) {
|
for key in std::mem::take(&mut ctx.stats) {
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,50 @@ fn every_template_is_translated() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Also guard the engine- and email-layer strings, which localise via
|
||||||
|
// echo_api::render / crate::render (with a literal msgid) rather than the
|
||||||
|
// module macros. Scoped to those call prefixes so email.rs's own private
|
||||||
|
// `render` HTML helper and test literals aren't picked up.
|
||||||
|
let render_re =
|
||||||
|
Regex::new(&format!(r"(?:echo_api|crate)::render\s*\(\s*[^,]+,\s*{str_lit}")).unwrap();
|
||||||
|
let render_plural_re = Regex::new(&format!(
|
||||||
|
r"(?:echo_api|crate)::render_plural\s*\(\s*[^,]+,\s*[^,]+,\s*{str_lit}\s*,\s*{str_lit}"
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
let mut extra = vec![PathBuf::from(ROOT).join("api/src/email.rs")];
|
||||||
|
let mut stack = vec![PathBuf::from(ROOT).join("src/engine")];
|
||||||
|
while let Some(dir) = stack.pop() {
|
||||||
|
let Ok(rd) = fs::read_dir(&dir) else { continue };
|
||||||
|
for e in rd.flatten() {
|
||||||
|
let p = e.path();
|
||||||
|
if p.is_dir() {
|
||||||
|
stack.push(p);
|
||||||
|
} else if p.extension().and_then(|s| s.to_str()) == Some("rs")
|
||||||
|
&& p.file_name().and_then(|s| s.to_str()) != Some("tests.rs")
|
||||||
|
{
|
||||||
|
extra.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for path in extra {
|
||||||
|
let Ok(src) = fs::read_to_string(&path) else { continue };
|
||||||
|
let rel = path.strip_prefix(ROOT).unwrap_or(&path).display().to_string();
|
||||||
|
for cap in render_re.captures_iter(&src) {
|
||||||
|
let id = unescape(&cap[1]);
|
||||||
|
if !reference.contains_key(&id) {
|
||||||
|
missing.push(format!("{rel}: render {id:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for cap in render_plural_re.captures_iter(&src) {
|
||||||
|
for g in [1usize, 2] {
|
||||||
|
let id = unescape(&cap[g]);
|
||||||
|
if !reference.contains_key(&id) {
|
||||||
|
missing.push(format!("{rel}: render_plural {id:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
missing.is_empty(),
|
missing.is_empty(),
|
||||||
"these templates have no catalog entry:\n{}",
|
"these templates have no catalog entry:\n{}",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue