ADR-0019 §9 sweep (1/2): replay/client_side/ok/mode/messages/project/parse
First half of the catalog migration sweep. Six categories of
user-visible literals moved from inline `format!` calls to the
i18n catalog via `t!()`:
- **replay.*** — `[ok] replay … N command(s) run`,
`replay … failed at line N: …`, the `> command` echo, and
the inner `could not open` / `parse error` / `nested replay`
wordings the runtime constructs inside `ReplayFailed.error`.
- **client_side.*** — the four [client-side] pedagogical notes
from ADR-0017 §6 / ADR-0018 §9 (transformed,
transformed_lossy, auto_fill_transition,
auto_fill_add_serial, auto_fill_add_shortid). The
`format_auto_fill_add_note` helper in db.rs now routes via
the catalog too.
- **ok.*** — the `[ok] {verb} {subject}` summary header
(consolidated through a new `App::note_ok_summary` helper)
plus the per-operation row-count footers
(`{count} row(s) inserted/updated/deleted`).
- **mode.*** — `mode: simple/advanced` set/show banners +
`usage: mode …` + `unknown mode '{value}' …` errors.
- **messages.*** — `messages: short/verbose` set/show + the
`unknown messages mode` error.
- **project.*** — `[ok] rebuild — {summary}`, `[ok] now
editing: {display_name}`, `[ok] export — wrote {path}`, plus
matching failure variants and the `usage: export/import`
+ `import: empty target after as` argument-parsing errors.
- **parse.*** — the `parse error: {detail}` wrapper around
chumsky's structural output, the `{padding}^` caret pointer,
and the `empty input` fallback for `ParseError::Empty`.
Catalog total: 99 lines of YAML across the new categories,
44 new entries declared in `keys.rs::KEYS_AND_PLACEHOLDERS`.
The validator (`keys_validate_against_catalog`) walks the
expanded list and confirms placeholder coverage / no format
specifiers / no engine vocabulary across every entry.
Anchor phrases (ADR-0019 §10) preserved verbatim; existing
substring assertions in the test suite hold.
## Tally
610 tests passing (no change in count — pure refactor).
Clippy clean with nursery lints. Release builds.
## Still ahead in the sweep
- Sweep 7: HELP_TEXT (CLI banner) + in-app `note_help` —
large multi-line blocks.
- Sweep 8: modal labels (load picker, rebuild confirm,
save-as path entry) + any remaining strays. Final pass.
Both shipping in a follow-up commit so this checkpoint
stays reviewable.
This commit is contained in:
+84
-73
@@ -359,12 +359,12 @@ impl App {
|
|||||||
}
|
}
|
||||||
AppEvent::RebuildSucceeded { summary } => {
|
AppEvent::RebuildSucceeded { summary } => {
|
||||||
self.modal = None;
|
self.modal = None;
|
||||||
self.note_system(format!("[ok] rebuild — {summary}"));
|
self.note_system(crate::t!("project.rebuild_ok", summary = summary));
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
AppEvent::RebuildFailed { error } => {
|
AppEvent::RebuildFailed { error } => {
|
||||||
self.modal = None;
|
self.modal = None;
|
||||||
self.note_error(format!("rebuild failed: {error}"));
|
self.note_error(crate::t!("project.rebuild_failed", error = error));
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
AppEvent::LoadPickerReady { entries } => {
|
AppEvent::LoadPickerReady { entries } => {
|
||||||
@@ -394,7 +394,10 @@ impl App {
|
|||||||
is_temp,
|
is_temp,
|
||||||
history_entries,
|
history_entries,
|
||||||
} => {
|
} => {
|
||||||
self.note_system(format!("[ok] now editing: {display_name}"));
|
self.note_system(crate::t!(
|
||||||
|
"project.switched_ok",
|
||||||
|
display_name = display_name
|
||||||
|
));
|
||||||
self.project_name = Some(display_name);
|
self.project_name = Some(display_name);
|
||||||
self.project_is_temp = is_temp;
|
self.project_is_temp = is_temp;
|
||||||
self.tables.clear();
|
self.tables.clear();
|
||||||
@@ -403,20 +406,25 @@ impl App {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
AppEvent::ProjectSwitchFailed { error } => {
|
AppEvent::ProjectSwitchFailed { error } => {
|
||||||
self.note_error(format!("project switch failed: {error}"));
|
self.note_error(crate::t!("project.switch_failed", error = error));
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
AppEvent::ExportSucceeded { path } => {
|
AppEvent::ExportSucceeded { path } => {
|
||||||
self.note_system(format!("[ok] export — wrote {}", path.display()));
|
self.note_system(crate::t!(
|
||||||
|
"project.export_ok",
|
||||||
|
path = path.display()
|
||||||
|
));
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
AppEvent::ExportFailed { error } => {
|
AppEvent::ExportFailed { error } => {
|
||||||
self.note_error(format!("export failed: {error}"));
|
self.note_error(crate::t!("project.export_failed", error = error));
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
AppEvent::ReplayCompleted { path, count } => {
|
AppEvent::ReplayCompleted { path, count } => {
|
||||||
self.note_system(format!(
|
self.note_system(crate::t!(
|
||||||
"[ok] replay {path} — {count} command(s) run"
|
"replay.completed",
|
||||||
|
path = path,
|
||||||
|
count = count
|
||||||
));
|
));
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
@@ -433,13 +441,23 @@ impl App {
|
|||||||
// it, mirroring how the interactive `running: …`
|
// it, mirroring how the interactive `running: …`
|
||||||
// path renders source-line context above an error.
|
// path renders source-line context above an error.
|
||||||
if line_number == 0 {
|
if line_number == 0 {
|
||||||
self.note_error(format!("replay {path} failed: {error}"));
|
self.note_error(crate::t!(
|
||||||
|
"replay.failed_open",
|
||||||
|
path = path,
|
||||||
|
error = error
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
self.note_error(format!(
|
self.note_error(crate::t!(
|
||||||
"replay {path} failed at line {line_number}: {error}"
|
"replay.failed_at_line",
|
||||||
|
path = path,
|
||||||
|
line_number = line_number,
|
||||||
|
error = error
|
||||||
));
|
));
|
||||||
if !command.is_empty() {
|
if !command.is_empty() {
|
||||||
self.note_error(format!(" > {command}"));
|
self.note_error(crate::t!(
|
||||||
|
"replay.command_echo",
|
||||||
|
command = command
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vec::new()
|
Vec::new()
|
||||||
@@ -705,7 +723,7 @@ impl App {
|
|||||||
other if other.starts_with("export ") => {
|
other if other.starts_with("export ") => {
|
||||||
let target = other["export ".len()..].trim();
|
let target = other["export ".len()..].trim();
|
||||||
if target.is_empty() {
|
if target.is_empty() {
|
||||||
self.note_error("usage: export [<path>]");
|
self.note_error(crate::t!("project.export_usage"));
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
return vec![Action::Export {
|
return vec![Action::Export {
|
||||||
@@ -800,17 +818,33 @@ impl App {
|
|||||||
let prefix = "running: ";
|
let prefix = "running: ";
|
||||||
let trimmed_offset = leading_trim_offset(input);
|
let trimmed_offset = leading_trim_offset(input);
|
||||||
let pad = prefix.chars().count() + trimmed_offset + position;
|
let pad = prefix.chars().count() + trimmed_offset + position;
|
||||||
self.note_error(format!("{}^", " ".repeat(pad)));
|
self.note_error(crate::t!(
|
||||||
|
"parse.caret",
|
||||||
|
padding = " ".repeat(pad)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
self.note_error(format!("parse error: {}", parse_error_message(&err)));
|
self.note_error(crate::t!(
|
||||||
|
"parse.error",
|
||||||
|
detail = parse_error_message(&err)
|
||||||
|
));
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emit the standard `[ok] <verb> <subject>` header used by
|
||||||
|
/// every successful DSL command. Routes through the i18n
|
||||||
|
/// catalog (ADR-0019 §9 sweep).
|
||||||
|
fn note_ok_summary(&mut self, command: &Command) {
|
||||||
|
self.note_system(crate::t!(
|
||||||
|
"ok.summary",
|
||||||
|
verb = command.verb(),
|
||||||
|
subject = command.display_subject()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_dsl_success(&mut self, command: &Command, description: Option<TableDescription>) {
|
fn handle_dsl_success(&mut self, command: &Command, description: Option<TableDescription>) {
|
||||||
let summary = format!("[ok] {} {}", command.verb(), command.display_subject());
|
self.note_ok_summary(command);
|
||||||
self.note_system(summary);
|
|
||||||
if let Some(desc) = description.as_ref() {
|
if let Some(desc) = description.as_ref() {
|
||||||
for line in crate::output_render::render_structure(desc) {
|
for line in crate::output_render::render_structure(desc) {
|
||||||
self.note_system(line);
|
self.note_system(line);
|
||||||
@@ -820,32 +854,23 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_dsl_query_success(&mut self, command: &Command, data: &DataResult) {
|
fn handle_dsl_query_success(&mut self, command: &Command, data: &DataResult) {
|
||||||
let summary = format!("[ok] {} {}", command.verb(), command.display_subject());
|
self.note_ok_summary(command);
|
||||||
self.note_system(summary);
|
|
||||||
for line in crate::output_render::render_data_table(data) {
|
for line in crate::output_render::render_data_table(data) {
|
||||||
self.note_system(line);
|
self.note_system(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_dsl_insert_success(&mut self, command: &Command, result: &InsertResult) {
|
fn handle_dsl_insert_success(&mut self, command: &Command, result: &InsertResult) {
|
||||||
self.note_system(format!(
|
self.note_ok_summary(command);
|
||||||
"[ok] {} {}",
|
self.note_system(crate::t!("ok.rows_inserted", count = result.rows_affected));
|
||||||
command.verb(),
|
|
||||||
command.display_subject()
|
|
||||||
));
|
|
||||||
self.note_system(format!(" {} row(s) inserted", result.rows_affected));
|
|
||||||
for line in crate::output_render::render_data_table(&result.data) {
|
for line in crate::output_render::render_data_table(&result.data) {
|
||||||
self.note_system(line);
|
self.note_system(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_dsl_update_success(&mut self, command: &Command, result: &UpdateResult) {
|
fn handle_dsl_update_success(&mut self, command: &Command, result: &UpdateResult) {
|
||||||
self.note_system(format!(
|
self.note_ok_summary(command);
|
||||||
"[ok] {} {}",
|
self.note_system(crate::t!("ok.rows_updated", count = result.rows_affected));
|
||||||
command.verb(),
|
|
||||||
command.display_subject()
|
|
||||||
));
|
|
||||||
self.note_system(format!(" {} row(s) updated", result.rows_affected));
|
|
||||||
for line in crate::output_render::render_data_table(&result.data) {
|
for line in crate::output_render::render_data_table(&result.data) {
|
||||||
self.note_system(line);
|
self.note_system(line);
|
||||||
}
|
}
|
||||||
@@ -856,8 +881,7 @@ impl App {
|
|||||||
command: &Command,
|
command: &Command,
|
||||||
result: AddColumnResult,
|
result: AddColumnResult,
|
||||||
) {
|
) {
|
||||||
let summary = format!("[ok] {} {}", command.verb(), command.display_subject());
|
self.note_ok_summary(command);
|
||||||
self.note_system(summary);
|
|
||||||
// ADR-0018 §9: emit auto-fill note(s) before the
|
// ADR-0018 §9: emit auto-fill note(s) before the
|
||||||
// structure render, so the pedagogical "the tool did
|
// structure render, so the pedagogical "the tool did
|
||||||
// this for you" line is in the user's eye-line next to
|
// this for you" line is in the user's eye-line next to
|
||||||
@@ -876,8 +900,7 @@ impl App {
|
|||||||
command: &Command,
|
command: &Command,
|
||||||
result: ChangeColumnTypeResult,
|
result: ChangeColumnTypeResult,
|
||||||
) {
|
) {
|
||||||
let summary = format!("[ok] {} {}", command.verb(), command.display_subject());
|
self.note_ok_summary(command);
|
||||||
self.note_system(summary);
|
|
||||||
if let Some(note) = result.client_side {
|
if let Some(note) = result.client_side {
|
||||||
// ADR-0017 §6 + ADR-0018 §9: pedagogical hook
|
// ADR-0017 §6 + ADR-0018 §9: pedagogical hook
|
||||||
// telling the learner "the tool did this for you;
|
// telling the learner "the tool did this for you;
|
||||||
@@ -888,19 +911,15 @@ impl App {
|
|||||||
// emitted in order (ADR-0018 §9 explicit rule).
|
// emitted in order (ADR-0018 §9 explicit rule).
|
||||||
if note.transformed > 0 {
|
if note.transformed > 0 {
|
||||||
let line = if note.lossy > 0 {
|
let line = if note.lossy > 0 {
|
||||||
format!(
|
crate::t!(
|
||||||
"[client-side] {n} row(s) transformed; {l} of those discarded \
|
"client_side.transformed_lossy",
|
||||||
information (lossy). In raw SQL this would need an explicit \
|
count = note.transformed,
|
||||||
`CAST` or application-level code.",
|
lossy = note.lossy
|
||||||
n = note.transformed,
|
|
||||||
l = note.lossy
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!(
|
crate::t!(
|
||||||
"[client-side] {n} row(s) were transformed before being stored. \
|
"client_side.transformed",
|
||||||
In raw SQL this would need an explicit `CAST` or \
|
count = note.transformed
|
||||||
application-level code.",
|
|
||||||
n = note.transformed
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
self.note_system(line);
|
self.note_system(line);
|
||||||
@@ -911,10 +930,10 @@ impl App {
|
|||||||
Some(crate::db::AutoFillKind::ShortId) => "shortid",
|
Some(crate::db::AutoFillKind::ShortId) => "shortid",
|
||||||
None => "auto-generated",
|
None => "auto-generated",
|
||||||
};
|
};
|
||||||
self.note_system(format!(
|
self.note_system(crate::t!(
|
||||||
"[client-side] {m} null cell(s) given auto-generated {kind} values. \
|
"client_side.auto_fill_transition",
|
||||||
In raw SQL this would need an explicit UPDATE to populate.",
|
count = note.auto_filled,
|
||||||
m = note.auto_filled,
|
kind = kind
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -925,12 +944,8 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_dsl_delete_success(&mut self, command: &Command, result: &DeleteResult) {
|
fn handle_dsl_delete_success(&mut self, command: &Command, result: &DeleteResult) {
|
||||||
self.note_system(format!(
|
self.note_ok_summary(command);
|
||||||
"[ok] {} {}",
|
self.note_system(crate::t!("ok.rows_deleted", count = result.rows_affected));
|
||||||
command.verb(),
|
|
||||||
command.display_subject()
|
|
||||||
));
|
|
||||||
self.note_system(format!(" {} row(s) deleted", result.rows_affected));
|
|
||||||
for effect in &result.cascade {
|
for effect in &result.cascade {
|
||||||
self.note_system(render_cascade_effect(effect));
|
self.note_system(render_cascade_effect(effect));
|
||||||
}
|
}
|
||||||
@@ -1065,7 +1080,7 @@ impl App {
|
|||||||
fn handle_import_command(&mut self, rest: &str) -> Vec<Action> {
|
fn handle_import_command(&mut self, rest: &str) -> Vec<Action> {
|
||||||
let rest = rest.trim();
|
let rest = rest.trim();
|
||||||
if rest.is_empty() {
|
if rest.is_empty() {
|
||||||
self.note_error("usage: import <zip-path> [as <target>]");
|
self.note_error(crate::t!("project.import_usage"));
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
// `submit()` trims trailing whitespace from the raw
|
// `submit()` trims trailing whitespace from the raw
|
||||||
@@ -1074,7 +1089,7 @@ impl App {
|
|||||||
// than silently treating "as" as part of the zip
|
// than silently treating "as" as part of the zip
|
||||||
// path.
|
// path.
|
||||||
if rest == "as" || rest.ends_with(" as") {
|
if rest == "as" || rest.ends_with(" as") {
|
||||||
self.note_error("import: empty target after `as`");
|
self.note_error(crate::t!("project.import_empty_target"));
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
let (zip_path, as_target) = match rest.split_once(" as ") {
|
let (zip_path, as_target) = match rest.split_once(" as ") {
|
||||||
@@ -1082,13 +1097,13 @@ impl App {
|
|||||||
None => (rest, None),
|
None => (rest, None),
|
||||||
};
|
};
|
||||||
if zip_path.is_empty() {
|
if zip_path.is_empty() {
|
||||||
self.note_error("usage: import <zip-path> [as <target>]");
|
self.note_error(crate::t!("project.import_usage"));
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
if let Some(t) = as_target.as_deref()
|
if let Some(t) = as_target.as_deref()
|
||||||
&& t.is_empty()
|
&& t.is_empty()
|
||||||
{
|
{
|
||||||
self.note_error("import: empty target after `as`");
|
self.note_error(crate::t!("project.import_empty_target"));
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
vec![Action::Import {
|
vec![Action::Import {
|
||||||
@@ -1419,19 +1434,17 @@ impl App {
|
|||||||
crate::friendly::Verbosity::Short => "short",
|
crate::friendly::Verbosity::Short => "short",
|
||||||
crate::friendly::Verbosity::Verbose => "verbose",
|
crate::friendly::Verbosity::Verbose => "verbose",
|
||||||
};
|
};
|
||||||
self.note_system(format!("messages: {current}"));
|
self.note_system(crate::t!("messages.show", current = current));
|
||||||
}
|
}
|
||||||
"short" => {
|
"short" => {
|
||||||
self.messages_verbosity = crate::friendly::Verbosity::Short;
|
self.messages_verbosity = crate::friendly::Verbosity::Short;
|
||||||
self.note_system("messages: short");
|
self.note_system(crate::t!("messages.set_short"));
|
||||||
}
|
}
|
||||||
"verbose" => {
|
"verbose" => {
|
||||||
self.messages_verbosity = crate::friendly::Verbosity::Verbose;
|
self.messages_verbosity = crate::friendly::Verbosity::Verbose;
|
||||||
self.note_system("messages: verbose");
|
self.note_system(crate::t!("messages.set_verbose"));
|
||||||
}
|
}
|
||||||
other => self.note_error(format!(
|
other => self.note_error(crate::t!("messages.unknown", value = other)),
|
||||||
"unknown messages mode '{other}' (expected 'short' or 'verbose')"
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1440,16 +1453,14 @@ impl App {
|
|||||||
match arg {
|
match arg {
|
||||||
"simple" => {
|
"simple" => {
|
||||||
self.mode = Mode::Simple;
|
self.mode = Mode::Simple;
|
||||||
self.note_system("mode: simple");
|
self.note_system(crate::t!("mode.set_simple"));
|
||||||
}
|
}
|
||||||
"advanced" => {
|
"advanced" => {
|
||||||
self.mode = Mode::Advanced;
|
self.mode = Mode::Advanced;
|
||||||
self.note_system("mode: advanced");
|
self.note_system(crate::t!("mode.set_advanced"));
|
||||||
}
|
}
|
||||||
"" => self.note_error("usage: mode simple | mode advanced"),
|
"" => self.note_error(crate::t!("mode.usage")),
|
||||||
other => self.note_error(format!(
|
other => self.note_error(crate::t!("mode.unknown", value = other)),
|
||||||
"unknown mode '{other}' (expected 'simple' or 'advanced')"
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1514,7 +1525,7 @@ impl App {
|
|||||||
fn parse_error_message(err: &ParseError) -> String {
|
fn parse_error_message(err: &ParseError) -> String {
|
||||||
match err {
|
match err {
|
||||||
ParseError::Invalid { message, .. } => message.clone(),
|
ParseError::Invalid { message, .. } => message.clone(),
|
||||||
ParseError::Empty => "empty input".to_string(),
|
ParseError::Empty => crate::t!("parse.empty"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1810,16 +1810,12 @@ fn generate_shortid_batch(
|
|||||||
/// on a non-empty table per ADR-0018 §9.
|
/// on a non-empty table per ADR-0018 §9.
|
||||||
fn format_auto_fill_add_note(ty: Type, row_count: usize) -> String {
|
fn format_auto_fill_add_note(ty: Type, row_count: usize) -> String {
|
||||||
match ty {
|
match ty {
|
||||||
Type::Serial => format!(
|
Type::Serial => {
|
||||||
"[client-side] {row_count} row(s) given auto-generated serial \
|
crate::t!("client_side.auto_fill_add_serial", count = row_count)
|
||||||
values 1..{row_count}. In raw SQL this would need an explicit \
|
}
|
||||||
UPDATE to populate."
|
Type::ShortId => {
|
||||||
),
|
crate::t!("client_side.auto_fill_add_shortid", count = row_count)
|
||||||
Type::ShortId => format!(
|
}
|
||||||
"[client-side] {row_count} row(s) given auto-generated shortid \
|
|
||||||
values. In raw SQL this would need an explicit UPDATE to \
|
|
||||||
populate."
|
|
||||||
),
|
|
||||||
_ => unreachable!("called only for serial/shortid"),
|
_ => unreachable!("called only for serial/shortid"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,50 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
|||||||
"error.type_mismatch.update.hint",
|
"error.type_mismatch.update.hint",
|
||||||
&["table", "column", "expected_type"],
|
&["table", "column", "expected_type"],
|
||||||
),
|
),
|
||||||
|
// ---- Parse error rendering ----
|
||||||
|
("parse.caret", &["padding"]),
|
||||||
|
("parse.empty", &[]),
|
||||||
|
("parse.error", &["detail"]),
|
||||||
|
// ---- Project lifecycle event notes ----
|
||||||
|
("project.export_failed", &["error"]),
|
||||||
|
("project.export_ok", &["path"]),
|
||||||
|
("project.export_usage", &[]),
|
||||||
|
("project.import_empty_target", &[]),
|
||||||
|
("project.import_usage", &[]),
|
||||||
|
("project.rebuild_failed", &["error"]),
|
||||||
|
("project.rebuild_ok", &["summary"]),
|
||||||
|
("project.switch_failed", &["error"]),
|
||||||
|
("project.switched_ok", &["display_name"]),
|
||||||
|
// ---- mode / messages banners ----
|
||||||
|
("messages.set_short", &[]),
|
||||||
|
("messages.set_verbose", &[]),
|
||||||
|
("messages.show", &["current"]),
|
||||||
|
("messages.unknown", &["value"]),
|
||||||
|
("mode.set_advanced", &[]),
|
||||||
|
("mode.set_simple", &[]),
|
||||||
|
("mode.show_advanced", &[]),
|
||||||
|
("mode.show_simple", &[]),
|
||||||
|
("mode.unknown", &["value"]),
|
||||||
|
("mode.usage", &[]),
|
||||||
|
// ---- DSL command success summaries (ADR-0019 §9 sweep) ----
|
||||||
|
("ok.rows_deleted", &["count"]),
|
||||||
|
("ok.rows_inserted", &["count"]),
|
||||||
|
("ok.rows_updated", &["count"]),
|
||||||
|
("ok.summary", &["verb", "subject"]),
|
||||||
|
// ---- Client-side success notes (ADR-0017 §6, ADR-0018 §9) ----
|
||||||
|
("client_side.auto_fill_add_serial", &["count"]),
|
||||||
|
("client_side.auto_fill_add_shortid", &["count"]),
|
||||||
|
("client_side.auto_fill_transition", &["count", "kind"]),
|
||||||
|
("client_side.transformed", &["count"]),
|
||||||
|
("client_side.transformed_lossy", &["count", "lossy"]),
|
||||||
|
// ---- Replay command surfaces (ADR-0019 §9 sweep) ----
|
||||||
|
("replay.command_echo", &["command"]),
|
||||||
|
("replay.completed", &["path", "count"]),
|
||||||
|
("replay.error_could_not_open", &["path", "detail"]),
|
||||||
|
("replay.error_nested", &[]),
|
||||||
|
("replay.error_parse", &["detail"]),
|
||||||
|
("replay.failed_at_line", &["path", "line_number", "error"]),
|
||||||
|
("replay.failed_open", &["path", "error"]),
|
||||||
// ---- UNIQUE violations (anchor: "already has the value") ----
|
// ---- UNIQUE violations (anchor: "already has the value") ----
|
||||||
(
|
(
|
||||||
"error.unique.insert.headline",
|
"error.unique.insert.headline",
|
||||||
|
|||||||
@@ -151,3 +151,102 @@ error:
|
|||||||
headline: "INSERT requires at least one column value."
|
headline: "INSERT requires at least one column value."
|
||||||
empty_update:
|
empty_update:
|
||||||
headline: "UPDATE requires at least one assignment."
|
headline: "UPDATE requires at least one assignment."
|
||||||
|
|
||||||
|
# ---- DSL parse error rendering --------------------------------------
|
||||||
|
parse:
|
||||||
|
# Wrapper around chumsky's structural error message. The
|
||||||
|
# caret pointer (visualising the failure column) is printed
|
||||||
|
# on its own preceding line via `parse.caret`.
|
||||||
|
error: "parse error: {detail}"
|
||||||
|
# Caret pointer showing where in the input the parser
|
||||||
|
# failed. `{padding}` is the leading whitespace; the
|
||||||
|
# template appends `^` so the rendered line places the
|
||||||
|
# marker under the offending character.
|
||||||
|
caret: "{padding}^"
|
||||||
|
# Default for the `ParseError::Empty` variant — surfaces as
|
||||||
|
# `{detail}` inside the wrapper.
|
||||||
|
empty: "empty input"
|
||||||
|
|
||||||
|
# ---- Project lifecycle event notes -----------------------------------
|
||||||
|
project:
|
||||||
|
rebuild_ok: "[ok] rebuild — {summary}"
|
||||||
|
rebuild_failed: "rebuild failed: {error}"
|
||||||
|
switched_ok: "[ok] now editing: {display_name}"
|
||||||
|
switch_failed: "project switch failed: {error}"
|
||||||
|
export_ok: "[ok] export — wrote {path}"
|
||||||
|
export_failed: "export failed: {error}"
|
||||||
|
# Usage / argument-parsing errors for app-level commands.
|
||||||
|
export_usage: "usage: export [<path>]"
|
||||||
|
import_usage: "usage: import <zip-path> [as <target>]"
|
||||||
|
import_empty_target: "import: empty target after `as`"
|
||||||
|
|
||||||
|
# ---- mode / messages banners (app-level commands) -------------------
|
||||||
|
mode:
|
||||||
|
set_simple: "mode: simple"
|
||||||
|
set_advanced: "mode: advanced"
|
||||||
|
show_simple: "mode: simple"
|
||||||
|
show_advanced: "mode: advanced"
|
||||||
|
usage: "usage: mode simple | mode advanced"
|
||||||
|
unknown: "unknown mode '{value}' (expected 'simple' or 'advanced')"
|
||||||
|
|
||||||
|
messages:
|
||||||
|
show: "messages: {current}"
|
||||||
|
set_short: "messages: short"
|
||||||
|
set_verbose: "messages: verbose"
|
||||||
|
unknown: "unknown messages mode '{value}' (expected 'short' or 'verbose')"
|
||||||
|
|
||||||
|
# ---- DSL command success summaries (ADR-0019 §9 sweep) --------------
|
||||||
|
ok:
|
||||||
|
# Generic `[ok] <verb> <subject>` header used for every
|
||||||
|
# successful DSL command. Verbs come from `Command::verb()`
|
||||||
|
# (already English keywords); subjects are the table /
|
||||||
|
# relationship endpoints derived in `Command::display_subject()`.
|
||||||
|
summary: "[ok] {verb} {subject}"
|
||||||
|
# Per-operation row-count footers shown beneath the summary.
|
||||||
|
rows_inserted: " {count} row(s) inserted"
|
||||||
|
rows_updated: " {count} row(s) updated"
|
||||||
|
rows_deleted: " {count} row(s) deleted"
|
||||||
|
|
||||||
|
# ---- Client-side success notes (ADR-0017 §6, ADR-0018 §9) ------------
|
||||||
|
client_side:
|
||||||
|
# Per-cell transformation notice when `change column ...` rewrote
|
||||||
|
# at least one stored value (storage-class change or non-identity
|
||||||
|
# mapping). `lossy` variant fires under --force-conversion when
|
||||||
|
# information was discarded.
|
||||||
|
transformed: |-
|
||||||
|
[client-side] {count} row(s) were transformed before being stored. In raw SQL this would need an explicit `CAST` or application-level code.
|
||||||
|
transformed_lossy: |-
|
||||||
|
[client-side] {count} row(s) transformed; {lossy} of those discarded information (lossy). In raw SQL this would need an explicit `CAST` or application-level code.
|
||||||
|
# Auto-fill notice when null cells were populated by the
|
||||||
|
# serial/shortid auto-generation contract (change column path).
|
||||||
|
auto_fill_transition: |-
|
||||||
|
[client-side] {count} null cell(s) given auto-generated {kind} values. In raw SQL this would need an explicit UPDATE to populate.
|
||||||
|
# Auto-fill notice for `add column T: x (serial)` on a
|
||||||
|
# non-empty table — values run 1..N.
|
||||||
|
auto_fill_add_serial: |-
|
||||||
|
[client-side] {count} row(s) given auto-generated serial values 1..{count}. In raw SQL this would need an explicit UPDATE to populate.
|
||||||
|
# Auto-fill notice for `add column T: x (shortid)` on a
|
||||||
|
# non-empty table.
|
||||||
|
auto_fill_add_shortid: |-
|
||||||
|
[client-side] {count} row(s) given auto-generated shortid values. In raw SQL this would need an explicit UPDATE to populate.
|
||||||
|
|
||||||
|
# ---- Replay command surfaces (ADR-0019 §9 sweep) ---------------------
|
||||||
|
replay:
|
||||||
|
# Success summary printed when `replay <path>` completes
|
||||||
|
# without per-line failure.
|
||||||
|
completed: "[ok] replay {path} — {count} command(s) run"
|
||||||
|
# File-open failure (line_number == 0 from the runtime).
|
||||||
|
failed_open: "replay {path} failed: {error}"
|
||||||
|
# Per-line failure header. The command echo is on a
|
||||||
|
# follow-up `command_echo` line so the renderer can format
|
||||||
|
# them as separate output rows.
|
||||||
|
failed_at_line: "replay {path} failed at line {line_number}: {error}"
|
||||||
|
# Indented echo of the offending command line, shown after
|
||||||
|
# `failed_at_line` when the runtime supplied the source.
|
||||||
|
command_echo: " > {command}"
|
||||||
|
# Errors the runtime constructs inside ReplayFailed.error
|
||||||
|
# before forwarding to App. Carried as plain text so they
|
||||||
|
# compose with `failed_at_line`'s `{error}` placeholder.
|
||||||
|
error_could_not_open: "could not open `{path}`: {detail}"
|
||||||
|
error_parse: "parse error: {detail}"
|
||||||
|
error_nested: "nested `replay` is not allowed inside a replay file"
|
||||||
|
|||||||
+7
-3
@@ -1355,7 +1355,11 @@ pub async fn run_replay(
|
|||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
line_number: 0,
|
line_number: 0,
|
||||||
command: String::new(),
|
command: String::new(),
|
||||||
error: format!("could not open `{}`: {e}", resolved.display()),
|
error: crate::t!(
|
||||||
|
"replay.error_could_not_open",
|
||||||
|
path = resolved.display(),
|
||||||
|
detail = e
|
||||||
|
),
|
||||||
});
|
});
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
@@ -1378,7 +1382,7 @@ pub async fn run_replay(
|
|||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
line_number,
|
line_number,
|
||||||
command: trimmed.to_string(),
|
command: trimmed.to_string(),
|
||||||
error: format!("parse error: {e}"),
|
error: crate::t!("replay.error_parse", detail = e),
|
||||||
});
|
});
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
@@ -1394,7 +1398,7 @@ pub async fn run_replay(
|
|||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
line_number,
|
line_number,
|
||||||
command: trimmed.to_string(),
|
command: trimmed.to_string(),
|
||||||
error: "nested `replay` is not allowed inside a replay file".to_string(),
|
error: crate::t!("replay.error_nested"),
|
||||||
});
|
});
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user