Files
rdbms-playground/src/friendly/strings/en-US.yaml
T
claude@clouddev1 12395a9a6c create table: column constraints — NOT NULL / UNIQUE / DEFAULT grammar (ADR-0029)
`create table … with pk` now parses the column-constraint
suffix; combined with the commit-1 db layer, a constrained
table works end to end.

- A shared constraint-suffix grammar fragment — `not null`,
  `unique`, `default <literal>` — sits after each column's
  `(type)` group; `build_create_table` walks the matched path
  per column and folds the constraints into `ColumnSpec`.
- §9 redundancy check: every `with pk` column is a primary-key
  column, so `not null` (any) and `unique` (single-column PK)
  are rejected with a friendly error
  (`parse.custom.constraint_redundant_on_pk`).
- `project.yaml` round-trip: `ColumnSchema` gains `not_null` /
  `default`; the YAML reader/writer and `build_read_schema`
  carry them, so `rebuild` / `export` / `import` preserve
  constraints.
- ADR-0029 §2.1's example corrected — `create table` columns
  are all PK columns, so its suffix is for `default` / `check`;
  `docs/simple-mode-limitations.md` records that non-PK
  columns at create time need advanced mode.

CHECK is deferred to the next commit. 1184 tests pass (+7);
clippy clean.
2026-05-19 14:41:29 +00:00

789 lines
39 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) ------------------
# ---- 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 <project-path> 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] [<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 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 (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 [<path>] — write a zip of project.yaml + data/ (excludes the database file and history.log)
import: |-
import <zip> [as <target>] — 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)
ddl:
create: |-
create table <T> with pk [<col>(<type>), ...] — create a table
drop: |-
drop table <T> — remove a table
drop column [from] [table] <T>: <col> [--cascade] — remove a column
(--cascade also drops any index that covers the column)
drop relationship <name> — remove a relationship
drop index <name> — remove an index
drop index on <T> (<col>, ...) — remove an index by its columns
add: |-
add column [to] [table] <T>: <col> (<type>) — add a column
(for serial/shortid on a non-empty table: existing rows auto-filled)
add 1:n relationship [as <name>] from <P>.<col> to <C>.<col>
[on delete <action>] [on update <action>] [--create-fk] — declare a relationship
add index [as <name>] on <T> (<col>, ...) — create an index
rename: |-
rename column [in] [table] <T>: <old> to <new> — rename a column
change: |-
change column [in] [table] <T>: <col> (<newtype>) [--force-conversion | --dont-convert]
— change a column's type (to serial/shortid: null cells auto-filled with generated values)
data:
show: |-
show table <T> — show a table's structure
show data <T> — show a table's rows
insert: |-
insert into <T> [(cols)] [values] (vals) — add a row
update: |-
update <T> set <c>=<v>, ... where <c>=<v> | --all-rows — change matching rows
delete: |-
delete from <T> where <c>=<v> | --all-rows — remove matching rows
replay: |-
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 project directory.
explain: |-
explain show data <T> | explain update <T> ... | explain delete from <T> ...
— show how the database would run a query, without
running it (safe even for update / delete)
# 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}"
# 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.)"
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 <name>(<type>)` 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."
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 <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>
drop_index: |-
drop index <Name>
drop index on <Table> (<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]
add_index: "add index [as <Name>] on <Table> (<col>[, ...])"
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)"
explain: |-
explain show data <Table> [where <expr>] [limit <n>]
explain update <Table> set <col>=<value>[, ...] (where <expr> | --all-rows)
explain delete from <Table> (where <expr> | --all-rows)
replay: "replay <path> | replay '<path with spaces>'"
# 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 [<path>]"
import: "import <zip-path> [as <target>]"
mode: "mode simple | mode advanced"
messages: "messages | messages short | messages verbose"
# ---- 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"
# ---- 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"
# --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 `"<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}"
# ---- 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 <zip> as <target>` 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 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: "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`."
# ---- 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"
# 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 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"