ADR-0019 §9 sweep (3/3): ui.rs prose strings (caught in manual sanity)

Surprise gap from the post-sweep sanity check — `ui.rs` had a
substantial set of TUI-rendered strings that the previous two
sweep passes didn't cover. Caught by grepping for capitalised
literals in `ui.rs` after running the binary smoke check.

## Migrated

- **modal.*** — load picker title / empty state / path
  prompt; rebuild confirm title / "Continue?" prompt.
  (modal.path_entry's title comes from `save.*` since it's
  the save / save-as dialog.)
- **save.*** — `save` no-op hint, modal titles for
  Save / Save as, modal prompt body.
- **status.*** — status bar `Project:` label and the
  `(no project)` placeholder.
- **panel.*** — `Tables` panel title, `(none yet)`
  placeholder for empty tables, `(no active hint)`
  placeholder for the hint panel.
- **shortcut.*** — the bottom-bar keyboard hint labels
  (submit, confirm, cancel, yes, no, load, select,
  browse_path, back_to_list, switch, advanced_once,
  cancel_one_shot, quit). Each is a translatable label
  paired with a key name (Enter / Esc / Ctrl-C / etc.) at
  the call site. Keystroke names are deliberately left as
  literals — translating them would mean retraining users
  away from what their keyboard says.

The `push_shortcut` closure's parameter type changed from
`&'static str` to `&str` so it accepts the catalog-returned
String.

## Deliberately left

- **Echo prefix tags**: `[simple] `, `[advanced] `,
  `[system] `, `[error]  `. Their column widths are
  hardcoded into the wrap-width calculation in
  `render_output_panel`; translating them would silently
  break alignment. Worth a follow-up pass if a future locale
  needs different prefixes (would need `mode.label()` and
  the echo-tag widths to live behind a single locale-aware
  function).
- **Mode labels**: `SIMPLE` / `ADVANCED` / `Advanced:`
  rendered in the input panel border. Same alignment
  reasoning as the echo tags — also they're keywords (the
  user types `mode simple` to switch), so translating the
  display label without translating the command word would
  be confusing. Left as is.
- **Visual decoration**: `[Y]`, `[N]`, `[TEMP] `, `>`
  cursor markers, `█` cursor block, `↑↓` arrow glyph,
  `›` selection marker. Universal symbols / labels rather
  than translatable prose.

## Catalog totals

The catalog now has ~170 entries across 16 categories.
`tests/engine_vocabulary_audit` passes — no engine
vocabulary leaks anywhere user-reachable.

## Tally

610 tests passing (no change — pure refactor with
identical-output catalog substitutions). Clippy clean
with nursery lints. Release builds at 7.8 MB.

ADR-0019 §9 is now genuinely complete.
This commit is contained in:
claude@clouddev1
2026-05-09 22:41:06 +00:00
parent 720511ef29
commit a6fd26d15a
4 changed files with 138 additions and 36 deletions
+8 -6
View File
@@ -1130,15 +1130,17 @@ impl App {
/// the named project is already persistent (ADR-0015 §11).
fn handle_save_command(&mut self, force_save_as: bool) -> Vec<Action> {
if !force_save_as && !self.project_is_temp {
self.note_system(
"already auto-saved; use `save as` to copy to a different location",
);
self.note_system(crate::t!("save.already_saved"));
return Vec::new();
}
let title = if force_save_as { "Save as" } else { "Save" };
let title = if force_save_as {
crate::t!("save.title_as")
} else {
crate::t!("save.title_save")
};
self.modal = Some(Modal::PathEntry(PathEntryModal {
title: title.to_string(),
prompt: "Name (under data dir/projects) or absolute path:".to_string(),
title,
prompt: crate::t!("save.path_prompt"),
input: String::new(),
cursor: 0,
purpose: PathEntryPurpose::SaveAs,