Files
rdbms-playground/src/friendly/strings/en-US.yaml
T
claude@clouddev1 9c4857eb50 ADR-0022 stage 5/8: hint panel ambient typing assistance
ParseError::Invalid gains an `expected: Vec<String>` field —
the human-rendered names of the patterns chumsky was looking
for at the failure point (`\`create\``, `identifier`, etc.).
Empty for custom errors, which have no expected-set framing.
Populated by a new `describe_expected()` helper in parser.rs
that humanise() also delegates to (eliminates duplication).

`input_render::ambient_hint(input) -> Option<String>` returns
the hint-panel content per ADR-0022 §6:
  - empty input → None (caller falls back to panel.hint_empty);
  - Valid → t!("hint.ambient_complete") ("submit with Enter");
  - IncompleteAtEof → t!("hint.ambient_expected", expected = …)
    listing the parser's expected next tokens, oxford-joined;
  - DefiniteErrorAt → t!("hint.ambient_error_with_usage", …)
    composing the parse-error message with the matching
    parse.usage.* template if a known entry keyword was
    consumed, else the bare message.

Catalog gains the three hint.ambient_* keys + validator
declarations.

ui::render_hint_panel resolution order:
  1. explicit app.hint (modal contexts) wins;
  2. simple-mode + non-empty input → ambient_hint;
  3. fallback to panel.hint_empty.
Advanced mode (persistent + one-shot `:`) bypasses ambient
hinting per ADR-0022 §12.

Snapshot: highlighted_input_all_token_classes rebaselined
because the hint panel now displays an ambient hint instead
of the empty placeholder when input is non-empty.

Tests: 698 passing, 0 failing, 1 ignored (693 baseline →
+5 ambient_hint cases). Clippy clean.

Stage 6 introduces the IdentSlot taxonomy + parser audit so
identifier-typed slots can yield schema-aware completion
candidates in stage 8.
2026-05-10 17:42:13 +00:00

523 lines
23 KiB
YAML

# 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] [<project-path>]
Arguments:
<project-path> 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 <light|dark> Override theme (default: auto-detect).
--data-dir <PATH> Use PATH as the data root instead of
the OS-standard location for this run.
--log-file <PATH> Write tracing output to PATH.
--resume Open the most-recently-used project
(path tracked under <data-root>/last_project).
Errors out if no previous project is
recorded. Mutually exclusive with
<project-path>.
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 [<path>] Write a zip of project.yaml + data/ to
<path> (relative paths under the data
root). Excludes playground.db and
history.log.
import <zip> [as <t>] Unpack <zip> into a new project and
switch to it. <t> 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 [<path>] — write a zip of project.yaml + data/ (excludes .db, history.log)
import <zip> [as <t>] — unpack a zip and switch to the new project
DSL data commands (in simple mode):
create table <T> with pk [<col>:<type>...]
drop table <T>
add column [to] [table] <T>: <col> (<type>)
(for serial/shortid on a non-empty table: existing rows auto-filled)
drop column [from] [table] <T>: <col>
rename column [in] [table] <T>: <old> to <new>
change column [in] [table] <T>: <col> (<newtype>)
[--force-conversion | --dont-convert]
(to serial/shortid: null cells auto-filled with generated values)
add 1:n relationship [as <name>] from <P>.<col> to <C>.<col>
[on delete <action>] [on update <action>] [--create-fk]
drop relationship <name>
insert into <T> [(cols)] [values] (vals)
update <T> set <c>=<v>... where <c>=<v> | --all-rows
delete from <T> where <c>=<v> | --all-rows
show table <T>
show data <T>
replay <path> — run each non-blank, non-`#`-comment line
of <path> 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 <Name> with pk [<col>:<type>[, ...]]"
drop_table: "drop table <Name>"
drop_column: "drop column [from] [table] <Table>: <Name>"
drop_relationship: |-
drop relationship <Name>
drop relationship from <Parent>.<col> to <Child>.<col>
add_column: "add column [to] [table] <Table>: <Name> (<Type>)"
add_relationship: |-
add 1:n relationship [as <Name>]
from <Parent>.<col> to <Child>.<col>
[on delete <action>] [on update <action>]
[--create-fk]
rename_column: "rename column [in] [table] <Table>: <Old> to <New>"
change_column: |-
change column [in] [table] <Table>: <Name> (<Type>)
[--force-conversion | --dont-convert]
show_data: "show data <Table>"
show_table: "show table <Table>"
insert: "insert into <Table> [(<col>[, ...])] [values] (<value>[, ...])"
update: "update <Table> set <col>=<value>[, ...] (where <col>=<value> | --all-rows)"
delete: "delete from <Table> (where <col>=<value> | --all-rows)"
replay: "replay <path> | replay '<path with spaces>'"
# 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 [<path>]"
import_usage: "usage: import <zip-path> [as <target>]"
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 `"<verb> <subject>" failed: <rendered>`.
failed: '"{verb} {subject}" failed: {rendered}'
# Echo line `running: <input>` 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] <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"