Files
Claude dev agent 667b8e9145
checks / unit (pull_request) Successful in 10m45s
infra-pin / fresh (pull_request) Successful in 1s
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>
2026-09-28 16:33:32 +02:00

59 lines
2.3 KiB
Python

"""STYLE.md sections 1 and 4 as code: the banned words and the counts.
Shared by test_messages.py (UX-08) and test_ui_text.py (UX-13 P5), so the two
checks cannot drift apart. Moved here unchanged from test_messages.py.
"""
import re
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