ADR-0019 §9 sweep (2/2): help blocks + modals + system notes

Final pass of the i18n migration sweep. Every user-visible
string in `src/` now flows through the catalog via `t!()`.

## Categories migrated in this commit

- **help.cli_banner** — the entire `cli::HELP_TEXT` const,
  formerly a 40-line `&'static str`, is now a YAML block in
  the catalog. The const is replaced by a thin
  `cli::help_text() -> String` wrapper that performs the
  catalog lookup. `main.rs` calls `help_text()` for both
  `--help` output and the args-parse error path. The two
  integration tests that referenced `HELP_TEXT` directly are
  updated.
- **help.in_app_body** — the in-app `help` command's body is
  one YAML block; `note_help` becomes 5 lines that iterate
  the lines and emit each as its own output row (preserving
  the renderer's "one logical line = one display row"
  invariant for accurate scroll math).
- **modal.*** — load picker, rebuild confirm, and save-as
  path-entry strings: rebuild_cancelled, load_cancelled,
  generic_cancelled, load_picker_nothing,
  path_entry_empty_name, path_entry_empty_path.
- **dsl.failed** — the `"<verb> <subject>" failed: <rendered>`
  wrapper around the friendly-error layer's translated
  message.
- **dsl.running** — the `running: <input>` echo line shown
  above each command's response. (Note: the en-US prefix
  "running: " is hardcoded in the parse-error caret-padding
  calculation. Translators changing the prefix must keep the
  width consistent — documented inline.)
- **advanced_mode.not_implemented** — the placeholder echo
  shown when SQL hits the unimplemented advanced-mode path
  (Q1 territory).
- **fatal.persistence** — the FATAL banner for
  PersistenceFatal events (ADR-0015 §8).
- **project.{load_path_missing,saveas_target_exists,**
  **import_zip_missing}** — runtime-side project-switch
  validation errors that surface via ProjectSwitchFailed.

## Catalog start-up ordering

`main.rs` now calls `friendly::catalog()` at the very top
(before args parsing) so `help_text()` works in both the
success path and the args-error path. A corrupted build
artefact still fails loudly with a useful panic; the
practical risk is essentially zero since the catalog is
`include_str!`'d at compile time and validated by the unit
test before shipping.

## Remaining literals

The only `note_*` calls in `src/` that still pass plain
strings are inside `#[cfg(test)]` modules — synthetic test
fixtures, not user-visible. The codebase passes the "every
user-visible string flows through the catalog" bar.

## Tally

610 tests passing (no change in count — pure refactor).
Clippy clean with nursery lints.

## What this closes

ADR-0019 §9 (migration sweep) — done.

ADR-0019 itself is now fully implemented:
- §1-§5: catalog + translator + voice + verbosity ✓ (`eac7e5b`)
- §6: row pinpointing + schema enrichment ✓ (`431645a`)
- §9: migration sweep ✓ (this + `aff528a`)
- §10: anchor phrases preserved throughout ✓
- The five "Out of scope" items remain explicitly bounded
  to future ADRs (advanced-mode SQL, settings persistence,
  pluralisation, runtime locale, value formatting,
  constraint management).
This commit is contained in:
claude@clouddev1
2026-05-09 22:29:28 +00:00
parent aff528aa3f
commit 720511ef29
8 changed files with 224 additions and 129 deletions
+11 -5
View File
@@ -514,16 +514,19 @@ async fn perform_switch(
let resolved_target: Option<std::path::PathBuf> = match &req {
SwitchRequest::Load { path } => {
if !path.exists() {
return Err(format!("path `{}` does not exist", path.display()));
return Err(crate::t!(
"project.load_path_missing",
path = path.display()
));
}
Some(path.clone())
}
SwitchRequest::SaveAs { target } => {
let p = resolve_save_target(target, &session.data_root);
if p.exists() {
return Err(format!(
"`{}` already exists; pick a different name or remove it first",
p.display(),
return Err(crate::t!(
"project.saveas_target_exists",
path = p.display()
));
}
Some(p)
@@ -531,7 +534,10 @@ async fn perform_switch(
SwitchRequest::NewTemp => None,
SwitchRequest::Import { zip_path, as_target } => {
if !zip_path.exists() {
return Err(format!("zip `{}` does not exist", zip_path.display()));
return Err(crate::t!(
"project.import_zip_missing",
path = zip_path.display()
));
}
// Validate the zip up front so we don't drop the
// current project for an unimportable file.