From 279740ff7e473e23766d289ece2ed2144d18da16 Mon Sep 17 00:00:00 2001 From: delchev Date: Thu, 3 Sep 2026 18:04:59 +0300 Subject: [PATCH] docs: what a field is called - label, and the variants the tenant's country resolves --- docs/reference.md | 1 + docs/spec/entities.md | 48 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/docs/reference.md b/docs/reference.md index 913c4fb..c7421b3 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -18,6 +18,7 @@ The quick lookup surface: one line and a minimal snippet per construct. For rule | [`dependsOn`](/spec/relations) | link a dropdown to another, copy a value from the referenced record, or default a line from the open document header | | [`function`](/spec/entities#function-the-presentation-role) | an explicit presentation role (Document, Setting, ...) | | [`label`](/spec/entities#label-a-stored-display-name) | a stored, read-only display name for lookups | +| [`label` / `countryLabels` (field)](/spec/entities#label-countrylabels-what-a-field-is-called) | what a field is called, and the variants the tenant's country resolves | | [`number`](/spec/entities#document-numbering) | a platform-numbered, gap-free document field | | [`checks`](/spec/entities#checks-declarative-validations) | cross-field / cross-line validations | | [`checks: kind: guard`](/spec/entities#kind-guard-a-precondition-over-an-aggregate) | a precondition over an aggregate: block, mark for a task, or reject | diff --git a/docs/spec/entities.md b/docs/spec/entities.md index 8e8b231..6ac9173 100644 --- a/docs/spec/entities.md +++ b/docs/spec/entities.md @@ -39,6 +39,8 @@ fields: | `pattern` | an input-format regular expression the value must match (string / text fields only) | | `defaultValue` | the field's default: the column default, the reason a `required` field is not demanded from the caller, and the value a **new** row is seeded with in the UI (see [Field defaults](#defaultvalue-field-defaults)) | | `unique` | a UNIQUE constraint (e.g. a code or business key) | +| `label` | what the field is called in the UI, replacing the humanized name (see [what a field is called](#label-countrylabels-what-a-field-is-called)) | +| `countryLabels` | label variants resolved from the tenant's country rather than the reader's language (see [what a field is called](#label-countrylabels-what-a-field-is-called)) | | `visibleTo` | an allow-list of roles that may read the field, enforced where the data leaves the server (see [Role-scoped field visibility](#role-scoped-field-visibility-visibleto)) | | `precision` / `scale` | override the decimal default (16, 2) | | `readOnly` | rendered read-only in the UI (e.g. a calculated total) | @@ -87,6 +89,52 @@ By default the generated UI controls follow declaration order - all fields first Names match field / relation names (case-insensitive). A partial order is fine - any property not listed keeps its default position and is appended after the listed ones. +## label / countryLabels — what a field is called + +A field's caption is the humanized form of its name, which is right most of the time and cannot +produce an acronym, a unit or a term of art: `nationalId` reads as "National Id". State it instead: + +```yaml +fields: + - { name: nationalId, type: string, label: National ID } + - { name: iban, type: string, label: IBAN } +``` + +The label is what every surface renders - the form caption, the list column header, the read-only +details block - and it seeds the field's entry in the default-language catalog, so it is translated +like any other label. + +Some terms are fixed by the **company's country**, not by the language its users read the interface +in. A national identification number is called ЕГН in Bulgaria and Steuer-ID in Germany; an +English-reading accountant at a Bulgarian company needs the Bulgarian term, and a Bulgarian-reading +user at a German company does not. Declare such a label per country and let the tenant's country +resolve it: + +```yaml +- name: nationalId + type: string + label: National ID + countryLabels: + BG: ЕГН + DE: Steuer-ID +``` + +Keys are ISO 3166-1 alpha-2 country codes. Which country a deployment serves is a property of the +tenant, not of the model - the intent declares which term applies where, and nothing else. + +::: info Normative +A field's `label:` MUST replace the humanized field name wherever the field is rendered, and MUST +seed its entry in the default-language catalog so it is translated like any other label. +`countryLabels:` declares label variants keyed by ISO 3166-1 alpha-2 country code, resolved from the +tenant's country and applied in **every** language: a label resolved by country is not a +translation, and MUST NOT be treated as one. +A country that declares no variant MUST fall back to `label:`, and a field that declares no label to +the humanized name, so a model declaring neither behaves as before. +A `countryLabels` key that is not a country MUST be rejected - it can never match a tenant, and +would leave the base label rendering with nothing reported. A blank `label:`, and a variant with no +label, MUST be rejected for the same reason. +::: + ## unique — a business key over more than one field `unique: true` on a field constrains one column. When what makes a row unique spans several,