UX-13 P5: Dutch text, STYLE check, DOCS screenshots, period editor
- 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:
@@ -47,57 +47,10 @@ def check(name, cond):
|
||||
fails.append(name)
|
||||
|
||||
|
||||
# --- the rule (STYLE.md sections 1 and 4) --------------------------------------
|
||||
BANNED = re.compile(
|
||||
r"\b(?:(?:SAFETY|TARIFF|TEL|CFG|FCST|OPT|UX|HW|FW|WITNESS|PKG|ARCH)-\d+|"
|
||||
r"integrator|anti-?windup|windup|clamp(?:ed|s)?|i_clamped|saturat\w*|"
|
||||
r"BUG SIGNAL|latch(?:ed)?|arbiter|allocator|registers?|0x[0-9A-Fa-f]+|"
|
||||
r"layer [123]|heartbeat|watchdog|telegrams?|ACKs?|CRC|OTA|Modbus|planner|"
|
||||
r"capaciteitstarief|journalctl|ssh|grep|"
|
||||
r"\d+\.\d+\.\d+\.\d+|[\w-]+\.(?:py|md|yaml|json|db))\b|§\s?\d", re.I)
|
||||
ENTITY = re.compile(r"\b(?:sensor|number|binary_sensor|input_\w+|switch|select)"
|
||||
r"\.[a-z0-9_*]+")
|
||||
SNAKE = re.compile(r"\b[a-z0-9]+(?:_[a-z0-9]+)+\b")
|
||||
SHOUT = re.compile(r"\b[A-Z]{2,}[A-Z0-9]*(?:[\s-]+[A-Z]{2,}[A-Z0-9]*)+\b")
|
||||
PLACEHOLDER = re.compile(r"\{[^{}]*\}|%[-+ #0-9.]*[sdrfgx%]")
|
||||
|
||||
|
||||
def words(text):
|
||||
return [w for w in re.findall(r"\S+", PLACEHOLDER.sub("X", text))
|
||||
if re.search(r"[A-Za-z0-9]", w)]
|
||||
|
||||
|
||||
def sentences(text):
|
||||
"""Sentence count: a stop followed by a space and a capital, or the end."""
|
||||
t = PLACEHOLDER.sub("X", text.strip())
|
||||
t = re.sub(r"\b(e\.g|i\.e|etc)\.", "eg", t)
|
||||
return len([s for s in re.split(r"[.!?](?:\s+|$)", t) if s.strip()])
|
||||
|
||||
|
||||
def title_problems(title):
|
||||
out = []
|
||||
if len(words(title)) > 5:
|
||||
out.append(f"title over 5 words ({len(words(title))})")
|
||||
if title.lower().startswith("goodwe"):
|
||||
out.append("title carries a GoodWe prefix")
|
||||
if title.rstrip().endswith("."):
|
||||
out.append("title ends in a full stop")
|
||||
return out + text_problems(title, keys=False, entities=False)
|
||||
|
||||
|
||||
def text_problems(text, *, keys, entities):
|
||||
t = PLACEHOLDER.sub("X", text)
|
||||
out = []
|
||||
hit = sorted({m.group(0) for m in BANNED.finditer(t)})
|
||||
if hit:
|
||||
out.append("banned: " + ", ".join(hit[:4]))
|
||||
if not entities and ENTITY.search(t):
|
||||
out.append("entity id: " + ENTITY.search(t).group(0))
|
||||
if not keys and SNAKE.search(ENTITY.sub("", t)):
|
||||
out.append("option key: " + SNAKE.search(ENTITY.sub("", t)).group(0))
|
||||
if SHOUT.search(t):
|
||||
out.append("capitals: " + SHOUT.search(t).group(0))
|
||||
return out
|
||||
# --- the rule (STYLE.md sections 1 and 4): tools/style_rules.py, shared with
|
||||
# test_ui_text.py since UX-13 P5.
|
||||
from style_rules import (BANNED, ENTITY, PLACEHOLDER, SHOUT, SNAKE, # noqa: E402,F401
|
||||
sentences, text_problems, title_problems, words)
|
||||
|
||||
|
||||
def body_problems(body, severity):
|
||||
@@ -483,10 +436,13 @@ def collect():
|
||||
if isinstance(v.get("fields"), dict):
|
||||
walk(v["fields"], f"{path}{key}.")
|
||||
walk(tr.get("configuration"), "")
|
||||
# UX-13: the new page's own words, held to the option texts' rule.
|
||||
# UX-13: the new page's own words, held to the option texts' rule. Home's
|
||||
# sentences (`home_words`) are templates, checked rendered in part 8.
|
||||
for f in sorted((APP / "ui_text").glob("*.json")):
|
||||
def walk_ui(node, path):
|
||||
for key, v in node.items():
|
||||
if not path and key == "home_words":
|
||||
continue
|
||||
if isinstance(v, dict):
|
||||
walk_ui(v, f"{path}{key}.")
|
||||
elif isinstance(v, str):
|
||||
@@ -1852,12 +1808,15 @@ def home_problems(kind, text):
|
||||
return out
|
||||
|
||||
|
||||
_home = homeview.text_samples()
|
||||
_home_bad = [(k, t, p) for k, t in _home for p in [home_problems(k, t)] if p]
|
||||
for k, t, p in _home_bad:
|
||||
print(f" {k}: {t!r}: {p}")
|
||||
check(f"every Home sentence obeys its STYLE limit ({len(_home)} rendered, "
|
||||
f"{len(_home_bad)} over)", len(_home) > 80 and not _home_bad)
|
||||
for _lang in ("en", "nl"): # UX-13 P5: Home in English and Dutch
|
||||
_home = homeview.text_samples(_lang)
|
||||
_home_bad = [(k, t, p) for k, t in _home for p in [home_problems(k, t)]
|
||||
if p]
|
||||
for k, t, p in _home_bad:
|
||||
print(f" {_lang} {k}: {t!r}: {p}")
|
||||
check(f"every Home sentence in {_lang} obeys its STYLE limit "
|
||||
f"({len(_home)} rendered, {len(_home_bad)} over)",
|
||||
len(_home) > 80 and not _home_bad)
|
||||
_shadowed = [t for k, t in homeview.text_samples() if "ould" in t]
|
||||
check("in shadow mode the Home plan says what the add-on WOULD do: every "
|
||||
"shadow variant of a plan text carries 'would'",
|
||||
|
||||
Reference in New Issue
Block a user