refactor: flatten Fluent selectors into one message per variant

This commit is contained in:
randogoth 2026-10-08 22:32:52 +03:00
parent 06952cd20e
commit c2525fd0f0
3 changed files with 48 additions and 48 deletions

View file

@ -33,34 +33,32 @@ mail-message-not-found = Message not found.
## Fetch — the status note after a fetch completes ## Fetch — the status note after a fetch completes
## ##
## `$variant` picks the one combination of "N rejected" and "(cancelled)" ## Split from one selector into one message per combination of "N
## that applies, so each case reads as one complete sentence rather than ## rejected" and "(cancelled)" that can apply; the caller in ops.rs
## stitched-together fragments. ## picks the id to match, since a flat message can't branch on a
## variable itself.
fetch-result-note = { $variant -> fetch-result-note-rejected = { $total } fetched, { $stored } stored, { $rejected } rejected
[rejected] { $total } fetched, { $stored } stored, { $rejected } rejected fetch-result-note-cancelled = { $total } fetched, { $stored } stored (cancelled)
[cancelled] { $total } fetched, { $stored } stored (cancelled) fetch-result-note-rejected-cancelled = { $total } fetched, { $stored } stored, { $rejected } rejected (cancelled)
[rejected-cancelled] { $total } fetched, { $stored } stored, { $rejected } rejected (cancelled) fetch-result-note-plain = { $total } fetched, { $stored } stored
*[plain] { $total } fetched, { $stored } stored
}
## Send — the status note after a send completes ## Send — the status note after a send completes
## ##
## `$variant` picks the one combination of trust-change clause and ## Split from one selector into one message per combination of
## accept-token clause that applies, each spelled out as a full sentence. ## trust-change clause and accept-token clause that can apply; the
## Any warning fumi itself reports is appended separately in code, since ## caller in ops.rs picks the id to match. Any warning fumi itself
## that text is fumi's own and not part of this catalogue. ## reports is appended separately in code, since that text is fumi's
## own and not part of this catalogue.
send-result-note = { $variant -> send-result-note-token = Sent to { $address } ({ $bytes } bytes), accept token attached
[token] Sent to { $address } ({ $bytes } bytes), accept token attached send-result-note-unverified = Sent to { $address } ({ $bytes } bytes); new contact learned from an unpinned server (unverified)
[unverified] Sent to { $address } ({ $bytes } bytes); new contact learned from an unpinned server (unverified) send-result-note-unverified-token = Sent to { $address } ({ $bytes } bytes), accept token attached; new contact learned from an unpinned server (unverified)
[unverified-token] Sent to { $address } ({ $bytes } bytes), accept token attached; new contact learned from an unpinned server (unverified) send-result-note-tofu = Sent to { $address } ({ $bytes } bytes); new contact learned (trust on first use)
[tofu] Sent to { $address } ({ $bytes } bytes); new contact learned (trust on first use) send-result-note-tofu-token = Sent to { $address } ({ $bytes } bytes), accept token attached; new contact learned (trust on first use)
[tofu-token] Sent to { $address } ({ $bytes } bytes), accept token attached; new contact learned (trust on first use) send-result-note-rotated = Sent to { $address } ({ $bytes } bytes); contact rotated to a new key (chain verified)
[rotated] Sent to { $address } ({ $bytes } bytes); contact rotated to a new key (chain verified) send-result-note-rotated-token = Sent to { $address } ({ $bytes } bytes), accept token attached; contact rotated to a new key (chain verified)
[rotated-token] Sent to { $address } ({ $bytes } bytes), accept token attached; contact rotated to a new key (chain verified) send-result-note-plain = Sent to { $address } ({ $bytes } bytes)
*[plain] Sent to { $address } ({ $bytes } bytes)
}
## Background-operation status notes ## Background-operation status notes
## ##
@ -143,15 +141,13 @@ contacts-import-button = Import
## Mail — the open-message meta line ## Mail — the open-message meta line
## ##
## `$kept` picks the one variant that applies, each a full sentence, ## Split from one selector into one message per variant, each a full
## rather than an optional clause spliced onto a shared prefix. ## sentence; the caller in view.rs picks `-yes`/`-no` to match `$kept`.
mail-meta-to = to mail-meta-to = to
mail-meta-from = from mail-meta-from = from
mail-meta-line = { $kept -> mail-meta-line-no = { $label } { $from } · { $time } UTC · id { $id }…
*[no] { $label } { $from } · { $time } UTC · id { $id }… mail-meta-line-yes = { $label } { $from } · { $time } UTC · id { $id }… · a server copy still exists
[yes] { $label } { $from } · { $time } UTC · id { $id }… · a server copy still exists
}
mail-no-subject = (no subject) mail-no-subject = (no subject)
mail-no-reply-address = no reply address: can't accept or block mail-no-reply-address = no reply address: can't accept or block
contacts-empty-hint = no contacts contacts-empty-hint = no contacts
@ -173,8 +169,7 @@ contacts-pin-button-unfetched = Pin server
mail-empty-title = No message selected mail-empty-title = No message selected
mail-empty-description = Pick a message from the list above to read it. mail-empty-description = Pick a message from the list above to read it.
# The two sentences shown under Fetch when the inbox is empty. # The two sentences shown under Fetch when the inbox is empty.
mail-empty-hint = Fetching removes mail from the server once it's stored here. mail-empty-hint = Fetching removes mail from the server once it's stored here. Turn on "Leave mail on server" in Settings to keep it there instead.
Turn on "Leave mail on server" in Settings to keep it there instead.
detail-qr-tooltip = Show the address as a QR code detail-qr-tooltip = Show the address as a QR code
detail-copy-tooltip = Copy the address detail-copy-tooltip = Copy the address
@ -194,11 +189,10 @@ contact-state-blocked = blocked
contact-state-verified = verified contact-state-verified = verified
contact-state-tofu = trust on first use contact-state-tofu = trust on first use
# `$state` is one of the four words above, already resolved by the caller # `$state` is one of the four words above, already resolved by the caller
# through `contact_state`; this message only adds the server's pin state. # through `contact_state`. Split from one selector into one message per
contact-meta-line = { $state } · server { $pinned -> # pin state; the caller in view.rs picks `-yes`/`-no` to match `$pinned`.
[yes] pinned contact-meta-line-yes = { $state } · server pinned
*[no] not pinned contact-meta-line-no = { $state } · server not pinned
}
## Detail pane — the monospace key/value block shared by contacts and the ## Detail pane — the monospace key/value block shared by contacts and the
## own identity view. Column widths are padded to these labels in code ## own identity view. Column widths are padded to these labels in code

View file

@ -509,11 +509,13 @@ pub fn fetch(
(false, false) => "plain", (false, false) => "plain",
}; };
let mut args = fluent_bundle::FluentArgs::new(); let mut args = fluent_bundle::FluentArgs::new();
args.set("variant", variant);
args.set("total", fetched.total as i64); args.set("total", fetched.total as i64);
args.set("stored", fetched.stored as i64); args.set("stored", fetched.stored as i64);
args.set("rejected", fetched.rejected.len() as i64); args.set("rejected", fetched.rejected.len() as i64);
Ok(i18n::t_args("fetch-result-note", Some(&args))) Ok(i18n::t_args(
&format!("fetch-result-note-{variant}"),
Some(&args),
))
} }
/// Fields fumi itself attaches to every message (sec 5.7, 5.8); a /// Fields fumi itself attaches to every message (sec 5.7, 5.8); a
@ -634,10 +636,9 @@ pub fn send(
(None, false) => "plain".to_string(), (None, false) => "plain".to_string(),
}; };
let mut args = fluent_bundle::FluentArgs::new(); let mut args = fluent_bundle::FluentArgs::new();
args.set("variant", variant);
args.set("address", addr.short()); args.set("address", addr.short());
args.set("bytes", sent.bytes as i64); args.set("bytes", sent.bytes as i64);
let mut note = i18n::t_args("send-result-note", Some(&args)); let mut note = i18n::t_args(&format!("send-result-note-{variant}"), Some(&args));
if let Some(warning) = sent.warning { if let Some(warning) = sent.warning {
// Fumi's own warning text, appended as a second, independent // Fumi's own warning text, appended as a second, independent
// clause rather than folded into the catalogue — it isn't ours // clause rather than folded into the catalogue — it isn't ours
@ -1112,12 +1113,11 @@ mod tests {
#[test] #[test]
fn interpolated_messages_carry_no_bidi_isolation_marks() { fn interpolated_messages_carry_no_bidi_isolation_marks() {
let mut args = fluent_bundle::FluentArgs::new(); let mut args = fluent_bundle::FluentArgs::new();
args.set("variant", "plain");
args.set("total", 5i64); args.set("total", 5i64);
args.set("stored", 5i64); args.set("stored", 5i64);
args.set("rejected", 0i64); args.set("rejected", 0i64);
assert_eq!( assert_eq!(
i18n::t_args("fetch-result-note", Some(&args)), i18n::t_args("fetch-result-note-plain", Some(&args)),
"5 fetched, 5 stored" "5 fetched, 5 stored"
); );
} }

View file

@ -1810,11 +1810,13 @@ fn render(model: &Carrier, w: &mut CarrierWidgets, sender: &ComponentSender<Carr
// `contact_state` collapses to accepted/blocked once a token // `contact_state` collapses to accepted/blocked once a token
// exists, which says nothing about where the key came from — // exists, which says nothing about where the key came from —
// so the server's pin state is spelled out beside it. // so the server's pin state is spelled out beside it.
let mut meta_args = fluent_bundle::FluentArgs::new(); let contact_meta_id = if contact.pinned {
meta_args.set("state", contact_state(contact)); "contact-meta-line-yes"
meta_args.set("pinned", if contact.pinned { "yes" } else { "no" }); } else {
"contact-meta-line-no"
};
w.article_meta w.article_meta
.set_text(&i18n::t_args("contact-meta-line", Some(&meta_args))); .set_text(&i18n::t1(contact_meta_id, "state", contact_state(contact)));
let mut body = detail_body(&contact.address, &contact.key, &contact.fingerprint); let mut body = detail_body(&contact.address, &contact.key, &contact.fingerprint);
if !contact.server.is_empty() { if !contact.server.is_empty() {
body.push_str(&format!( body.push_str(&format!(
@ -1944,13 +1946,17 @@ fn render(model: &Carrier, w: &mut CarrierWidgets, sender: &ComponentSender<Carr
} else { } else {
address address
}; };
let mail_meta_id = if msg.kept {
"mail-meta-line-yes"
} else {
"mail-meta-line-no"
};
let mut meta_args = fluent_bundle::FluentArgs::new(); let mut meta_args = fluent_bundle::FluentArgs::new();
meta_args.set("label", label); meta_args.set("label", label);
meta_args.set("from", from); meta_args.set("from", from);
meta_args.set("time", escape(&ops::when(msg.time))); meta_args.set("time", escape(&ops::when(msg.time)));
meta_args.set("id", msg.id[..msg.id.len().min(8)].to_string()); meta_args.set("id", msg.id[..msg.id.len().min(8)].to_string());
meta_args.set("kept", if msg.kept { "yes" } else { "no" }); let meta = i18n::t_args(mail_meta_id, Some(&meta_args));
let meta = i18n::t_args("mail-meta-line", Some(&meta_args));
w.article_meta.set_markup(&meta); w.article_meta.set_markup(&meta);
let headers: Vec<String> = msg let headers: Vec<String> = msg
.headers .headers