# 0053. Frontend labels live in the database

- **Status:** Accepted
- **Date:** 2026-09-24

## Context

The site's wording — the `frontend` translation category, 437 keys in `messages/{hu,en,de}/frontend.php` —
could only change through a developer, a commit and a deploy. The client wants to reword it himself, as he
already can on Molino (its ADR 0037).

## Decision

Port Molino's solution: the `frontend` category is read by `LabelMessageSource` from a `label` table
(`category`, `label`, multilingual `value`), seeded from the message files by `m260924_110000_labels`. The
files stay as seed data only. `admin` stays on `PhpMessageSource`.

The Szövegcímkék admin screen edits the text only: no create, no delete, and `Label::beforeSave()` restores
the key and its group on every update. A new label arrives with a migration through `Label::add()`.

**Unlike Molino, the cache has no dependency.** Each language's set is cached with no expiry and invalidated
by the flush-all of [0020](0020-cache-invalidation-is-flush-all.md) — admin save, deploy — plus an explicit
flush in the migration and in `Label::add()`.

## Alternatives

- **Molino's `DbDependency`** (row count, timestamps, `SUM( CRC32( … ) )`). Rejected: every write path here
  already flushes the whole cache, so it would buy one aggregate query per request per language for nothing,
  and break 0020's no-selective-invalidation rule.
- **Yii's `DbMessageSource`.** Rejected for the same reasons as on Molino: two tables of its own, no room for
  the editorial grouping.

## Consequences

- A `label` row changed outside the admin, `Label::add()` and a deploy — raw SQL — stays stale on the site
  until a flush.
- Editing a Hungarian value drops its English and German translation when auto-translation is on; the site
  shows Hungarian until `translate/auto` refills it.
- The `frontend` strings the admin itself uses (status names, offer texts) become client-editable too.
- The suite has no `db`, so `tests/bootstrap.php` pins `frontend` back to the files; the real message source is
  not exercised by the tests.
- An admin grid column with `-truncate` makes the table `width: 100%`, found while building this screen.
