# 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 (ADR-0029 §10). When the # runtime resolves the column's compiled CHECK expression, # `hint_with_rule` names both the offending value and the # rule; the plain `hint` is the fallback when enrichment # could not resolve the rule. check: insert: headline: "check constraint refused `{table}.{column}`." hint: "A check constraint requires `{column}` to satisfy a rule the inserted value did not." hint_with_rule: "The value {value} breaks the rule `{rule}` — `{column}` must satisfy it." update: headline: "check constraint refused `{table}.{column}`." hint: "A check constraint requires `{column}` to satisfy a rule the new value did not." hint_with_rule: "The new value {value} breaks the rule `{rule}` — `{column}` must satisfy it." # 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}`." # Used when no table is in context (e.g. contextless `friendly_message()` # callsites: replay, undo, rebuild, export) so the hint never leaks a # literal `{table}` placeholder. hint_no_table: "The operation could not be completed against the current database state." # 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) ------------------ # ---- CLI argument-parsing errors (stderr before TUI starts) --------- cli: missing_value: "missing value for --{flag}" invalid_value: "invalid value for --{flag}: {value} (expected one of: {expected})" unknown_argument: "unknown argument: {arg}" multiple_paths: "only one project path may be supplied; got both `{first}` and `{second}`" resume_with_path: |- --resume and a positional are mutually exclusive; pass one or the other 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 . --no-undo Disable the undo machinery for this run: no snapshot is taken before each change (no per-command overhead), and undo/redo report that undo is turned off. App-level commands (typed inside the app, available in both modes): quit 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 (ADR-0024 §help_id). The # renderer iterates the command REGISTRY and translates each # CommandNode's `help_id` — so a newly-registered command # appears in `help` automatically, with no edit here. `intro` # and `dsl_section` frame the two command groups; each # `app.*` / `ddl.*` / `data.*` entry is keyed by a command's # `help_id`; `types_reference` closes the block. All entries # are multi-line-capable — the renderer emits one output row # per line so scroll math stays accurate. intro: "Supported commands:" dsl_section: "DSL data commands (in simple mode):" # Per-command help, keyed by `CommandNode.help_id`. Block # scalars (`|-`) so the column alignment survives — the # double-quoted form trips a libyml scanner bug on long # internal space runs. The renderer emits one output row per # line, so multi-form commands list each form on its own line. app: quit: |- quit — exit the app help: |- help — show this command list rebuild: |- rebuild — rebuild the project database from project.yaml + data/ (with confirmation) save: |- save — save the current temp project under a name save as — copy the current project to a new name/path new: |- new — close the current project, start a fresh temp project load: |- load — open the project picker export: |- export [] — write a zip of project.yaml + data/ (excludes the database file and history.log) import: |- import [as ] — unpack a zip into a new project and switch to it mode: |- mode simple|advanced — switch input mode messages: |- messages [short|verbose] — show or switch error-message verbosity (verbose is the default) undo: |- undo — undo the last change (with confirmation) redo: |- redo — redo the last undone change (with confirmation) ddl: create: |- create table with pk [(), ...] — create a table sql_create_table: |- create table [if not exists] ( [not null] [unique] [primary key] [default ] [check ()] [references

[()]], ... [, primary key (, ...)] [, unique (, ...)] [, check ()] [, [constraint ] foreign key () references

[()]]) — create a table (advanced SQL) sql_drop_table: |- drop table [if exists] — remove a table (advanced SQL) sql_create_index: |- create [unique] index [if not exists] [] on (, ...) — create an index (advanced SQL) sql_drop_index: |- drop index [if exists] — remove an index (advanced SQL) sql_alter_table: |- alter table add column [not null] [unique] [default …] [check …] alter table drop column alter table rename column to alter table rename to alter table alter column type alter table add [constraint ] check () | unique (, …) | foreign key () references

[()] alter table drop constraint — evolve a table's columns and constraints (advanced SQL) drop: |- drop table — remove a table drop column [from] [table] : [--cascade] — remove a column (--cascade also drops any index that covers the column) drop relationship — remove a relationship drop index — remove an index drop index on (, ...) — remove an index by its columns add: |- add column [to] [table] : () — add a column (for serial/shortid on a non-empty table: existing rows auto-filled) add 1:n relationship [as ] from

. to . [on delete ] [on update ] [--create-fk] — declare a relationship add index [as ] on (, ...) — create an index rename: |- rename column [in] [table] : to — rename a column change: |- change column [in] [table] : () [--force-conversion | --dont-convert] — change a column's type (to serial/shortid: null cells auto-filled with generated values) data: show: |- show table — show a table's structure show data — show a table's rows insert: |- insert into [(cols)] [values] (vals) — add a row update: |- update set =, ... where = | --all-rows — change matching rows delete: |- delete from where = | --all-rows — remove matching rows replay: |- replay — run each non-blank, non-`#`-comment line of as a command. Stops at the first error (no rollback); relative paths resolve under the project directory. explain: |- explain show data | explain update ... | explain delete from ... — show how the database would run a query, without running it (safe even for update / delete) explain (advanced mode) — the same plan for the SQL you wrote # Type reference, appended after the command list. types_reference: | 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. # Hint sentences are full standalone phrases, capitalised # at the start. Inline `{message}` substitutions inherit # whatever case the source produced (parser errors, # engine messages) — they're embedded mid-sentence so they # stay lowercase by convention. ambient_complete: "Submit with Enter" ambient_expected: "Next: {expected}" ambient_error_with_usage: "{message} — usage: {usage}" # Invalid identifier in a schema slot (ADR-0022 stage 8e # + the user's #5). Voice mirrors ADR-0019's "no such # {kind}" wording for consistency with engine errors. ambient_invalid_ident: "No such {kind}: `{found}`" # User-invented-name slot (NewName per IdentSlot). The # probe-derived `{next}` is what comes after the name — # e.g. `(` after a new column name. Empty/unknown `next` # falls through to `ambient_typing_name` instead. ambient_typing_name: "Type a name" ambient_typing_name_then: "Type a name, then {next}" # Issue #4 — advanced-mode CREATE TABLE element slot. Surfaced # at `create table T (` so the column-name role is visible # alongside the table-level constraint keywords. create_table_element: "Type a column name, or a table-level constraint: `primary`, `unique`, `check`, `constraint`, `foreign`" # Value-literal slot — `insert ... values (`, `update ... set # col=`, `where col=`. Replaces the misleading "null true # false" keyword candidate list with format guidance for all # accepted literal forms. Used when the walker can't resolve a # column type (schemaless parse, missing table, unknown column). value_literal_slot: "Type a value: number, 'text', true/false, null (dates as 'YYYY-MM-DD', datetimes as 'YYYY-MM-DDTHH:MM:SS')" # Per-column-type value-slot hints (ADR-0024 §Phase D §typed-value-slots). # Fired when the walker resolved the column's user-facing type # at the current value slot; narrows the prose to the relevant # literal forms for that type. Falls back to # `value_literal_slot` when the type can't be resolved. value_slot_int: "Type an integer (e.g. 42, -7) or null" value_slot_real: "Type a number (e.g. 3.14, -0.5) or null" value_slot_decimal: "Type a number (e.g. 19.95, -2.50) or null" value_slot_bool: "Type true, false, or null" value_slot_text: "Type a quoted string (e.g. 'Alice') or null" value_slot_date: "Type a quoted date as 'YYYY-MM-DD' or null" value_slot_datetime: "Type a quoted datetime as 'YYYY-MM-DD HH:MM:SS' or null" value_slot_blob: "Type a quoted blob literal or null" # Serial / shortid in `values (…)` form: the user must enter # something at this position (no "skip column" syntax). `null` # is the auto-fill path (ADR-0018: serial / shortid columns # auto-fill null cells on insert), so the prose leads with # null and offers an explicit value as the alternative. value_slot_serial: "Type null to auto-generate, or an explicit integer" value_slot_shortid: "Type null to auto-generate, or a quoted shortid" # Wrapper that prefixes a per-column-type slot hint with the # actual column name so the user sees "for `Email`: Type a # quoted string …" instead of the generic type prose. value_slot_for_column: "for `{column}`: {detail}" # Pedagogical note appended at the first value slot of a # Form B `insert into T values (…)` when T has auto-generated # columns the value list skips — points the user at the # explicit-column form so the skipped column is discoverable. value_slot_autogen_skipped: "({columns} auto-generated — skipped here; list columns explicitly, e.g. `insert into T (...) values (...)`, to set it.)" # Advanced-mode SQL DDL execution notes (ADR-0035). ddl: # `create table if not exists ` where the table is already # present: a no-op that succeeds with this note instead of an # "already exists" error. create_skipped_exists: "table '{name}' already exists — skipped (no changes made)" # `drop table if exists ` where the table is absent: a no-op that # succeeds with this note instead of a "doesn't exist" error. drop_skipped_absent: "table '{name}' doesn't exist — skipped (no changes made)" # `create [unique] index if not exists …` where the index name # already exists: a no-op that succeeds with this note (ADR-0035 §4d). create_index_skipped_exists: "index '{name}' already exists — skipped (no changes made)" # `drop index if exists ` where the index is absent: a no-op that # succeeds with this note instead of a "doesn't exist" error. drop_index_skipped_absent: "index '{name}' doesn't exist — skipped (no changes made)" 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}" # Wrapper used by `ParseError::Display` (so any to_string() # call on a parse error renders consistently with the in-app # error rendering). error_wrapper: "could not parse command: {detail}" # Custom (try_map / source-slice) error messages raised by # the DSL parser. These were hand-written strings in # `src/dsl/parser.rs` until the catalog migration brought # them under the same roof as the rest of the user-facing # vocabulary. Wording is unchanged from the inline source # form so existing anchor-phrase tests still match. custom: replay_path_expected: "expected a path after `replay`" create_table_needs_pk: |- tables need at least one column. Add `with pk` for a default `id INTEGER PRIMARY KEY`, or `with pk ()` to choose. Use a comma-separated list for compound primary keys. # ADR-0026 §1: the recursion-depth guard on the # WHERE-expression grammar. Input nested past the cap # (`((((…))))`) stops here with a friendly error instead # of overflowing the parser stack. expression_too_deep: "expression nested too deeply" on_action_specified_twice: "`on {target}` specified twice" change_column_flags_exclusive: "`--force-conversion` and `--dont-convert` are mutually exclusive — pick one." # ADR-0035 §4g: adding a primary key to an existing table is not # supported — every table is created with its primary key. alter_add_primary_key: "a table's primary key is fixed at creation — `alter table … add primary key` is not supported." # ADR-0035 §4g: composite UNIQUE constraints are unnamed in this # tool, so a `constraint ` prefix on UNIQUE has nowhere to go. alter_named_unique: "a UNIQUE constraint cannot be named — use `alter table add unique (, …)` without `constraint `." unknown_type: "unknown type '{found}' (expected one of: {expected})" unknown_action: "unknown referential action '{found}' (expected one of: {expected})" # Phase D typed-value-slot mismatch (ADR-0024 §Phase D): # surfaced when a column's value slot rejects the literal # the user typed (e.g. `3.14` at an `int` column). `{found}` # is the literal text; `{expected}` names the required # shape (`integer`, `number`, …). bind_type_mismatch: "value '{found}' is not a valid {expected}" # `insert into T (col)` with no `values (...)` afterwards # — the shared `(…)` opener matched as a Form-A column # list but the user hasn't typed the value clause yet. # Surfaced as a parse-time error so the input renderer # classifies the input as mid-typing rather than # dispatching a logically-empty Form C insert. insert_form_a_missing_values: "`insert into ...({columns})` looks like Form A — add `values (...)` to supply the matching values." # ADR-0029 §9: a primary-key column is already NOT NULL, # and a single-column primary key is already UNIQUE — # declaring either explicitly is redundant. constraint_redundant_on_pk: "`{column}` is a primary-key column, so it is already {constraint} — drop the redundant constraint." # 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 [()[, ...]]" # Terse one-line synopsis (issue #12): the full grammar — every # column- and table-level constraint — lives in `help.ddl.sql_create_table`. sql_create_table: "create table [if not exists] ( [constraints], ...)" sql_drop_table: "drop table [if exists] " sql_create_index: "create [unique] index [if not exists] [] on ([, ...])" sql_drop_index: "drop index [if exists] " sql_alter_table: |- alter table
add column [not null] [unique] [default ] [check ()] alter table
drop column alter table
rename column to alter table
rename to alter table
alter column type alter table
add [constraint ] check () | unique (, ...) | foreign key () references [()] alter table
drop constraint drop_table: "drop table " drop_column: "drop column [from] [table]
: " drop_relationship: |- drop relationship drop relationship from .to .drop_index: |- drop index drop index on
([, ...]) drop_constraint: "drop constraint (not null | unique | default | check) from
." add_column: "add column [to] [table]
: ()" add_relationship: |- add 1:n relationship [as ] from .to .[on delete ] [on update ] [--create-fk] add_index: "add index [as ] on
([, ...])" add_constraint: |- add constraint not null to
.add constraint unique to
.add constraint default to
.add constraint check () to
.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)" explain: |- explain show data
[where ] [limit ] explain update
set =[, ...] (where | --all-rows) explain delete from
(where | --all-rows) explain (advanced mode) replay: "replay | replay ''" # SQL `SELECT` (advanced mode; ADR-0030 / ADR-0031). select: "select (* | [ as ][, ...]) from
[where ] [order by [ asc|desc][, ...]] [limit ]" # App-lifecycle commands (per ADR-0003, surfaced through # the parser so they participate in usage templates + # completion). Templates here describe the surface # grammar that the parser accepts; the in-app `help` # listing in `help.in_app_body` carries the user-facing # description. quit: "quit" help: "help" rebuild: "rebuild" save: "save | save as" new: "new" load: "load" export: "export []" import: "import [as ]" mode: "mode simple | mode advanced" messages: "messages | messages short | messages verbose" undo: "undo" redo: "redo" # ---- Pre-submit diagnostics (ADR-0027) ------------------------------- # Surfaced by the validity indicator and the hint panel before # a command is submitted, so a learner sees the problem without # having to run the command and read the engine's error. diagnostic: unknown_table: "no such table: `{name}`" unknown_column: "no such column `{name}` on table `{table}`" # WARNING-severity flags (ADR-0026 §7) — the command still # runs, but the comparison is very likely not intended. type_mismatch: "`{column}` is {type} — this comparison uses a value of a different type" eq_null: "`= NULL` is never true — use `IS NULL` or `IS NOT NULL`" # ADR-0027 Amendment 1: LIKE is a text-pattern match. like_numeric: "`{column}` is {type} — `LIKE` is a text-pattern match, not a {type} comparison" # Phase-2 diagnostic keys (ADR-0032 §11.5). unknown_qualifier: "no such table or alias in scope: `{qualifier}`" ambiguous_column: "`{column}` is ambiguous — appears in {qualifiers}" projection_alias_misplaced: "alias `{alias}` cannot be used in {clause} — aliases are not bound until after `SELECT`'s projection list" cte_arity_mismatch: "CTE `{cte}` declares {declared} columns but its body has {actual}" compound_arity_mismatch: "`{op}` requires both sides to have the same number of columns — left has {left_n}, right has {right_n}" duplicate_cte: "duplicate `WITH` table name: `{name}`" # ADR-0033 §8 — Phase-3 DML diagnostic keys. auto_column_overridden: "column `{column}` is auto-generated (`{type}`); providing an explicit value bypasses the auto-counter and may collide with later auto-generated values" insert_arity_mismatch: "the column list names {expected} column(s) but {actual} value(s) are given" # ADR-0033 §8.1 / Amendment 5: Form B (no column list) variant # (advanced mode — auto-fills nothing, so every column needs a value). insert_arity_mismatch_form_b: "with no column list, all {expected} column(s) need a value but {actual} value(s) are given" # ADR-0036 Amendment 1 / issue #17: simple-mode Form B. The DSL # auto-fills serial/shortid columns, so only the user-fillable columns # take values — name both sets so the learner understands the skip. insert_arity_mismatch_form_b_simple: "without a column list, supply {expected} value(s) for {columns} — {skipped} auto-generated; {actual} given" # ADR-0036 Amendment 1 / issue #17: simple-mode Form B where every # column is auto-generated, so the values list takes nothing. insert_arity_mismatch_all_auto: "every column of `{table}` is auto-generated, so no values are needed, but {actual} value(s) are given" not_null_missing: "column `{column}` is required (`NOT NULL`, no default); the statement will fail when run" # Engine-error translations: an engine-rejected SQL statement # reaches the friendly-error layer (ADR-0019) and these keys # render its message in engine-neutral wording (ADR-0030 §7). # The keys are reached only after a parse-time pass let the # query through and the engine rejected at execution. engine: no_such_table: "no such table: `{name}`" no_such_column: "no such column: `{name}`" ambiguous_column: "`{column}` is ambiguous — qualify it with a table or alias" aggregate_misuse: "`{name}()` cannot be used here — aggregates belong in projection or `HAVING`, not `WHERE`" group_by_required: "non-aggregated columns must appear in a `GROUP BY` clause or be wrapped in an aggregate function" compound_arity_mismatch: "`{op}` requires both sides to have the same number of columns" scalar_subquery_too_many_rows: "scalar subquery returned more than one row — use `IN (…)` or limit the inner query" recursive_cte_malformed: "recursive CTE shape is invalid — needs a non-recursive base case combined with `UNION`/`UNION ALL`" # ---- 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" # --resume CLI failures printed to stderr before the TUI # starts (ADR-0015 §7). Wording stays one line for clean # piping; the runtime prepends `rdbms-playground: ` from # the calling code itself. resume_recorded_missing: "--resume: recorded project `{path}` no longer exists" resume_no_previous: "--resume: no previous project recorded under `{data_root}`" # Project-lock errors (single-instance enforcement, ADR-0015 §6). lock: already_held: |- project is already open in another rdbms-playground process (pid {pid} on host `{hostname}`); close that process or remove `{path}` if you're sure it's not running write: "could not write lock file `{path}`: {source}" read: "could not read existing lock file `{path}`: {source}" # Temp-project name generation failures. naming: wordlist_too_small: "wordlist must contain at least 3 entries; found {count}" too_many_collisions: |- could not generate a non-colliding temp project name after {attempts} attempts # User-typed project name validation failures. user_name: empty: "project name cannot be empty" leading_dot: "project name cannot start with `.`" invalid_char: "project name cannot contain `{ch}`; use letters, digits, `-`, `_`, or `.` only" # ProjectError variants (ProjectError Display path). data_root_unavailable: |- could not determine the OS-standard data directory; pass --data-dir to override path_not_found: "project path `{path}` does not exist" not_a_project: |- path `{path}` does not look like a project directory (no `project.yaml` and no `playground.db`) already_exists: "path `{path}` already exists; pick a different name or remove it first" io: "filesystem error at `{path}`: {source}" # SafeDeleteError — surfaces in logs when temp-project cleanup # refuses to delete a path (ADR-0015 §13). safe_delete: refused: "refusing to delete `{path}`: {reason}" io: "io error on `{path}`: {source}" # ---- 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}" # ---- Value-validation errors (per-column at bind time) -------------- value: type_mismatch: "column `{column}` expects {expected_human}, got {got}" format: "column `{column}`: {message}" # ---- Archive / zip errors (export / import) ------------------------- archive: io: "io error on `{path}`: {source}" zip: "zip error on `{path}`: {message}" export_sequence_exhausted: |- could not pick an export filename for `{project}` in `{target_dir}`: all sequence numbers up to {limit} are taken import_collision_exhausted: |- destination `{path}` already exists and the auto-suffix retries (-02 through -{limit}) are also taken; use `import as ` to choose a different name invalid_zip: "zip is malformed: {detail}" not_a_project_archive: |- zip does not contain a project (no `project.yaml` under a single top-level folder) multiple_top_folders: "zip contains more than one top-level folder; refusing to extract" unsafe_entry: "zip entry `{entry}` would escape the target directory; refusing to extract" # ---- Persistence-layer errors (CSV/YAML/IO) ------------------------- # These were thiserror Display attributes pre-round-6. Most surface # only as the inner `{message}` of `fatal.persistence` or as the # wrapped detail inside `DbError::PersistenceFatal`. persistence: io: "could not {operation} `{path}`: {source}" encode: "could not encode {kind} for `{path}`: {message}" csv: empty: "CSV is empty" invalid_utf8: "invalid UTF-8 in CSV body" unterminated_quote: "unterminated quoted field" yaml: syntax: "project.yaml syntax error: {detail}" unsupported_version: "unsupported project.yaml version: {version} (expected 1)" unknown_type: "unknown user-facing column type `{raw}` for `{table}.{column}`" unknown_action: "unknown referential action `{raw}`" migrate: version_parse: "could not read version field from project.yaml: {detail}" newer_than_supported: |- project.yaml is at version {file} but this build only understands up to version {latest}; upgrade the application or restore an older project.yaml no_migrator: |- no migrator registered for version {version} (programmer error: registry latest_version disagrees with migrators length) step_failed: "migrator from v{from} to v{to} failed: {source}" bad_output: "migrator produced an unparseable result: {detail}" io: "io error during migration on `{path}`: {source}" # ---- Advanced-mode SQL surface (ADR-0030) --------------------------- advanced_mode: not_implemented: "advanced mode SQL not implemented yet — echo: {input}" sql_in_simple: "`{command}` is SQL — available in advanced mode. Switch with `mode advanced`, or prefix the line with `:` to run it once." # Appended to a simple-mode DSL error when the same line would run # in advanced mode — keeps the actionable DSL fix and adds the mode # pointer (ADR-0033 Amendment 3). Suppressed by `advanced_alternative_note` # for cases the advanced engine would also reject (issue #1). also_valid_sql: "(trying to write SQL? switch with `mode advanced`, or prefix `:` to run once)" # ---- Insert education hints (issue #1 sub-task 2) ------------------- # Appended to a simple-mode INSERT Form B parse error when the user # supplied more values than Form B's non-auto-generated columns expect # (but not more than every column). Teaches the contract that excludes # `serial` / `shortid` columns and points at the column-list form. insert: form_b_extra_values_note: "`insert into {table} values (…)` expects {expected_phrase} — {auto_phrase}. To set them explicitly, list every column: `insert into {table} ({all_cols}) values (…)`." # Pre-flight teaching note for advanced-mode positional # `INSERT INTO T VALUES (…)` (no column list) when the value count # doesn't match the column count (issue #1 sub-task 3). The override # uses the non-auto columns so the user can omit serial/shortid. form_b_positional_count_mismatch_note: "Positional `insert into {table} values (…)` requires a value for every column. `{table}` has {col_count} columns ({col_list}); you supplied {supplied}. To omit auto-generated columns, list the columns you want: `insert into {table} ({non_auto_csv}) values (…)`." # ---- SQL SELECT (advanced mode; ADR-0030 / ADR-0031) ---------------- select: internal_table: "`{table}` is an internal system table and cannot be queried." # ---- 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?" # Undo / redo confirmation (ADR-0006 Amendment 1). undo_confirm_title: "Undo last change" redo_confirm_title: "Redo last undone change" undo_confirm_command: "This will undo:" redo_confirm_command: "This will re-apply:" undo_confirm_when: "Snapshot taken {timestamp}" undo_confirm_prompt: "Restore that earlier state?" undo_cancelled: "undo cancelled" redo_cancelled: "redo cancelled" # ---- Undo / redo command surfaces (ADR-0006 Amendment 1) ------------- undo: disabled: "undo is turned off for this session (started with --no-undo)" nothing_to_undo: "nothing to undo" nothing_to_redo: "nothing to redo" undone_ok: "undone: {command}" redone_ok: "re-applied: {command}" undo_failed: "could not undo: {error}" redo_failed: "could not redo: {error}" # ---- 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: "Type a command — press Tab for options, `help` for a list" # Panel titles for the output and hint panels (rendered inside # the rounded border, hence the leading/trailing space). output_title: "Output" hint_title: "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')" # Labels rendered inside the input panel's border to mark the # current input mode. `label_advanced_one_shot` is shown # while a `:` one-shot is in flight from simple mode. label_simple: "SIMPLE" label_advanced: "ADVANCED" label_advanced_one_shot: "Advanced:" messages: show: "messages: {current}" set_short: "messages: short" set_verbose: "messages: verbose" unknown: "unknown messages mode '{value}' (expected 'short' or 'verbose')" # ---- Database-error fallback wording + cascade summaries ------------ db: # DbError variants — fallback Display wording for paths that # bypass the structured friendly translator (fatal banners, # plain to_string() calls). The normal path goes through # `friendly::translate_error` which routes by the `kind` # field and renders catalog wording from `error.*` instead. error: sqlite: "database error: {message}" unsupported: "operation not supported: {detail}" invalid_value: "invalid value: {detail}" persistence_fatal: "could not {operation} `{path}`: {message}" rebuild_row_failed: |- unable to load row {row_number} from `{csv_path}` into table `{table}`: {detail} worker_gone: "database worker is no longer available" io: "io error: {detail}" cascade: # Per-relationship cascade summary appended to a delete # success note. The same template handles cascade, # set-null, and restrict/no-action cases — `{action}` is # one of the three action phrases below. summary: " related: {count} row(s) {action} in `{child_table}` for relationship `{rel}` (on delete {on_delete})" action_deleted: "deleted" action_set_null: "had FK set to null" action_blocked: "blocked" # `change column ... (newtype)` dry-run diagnostics (ADR-0017). # Surface when the migration would lose information (lossy), # produce values the target type can't represent # (incompatible), or violate a uniqueness contract (collision). diagnostic: # Column headers for the diagnostic tables. header_from: "From" header_to: "To" header_reason: "Reason" header_value: "Value" header_becomes: "Becomes" header_source_rows: "Source rows ({pk_label})" header_source_values: "Source values" # Summary lines printed above each diagnostic table. lossy_summary: |- Cannot change `{table}.{column}` from {src_ty} to {target_ty}: {total} row(s) would discard information. incompatible_summary: |- Cannot change `{table}.{column}` from {src_ty} to {target_ty}: {total} row(s) cannot be converted. uniqueness_summary: |- Cannot change `{table}.{column}` from {src_ty} to {target_ty}: {total} collision(s) would violate uniqueness. # Follow-up suggestion appended to the lossy diagnostic # (only — incompatibles can't be force-overridden). force_conversion_hint: "if you want to execute this conversion in spite of the problems, re-run with `--force-conversion`." # `add constraint ...` dry-run refusals (ADR-0029 §5). # Surface when the column's existing rows would violate the # constraint being added; the offending rows follow in a # diagnostic table. There is no force override — the user # fixes the data and retries. add_not_null_summary: |- Cannot add NOT NULL to `{table}.{column}`: {total} row(s) hold a null value. add_unique_summary: |- Cannot add UNIQUE to `{table}.{column}`: {total} value(s) appear in more than one row. add_check_summary: |- Cannot add this CHECK to `{table}.{column}`: {total} row(s) do not satisfy `{rule}`. # ---- DSL command success summaries (ADR-0019 §9 sweep) -------------- # (The DSL → SQL teaching echo's `Executing SQL:` prefix used to live # here as `echo.executing_sql`; with the ADR-0038 §4 styled-runs polish # the line is now built from a hardcoded constant in `crate::echo` # because the dim-prefix + lex-the-rest rendering path in # `ui::render_output_line` needs a fixed byte boundary the i18n # template couldn't provide. Re-introduce a key here if a non-English # locale lands.) 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" # Shown beneath a `drop column --cascade` summary, once per # index removed because it covered the dropped column. index_dropped_with_column: " also dropped index `{index}` (it covered the column)" # ---- 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 when information was discarded — # under simple-mode `--force-conversion`, and under advanced-mode # `alter table … alter column … type …`, which always converts # (ADR-0035 §7). 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. # `change column … --dont-convert` caveat (ADR-0038 §6 category 3, # Phase 3). The only category-3 *caveat* — the others are illuminating # (the headline echo already matches the effect). Here the headline # SQL (`ALTER TABLE … SET DATA TYPE …`) *would* convert if run, but # `--dont-convert` skipped the client-side layer, so the line above is # not equivalent. Fires only in an advanced effective mode (the line # references "the line above," i.e. the echo). dont_convert_caveat: |- [client-side] `--dont-convert` kept the stored values as-is; standard SQL always converts, so running the line above would transform them instead. # ---- 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}" # Skipped during replay (ADR-0034): app-lifecycle commands are # not re-applied. Most skip silently; `import` and a nested # `replay` warn because skipping them can leave the replayed # state incomplete (imported data / the nested file's commands # are not reconstructed). skipped_import: "[skip] line {line}: `{command}` — replay does not re-import; the imported data is not reconstructed" skipped_replay: "[skip] line {line}: nested `{command}` — its commands were not replayed"