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:
+181
-176
@@ -16,7 +16,7 @@ section at the bottom and is held to STYLE.md by test_messages.py.
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from . import arbitrage, messages, peakguard
|
||||
from . import arbitrage, messages, peakguard, uitext
|
||||
from .maintenance import CHARGE as M_CHARGE, DRAIN as M_DRAIN, HOLD as M_HOLD
|
||||
from .maintenance import IDLE as M_IDLE
|
||||
from .price import Absence, Uncertainty, brussels
|
||||
@@ -54,18 +54,6 @@ def hhmm(t: datetime) -> str:
|
||||
return f"{brussels(t):%H:%M}"
|
||||
|
||||
|
||||
def _kw(w) -> str:
|
||||
return f"{abs(float(w)) / 1000:.1f} kW"
|
||||
|
||||
|
||||
def _eur(v) -> str:
|
||||
return f"€{float(v):.2f}"
|
||||
|
||||
|
||||
def _kwh(v) -> str:
|
||||
return f"{float(v):.1f} kWh"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Now
|
||||
# --------------------------------------------------------------------------- #
|
||||
@@ -490,8 +478,7 @@ def build_plan(c, now, shadow, running, held=False, settled=True):
|
||||
"day": brussels(now).date().isoformat(),
|
||||
"now_h": round(_hours(now, day0), 3),
|
||||
"hours": int(round(_hours(day1, day0))),
|
||||
"verdict": (VERDICT[verdict_key] if settled or verdict_key != "held"
|
||||
else ZERO_GOING),
|
||||
"verdict": verdict_words(verdict_key, settled),
|
||||
"verdict_key": verdict_key,
|
||||
"head": plan_head(verdict_key, shadow, settled),
|
||||
"prices": bars,
|
||||
@@ -537,7 +524,7 @@ def battery_rows(c, shadow, running, held=False):
|
||||
"stored_kwh": (None if cap is None or inv.soc is None
|
||||
else round(cap * float(inv.soc) / 100.0, 2)),
|
||||
"state": flow(inv.batt, shadow=shadow),
|
||||
"mode": MODE_HOUSE,
|
||||
"mode": mode_house(),
|
||||
"maintenance": phase,
|
||||
"now": battery_now(inv.soc, inv.batt, phase=phase, shadow=shadow,
|
||||
running=running, held=held,
|
||||
@@ -549,28 +536,6 @@ def battery_rows(c, shadow, running, held=False):
|
||||
return rows
|
||||
|
||||
|
||||
# Row id -> the title on Home. The row's own sentence is the body.
|
||||
ATTN_TITLES = {
|
||||
"controller": "Control is not running",
|
||||
"meter": "Meter reading",
|
||||
"meter_frozen": messages.T_METER_FROZEN,
|
||||
"freeze_check": "Stuck meter check",
|
||||
"month_peak": "This month's peak",
|
||||
"peak_guard": "Peak limit",
|
||||
"peak_forecast": "Peak limit forecast",
|
||||
"sensor": "Battery readings",
|
||||
"control": "Inverter control",
|
||||
"power_too_high": messages.T_OUTPUT_CLAMP,
|
||||
"not_keeping_up": messages.T_LOOP_BOUND,
|
||||
"write": "Power request not accepted",
|
||||
"direction": "Power went the wrong way",
|
||||
"site_direction": "Power went the wrong way",
|
||||
"circulating": messages.T_CIRCULATING,
|
||||
"delivery": messages.S_NOT_DELIVERING,
|
||||
"estop": "Emergency stop",
|
||||
"pv": "Solar reading",
|
||||
None: "Inverter connection",
|
||||
}
|
||||
# Row id -> the DOCS.md heading under Notifications, where one exists.
|
||||
ATTN_DOCS = {
|
||||
"meter_frozen": messages.T_METER_FROZEN,
|
||||
@@ -594,7 +559,7 @@ def attention(checks):
|
||||
sev = INFO if ok else (WARNING if warn else ALARM)
|
||||
rid = row.get("id")
|
||||
out.append({"severity": sev, "id": rid,
|
||||
"title": ATTN_TITLES.get(rid, ATTN_TITLES[None]),
|
||||
"title": attn_title(rid),
|
||||
"body": row.get("text", ""),
|
||||
"docs": ATTN_DOCS.get(rid)})
|
||||
out.sort(key=lambda a: rank[a["severity"]])
|
||||
@@ -605,7 +570,7 @@ def month_peak(c, now):
|
||||
"""This month's peak against the limit, and the 15-minute period so far."""
|
||||
limit_w = c.peak.cap_w # the limit the controller parsed at start
|
||||
local = brussels(now)
|
||||
out = {"month": f"{local:%B}", "limit_w": limit_w, "peak_w": None,
|
||||
out = {"month": month_name(local), "limit_w": limit_w, "peak_w": None,
|
||||
"pct": None, "period_w": None, "state": "not_covered",
|
||||
"line": ""}
|
||||
if not c.p1_enabled:
|
||||
@@ -644,8 +609,18 @@ def state_of(c, level, shadow):
|
||||
return "running"
|
||||
|
||||
|
||||
def build(c, *, level: str, checks: list, now: datetime | None = None) -> dict:
|
||||
"""The structured half of `/status`. Pure reads of the controller."""
|
||||
def build(c, *, level: str, checks: list, now: datetime | None = None,
|
||||
lang: str = "en") -> dict:
|
||||
"""The structured half of `/status`, in `lang` (UX-13 P5). Pure reads of
|
||||
the controller."""
|
||||
_LANG[0] = uitext.lang_of(lang)
|
||||
try:
|
||||
return _build(c, level=level, checks=checks, now=now)
|
||||
finally:
|
||||
_LANG[0] = "en"
|
||||
|
||||
|
||||
def _build(c, *, level, checks, now):
|
||||
now = now or datetime.now(timezone.utc)
|
||||
shadow = bool(c.shadow)
|
||||
kind = state_of(c, level, shadow)
|
||||
@@ -667,7 +642,7 @@ def build(c, *, level: str, checks: list, now: datetime | None = None) -> dict:
|
||||
"plan": build_plan(c, now, shadow, running, held=held,
|
||||
settled=settled),
|
||||
"attention": ([{"severity": INFO, "id": "shadow",
|
||||
"title": ATTN_SHADOW, "body": attn_shadow_body(),
|
||||
"title": attn_shadow(), "body": attn_shadow_body(),
|
||||
"docs": None}] if shadow else []) + attention(checks),
|
||||
"month_peak": month_peak(c, now),
|
||||
"control": {"running": bool(c.auto), "shadow": shadow,
|
||||
@@ -687,279 +662,309 @@ def _docs_url():
|
||||
# The words. STYLE.md: plain, short, present tense. A plan line is at most
|
||||
# two sentences of at most 12 words each (UX-13 design, section 4); a title at
|
||||
# most 5 words; everything else one sentence of at most 25 words. In shadow
|
||||
# mode a text says what the add-on WOULD do. test_messages.py renders every
|
||||
# function below through `text_samples()` and checks it.
|
||||
# mode a text says what the add-on WOULD do. The sentences themselves are in
|
||||
# app/ui_text/<lang>.json under `home_words` (UX-13 P5: English and Dutch).
|
||||
# test_messages.py and test_ui_text.py render every function below through
|
||||
# `text_samples()` in each language and check it.
|
||||
# =========================================================================== #
|
||||
MODE_HOUSE = "Follows the house"
|
||||
ATTN_SHADOW = "Shadow mode is on"
|
||||
_LANG = ["en"] # the language of the build in progress (build())
|
||||
_WORDS: dict = {}
|
||||
|
||||
|
||||
def _hw() -> dict:
|
||||
lang = _LANG[0]
|
||||
if lang not in _WORDS:
|
||||
_WORDS[lang] = uitext.words(lang)
|
||||
return _WORDS[lang]
|
||||
|
||||
|
||||
def _t(path, **values) -> str:
|
||||
node = _hw()["home_words"]
|
||||
for k in path.split("."):
|
||||
node = node[k]
|
||||
return uitext.say(node, **values)
|
||||
|
||||
|
||||
def _dec(text) -> str:
|
||||
"""A decimal comma in Dutch."""
|
||||
return text.replace(".", ",") if _LANG[0] == "nl" else text
|
||||
|
||||
|
||||
def _kw(w) -> str:
|
||||
return _dec(f"{abs(float(w)) / 1000:.1f}") + " kW"
|
||||
|
||||
|
||||
def _eur(v) -> str:
|
||||
return "€" + _dec(f"{float(v):.2f}")
|
||||
|
||||
|
||||
def _kwh(v) -> str:
|
||||
return _dec(f"{float(v):.1f}") + " kWh"
|
||||
|
||||
|
||||
# Row id -> the title on Home. The row's own sentence is the body, and it stays
|
||||
# in English (app/messages.py). Five titles are the notification's own title,
|
||||
# which test_ui_text.py checks.
|
||||
_EN = uitext.words("en")["home_words"]
|
||||
ATTN_TITLES = {(None if k == "none" else k): v for k, v in _EN["attn"].items()}
|
||||
ATTN_FROM_MESSAGES = {
|
||||
"meter_frozen": messages.T_METER_FROZEN,
|
||||
"power_too_high": messages.T_OUTPUT_CLAMP,
|
||||
"not_keeping_up": messages.T_LOOP_BOUND,
|
||||
"circulating": messages.T_CIRCULATING,
|
||||
"delivery": messages.S_NOT_DELIVERING,
|
||||
}
|
||||
VERDICT = dict(_EN["verdict"])
|
||||
MODE_HOUSE, ATTN_SHADOW = _EN["mode_house"], _EN["attn_shadow"]
|
||||
ZERO_HELD, ZERO_GOING = _EN["zero_held"], _EN["zero_going"]
|
||||
|
||||
|
||||
def mode_house() -> str:
|
||||
return _t("mode_house")
|
||||
|
||||
|
||||
def attn_shadow() -> str:
|
||||
return _t("attn_shadow")
|
||||
|
||||
|
||||
def attn_shadow_body() -> str:
|
||||
return "The add-on decides everything, but every inverter stays at 0 W."
|
||||
|
||||
ZERO_HELD, ZERO_GOING = "Held at 0 W", "Going to 0 W"
|
||||
VERDICT = {
|
||||
"held": ZERO_HELD,
|
||||
"stopped": "Stopped",
|
||||
"cycle": "Monthly cycle today",
|
||||
"trade": "Trading on price",
|
||||
"house": "Follows the house",
|
||||
}
|
||||
return _t("attn_shadow_body")
|
||||
|
||||
|
||||
def _w(shadow, live, would):
|
||||
return would if shadow else live
|
||||
def verdict_words(key, settled=True) -> str:
|
||||
return _t("verdict." + key) if settled or key != "held" \
|
||||
else _t("zero_going")
|
||||
|
||||
|
||||
def attn_title(rid) -> str:
|
||||
table = _hw()["home_words"]["attn"]
|
||||
return table.get(rid or "none", table["none"])
|
||||
|
||||
|
||||
def month_name(local) -> str:
|
||||
return _hw()["home_words"]["months"][local.month - 1]
|
||||
|
||||
|
||||
def _w(shadow, key, **values) -> str:
|
||||
"""`key`, or its `key_would` form in shadow mode."""
|
||||
return _t(key + "_would" if shadow else key, **values)
|
||||
|
||||
|
||||
def pill(kind) -> str:
|
||||
return {"running": "Running", "shadow": "Shadow mode",
|
||||
"stopped": "Stopped", "alarm": "Needs you now",
|
||||
"warning": "Running, with a warning"}[kind]
|
||||
return _t("pill." + kind)
|
||||
|
||||
|
||||
def banner_sub(kind) -> str:
|
||||
return {
|
||||
"running": "The batteries follow the plan below.",
|
||||
"shadow": "The plans below say what the add-on would do.",
|
||||
"stopped": "Start control below when you are ready.",
|
||||
"alarm": "See Needs attention below for what to do.",
|
||||
"warning": "See Needs attention below.",
|
||||
}[kind]
|
||||
return _t("banner_sub." + kind)
|
||||
|
||||
|
||||
def tile_solar(pv_w, has_solar) -> str:
|
||||
if not has_solar:
|
||||
return "Not set up"
|
||||
return _t("tile.not_set_up")
|
||||
if pv_w is None:
|
||||
return "No reading"
|
||||
return "Producing" if pv_w > IDLE_W else "Not producing"
|
||||
return _t("tile.no_reading")
|
||||
return _t("tile.producing") if pv_w > IDLE_W else _t("tile.not_producing")
|
||||
|
||||
|
||||
def tile_house(house_w, has_solar, disagree=False) -> str:
|
||||
if not has_solar:
|
||||
return "Needs a solar reading"
|
||||
return _t("tile.needs_solar")
|
||||
if disagree:
|
||||
return "Readings do not add up"
|
||||
return "No reading" if house_w is None else "Using"
|
||||
return _t("tile.not_adding_up")
|
||||
return _t("tile.no_reading") if house_w is None else _t("tile.using")
|
||||
|
||||
|
||||
def tile_grid(grid_w) -> str:
|
||||
if grid_w is None:
|
||||
return "No reading"
|
||||
return _t("tile.no_reading")
|
||||
if grid_w > IDLE_W:
|
||||
return "Taking from the grid"
|
||||
return _t("tile.taking")
|
||||
if grid_w < -IDLE_W:
|
||||
return "Feeding in"
|
||||
return "Balanced"
|
||||
return _t("tile.feeding")
|
||||
return _t("tile.balanced")
|
||||
|
||||
|
||||
def _zero(settled) -> str:
|
||||
return ZERO_HELD if settled else ZERO_GOING
|
||||
return _t("zero_held") if settled else _t("zero_going")
|
||||
|
||||
|
||||
def tile_batt(batt_w, shadow, settled=True) -> str:
|
||||
if shadow:
|
||||
return _zero(settled)
|
||||
return {"giving": "Giving power", "charging": "Charging",
|
||||
"resting": "Resting", None: "No reading"}[flow(batt_w)]
|
||||
return _t("tile." + {"giving": "giving", "charging": "charging",
|
||||
"resting": "resting",
|
||||
None: "no_reading"}[flow(batt_w)])
|
||||
|
||||
|
||||
def battery_now(soc, batt_w, *, phase, shadow, running, held=False,
|
||||
would_w=None, settled=True) -> str:
|
||||
if soc is None or batt_w is None:
|
||||
return "No reading from this battery."
|
||||
return _t("now.no_reading")
|
||||
zero = _zero(settled)
|
||||
if shadow:
|
||||
if would_w is None or abs(would_w) <= IDLE_W:
|
||||
return f"{zero}. Would rest now."
|
||||
verb = "give" if would_w > 0 else "charge"
|
||||
return f"{zero}. Would {verb} {_kw(would_w)} now."
|
||||
return _t("now.would_rest", zero=zero)
|
||||
verb = "would_give" if would_w > 0 else "would_charge"
|
||||
return _t("now." + verb, zero=zero, kw=_kw(would_w))
|
||||
if held:
|
||||
return f"{zero} until the alarm clears."
|
||||
return _t("now.held", zero=zero)
|
||||
if not running:
|
||||
return f"{zero} while control is stopped."
|
||||
return _t("now.stopped", zero=zero)
|
||||
if phase != M_IDLE:
|
||||
return {M_DRAIN: "Monthly cycle: emptying.",
|
||||
M_CHARGE: "Monthly cycle: refilling.",
|
||||
M_HOLD: "Monthly cycle: holding full."}.get(
|
||||
phase, "Monthly cycle under way.")
|
||||
return _t("now." + {M_DRAIN: "cycle_drain", M_CHARGE: "cycle_charge",
|
||||
M_HOLD: "cycle_hold"}.get(phase, "cycle"))
|
||||
st = flow(batt_w)
|
||||
if st == "giving":
|
||||
return f"Giving {_kw(batt_w)} now."
|
||||
return _t("now.giving", kw=_kw(batt_w))
|
||||
if st == "charging":
|
||||
return f"Charging {_kw(batt_w)} now."
|
||||
return "Resting now."
|
||||
return _t("now.charging", kw=_kw(batt_w))
|
||||
return _t("now.resting")
|
||||
|
||||
|
||||
def plan_head(key, shadow, settled=True) -> str:
|
||||
if key == "held":
|
||||
return ("Every inverter is at 0 W until the alarm clears." if settled
|
||||
else "Every inverter goes to 0 W until the alarm clears.")
|
||||
return _t("head.held" if settled else "head.held_going")
|
||||
if key == "stopped":
|
||||
return "Control is stopped, so nothing is planned."
|
||||
return _t("head.stopped")
|
||||
if not shadow:
|
||||
return {
|
||||
"cycle": "A battery runs its monthly cycle today.",
|
||||
"trade": "Charges in the cheapest hour, gives power in the dearest.",
|
||||
"house": "Charges from spare solar and covers the house.",
|
||||
}[key]
|
||||
return "Shadow mode: this is what the add-on would do. " + {
|
||||
"cycle": "A battery would run its monthly cycle today.",
|
||||
"trade": "Would charge when cheapest and give power when dearest.",
|
||||
"house": "Would charge from spare solar and cover the house.",
|
||||
}[key]
|
||||
return _t("head." + key)
|
||||
return _t("head.shadow") + " " + _t("head." + key + "_would")
|
||||
|
||||
|
||||
def run_grid_what(kwh, shadow) -> str:
|
||||
return _w(shadow, "Charges", "Would charge") + \
|
||||
f" up to {_kwh(kwh)} from the grid."
|
||||
return _w(shadow, "run.grid_what", kwh=_kwh(kwh))
|
||||
|
||||
|
||||
# The trade legs and the price line use the feed's own points (a 15-minute
|
||||
# period on a quarter-hourly feed), so both name the same price and time.
|
||||
def run_grid_why(price) -> str:
|
||||
return f"Lowest published price, {_eur(price)} per kWh."
|
||||
return _t("run.grid_why", eur=_eur(price))
|
||||
|
||||
|
||||
def run_grid_amount(eur) -> str:
|
||||
return f"Costs up to {_eur(eur)}."
|
||||
return _t("run.grid_amount", eur=_eur(eur))
|
||||
|
||||
|
||||
def run_give_what(kwh, shadow) -> str:
|
||||
return _w(shadow, "Gives", "Would give") + \
|
||||
f" up to {_kwh(kwh)}, to the house first."
|
||||
return _w(shadow, "run.give_what", kwh=_kwh(kwh))
|
||||
|
||||
|
||||
def run_give_why(price) -> str:
|
||||
return f"Highest published price, {_eur(price)} per kWh."
|
||||
return _t("run.give_why", eur=_eur(price))
|
||||
|
||||
|
||||
def run_give_amount(eur) -> str:
|
||||
return f"Worth up to {_eur(eur)} at that price."
|
||||
return _t("run.give_amount", eur=_eur(eur))
|
||||
|
||||
|
||||
def run_cycle_what(phase, shadow) -> str:
|
||||
if phase == M_IDLE:
|
||||
return _w(shadow, "Starts", "Would start") + \
|
||||
" the monthly battery cycle."
|
||||
return _w(shadow, "Runs", "Would run") + " the monthly battery cycle now."
|
||||
return _w(shadow, "run.cycle_start" if phase == M_IDLE else "run.cycle_now")
|
||||
|
||||
|
||||
def run_cycle_why(shadow=False) -> str:
|
||||
return _w(shadow, "Empties, refills and holds full",
|
||||
"Would empty, refill and hold full") + \
|
||||
", so the level reads right."
|
||||
return _w(shadow, "run.cycle_why")
|
||||
|
||||
|
||||
def run_cycle_amount(hours) -> str:
|
||||
if hours is None:
|
||||
return "How long depends on the battery."
|
||||
return f"Takes about {max(1, round(hours))} hours."
|
||||
return _t("run.cycle_amount_unknown")
|
||||
return _t("run.cycle_amount", hours=max(1, round(hours)))
|
||||
|
||||
|
||||
def run_peak_what(limit_w, shadow) -> str:
|
||||
return _w(shadow, "Gives", "Would give") + \
|
||||
f" power to stay under {_kw(limit_w)}."
|
||||
return _w(shadow, "run.peak_what", kw=_kw(limit_w))
|
||||
|
||||
|
||||
def run_peak_why() -> str:
|
||||
return "The peak limit comes before everything else."
|
||||
return _t("run.peak_why")
|
||||
|
||||
|
||||
def run_house_what(shadow) -> str:
|
||||
return _w(shadow, "Follows", "Would follow") + " the house."
|
||||
return _w(shadow, "run.house_what")
|
||||
|
||||
|
||||
def run_house_why(shadow=False) -> str:
|
||||
return _w(shadow, "Charges from spare solar, gives power",
|
||||
"Would charge from spare solar, give power") + \
|
||||
" when the house needs it."
|
||||
return _w(shadow, "run.house_why")
|
||||
|
||||
|
||||
def run_house_amount() -> str:
|
||||
return "Amounts depend on the sun and the house."
|
||||
return _t("run.house_amount")
|
||||
|
||||
|
||||
def line_no_trade(key) -> str:
|
||||
return {
|
||||
"off": "",
|
||||
"not_covered": "No trading on price: nothing measures the 15-minute "
|
||||
"peak.",
|
||||
"no_prices": "No trading on price yet: no prices have arrived.",
|
||||
"peak": "No trading on price now: the peak limit comes first.",
|
||||
"not_firm": "No trading on price yet: the prices are not published.",
|
||||
"not_worth": "No trading on price today. The price gap is too small.",
|
||||
"no_level": "No trading on price now: a battery level is unknown.",
|
||||
}[key]
|
||||
return "" if key == "off" else _t("no_trade." + key)
|
||||
|
||||
|
||||
def line_trade_tomorrow(kind, at, price, shadow) -> str:
|
||||
verb = {"grid": _w(shadow, "Charges", "Would charge"),
|
||||
"give": _w(shadow, "Gives power", "Would give power")}[kind]
|
||||
return f"Tomorrow: {verb.lower()} at {at}, {_eur(price)} per kWh."
|
||||
return _w(shadow, "tomorrow_trade." + kind, at=at, eur=_eur(price))
|
||||
|
||||
|
||||
def line_cycle_waits(name) -> str:
|
||||
return f"{name} waits its turn for the monthly cycle."
|
||||
return _t("cycle_waits", name=name)
|
||||
|
||||
|
||||
def line_peak_limit(limit_w, shadow) -> str:
|
||||
return _w(shadow, "The batteries step in", "The batteries would step in") \
|
||||
+ f" above {_kw(limit_w)}."
|
||||
return _w(shadow, "step_in", kw=_kw(limit_w))
|
||||
|
||||
|
||||
def line_prices(now_eur, lo, hi) -> str:
|
||||
parts = []
|
||||
if now_eur is not None:
|
||||
parts.append(f"Price now {_eur(now_eur)} per kWh, all in.")
|
||||
parts.append(_t("prices.now", eur=_eur(now_eur)))
|
||||
if lo is not None and hi is not None:
|
||||
parts.append(f"Today: lowest {_eur(lo['eur_kwh'])} at "
|
||||
f"{lo['label']}, highest {_eur(hi['eur_kwh'])} at "
|
||||
f"{hi['label']}.")
|
||||
return " ".join(parts) or "No prices for today."
|
||||
parts.append(_t("prices.today", lo=_eur(lo["eur_kwh"]),
|
||||
lo_at=lo["label"], hi=_eur(hi["eur_kwh"]),
|
||||
hi_at=hi["label"]))
|
||||
return " ".join(parts) or _t("prices.none")
|
||||
|
||||
|
||||
def line_tomorrow(known) -> str:
|
||||
return ("Tomorrow's prices are in." if known else
|
||||
"Tomorrow's prices usually arrive after 13:00.")
|
||||
return _t("tomorrow.known" if known else "tomorrow.unknown")
|
||||
|
||||
|
||||
def line_region(rid, year) -> str:
|
||||
if rid in REGION_PRESETS and year:
|
||||
return f"Levies and VAT: {REGION_WORDS.get(rid, rid)}, {year} figures."
|
||||
return "Levies and VAT: your own figure."
|
||||
|
||||
|
||||
REGION_WORDS = {"flanders": "Flanders, Fluvius Antwerpen"}
|
||||
name = _hw()["choices"]["region"].get(rid, rid)
|
||||
return _t("region.preset", region=name, year=year)
|
||||
return _t("region.own")
|
||||
|
||||
|
||||
def line_peak_not_covered() -> str:
|
||||
return "No peak limit: this meter does not measure 15-minute periods."
|
||||
return _t("peak_not_covered")
|
||||
|
||||
|
||||
def line_period(period_w, limit_w, shadow) -> str:
|
||||
step = _w(shadow, "The batteries step in", "The batteries would step in")
|
||||
step = _w(shadow, "step_in", kw=_kw(limit_w))
|
||||
if period_w is None:
|
||||
return f"{step} above {_kw(limit_w)}."
|
||||
return (f"This 15-minute period is heading for {_kw(period_w)}. "
|
||||
f"{step} above {_kw(limit_w)}.")
|
||||
return step
|
||||
return _t("period", kw=_kw(period_w)) + " " + step
|
||||
|
||||
|
||||
def control_text(running, shadow) -> str:
|
||||
if shadow:
|
||||
return "Shadow mode: every inverter stays at 0 W."
|
||||
return _t("control.shadow")
|
||||
if running:
|
||||
return "Control is running."
|
||||
return "Control is stopped. Every inverter is at 0 W."
|
||||
return _t("control.running")
|
||||
return _t("control.stopped")
|
||||
|
||||
|
||||
def text_samples() -> list:
|
||||
"""`(kind, text)` for every sentence above, rendered with real values, so
|
||||
test_messages.py can hold each to its STYLE limit. Kinds: title, plan
|
||||
(2 sentences of 12 words), line (1 sentence of 25 words)."""
|
||||
out = [("title", t) for t in VERDICT.values()] + [("title", ZERO_GOING)]
|
||||
out += [("title", t) for t in ATTN_TITLES.values()]
|
||||
out += [("title", MODE_HOUSE), ("title", ATTN_SHADOW),
|
||||
def text_samples(lang="en") -> list:
|
||||
"""`(kind, text)` for every sentence above in `lang`, rendered with real
|
||||
values, so test_messages.py can hold each to its STYLE limit. Kinds: title,
|
||||
plan (2 sentences of 12 words), line (1 sentence of 25 words)."""
|
||||
_LANG[0] = uitext.lang_of(lang)
|
||||
try:
|
||||
return _samples()
|
||||
finally:
|
||||
_LANG[0] = "en"
|
||||
|
||||
|
||||
def _samples() -> list:
|
||||
words = _hw()["home_words"]
|
||||
out = [("title", t) for t in words["verdict"].values()]
|
||||
out += [("title", _t("zero_going")), ("title", _t("zero_held"))]
|
||||
out += [("title", t) for t in words["attn"].values()]
|
||||
out += [("title", mode_house()), ("title", attn_shadow()),
|
||||
("line", attn_shadow_body())]
|
||||
for k in ("running", "shadow", "stopped", "alarm", "warning"):
|
||||
out += [("title", pill(k)), ("line", banner_sub(k))]
|
||||
|
||||
Reference in New Issue
Block a user