Add Phase 5 enhancements: security, i18n, analysis, backup, notifications

This commit is contained in:
2026-02-27 17:16:41 +02:00
parent 870ef2a739
commit 40f7f12834
50 changed files with 10446 additions and 481 deletions

View File

@@ -7,6 +7,7 @@ use crate::core::database::Database;
use crate::core::duplicates;
use crate::core::footprint;
use crate::core::orphan;
use crate::i18n::{i18n, i18n_f};
use super::widgets;
/// A reclaimable item discovered during analysis.
@@ -28,11 +29,11 @@ enum ReclaimCategory {
}
impl ReclaimCategory {
fn label(&self) -> &'static str {
fn label(&self) -> String {
match self {
ReclaimCategory::OrphanedDesktopEntry => "Orphaned desktop entries",
ReclaimCategory::CacheData => "Cache data",
ReclaimCategory::DuplicateAppImage => "Duplicate AppImages",
ReclaimCategory::OrphanedDesktopEntry => i18n("Orphaned desktop entries"),
ReclaimCategory::CacheData => i18n("Cache data"),
ReclaimCategory::DuplicateAppImage => i18n("Duplicate AppImages"),
}
}
@@ -48,7 +49,7 @@ impl ReclaimCategory {
/// Show the disk space reclamation wizard as an AdwDialog.
pub fn show_cleanup_wizard(parent: &impl IsA<gtk::Widget>, _db: &Rc<Database>) {
let dialog = adw::Dialog::builder()
.title("Disk Space Cleanup")
.title(&i18n("Disk Space Cleanup"))
.content_width(500)
.content_height(550)
.build();
@@ -118,8 +119,8 @@ pub fn show_cleanup_wizard(parent: &impl IsA<gtk::Widget>, _db: &Rc<Database>) {
Err(_) => {
let error_page = adw::StatusPage::builder()
.icon_name("dialog-error-symbolic")
.title("Analysis Failed")
.description("Could not analyze disk usage.")
.title(&i18n("Analysis Failed"))
.description(&i18n("Could not analyze disk usage."))
.build();
if let Some(child) = stack_ref.child_by_name("review") {
stack_ref.remove(&child);
@@ -148,13 +149,13 @@ fn build_analysis_step() -> gtk::Box {
page.append(&spinner);
let label = gtk::Label::builder()
.label("Analyzing disk usage...")
.label(&i18n("Analyzing disk usage..."))
.css_classes(["title-3"])
.build();
page.append(&label);
let subtitle = gtk::Label::builder()
.label("Checking for orphaned files, cache data, and duplicates")
.label(&i18n("Checking for orphaned files, cache data, and duplicates"))
.css_classes(["dimmed"])
.build();
page.append(&subtitle);
@@ -178,8 +179,8 @@ fn build_review_step(
if items_ref.is_empty() {
let empty = adw::StatusPage::builder()
.icon_name("emblem-ok-symbolic")
.title("All Clean")
.description("No reclaimable disk space found.")
.title(&i18n("All Clean"))
.description(&i18n("No reclaimable disk space found."))
.vexpand(true)
.build();
page.append(&empty);
@@ -189,7 +190,7 @@ fn build_review_step(
// Summary header
let total_size: u64 = items_ref.iter().map(|i| i.size_bytes).sum();
let summary_label = gtk::Label::builder()
.label(&format!("Found {} reclaimable", widgets::format_size(total_size as i64)))
.label(&i18n_f("Found {} reclaimable", &[("{}", &widgets::format_size(total_size as i64))]))
.css_classes(["title-3"])
.margin_top(12)
.margin_start(18)
@@ -199,7 +200,7 @@ fn build_review_step(
page.append(&summary_label);
let desc_label = gtk::Label::builder()
.label("Select items to remove")
.label(&i18n("Select items to remove"))
.css_classes(["dimmed"])
.margin_start(18)
.margin_end(18)
@@ -251,8 +252,9 @@ fn build_review_step(
let cat_icon = gtk::Image::from_icon_name(cat.icon_name());
cat_icon.set_pixel_size(16);
cat_header.append(&cat_icon);
let cat_label_text = cat.label();
let cat_label = gtk::Label::builder()
.label(&format!("{} ({})", cat.label(), widgets::format_size(cat_size as i64)))
.label(&format!("{} ({})", cat_label_text, widgets::format_size(cat_size as i64)))
.css_classes(["title-4"])
.halign(gtk::Align::Start)
.build();
@@ -263,7 +265,7 @@ fn build_review_step(
list_box.add_css_class("boxed-list");
list_box.set_selection_mode(gtk::SelectionMode::None);
list_box.update_property(&[
gtk::accessible::Property::Label(cat.label()),
gtk::accessible::Property::Label(&cat_label_text),
]);
for (idx, item) in &cat_items {
@@ -305,11 +307,11 @@ fn build_review_step(
.build();
let clean_button = gtk::Button::builder()
.label("Clean Selected")
.label(&i18n("Clean Selected"))
.build();
clean_button.add_css_class("destructive-action");
clean_button.update_property(&[
gtk::accessible::Property::Label("Clean selected items"),
gtk::accessible::Property::Label(&i18n("Clean selected items")),
]);
let items_clone = items.clone();
@@ -337,17 +339,16 @@ fn build_review_step(
}
let confirm = adw::AlertDialog::builder()
.heading("Confirm Cleanup")
.body(&format!(
"Remove {} item{}?",
count,
if count == 1 { "" } else { "s" }
.heading(&i18n("Confirm Cleanup"))
.body(&i18n_f(
"Remove {} items?",
&[("{}", &count.to_string())],
))
.close_response("cancel")
.default_response("clean")
.build();
confirm.add_response("cancel", "Cancel");
confirm.add_response("clean", "Clean");
confirm.add_response("cancel", &i18n("Cancel"));
confirm.add_response("clean", &i18n("Clean"));
confirm.set_response_appearance("clean", adw::ResponseAppearance::Destructive);
let on_confirm_inner = on_confirm_ref.clone();
@@ -416,31 +417,32 @@ fn build_complete_step(count: usize, size: u64, dialog: &adw::Dialog) -> gtk::Bo
if count == 0 {
let status = adw::StatusPage::builder()
.icon_name("emblem-ok-symbolic")
.title("Nothing Selected")
.description("No items were selected for cleanup.")
.title(&i18n("Nothing Selected"))
.description(&i18n("No items were selected for cleanup."))
.build();
page.append(&status);
} else {
let status = adw::StatusPage::builder()
.icon_name("user-trash-symbolic")
.title("Cleanup Complete")
.description(&format!(
"Removed {} item{}, freeing {}",
count,
if count == 1 { "" } else { "s" },
widgets::format_size(size as i64),
.title(&i18n("Cleanup Complete"))
.description(&i18n_f(
"Removed {count} items, freeing {size}",
&[
("{count}", &count.to_string()),
("{size}", &widgets::format_size(size as i64)),
],
))
.build();
page.append(&status);
}
let close_button = gtk::Button::builder()
.label("Close")
.label(&i18n("Close"))
.halign(gtk::Align::Center)
.build();
close_button.add_css_class("pill");
close_button.update_property(&[
gtk::accessible::Property::Label("Close cleanup dialog"),
gtk::accessible::Property::Label(&i18n("Close cleanup dialog")),
]);
let dialog_ref = dialog.clone();