feat: extract all user-facing strings into a Fluent catalogue
This commit is contained in:
parent
4c5665a6ff
commit
57e75af3ac
8 changed files with 1024 additions and 326 deletions
59
src/app.rs
59
src/app.rs
|
|
@ -15,6 +15,7 @@ use fumi_core::crypto::{fingerprint, unb32, KEY_LEN};
|
|||
use fumi_core::store::Store;
|
||||
use gtk::prelude::*;
|
||||
|
||||
use crate::i18n;
|
||||
use crate::ops::{self, Draft, Folder, OpenedMsg, Row, Snapshot};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -514,7 +515,7 @@ impl Carrier {
|
|||
at: fumi_core::store::now(),
|
||||
});
|
||||
ops::save_drafts(&self.drafts_path(), &self.drafts);
|
||||
self.note("draft saved".into());
|
||||
self.note(i18n::t("draft-saved-note"));
|
||||
}
|
||||
|
||||
/// Tapping a draft in the folder puts it back in the compose it came
|
||||
|
|
@ -555,7 +556,7 @@ impl Carrier {
|
|||
}
|
||||
let (app, _) = Carrier::boot(self.dir.clone());
|
||||
*self = app;
|
||||
self.note("logged out. Create a new identity or restore a seed.".into());
|
||||
self.note(i18n::t("logged-out-note"));
|
||||
}
|
||||
|
||||
/// Cancelling out of a not-yet-registered identity removes the key
|
||||
|
|
@ -853,9 +854,7 @@ impl Carrier {
|
|||
self.master = Some(master);
|
||||
self.onboard.seed_shown = ops::seed_text(&master);
|
||||
self.onboard.mode = OnboardMode::Created;
|
||||
self.note(
|
||||
"Identity created. Write the seed down; it is the only secret.".into(),
|
||||
);
|
||||
self.note(i18n::t("identity-created-note"));
|
||||
}
|
||||
}
|
||||
Message::OnboardRestore => {
|
||||
|
|
@ -877,7 +876,7 @@ impl Carrier {
|
|||
} else {
|
||||
self.master = Some(master);
|
||||
self.page = Page::Signup;
|
||||
self.note("Seed restored. Bind a username or recall an address.".into());
|
||||
self.note(i18n::t("seed-restored-note"));
|
||||
}
|
||||
}
|
||||
Err(e) => self.fail(e),
|
||||
|
|
@ -904,14 +903,14 @@ impl Carrier {
|
|||
return Vec::new();
|
||||
}
|
||||
let Some(addr) = self.signup_host_address() else {
|
||||
self.fail("enter the server host first".into());
|
||||
self.fail(i18n::t("signup-enter-host-error"));
|
||||
return Vec::new();
|
||||
};
|
||||
return vec![PendingOp::LearnKey { addr }];
|
||||
}
|
||||
Message::SignupRegister => {
|
||||
let Some(addr) = self.signup_address() else {
|
||||
self.fail("enter user and host".into());
|
||||
self.fail(i18n::t("signup-enter-user-host-error"));
|
||||
return Vec::new();
|
||||
};
|
||||
let invite = if self.signup.invite.is_empty() {
|
||||
|
|
@ -926,7 +925,7 @@ impl Carrier {
|
|||
}
|
||||
Message::SignupRecall => {
|
||||
let Some(addr) = self.signup_address() else {
|
||||
self.fail("enter user and host".into());
|
||||
self.fail(i18n::t("signup-enter-user-host-error"));
|
||||
return Vec::new();
|
||||
};
|
||||
if !self.start("restoring") {
|
||||
|
|
@ -993,7 +992,7 @@ impl Carrier {
|
|||
let to = if let Some(uri) = &msg.reply_to {
|
||||
uri.clone()
|
||||
} else if msg.from.starts_with('<') {
|
||||
self.fail("this sender left no address to reply to".into());
|
||||
self.fail(i18n::t("reply-no-address-error"));
|
||||
return Vec::new();
|
||||
} else {
|
||||
msg.from.clone()
|
||||
|
|
@ -1001,7 +1000,7 @@ impl Carrier {
|
|||
let subject = if msg.subject.is_empty() || msg.subject.starts_with("Re:") {
|
||||
msg.subject.clone()
|
||||
} else {
|
||||
format!("Re: {}", msg.subject)
|
||||
i18n::t1("reply-subject-prefix", "subject", msg.subject.clone())
|
||||
};
|
||||
// The sender's own fields are the one thing a reply has to
|
||||
// decide about: carry copies, or start clean. Only the
|
||||
|
|
@ -1057,7 +1056,7 @@ impl Carrier {
|
|||
return Vec::new();
|
||||
};
|
||||
if compose.to.trim().is_empty() {
|
||||
self.fail("enter a recipient".into());
|
||||
self.fail(i18n::t("compose-enter-recipient-error"));
|
||||
return Vec::new();
|
||||
}
|
||||
let mut headers = Vec::new();
|
||||
|
|
@ -1066,7 +1065,10 @@ impl Carrier {
|
|||
Ok(Some(pair)) => headers.push(pair),
|
||||
Ok(None) => {}
|
||||
Err(reason) => {
|
||||
self.fail(format!("header {}: {reason}", index + 1));
|
||||
let mut args = fluent_bundle::FluentArgs::new();
|
||||
args.set("index", (index + 1) as i64);
|
||||
args.set("reason", reason);
|
||||
self.fail(i18n::t_args("compose-header-row-error", Some(&args)));
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
|
|
@ -1109,11 +1111,10 @@ impl Carrier {
|
|||
}
|
||||
if self.strict_pinning {
|
||||
if let Some(addr) = self.unpinned_first_contact(&to) {
|
||||
self.fail(format!(
|
||||
"{} is not a contact yet and {} is not pinned — pin their server's key to send.",
|
||||
addr.short(),
|
||||
trust_label(&addr.host, addr.port),
|
||||
));
|
||||
let mut args = fluent_bundle::FluentArgs::new();
|
||||
args.set("address", addr.short());
|
||||
args.set("host", trust_label(&addr.host, addr.port));
|
||||
self.fail(i18n::t_args("first-contact-blocked-error", Some(&args)));
|
||||
self.pin_prompt = Some(PinPrompt {
|
||||
addr,
|
||||
offered: None,
|
||||
|
|
@ -1139,7 +1140,7 @@ impl Carrier {
|
|||
return Vec::new();
|
||||
};
|
||||
let Ok(addr) = Address::parse(&to) else {
|
||||
self.fail("that address does not parse".into());
|
||||
self.fail(i18n::t("address-invalid-error"));
|
||||
return Vec::new();
|
||||
};
|
||||
let fetched = self
|
||||
|
|
@ -1203,17 +1204,17 @@ impl Carrier {
|
|||
return Vec::new();
|
||||
}
|
||||
let Some(uri) = msg.reply_to.clone() else {
|
||||
self.fail("this sender left no address to accept".into());
|
||||
self.fail(i18n::t("contacts-accept-no-address-error"));
|
||||
return Vec::new();
|
||||
};
|
||||
let Ok(addr) = Address::parse(&uri) else {
|
||||
self.fail("the sender's reply address does not parse".into());
|
||||
self.fail(i18n::t("contacts-reply-address-invalid-error"));
|
||||
return Vec::new();
|
||||
};
|
||||
let Ok(identity) =
|
||||
<[u8; KEY_LEN]>::try_from(unb32(&msg.sender).unwrap_or_default())
|
||||
else {
|
||||
self.fail("the sender's key does not parse".into());
|
||||
self.fail(i18n::t("contacts-sender-key-invalid-error"));
|
||||
return Vec::new();
|
||||
};
|
||||
return vec![PendingOp::Accept {
|
||||
|
|
@ -1226,11 +1227,11 @@ impl Carrier {
|
|||
return Vec::new();
|
||||
};
|
||||
let Some(uri) = msg.reply_to.clone() else {
|
||||
self.fail("this sender left no address to block".into());
|
||||
self.fail(i18n::t("contacts-block-no-address-error"));
|
||||
return Vec::new();
|
||||
};
|
||||
let Ok(addr) = Address::parse(&uri) else {
|
||||
self.fail("the sender's reply address does not parse".into());
|
||||
self.fail(i18n::t("contacts-reply-address-invalid-error"));
|
||||
return Vec::new();
|
||||
};
|
||||
return vec![PendingOp::Block {
|
||||
|
|
@ -1414,11 +1415,7 @@ impl Carrier {
|
|||
Some(prompt) => prompt.offered = Some(key),
|
||||
None => self.pin_offer = Some((addr, key)),
|
||||
}
|
||||
self.note(
|
||||
"The server presented this key. Pin it only if the fingerprint \
|
||||
matches what the operator published."
|
||||
.into(),
|
||||
);
|
||||
self.note(i18n::t("server-key-offered-note"));
|
||||
None
|
||||
}
|
||||
OpResult::PinOffered(Ok(None)) => {
|
||||
|
|
@ -1428,7 +1425,7 @@ impl Carrier {
|
|||
if self.pin_prompt.take().is_none() {
|
||||
self.signup.key_trusted = true;
|
||||
}
|
||||
self.note("This server's key is already pinned.".into());
|
||||
self.note(i18n::t("server-key-already-pinned-note"));
|
||||
None
|
||||
}
|
||||
OpResult::PinOffered(Err(e)) => {
|
||||
|
|
@ -1438,7 +1435,7 @@ impl Carrier {
|
|||
}
|
||||
OpResult::Pinned(Ok(())) => {
|
||||
self.done();
|
||||
self.note("server key pinned".into());
|
||||
self.note(i18n::t("server-key-pinned-note"));
|
||||
// The contact detail shows the pin state, so that pane
|
||||
// needs the fresh row; signup's own pin changes nothing
|
||||
// that is on screen.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue