# en-US catalog (ADR-0019). # # Hierarchical groups flatten to dot-paths internally: # error.unique.insert.headline # error.unique.insert.hint # help.cli_banner # replay.completed # … etc. # # Each error entry has a `headline` (one line, used in both # short and verbose modes) and may have a `hint` (one or more # lines, surfaced only in verbose mode). Short mode = headline # only; verbose mode = headline + hint + (if present) the # diagnostic table the translator built (ADR-0019 §7). # # Anchor phrases per ADR-0019 §10 are kept stable across # wording changes: # "no such table" # "no such column" # "no such relationship" # "already exists" # "already has the value" # "cannot be converted" # "discard information" # "referenced by" # "[client-side]" # # Placeholders use `{name}` substitution; format specifiers # (`{name:08.2}`, `{name:>10}`, …) are explicitly rejected by # the substitution helper (ADR-0019 §8.4). # Sanity entry exercised by the loader's unit tests; not # user-facing. _test: hello: "Hello, {name}!" # ---- Error category -------------------------------------------------- error: # UNIQUE constraint violations. Anchor: "already has the value". unique: insert: headline: "`{table}.{column}` already has the value `{value}`." hint: "The `{column}` column on `{table}` is unique — pick a different value, or update the existing row instead." update: headline: "`{table}.{column}` already has the value `{value}`." hint: "The `{column}` column on `{table}` is unique — your update would create a duplicate." # Primary-key collisions get distinct wording — the user # learns that PK is the canonical unique constraint. pk: insert: headline: "`{table}` already has a row with primary key `{value}`." hint: "Primary keys must be unique — pick a different value or update the existing row." update: headline: "`{table}` already has a row with primary key `{value}`." hint: "Primary keys must be unique — your update would create a duplicate." # FOREIGN KEY violations. Anchor: "referenced by". foreign_key: # Child-side: insert/update sets a value that has no parent. child_side: insert: headline: "no parent row in `{parent_table}` has `{parent_column}` = `{value}`." hint: "Foreign keys must point at an existing parent row. Insert a matching parent first, or pick a value that already exists in `{parent_table}.{parent_column}`." update: headline: "no parent row in `{parent_table}` has `{parent_column}` = `{value}`." hint: "Foreign keys must point at an existing parent row. Pick a value that already exists in `{parent_table}.{parent_column}`." # Parent-side: delete/update on a row referenced by children. # The engine refuses unless the relationship's `on delete / # on update` says cascade or set null. parent_side: delete: headline: "`{table}` rows are referenced by `{child_table}`." hint: "Deleting these rows would orphan the children. Delete the children first, or change the relationship's `on delete` action to `cascade` or `set null`." update: headline: "`{table}` rows are referenced by `{child_table}`." hint: "Updating the referenced column would orphan the children. Update the children first, or change the relationship's `on update` action to `cascade`." # NOT NULL constraint violations. not_null: insert: headline: "`{table}.{column}` cannot be null." hint: "The `{column}` column is required — provide a value for it in the row you are inserting." update: headline: "`{table}.{column}` cannot be null." hint: "The `{column}` column is required — pick a non-null value, or do not include `{column}` in your `set` list." # CHECK constraint violations. Placeholder coverage — # the playground does not emit CHECK constraints today # (track C3), but the catalog is wired so the wording # is ready when constraint-management lands. check: insert: headline: "check constraint refused `{table}.{column}`." hint: "A check constraint requires `{column}` to satisfy a rule the inserted value did not." update: headline: "check constraint refused `{table}.{column}`." hint: "A check constraint requires `{column}` to satisfy a rule the new value did not." # Type mismatch — engine-side STRICT refusal of a wrong-shape # value. Mostly the `change column ... --dont-convert` path # today (subsumes `friendly_change_column_engine_error`). type_mismatch: change_column: headline: "cannot change `{table}.{column}` from `{src_type}` to `{target_type}` with `--dont-convert`." hint: "The database refused at least one cell as the wrong shape for `{target_type}`. Re-run without `--dont-convert` to see which rows." insert: headline: "value `{value}` is not a `{expected_type}`." hint: "The `{column}` column on `{table}` is `{expected_type}` — provide a `{expected_type}` value, or change the column's type with `change column`." update: headline: "value `{value}` is not a `{expected_type}`." hint: "The `{column}` column on `{table}` is `{expected_type}` — provide a `{expected_type}` value, or change the column's type with `change column`." # Object-not-found errors. Anchor: "no such ...". These are # genuinely single-line errors — no hint adds value. not_found: table: headline: "no such table: `{name}`" column: headline: "no such column: `{table}.{column}`" column_unqualified: headline: "no such column: `{column}`" relationship: headline: "no such relationship: `{name}`" # Name-collision errors. Anchor: "already exists". already_exists: table: headline: "table `{name}` already exists" column: headline: "column `{table}.{column}` already exists" relationship: headline: "relationship `{name}` already exists" # Generic catch-all when the translator can't classify the # engine error into a known category. The wording stays # engine-neutral; the message text from the engine is NOT # surfaced (ADR-0002 user-facing posture). generic: headline: "the database refused this `{operation}`." hint: "The operation could not be completed against the current state of `{table}`." # Errors that are specifically about value validation # (DbError::InvalidValue) — wrong arity, wrong literal # form, etc. Pre-engine; the catalog covers what the # parser / validator already decided was wrong. invalid_value: arity: headline: "expected {expected} value(s), got {actual}." empty_insert: headline: "INSERT requires at least one column value." empty_update: headline: "UPDATE requires at least one assignment." # ---- Help text (CLI banner + in-app `help` command) ------------------ help: # CLI usage banner. Printed by `--help` / `-h` and on # argument-parse failure. Multi-line block; consumers # iterate lines or print as-is. cli_banner: | rdbms-playground — a TUI playground for relational database concepts Usage: rdbms-playground [options] [] Arguments: Path to an existing project directory. Without this, a fresh auto-named temp project is created in the data dir. Options: -h, --help Print this help and exit. --theme Override theme (default: auto-detect). --data-dir Use PATH as the data root instead of the OS-standard location for this run. --log-file Write tracing output to PATH. --resume Open the most-recently-used project (path tracked under /last_project). Errors out if no previous project is recorded. Mutually exclusive with . App-level commands (typed inside the app, available in both modes): quit / q Exit cleanly. mode simple|advanced Switch input mode. help Show this list of commands in-app. save Save the current temp project under a chosen name (or `save as` to copy a named project to a new location). save as Always prompt for a target name/path. new Close current, create a fresh temp. load Open the project picker. rebuild Rebuild playground.db from project.yaml + data/, with confirmation. export [] Write a zip of project.yaml + data/ to (relative paths under the data root). Excludes playground.db and history.log. import [as ] Unpack into a new project and switch to it. overrides the target name (else taken from the zip). # In-app `help` command output. Same shape as # `cli_banner` — multi-line block, consumers iterate # lines and emit each as its own output row so scroll # math stays accurate. in_app_body: | Supported commands: quit / q — exit help — this list mode simple|advanced — switch input mode messages — show current verbosity messages short|verbose— switch error wording (verbose is the default) rebuild — rebuild .db from project.yaml + data/ (with confirmation) save — save current temp project under a name save as — copy current project to a new name/path new — close current, start a fresh temp project load — open the project picker export [] — write a zip of project.yaml + data/ (excludes .db, history.log) import [as ] — unpack a zip and switch to the new project DSL data commands (in simple mode): create table with pk [:...] drop table add column [to] [table] : () (for serial/shortid on a non-empty table: existing rows auto-filled) drop column [from] [table] : rename column [in] [table] : to change column [in] [table] : () [--force-conversion | --dont-convert] (to serial/shortid: null cells auto-filled with generated values) add 1:n relationship [as ] from

. to . [on delete ] [on update ] [--create-fk] drop relationship insert into [(cols)] [values] (vals) update set =... where = | --all-rows delete from where = | --all-rows show table show data replay — run each non-blank, non-`#`-comment line of as a command. Stops at the first error (no rollback). Relative paths resolve under the current project's directory. Types: text, int, real, decimal, bool, date, datetime, blob, serial, shortid Auto-generated types (serial, shortid): serial — integer that auto-fills with the next sequence value (MAX(col)+1) on insert. Outside a primary key it carries a UNIQUE contract. shortid — short base58 identifier auto-filled at insert time. Always carries a UNIQUE contract. Adding or changing-to either type on a non-empty table auto-fills existing/null cells in the same operation. # ---- DSL parse error rendering -------------------------------------- # ---- Hint panel ambient typing assistance (ADR-0022 §6) ------------- hint: # The hint panel goes ambient as soon as the user types # anything — empty input keeps the existing # `panel.hint_empty` content. ambient_complete: "submit with Enter" ambient_expected: "expected: {expected}" ambient_error_with_usage: "{message} — usage: {usage}" 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" # No-prefix fallback (ADR-0021 §5): when the parse fails # before any keyword is consumed, the renderer lists every # command-entry keyword instead of attempting a per-command # usage block. `{commands}` is an oxford-joined list of # command-keyword renderings (each from # `parse.token.keyword.*`). available_commands: "available commands: {commands}" # Per-command usage templates (ADR-0021 §1). Rendered under a # "usage:" prefix when a parse fails after consuming a # known command-entry keyword. The bracket convention `[...]` # marks optional parts; angle-bracket `<...>` marks # placeholders. ADR-0009's surface conventions apply. usage: create_table: "create table with pk [:[, ...]]" drop_table: "drop table " drop_column: "drop column [from] [table] : " drop_relationship: |- drop relationship drop relationship from .to .add_column: "add column [to] [table]
: ()" add_relationship: |- add 1:n relationship [as ] from .to .[on delete ] [on update ] [--create-fk] rename_column: "rename column [in] [table]
: to " change_column: |- change column [in] [table]
: () [--force-conversion | --dont-convert] show_data: "show data
" show_table: "show table
" insert: "insert into
[([, ...])] [values] ([, ...])" update: "update
set =[, ...] (where = | --all-rows)" delete: "delete from
(where = | --all-rows)" replay: "replay | replay ''" # Single-token vocabulary the renderer uses to translate # chumsky's expected-set patterns. One key per Keyword variant # (validated against `Keyword::ALL`), one per Punct variant, # one per token-class label, one per LexError kind. token: keyword: create: "`create`" drop: "`drop`" add: "`add`" rename: "`rename`" change: "`change`" show: "`show`" insert: "`insert`" update: "`update`" delete: "`delete`" replay: "`replay`" table: "`table`" column: "`column`" data: "`data`" relationship: "`relationship`" pk: "`pk`" with: "`with`" from: "`from`" to: "`to`" into: "`into`" as: "`as`" in: "`in`" on: "`on`" set: "`set`" where: "`where`" values: "`values`" "null": "`null`" "true": "`true`" "false": "`false`" cascade: "`cascade`" restrict: "`restrict`" action: "`action`" "no": "`no`" punct: colon: "`:`" open_paren: "`(`" close_paren: "`)`" comma: "`,`" equals: "`=`" dot: "`.`" identifier: "identifier" number: "number" string_literal: "string literal" flag: "flag (--name)" end_of_input: "end of input" error: unterminated_string: "unterminated string literal" unknown_char: "unrecognised character `{found}`" bad_flag: "malformed flag (bare `--`)" # ---- 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 []" import_usage: "usage: import [as ]" import_empty_target: "import: empty target after `as`" # Project-switch validation failures (load / save-as / # import). Returned from the runtime as Err(String) and # surfaced via project.switch_failed. load_path_missing: "path `{path}` does not exist" saveas_target_exists: "`{path}` already exists; pick a different name or remove it first" import_zip_missing: "zip `{path}` does not exist" # ---- DSL failure wrapper + advanced-mode placeholder + fatal -------- dsl: # Wrapper around the friendly-error layer's translated # message, surfaced as `" " failed: `. failed: '"{verb} {subject}" failed: {rendered}' # Echo line `running: ` shown above each command's # response so the user has on-screen context for the # output that follows. running: "running: {input}" # ---- Advanced-mode placeholder until SQL parser lands (Q1) ---------- advanced_mode: not_implemented: "advanced mode SQL not implemented yet — echo: {input}" # ---- Persistence-fatal banner (ADR-0015 §8) ------------------------- fatal: persistence: "FATAL: failed to {operation} `{path}` — {message}. Quitting; investigate and restart." # ---- Modal labels (load picker, rebuild confirm, save-as path) ------ modal: rebuild_cancelled: "rebuild cancelled" load_cancelled: "load cancelled" generic_cancelled: "{title} cancelled" path_entry_empty_name: "path entry: empty name" path_entry_empty_path: "path entry: empty path" load_picker_nothing: "nothing to load" # Modal titles + body prose rendered by ui.rs. load_picker_title: "Load project" load_picker_empty: "(no projects in data directory)" load_picker_path_prompt: "Path to project directory:" rebuild_confirm_title: "Rebuild project" rebuild_confirm_prompt: "Continue?" # ---- Save / save-as command surfaces --------------------------------- save: # `save` on a named project is a no-op with a friendly hint. already_saved: "already auto-saved; use `save as` to copy to a different location" # Modal titles for `save` (on a temp) vs `save as`. title_as: "Save as" title_save: "Save" # Prompt body for the path-entry modal opened by save / save as. path_prompt: "Name (under data dir/projects) or absolute path:" # ---- Status bar (project label) and panels --------------------------- status: no_project: "(no project)" project_label: "Project: " panel: tables_title: "Tables" tables_empty: "(none yet)" hint_empty: "(no active hint)" # ---- Shortcut hints (paired with key names in the bottom bar) ------- shortcut: submit: "submit" confirm: "confirm" cancel: "cancel" yes: "yes" no: "no" load: "load" select: "select" browse_path: "browse path" back_to_list: "back to list" switch: "switch" advanced_once: "advanced once" cancel_one_shot: "cancel one-shot" quit: "quit" # ---- 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] ` 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 ` 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"