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))
|
||||
}
|
||||
|
||||
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 {
|
||||
subject: format!("Password reset for {account}"),
|
||||
text: format!(
|
||||
"Your password reset code for {account} is: {code}\nIt expires in 15 minutes.\nReset with:\n /msg NickServ RESETPASS {account} {code} <newpassword>\n"
|
||||
subject: crate::render(lang, "Password reset for {account}", &acc()),
|
||||
text: crate::render(
|
||||
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(
|
||||
brand,
|
||||
accent,
|
||||
logo,
|
||||
"Password reset",
|
||||
&format!("Use this code to reset the password for your account {account}."),
|
||||
&crate::render(lang, "Password reset", &[]),
|
||||
&crate::render(lang, "Use this code to reset the password for your account {account}.", &acc()),
|
||||
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 {
|
||||
subject: format!("Confirm your {account} registration"),
|
||||
text: format!(
|
||||
"Confirm your account {account} with:\n /msg NickServ CONFIRM {code}\nThe code expires in 15 minutes.\n"
|
||||
subject: crate::render(lang, "Confirm your {account} registration", &acc()),
|
||||
text: crate::render(
|
||||
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(
|
||||
brand,
|
||||
accent,
|
||||
logo,
|
||||
"Confirm your account",
|
||||
&format!("Welcome! Confirm the email for your account {account} with the code below."),
|
||||
&crate::render(lang, "Confirm your account", &[]),
|
||||
&crate::render(lang, "Welcome! Confirm the email for your account {account} with the code below.", &acc()),
|
||||
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.
|
||||
// `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 {
|
||||
let word = kind.word();
|
||||
let keep = if kind == ExpiryTarget::Channel {
|
||||
"To keep it, have a member join the channel before then. Otherwise it will be removed."
|
||||
} else {
|
||||
"To keep it, just identify to it before then. Otherwise it will be removed."
|
||||
pub fn expiry_warning(brand: &str, accent: &str, logo: &str, kind: ExpiryTarget, name: &str, remaining: &str, lang: &str) -> Mail {
|
||||
let word = crate::render(lang, kind.word(), &[]);
|
||||
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."
|
||||
} else {
|
||||
"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 {
|
||||
subject: format!("Your {word} {name} is about to expire"),
|
||||
text: format!(
|
||||
"Your {word} {name} has been inactive and will expire in {remaining}.\n{keep}\n"
|
||||
subject: crate::render(lang, "Your {word} {name} is about to expire", &[("word", word.clone()), ("name", name.to_string())]),
|
||||
text: crate::render(
|
||||
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(
|
||||
brand,
|
||||
accent,
|
||||
logo,
|
||||
"About to expire",
|
||||
&format!("Your {word} {name} has been inactive and will expire in {remaining}."),
|
||||
&crate::render(lang, "About to expire", &[]),
|
||||
&crate::render(lang, "Your {word} {name} has been inactive and will expire in {remaining}.", &base()),
|
||||
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
|
||||
/// 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.
|
||||
/// `plural!(ctx, n, one = "…{n} entry…", other = "…{n} entries…", n = n, …)`.
|
||||
#[macro_export]
|
||||
macro_rules! plural {
|
||||
($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::Plural::One => $one,
|
||||
$crate::Plural::Other => $other,
|
||||
};
|
||||
$crate::render($ctx.lang(), __key, &[$((stringify!($name), format!("{}", $val))),*])
|
||||
}};
|
||||
($ctx:expr, $n:expr, one = $one:literal, other = $other:literal $(, $name:ident = $val:expr)* $(,)?) => {
|
||||
$crate::render_plural($ctx.lang(), ($n) as u64, $one, $other, &[$((stringify!($name), format!("{}", $val))),*])
|
||||
};
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
};
|
||||
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue