fix(ui): prevent translated tool labels from overflowing and align German terminology (#939)
* fix(ui): bound translated tool labels and polish German terminology (#828) * style(ui): keep bounded-label layout readable * German: keep „Ausfüllen und unterschreiben“ (Fill & Sign isn't a certificate signature) and „Für Barrierefreiheit vorbereiten“ * Refresh catalog hashes * Test: find the German Fill & Sign heading whole or elided --------- Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
21d5323132
commit
a3d30d5866
|
|
@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
|
||||||
licence_file = "LICENSE-MIT"
|
licence_file = "LICENSE-MIT"
|
||||||
kind = "translation"
|
kind = "translation"
|
||||||
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||||
sha256 = "386d5e5e5dcdf80487d304f914a0ac0f7e17c5d2134ff5df37b477b8cb5bf3ca"
|
sha256 = "6bb45afb959816bc6c69a1b262d568d49fe8c9a3dcec8a321231196b0831251f"
|
||||||
|
|
||||||
[[asset]]
|
[[asset]]
|
||||||
path = "crates/ui-egui/src/i18n/te.tsv"
|
path = "crates/ui-egui/src/i18n/te.tsv"
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
Create PDF from images… PDF aus Bildern erstellen…
|
Create PDF from images… PDF aus Bildern erstellen…
|
||||||
Create PDF from multiple files… PDF aus mehreren Dateien erstellen…
|
Create PDF from multiple files… PDF aus mehreren Dateien erstellen…
|
||||||
Create PDF from clipboard PDF aus Zwischenablage erstellen
|
Create PDF from clipboard PDF aus Zwischenablage erstellen
|
||||||
Combine files… Dateien kombinieren…
|
Combine files… Dateien zusammenführen…
|
||||||
Save Speichern
|
Save Speichern
|
||||||
Save as… Speichern unter…
|
Save as… Speichern unter…
|
||||||
Close file Datei schließen
|
Close file Datei schließen
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
Comments panel Kommentarbereich
|
Comments panel Kommentarbereich
|
||||||
Form fields panel Formularfeldbereich
|
Form fields panel Formularfeldbereich
|
||||||
Clear form Formular zurücksetzen
|
Clear form Formular zurücksetzen
|
||||||
Find tools and commands… Tools und Befehle suchen…
|
Find tools and commands… Werkzeuge und Befehle suchen…
|
||||||
Zoom Zoom
|
Zoom Zoom
|
||||||
Actual size Originalgröße
|
Actual size Originalgröße
|
||||||
Zoom to page level Ganze Seite
|
Zoom to page level Ganze Seite
|
||||||
|
|
@ -96,7 +96,7 @@
|
||||||
Print ({key}) Drucken ({key})
|
Print ({key}) Drucken ({key})
|
||||||
Save ({key}) Speichern ({key})
|
Save ({key}) Speichern ({key})
|
||||||
Document properties ({key}) Dokumenteigenschaften ({key})
|
Document properties ({key}) Dokumenteigenschaften ({key})
|
||||||
Find tools and commands Tools und Befehle suchen
|
Find tools and commands Werkzeuge und Befehle suchen
|
||||||
Fit to height Seitenhöhe
|
Fit to height Seitenhöhe
|
||||||
Zoom in Vergrößern
|
Zoom in Vergrößern
|
||||||
Zoom out Verkleinern
|
Zoom out Verkleinern
|
||||||
|
|
@ -322,9 +322,9 @@
|
||||||
Images Bilder
|
Images Bilder
|
||||||
Clipboard Zwischenablage
|
Clipboard Zwischenablage
|
||||||
Blank page Leere Seite
|
Blank page Leere Seite
|
||||||
Combine files Dateien kombinieren
|
Combine files Dateien zusammenführen
|
||||||
Combine Kombinieren
|
Combine Zusammenführen
|
||||||
Add files to combine Dateien zum Kombinieren hinzufügen
|
Add files to combine Dateien zum Zusammenführen hinzufügen
|
||||||
Page options Seitenoptionen
|
Page options Seitenoptionen
|
||||||
Page grid Seitenraster
|
Page grid Seitenraster
|
||||||
Rotate Drehen
|
Rotate Drehen
|
||||||
|
|
@ -337,7 +337,7 @@
|
||||||
Set page boxes Seitenrahmen festlegen
|
Set page boxes Seitenrahmen festlegen
|
||||||
Page labels Seitenbeschriftungen
|
Page labels Seitenbeschriftungen
|
||||||
Page transitions Seitenübergänge
|
Page transitions Seitenübergänge
|
||||||
Add comments Kommentieren
|
Add comments Kommentare hinzufügen
|
||||||
Markup Markierungen
|
Markup Markierungen
|
||||||
Sticky note Notiz
|
Sticky note Notiz
|
||||||
Underline Unterstreichen
|
Underline Unterstreichen
|
||||||
|
|
|
||||||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
|
|
@ -35,12 +35,14 @@ fn panel_header(ui: &mut egui::Ui, t: &Tokens, title: &str, back: bool) -> (bool
|
||||||
if back && icons::button(ui, "chevron-left", 26.0, false, tl!("Back to all tools")).clicked() {
|
if back && icons::button(ui, "chevron-left", 26.0, false, tl!("Back to all tools")).clicked() {
|
||||||
go_back = true;
|
go_back = true;
|
||||||
}
|
}
|
||||||
ui.label(egui::RichText::new(tl!(title)).font(theme::semibold(15.5)).color(t.text));
|
// Reserve space for Close before laying out the translated title. Long tool names
|
||||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
// must never expand the fixed-width sidebar or push the Close control off-screen.
|
||||||
if icons::button(ui, "x", 26.0, false, tl!("Close panel")).clicked() {
|
let title_width = (ui.available_width() - 26.0 - ui.spacing().item_spacing.x).max(1.0);
|
||||||
close = true;
|
ui.add_sized([title_width, 26.0], egui::Label::new(egui::RichText::new(tl!(title)).font(theme::semibold(15.5)).color(t.text)).truncate())
|
||||||
}
|
.on_hover_text(tl!(title));
|
||||||
});
|
if icons::button(ui, "x", 26.0, false, tl!("Close panel")).clicked() {
|
||||||
|
close = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
(go_back, close)
|
(go_back, close)
|
||||||
|
|
@ -66,6 +68,17 @@ fn all_tools(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw a one-line row label without painting into the status chip or beyond the sidebar.
|
||||||
|
/// Accessibility retains the complete name through the row's WidgetInfo.
|
||||||
|
fn row_label(ui: &mut egui::Ui, text: &str, font: egui::FontId, color: Color32, left_center: egui::Pos2, max_width: f32) -> bool {
|
||||||
|
let mut job = egui::text::LayoutJob::simple_singleline(text.to_owned(), font, color);
|
||||||
|
job.wrap = egui::text::TextWrapping { max_width: max_width.max(1.0), max_rows: 1, break_anywhere: true, ..Default::default() };
|
||||||
|
let galley = ui.fonts_mut(|f| f.layout_job(job));
|
||||||
|
let elided = galley.elided;
|
||||||
|
ui.painter().galley(left_center - vec2(0.0, galley.size().y / 2.0), galley, color);
|
||||||
|
elided
|
||||||
|
}
|
||||||
|
|
||||||
fn tool_row(ui: &mut egui::Ui, t: &Tokens, g: &ToolGroup) -> egui::Response {
|
fn tool_row(ui: &mut egui::Ui, t: &Tokens, g: &ToolGroup) -> egui::Response {
|
||||||
let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 34.0), Sense::click());
|
let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 34.0), Sense::click());
|
||||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, tl!(g.label)));
|
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, tl!(g.label)));
|
||||||
|
|
@ -73,7 +86,16 @@ fn tool_row(ui: &mut egui::Ui, t: &Tokens, g: &ToolGroup) -> egui::Response {
|
||||||
ui.painter().rect_filled(rect, CornerRadius::same(6), t.hover);
|
ui.painter().rect_filled(rect, CornerRadius::same(6), t.hover);
|
||||||
}
|
}
|
||||||
icons::paint(ui, Rect::from_min_size(rect.min + vec2(6.0, 7.0), vec2(20.0, 20.0)), g.icon, 19.0, hue(g));
|
icons::paint(ui, Rect::from_min_size(rect.min + vec2(6.0, 7.0), vec2(20.0, 20.0)), g.icon, 19.0, hue(g));
|
||||||
ui.painter().text(rect.left_center() + vec2(36.0, 0.0), Align2::LEFT_CENTER, tl!(g.label), theme::regular(13.5), t.text);
|
let right_reserved = match (g.badge, g.availability) {
|
||||||
|
(Some(b), _) => ui.fonts_mut(|f| f.layout_no_wrap(tl!(b).to_owned(), theme::semibold(9.5), Color32::WHITE).size().x) + 26.0,
|
||||||
|
(None, Availability::Planned(m)) if resp.hovered() => {
|
||||||
|
let hint = format!("{} · {m}", tl!("Planned"));
|
||||||
|
ui.fonts_mut(|f| f.layout_no_wrap(hint, theme::medium(10.5), t.text_faint).size().x) + 18.0
|
||||||
|
}
|
||||||
|
_ => 8.0,
|
||||||
|
};
|
||||||
|
let clipped =
|
||||||
|
row_label(ui, tl!(g.label), theme::regular(13.5), t.text, rect.left_center() + vec2(36.0, 0.0), rect.width() - 36.0 - right_reserved);
|
||||||
match (g.badge, g.availability) {
|
match (g.badge, g.availability) {
|
||||||
(Some(b), _) => {
|
(Some(b), _) => {
|
||||||
let font = theme::semibold(9.5);
|
let font = theme::semibold(9.5);
|
||||||
|
|
@ -100,6 +122,7 @@ fn tool_row(ui: &mut egui::Ui, t: &Tokens, g: &ToolGroup) -> egui::Response {
|
||||||
Availability::Planned(m) => format!("{} {m} — {}", tl!("Planned for milestone"), tl!("open to see what it will include")),
|
Availability::Planned(m) => format!("{} {m} — {}", tl!("Planned for milestone"), tl!("open to see what it will include")),
|
||||||
Availability::Provider => tl!("Optional: needs an AI provider you configure").into(),
|
Availability::Provider => tl!("Optional: needs an AI provider you configure").into(),
|
||||||
};
|
};
|
||||||
|
let tip = if clipped { format!("{} — {tip}", tl!(g.label)) } else { tip };
|
||||||
resp.on_hover_text(tip)
|
resp.on_hover_text(tip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +208,6 @@ fn tool_detail(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens, g: &'static
|
||||||
17.0,
|
17.0,
|
||||||
if ready { hue(g) } else { t.text_faint },
|
if ready { hue(g) } else { t.text_faint },
|
||||||
);
|
);
|
||||||
ui.painter().text(rect.left_center() + vec2(34.0, 0.0), Align2::LEFT_CENTER, tl!(item.label), theme::regular(13.0), fg);
|
|
||||||
let (chip, fill, cfg) = match item.availability {
|
let (chip, fill, cfg) = match item.availability {
|
||||||
Availability::Ready => (tl!("Ready"), Color32::from_rgb(0xDD, 0xF3, 0xE4), Color32::from_rgb(0x1E, 0x7B, 0x43)),
|
Availability::Ready => (tl!("Ready"), Color32::from_rgb(0xDD, 0xF3, 0xE4), Color32::from_rgb(0x1E, 0x7B, 0x43)),
|
||||||
Availability::Planned(m) => (m, t.pressed, t.text_muted),
|
Availability::Planned(m) => (m, t.pressed, t.text_muted),
|
||||||
|
|
@ -194,9 +216,10 @@ fn tool_detail(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens, g: &'static
|
||||||
let font = theme::semibold(9.5);
|
let font = theme::semibold(9.5);
|
||||||
let w = ui.fonts_mut(|f| f.layout_no_wrap(tl!(chip).to_string(), font.clone(), cfg).size().x);
|
let w = ui.fonts_mut(|f| f.layout_no_wrap(tl!(chip).to_string(), font.clone(), cfg).size().x);
|
||||||
let r = Rect::from_center_size(rect.right_center() - vec2(w / 2.0 + 10.0, 0.0), vec2(w + 10.0, 16.0));
|
let r = Rect::from_center_size(rect.right_center() - vec2(w / 2.0 + 10.0, 0.0), vec2(w + 10.0, 16.0));
|
||||||
|
row_label(ui, tl!(item.label), theme::regular(13.0), fg, rect.left_center() + vec2(34.0, 0.0), r.left() - rect.left() - 42.0);
|
||||||
ui.painter().rect_filled(r, CornerRadius::same(4), fill);
|
ui.painter().rect_filled(r, CornerRadius::same(4), fill);
|
||||||
ui.painter().text(r.center(), Align2::CENTER_CENTER, tl!(chip), font, cfg);
|
ui.painter().text(r.center(), Align2::CENTER_CENTER, tl!(chip), font, cfg);
|
||||||
if resp.on_hover_text(item.command).clicked() {
|
if resp.on_hover_text(format!("{} — {}", tl!(item.label), item.command)).clicked() {
|
||||||
run = Some(item.command);
|
run = Some(item.command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -937,6 +937,27 @@ fn cancelling_then_discarding_an_earlier_dirty_tab_preserves_the_selected_docume
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn long_german_tool_labels_stay_within_the_sidebar() {
|
||||||
|
use pdfcraft_ui_egui::{LeftPanel, Mode};
|
||||||
|
let mut h = harness(|app| app.open_bytes("fixture.pdf", None, FIXTURE.to_vec()).expect("fixture opens"));
|
||||||
|
h.state_mut().set_option("language", "de").unwrap();
|
||||||
|
h.state_mut().mode = Mode::AllTools;
|
||||||
|
h.state_mut().left_open = true;
|
||||||
|
// The tool heading has to leave room for Back and Close even when translated.
|
||||||
|
h.state_mut().left = LeftPanel::Tool("fill_sign");
|
||||||
|
h.run_steps(4);
|
||||||
|
let heading =
|
||||||
|
painted_text(&h).into_iter().find(|(s, ..)| s.starts_with("Ausfüllen und")).expect("translated heading is painted, whole or elided");
|
||||||
|
assert!(heading.2.right() <= 272.0, "translated heading runs past the sidebar: {:?}", heading.2);
|
||||||
|
// A long sub-tool name must stop before its status chip, not paint over the PDF.
|
||||||
|
h.state_mut().left = LeftPanel::Tool("protect");
|
||||||
|
h.run_steps(4);
|
||||||
|
let item =
|
||||||
|
painted_text(&h).into_iter().find(|(s, ..)| s == "Ausgeblendete Informationen entfernen").expect("long translated sub-tool is painted");
|
||||||
|
assert!(item.2.right() <= 272.0, "translated tool item runs past the sidebar: {:?}", item.2);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn document_tabs_expose_selection_and_activate_from_keyboard() {
|
fn document_tabs_expose_selection_and_activate_from_keyboard() {
|
||||||
use egui::accesskit::Role;
|
use egui::accesskit::Role;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue