UX-13 P5: Dutch text, STYLE check, DOCS screenshots, period editor
checks / unit (pull_request) Successful in 10m45s
infra-pin / fresh (pull_request) Successful in 1s

- app/ui_text/nl.json and app/uitext.py: every page word in Dutch, English
  fallback per key; the page picks by browser language. Home's sentences
  move into ui_text `home_words`, so /status?lang=nl builds them in Dutch.
- test_ui_text.py: EN/NL key and placeholder parity, STYLE limits, jargon
  lists for both languages, no inline details, untranslated Dutch caught.
  Shared rules moved unchanged to tools/style_rules.py.
- tools/ux13_screenshots.sh/.py: DOCS screenshots from the demo fixtures in
  headless Chromium; docs/img/ux13/. Battery modes (P3): no screenshot.
- "Other time windows": a list editor (ui.periods) that builds
  static_profile and names bad times, empty prices, overlaps and gaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Claude dev agent
2026-09-28 16:33:32 +02:00
parent 8c34647d05
commit 667b8e9145
38 changed files with 1633 additions and 300 deletions
+21 -15
View File
@@ -9,26 +9,23 @@ are (DECISIONS/UX-13-one-layout-table.md).
"""
import copy
import io
import json
import os
import re
from . import uilayout as L
from . import uitext
from . import uimodel as U
from .configui import help_of, label_of, problems_by_field, split_group
from .validate import power_request_entities
UI_TEXT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ui_text",
"en.json")
# A choice the page cannot make, only keep: odd tariff windows (P5 editor).
ONLY_IF_HELD = ("custom",)
# A choice the page cannot make, only keep. None since P5's period editor.
ONLY_IF_HELD = ()
_TIERS = ("bad", "warn", "info")
def ui_text() -> dict:
with io.open(UI_TEXT, encoding="utf-8") as f:
return json.load(f)
def ui_text(lang="en") -> dict:
"""The page words in `lang`, English where Dutch has no key (P5)."""
return copy.deepcopy(uitext.words(lang))
def _roster():
@@ -122,6 +119,10 @@ def _field(fld, schema_ui, translations, lang, T):
if hints:
out["hints"] = {v: e.get("description", "")
for v, e in hints.items()}
if fld.control == L.PERIOD_LIST:
# P5: the day choices of one time window, in the editor's order.
names = T["choices"]["period_days"]
out["days"] = [[d, names.get(d, d)] for d in U.PERIOD_DAYS]
if fld.mirror:
out["mirror"] = fld.mirror
if fld.fills:
@@ -145,7 +146,7 @@ def _field(fld, schema_ui, translations, lang, T):
def layout(schema_ui, translations, lang="en") -> dict:
"""Every group, part and field of the Installer view, as the page draws
it. Built from the table only: a field the table places is on the page."""
T = ui_text()
T = ui_text(lang)
groups = []
for g in L.GROUPS:
parts = []
@@ -220,14 +221,19 @@ def _settle(out: dict, stored: dict | None) -> dict:
| {k: v for k, v in out.items() if k not in stored})
def derive_message(err: U.DeriveError) -> dict:
words = ui_text()["settings"]["derive"]
def derive_message(err: U.DeriveError, lang="en") -> dict:
T = uitext.words(lang)
words = T["settings"]["derive"]
text = words.get(err.code, words["unknown"])["description"]
args = dict(getattr(err, "values", None) or {})
# A time window's day is shown by name (P5 period editor).
if "day" in args:
args["day"] = T["choices"]["period_days"].get(args["day"], args["day"])
return {"field": err.field or None, "card": None, "tier": "bad",
"text": text}
"text": uitext.say(text, **args)}
def derive_messages(errors, problems, notes, model, keys):
def derive_messages(errors, problems, notes, model, keys, lang="en"):
"""The page's messages when part of the model could not become options:
each conversion's own sentence beside its field, then the checks of the
rest. A refusal or note about a key the failed conversion writes is left
@@ -238,7 +244,7 @@ def derive_messages(errors, problems, notes, model, keys):
for t in texts] + list(unanchored)
kept = [p for p in problems if p in kept]
notes = [n for n in notes if not set(n.keys or ()) & failed]
return ([derive_message(err) for err in errors]
return ([derive_message(err, lang) for err in errors]
+ anchors(kept, notes, model, keys))