Add Phase 5 enhancements: security, i18n, analysis, backup, notifications
This commit is contained in:
@@ -5,6 +5,7 @@ use std::rc::Rc;
|
||||
use crate::core::database::Database;
|
||||
use crate::core::duplicates::{self, DuplicateGroup, MatchReason, MemberRecommendation};
|
||||
use crate::core::integrator;
|
||||
use crate::i18n::{i18n, i18n_f, ni18n_f};
|
||||
use super::widgets;
|
||||
|
||||
/// Show a dialog listing duplicate/multi-version AppImages with resolution options.
|
||||
@@ -17,10 +18,10 @@ pub fn show_duplicate_dialog(
|
||||
|
||||
if groups.is_empty() {
|
||||
let dialog = adw::AlertDialog::builder()
|
||||
.heading("No Duplicates Found")
|
||||
.body("No duplicate or multi-version AppImages were detected.")
|
||||
.heading(&i18n("No Duplicates Found"))
|
||||
.body(&i18n("No duplicate or multi-version AppImages were detected."))
|
||||
.build();
|
||||
dialog.add_response("ok", "OK");
|
||||
dialog.add_response("ok", &i18n("OK"));
|
||||
dialog.set_default_response(Some("ok"));
|
||||
dialog.present(Some(parent));
|
||||
return;
|
||||
@@ -29,7 +30,7 @@ pub fn show_duplicate_dialog(
|
||||
let summary = duplicates::summarize_duplicates(&groups);
|
||||
|
||||
let dialog = adw::Dialog::builder()
|
||||
.title("Duplicates & Old Versions")
|
||||
.title(&i18n("Duplicates & Old Versions"))
|
||||
.content_width(600)
|
||||
.content_height(500)
|
||||
.build();
|
||||
@@ -39,12 +40,12 @@ pub fn show_duplicate_dialog(
|
||||
|
||||
// "Remove All Suggested" bulk action button
|
||||
let bulk_btn = gtk::Button::builder()
|
||||
.label("Remove All Suggested")
|
||||
.tooltip_text("Delete all items recommended for removal")
|
||||
.label(&i18n("Remove All Suggested"))
|
||||
.tooltip_text(&i18n("Delete all items recommended for removal"))
|
||||
.build();
|
||||
bulk_btn.add_css_class("destructive-action");
|
||||
bulk_btn.update_property(&[
|
||||
gtk::accessible::Property::Label("Remove all suggested duplicates"),
|
||||
gtk::accessible::Property::Label(&i18n("Remove all suggested duplicates")),
|
||||
]);
|
||||
header.pack_end(&bulk_btn);
|
||||
|
||||
@@ -64,13 +65,14 @@ pub fn show_duplicate_dialog(
|
||||
.build();
|
||||
|
||||
// Summary banner
|
||||
let summary_text = format!(
|
||||
"{} groups found ({} exact duplicates, {} with multiple versions). \
|
||||
Potential savings: {}",
|
||||
summary.total_groups,
|
||||
summary.exact_duplicates,
|
||||
summary.multi_version,
|
||||
widgets::format_size(summary.total_potential_savings as i64),
|
||||
let summary_text = i18n_f(
|
||||
"{groups} groups found ({exact} exact duplicates, {multi} with multiple versions). Potential savings: {savings}",
|
||||
&[
|
||||
("{groups}", &summary.total_groups.to_string()),
|
||||
("{exact}", &summary.exact_duplicates.to_string()),
|
||||
("{multi}", &summary.multi_version.to_string()),
|
||||
("{savings}", &widgets::format_size(summary.total_potential_savings as i64)),
|
||||
],
|
||||
);
|
||||
let summary_label = gtk::Label::builder()
|
||||
.label(&summary_text)
|
||||
@@ -101,13 +103,17 @@ pub fn show_duplicate_dialog(
|
||||
return;
|
||||
}
|
||||
|
||||
let plural = if count == 1 { "" } else { "s" };
|
||||
let confirm = adw::AlertDialog::builder()
|
||||
.heading("Confirm Removal")
|
||||
.body(&format!("Remove {} suggested duplicate{}?", count, plural))
|
||||
.heading(&i18n("Confirm Removal"))
|
||||
.body(&ni18n_f(
|
||||
"Remove {count} suggested duplicate?",
|
||||
"Remove {count} suggested duplicates?",
|
||||
count as u32,
|
||||
&[("{count}", &count.to_string())],
|
||||
))
|
||||
.build();
|
||||
confirm.add_response("cancel", "Cancel");
|
||||
confirm.add_response("remove", "Remove");
|
||||
confirm.add_response("cancel", &i18n("Cancel"));
|
||||
confirm.add_response("remove", &i18n("Remove"));
|
||||
confirm.set_response_appearance("remove", adw::ResponseAppearance::Destructive);
|
||||
confirm.set_default_response(Some("cancel"));
|
||||
confirm.set_close_response("cancel");
|
||||
@@ -136,9 +142,14 @@ pub fn show_duplicate_dialog(
|
||||
removed_count += 1;
|
||||
}
|
||||
if removed_count > 0 {
|
||||
toast_confirm.add_toast(adw::Toast::new(&format!("Removed {} items", removed_count)));
|
||||
toast_confirm.add_toast(adw::Toast::new(&ni18n_f(
|
||||
"Removed {count} item",
|
||||
"Removed {count} items",
|
||||
removed_count as u32,
|
||||
&[("{count}", &removed_count.to_string())],
|
||||
)));
|
||||
btn_ref.set_sensitive(false);
|
||||
btn_ref.set_label("Done");
|
||||
btn_ref.set_label(&i18n("Done"));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -158,23 +169,27 @@ fn build_group_widget(
|
||||
toast_overlay: &adw::ToastOverlay,
|
||||
) -> (adw::PreferencesGroup, Vec<(i64, String, String, bool)>) {
|
||||
let reason_text = match group.match_reason {
|
||||
MatchReason::ExactDuplicate => "Exact duplicate",
|
||||
MatchReason::MultiVersion => "Multiple versions",
|
||||
MatchReason::SameVersionDifferentPath => "Same version, different path",
|
||||
MatchReason::ExactDuplicate => i18n("Exact duplicate"),
|
||||
MatchReason::MultiVersion => i18n("Multiple versions"),
|
||||
MatchReason::SameVersionDifferentPath => i18n("Same version, different path"),
|
||||
};
|
||||
|
||||
let description = if group.potential_savings > 0 {
|
||||
format!(
|
||||
"{} - Total: {} - Potential savings: {}",
|
||||
reason_text,
|
||||
widgets::format_size(group.total_size as i64),
|
||||
widgets::format_size(group.potential_savings as i64),
|
||||
i18n_f(
|
||||
"{reason} - Total: {total} - Potential savings: {savings}",
|
||||
&[
|
||||
("{reason}", &reason_text),
|
||||
("{total}", &widgets::format_size(group.total_size as i64)),
|
||||
("{savings}", &widgets::format_size(group.potential_savings as i64)),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{} - Total: {}",
|
||||
reason_text,
|
||||
widgets::format_size(group.total_size as i64),
|
||||
i18n_f(
|
||||
"{reason} - Total: {total}",
|
||||
&[
|
||||
("{reason}", &reason_text),
|
||||
("{total}", &widgets::format_size(group.total_size as i64)),
|
||||
],
|
||||
)
|
||||
};
|
||||
|
||||
@@ -187,11 +202,12 @@ fn build_group_widget(
|
||||
|
||||
for member in &group.members {
|
||||
let record = &member.record;
|
||||
let version = record.app_version.as_deref().unwrap_or("unknown");
|
||||
let unknown = i18n("unknown");
|
||||
let version = record.app_version.as_deref().unwrap_or(&unknown);
|
||||
let size = widgets::format_size(record.size_bytes);
|
||||
|
||||
let title = if member.is_recommended {
|
||||
format!("{} ({}) - Recommended", version, size)
|
||||
i18n_f("{version} ({size}) - Recommended", &[("{version}", version), ("{size}", &size)])
|
||||
} else {
|
||||
format!("{} ({})", version, size)
|
||||
};
|
||||
@@ -225,7 +241,7 @@ fn build_group_widget(
|
||||
|
||||
let delete_btn = gtk::Button::builder()
|
||||
.icon_name("user-trash-symbolic")
|
||||
.tooltip_text("Delete this AppImage")
|
||||
.tooltip_text(&i18n("Delete this AppImage"))
|
||||
.css_classes(["flat", "circular"])
|
||||
.valign(gtk::Align::Center)
|
||||
.build();
|
||||
@@ -235,7 +251,7 @@ fn build_group_widget(
|
||||
let record_path = record.path.clone();
|
||||
let record_name = record.app_name.clone().unwrap_or_else(|| record.filename.clone());
|
||||
delete_btn.update_property(&[
|
||||
gtk::accessible::Property::Label(&format!("Delete {}", record_name)),
|
||||
gtk::accessible::Property::Label(&i18n_f("Delete {name}", &[("{name}", &record_name)])),
|
||||
]);
|
||||
let db_ref = db.clone();
|
||||
let toast_ref = toast_overlay.clone();
|
||||
@@ -253,7 +269,7 @@ fn build_group_widget(
|
||||
db_ref.remove_appimage(record_id).ok();
|
||||
// Update UI
|
||||
btn.set_sensitive(false);
|
||||
toast_ref.add_toast(adw::Toast::new(&format!("Removed {}", record_name)));
|
||||
toast_ref.add_toast(adw::Toast::new(&i18n_f("Removed {name}", &[("{name}", &record_name)])));
|
||||
});
|
||||
|
||||
row.add_suffix(&delete_btn);
|
||||
|
||||
Reference in New Issue
Block a user