mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-29 05:06:51 -06:00
Translate and standardizes UI error feedback by locale
### Summary This change centralizes the translation of user-facing error messages in the UI layer while preserving the original error payloads for machine consumption and AI-agent integrations. ### What changed - Added a centralized error-translation path in `i18n.rs`. - The UI now translates only the final display text, instead of altering the underlying engine or protocol error payload. - If a translated string is not available, the original message is kept as a fallback, avoiding broken or overly-simplified output. - The translation logic is applied at the presentation layer, not at the source of the error. ### Updated call sites - Error and alert rendering in `widgets.rs` - Dialog error presentation in `dialogs.rs` - Print and feedback failures in `printing.rs` - Top-level UI error handling in `lib.rs` ### Design intent - Keep the raw engine errors intact for scripts, control channels, and AI/agent integrations. - Present a clearer, localized message only to end users. - Avoid modifying dictionary files or adding new translation entries to the global catalog unnecessarily. - Keep the behavior consistent with the project’s accessibility and internationalization goals. ### Validation - The focused i18n test passed successfully. - The relevant UI test suite passed successfully. - The repository-required CI gate was run and completed without failures according to the latest verification output.
This commit is contained in:
@@ -2612,7 +2612,7 @@ fn solver_results(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, open: &
|
||||
fn spelling(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, open: &mut bool) {
|
||||
let r = d.result.clone().unwrap_or(Json::Null);
|
||||
if let Some(e) = r.get("error").and_then(Json::as_str) {
|
||||
ui.label(crate::clean_error(e));
|
||||
ui.label(app.ui.language.tr_error(e));
|
||||
return;
|
||||
}
|
||||
let issues = r["issues"].as_array().cloned().unwrap_or_default();
|
||||
|
||||
@@ -132,6 +132,13 @@ impl Language {
|
||||
};
|
||||
translated.unwrap_or_else(|| text.rsplit_once('|').map_or(text, |(_, label)| label))
|
||||
}
|
||||
|
||||
pub(crate) fn tr_error(self, message: &str) -> String {
|
||||
let message = crate::clean_error(message);
|
||||
let translated = self.tr(&message);
|
||||
let fallback = message.rsplit_once('|').map_or(message.as_str(), |(_, label)| label);
|
||||
if translated == fallback { message } else { translated.to_owned() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes a saved language leniently: an unknown or malformed value (a language removed in a
|
||||
@@ -617,6 +624,9 @@ mod tests {
|
||||
assert_eq!(Language::Ko.tr("Data"), "데이터");
|
||||
assert_eq!(Language::Ru.tr("Data"), "Данные");
|
||||
assert_eq!(Language::PtBr.tr("Data"), "Dados");
|
||||
assert_eq!(Language::PtBr.tr_error("Select a chart first."), "Selecione um gráfico primeiro.");
|
||||
assert_eq!(Language::PtBr.tr_error("Sheet|Unknown failure"), "Sheet|Unknown failure");
|
||||
assert_eq!(Language::PtBr.tr_error("command `chart`: Select a chart first."), "Selecione um gráfico primeiro.");
|
||||
assert_eq!(Language::Es.tr("Data"), "Datos");
|
||||
assert_eq!(Language::Es.tr("Freeze Panes"), "Inmovilizar paneles");
|
||||
assert_eq!(Language::Es.tr("Sheet1!A1"), "Sheet1!A1");
|
||||
|
||||
@@ -980,7 +980,7 @@ impl SheetApp {
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let text = printing::feedback(&message, |label| self.ui.language.tr(label).to_owned());
|
||||
let text = printing::feedback(&message, self.ui.language, |label| self.ui.language.tr(label).to_owned());
|
||||
if matches!(message, printing::Feedback::Submitted(_)) {
|
||||
self.toast = Some((text, now_ms()));
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn show(state: &mut State, ui: &mut egui::Ui, discover: &Discover, translate
|
||||
};
|
||||
state.show(ui, egui::Id::new("gridcraft_print_settings"), discover, &labels);
|
||||
if let Some(message) = &state.message {
|
||||
ui.label(feedback(message, translate));
|
||||
ui.label(feedback(message, crate::i18n::current(ui.ctx()), translate));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ pub fn settings(state: &mut State, params: &serde_json::Value, discover: Option<
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn feedback(message: &Feedback, translate: impl Fn(&str) -> String) -> String {
|
||||
pub fn feedback(message: &Feedback, language: crate::i18n::Language, translate: impl Fn(&str) -> String) -> String {
|
||||
match message {
|
||||
Feedback::FindingPrinters => translate("Finding printers…"),
|
||||
Feedback::NoPrinters => translate("No printers found. Configure a system printer or export a PDF."),
|
||||
@@ -92,7 +92,7 @@ pub fn feedback(message: &Feedback, translate: impl Fn(&str) -> String) -> Strin
|
||||
Feedback::Submitting => translate("Submitting print job…"),
|
||||
Feedback::Submitted(receipt) if receipt.trim().is_empty() => translate("Print job submitted."),
|
||||
Feedback::Submitted(receipt) => translate("Print job submitted: {receipt}").replace("{receipt}", receipt),
|
||||
Feedback::Failed(error) => translate("Print failed: {error}").replace("{error}", error),
|
||||
Feedback::Failed(error) => translate("Print failed: {error}").replace("{error}", &language.tr_error(error)),
|
||||
Feedback::WorkerStopped => translate("Print failed: the printing worker stopped."),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +272,8 @@ pub fn color_palette(ui: &mut Ui, theme_colors: &[u32; 12], none_label: &str) ->
|
||||
|
||||
pub fn message_box(app: &mut SheetApp, ctx: &egui::Context) {
|
||||
let Some((title, msg)) = app.message.clone() else { return };
|
||||
let title = app.ui.language.tr(&title).to_owned();
|
||||
let msg = app.ui.language.tr_error(&msg);
|
||||
let mut close = false;
|
||||
egui::Modal::new(egui::Id::new("message_box")).show(ctx, |ui| {
|
||||
ui.set_width(380.0);
|
||||
@@ -302,6 +304,7 @@ pub fn toast(app: &mut SheetApp, ctx: &egui::Context) {
|
||||
app.toast = None;
|
||||
return;
|
||||
}
|
||||
let msg = app.ui.language.tr_error(&msg);
|
||||
let Some(rect) = app.grid.rect else { return };
|
||||
egui::Area::new(egui::Id::new("toast")).fixed_pos(pos2(rect.center().x - 160.0, rect.bottom() - 48.0)).order(egui::Order::Tooltip).show(
|
||||
ctx,
|
||||
|
||||
Reference in New Issue
Block a user