UX-13 P5 review: every STYLE rule has a mutation check, fuller jargon lists, Dutch wording, overlap partner test
- test_ui_text.py: key parity, placeholder parity, English-left-in-Dutch, semicolon, 25-word description and 2-sentence line each now fail on a spoiled input (the English-left check was not a real mutation). - Jargon lists now cover the whole 'Not' column of STYLE.md section 4, in English, with the Dutch equivalents. - nl.json: 'gewijzigd worden' where 'wijzigen' read as intransitive, 'Bezig met bewaren' for the saving state, 'Metingen kloppen niet'. - test_uimodel.py: an overlap that changes partners is named piece by piece (a surviving mutant of _tiling's partner check). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,12 +119,12 @@
|
|||||||
"periods": {"window": "Tijdvak", "from": "Van", "to": "Tot", "days": "Dagen", "price": "Prijs", "add": "Tijdvak toevoegen"},
|
"periods": {"window": "Tijdvak", "from": "Van", "to": "Tot", "days": "Dagen", "price": "Prijs", "add": "Tijdvak toevoegen"},
|
||||||
"reversed": "Meet negatief",
|
"reversed": "Meet negatief",
|
||||||
"phase": "Fase",
|
"phase": "Fase",
|
||||||
"raw_roster": {"description": "Deze lijst met omvormers kan alleen op het tabblad Configuratie wijzigen."},
|
"raw_roster": {"description": "Deze lijst met omvormers kan alleen op het tabblad Configuratie gewijzigd worden."},
|
||||||
"region_year": "cijfers van {year}",
|
"region_year": "cijfers van {year}",
|
||||||
"hour": "{hour}:00 UTC",
|
"hour": "{hour}:00 UTC",
|
||||||
"preset": {"description": "Ingevuld door de regio hierboven, dus kies een eigen bedrag om het te wijzigen."},
|
"preset": {"description": "Ingevuld door de regio hierboven, dus kies een eigen bedrag om het te wijzigen."},
|
||||||
"cycle": {"name": "Een cyclus met de hand starten", "description": "Dezelfde acties als op Start, die elk eerst om bevestiging vragen."},
|
"cycle": {"name": "Een cyclus met de hand starten", "description": "Dezelfde acties als op Start, die elk eerst om bevestiging vragen."},
|
||||||
"savebar": {"label": "Niet bewaarde wijzigingen", "one": "wijziging niet bewaard", "many": "wijzigingen niet bewaard", "discard": "Verwerpen", "save": "Bewaren", "saving": "Bewaren"},
|
"savebar": {"label": "Niet bewaarde wijzigingen", "one": "wijziging niet bewaard", "many": "wijzigingen niet bewaard", "discard": "Verwerpen", "save": "Bewaren", "saving": "Bezig met bewaren"},
|
||||||
"saved": {"name": "Bewaard, nog niet actief", "description": "De add-on werkt met de instellingen waarmee hij startte tot u hem herstart."},
|
"saved": {"name": "Bewaard, nog niet actief", "description": "De add-on werkt met de instellingen waarmee hij startte tot u hem herstart."},
|
||||||
"differs": {"name": "Bewaarde instellingen zijn niet actief", "description": "Sommige bewaarde instellingen verschillen van wat nu draait, dus herstart de add-on om ze toe te passen."},
|
"differs": {"name": "Bewaarde instellingen zijn niet actief", "description": "Sommige bewaarde instellingen verschillen van wat nu draait, dus herstart de add-on om ze toe te passen."},
|
||||||
"stale": {"name": "Elders gewijzigd", "description": "Deze instellingen zijn elders gewijzigd terwijl u bewerkte, dus bewaar opnieuw om de uwe te houden of verwerp om de andere te laden."},
|
"stale": {"name": "Elders gewijzigd", "description": "Deze instellingen zijn elders gewijzigd terwijl u bewerkte, dus bewaar opnieuw om de uwe te houden of verwerp om de andere te laden."},
|
||||||
@@ -134,11 +134,11 @@
|
|||||||
"toast": {"saved": "Bewaard. Herstart om toe te passen.", "discarded": "Wijzigingen verworpen.", "failed": "Dat lukte niet. Probeer opnieuw.", "blocked": {"description": "Los de rood gemarkeerde instellingen op en bewaar opnieuw."}, "restarting": {"description": "Herstart gevraagd, dus deze pagina antwoordt even niet."}},
|
"toast": {"saved": "Bewaard. Herstart om toe te passen.", "discarded": "Wijzigingen verworpen.", "failed": "Dat lukte niet. Probeer opnieuw.", "blocked": {"description": "Los de rood gemarkeerde instellingen op en bewaar opnieuw."}, "restarting": {"description": "Herstart gevraagd, dus deze pagina antwoordt even niet."}},
|
||||||
"tier": {"bad": "Oplossen", "warn": "Nakijken", "info": "Opmerking", "ok": "In orde"},
|
"tier": {"bad": "Oplossen", "warn": "Nakijken", "info": "Opmerking", "ok": "In orde"},
|
||||||
"derive": {
|
"derive": {
|
||||||
"raw_roster": {"description": "Deze lijst met omvormers kan alleen op het tabblad Configuratie wijzigen."},
|
"raw_roster": {"description": "Deze lijst met omvormers kan alleen op het tabblad Configuratie gewijzigd worden."},
|
||||||
"no_inverter": {"description": "Voeg minstens één omvormer toe."},
|
"no_inverter": {"description": "Voeg minstens één omvormer toe."},
|
||||||
"flat_named": {"description": "Voeg een tweede omvormer toe voor u deze een naam geeft."},
|
"flat_named": {"description": "Voeg een tweede omvormer toe voor u deze een naam geeft."},
|
||||||
"no_source": {"description": "Kies waar de dynamische prijzen vandaan komen."},
|
"no_source": {"description": "Kies waar de dynamische prijzen vandaan komen."},
|
||||||
"custom_profile": {"description": "Deze tijdvakken kunnen alleen op het tabblad Configuratie wijzigen."},
|
"custom_profile": {"description": "Deze tijdvakken kunnen alleen op het tabblad Configuratie gewijzigd worden."},
|
||||||
"no_price": {"description": "Vul een prijs per kWh in."},
|
"no_price": {"description": "Vul een prijs per kWh in."},
|
||||||
"no_periods": {"description": "Voeg minstens één tijdvak toe."},
|
"no_periods": {"description": "Voeg minstens één tijdvak toe."},
|
||||||
"period_time": {"description": "Tijdvak {row}: schrijf elk tijdstip als uren en minuten, zoals 07:00 of 24:00."},
|
"period_time": {"description": "Tijdvak {row}: schrijf elk tijdstip als uren en minuten, zoals 07:00 of 24:00."},
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
},
|
},
|
||||||
"setup": {
|
"setup": {
|
||||||
"name": "Installatie",
|
"name": "Installatie",
|
||||||
"description": "Voor installateurs: zes stappen naar een werkende installatie, en elke stap kan later wijzigen onder Instellingen.",
|
"description": "Voor installateurs: zes stappen naar een werkende installatie, en elke stap kan later gewijzigd worden onder Instellingen.",
|
||||||
"done_before": {"description": "Deze installatie is al ingesteld, dus een wijziging hier vervangt de bewaarde instellingen."},
|
"done_before": {"description": "Deze installatie is al ingesteld, dus een wijziging hier vervangt de bewaarde instellingen."},
|
||||||
"step": "Stap {n} van {total}",
|
"step": "Stap {n} van {total}",
|
||||||
"back": "Terug",
|
"back": "Terug",
|
||||||
@@ -209,7 +209,7 @@
|
|||||||
"verdict": {"held": "Op 0 W gehouden", "stopped": "Gestopt", "cycle": "Vandaag maandelijkse cyclus", "trade": "Handelt op prijs", "house": "Volgt het huis"},
|
"verdict": {"held": "Op 0 W gehouden", "stopped": "Gestopt", "cycle": "Vandaag maandelijkse cyclus", "trade": "Handelt op prijs", "house": "Volgt het huis"},
|
||||||
"pill": {"running": "Actief", "shadow": "Schaduwmodus", "stopped": "Gestopt", "alarm": "Heeft u nu nodig", "warning": "Actief, met een waarschuwing"},
|
"pill": {"running": "Actief", "shadow": "Schaduwmodus", "stopped": "Gestopt", "alarm": "Heeft u nu nodig", "warning": "Actief, met een waarschuwing"},
|
||||||
"banner_sub": {"running": "De batterijen volgen het plan hieronder.", "shadow": "De plannen hieronder tonen wat de add-on zou doen.", "stopped": "Start de regeling hieronder als u klaar bent.", "alarm": "Zie Vraagt aandacht hieronder voor wat u moet doen.", "warning": "Zie Vraagt aandacht hieronder."},
|
"banner_sub": {"running": "De batterijen volgen het plan hieronder.", "shadow": "De plannen hieronder tonen wat de add-on zou doen.", "stopped": "Start de regeling hieronder als u klaar bent.", "alarm": "Zie Vraagt aandacht hieronder voor wat u moet doen.", "warning": "Zie Vraagt aandacht hieronder."},
|
||||||
"tile": {"not_set_up": "Niet ingesteld", "no_reading": "Geen meting", "producing": "Produceert", "not_producing": "Produceert niet", "needs_solar": "Zonnemeting nodig", "not_adding_up": "Metingen kloppen niet samen", "using": "Verbruikt", "taking": "Neemt af van het net", "feeding": "Injecteert", "balanced": "In evenwicht", "giving": "Levert stroom", "charging": "Laadt", "resting": "Rust"},
|
"tile": {"not_set_up": "Niet ingesteld", "no_reading": "Geen meting", "producing": "Produceert", "not_producing": "Produceert niet", "needs_solar": "Zonnemeting nodig", "not_adding_up": "Metingen kloppen niet", "using": "Verbruikt", "taking": "Neemt af van het net", "feeding": "Injecteert", "balanced": "In evenwicht", "giving": "Levert stroom", "charging": "Laadt", "resting": "Rust"},
|
||||||
"now": {
|
"now": {
|
||||||
"no_reading": "Geen meting van deze batterij.",
|
"no_reading": "Geen meting van deze batterij.",
|
||||||
"would_rest": "{zero}. Zou nu rusten.",
|
"would_rest": "{zero}. Zou nu rusten.",
|
||||||
|
|||||||
@@ -68,18 +68,30 @@ def where(lang, path):
|
|||||||
# ============================================================================ #
|
# ============================================================================ #
|
||||||
print("1. English and Dutch have the same keys and placeholders")
|
print("1. English and Dutch have the same keys and placeholders")
|
||||||
_en, _nl = dict(leaves(EN)), dict(leaves(NL))
|
_en, _nl = dict(leaves(EN)), dict(leaves(NL))
|
||||||
_only_en = sorted(where("en", p) for p in _en if p not in _nl)
|
|
||||||
_only_nl = sorted(where("nl", p) for p in _nl if p not in _en)
|
|
||||||
|
def missing(en, nl):
|
||||||
|
"""The keys only one language has."""
|
||||||
|
return (sorted(where("en", p) for p in en if p not in nl),
|
||||||
|
sorted(where("nl", p) for p in nl if p not in en))
|
||||||
|
|
||||||
|
|
||||||
|
def holes(en, nl):
|
||||||
|
"""The Dutch strings whose {placeholders} differ from the English."""
|
||||||
|
return sorted(
|
||||||
|
f"{where('nl', p)}: {sorted(PLACEHOLDER.findall(str(en[p])))} vs "
|
||||||
|
f"{sorted(PLACEHOLDER.findall(str(t)))}"
|
||||||
|
for p, t in nl.items() if p in en
|
||||||
|
and sorted(PLACEHOLDER.findall(str(t)))
|
||||||
|
!= sorted(PLACEHOLDER.findall(str(en[p]))))
|
||||||
|
|
||||||
|
|
||||||
|
_only_en, _only_nl = missing(_en, _nl)
|
||||||
for x in _only_en + _only_nl:
|
for x in _only_en + _only_nl:
|
||||||
print(f" MISSING in the other language: {x}")
|
print(f" MISSING in the other language: {x}")
|
||||||
check(f"every one of the {len(_en)} English strings has a Dutch one, and no "
|
check(f"every one of the {len(_en)} English strings has a Dutch one, and no "
|
||||||
"Dutch string is extra", not _only_en and not _only_nl and len(_en) > 300)
|
"Dutch string is extra", not _only_en and not _only_nl and len(_en) > 300)
|
||||||
_holes = sorted(
|
_holes = holes(_en, _nl)
|
||||||
f"{where('nl', p)}: {sorted(PLACEHOLDER.findall(str(_en[p])))} vs "
|
|
||||||
f"{sorted(PLACEHOLDER.findall(str(t)))}"
|
|
||||||
for p, t in _nl.items() if p in _en
|
|
||||||
and sorted(PLACEHOLDER.findall(str(t)))
|
|
||||||
!= sorted(PLACEHOLDER.findall(str(_en[p]))))
|
|
||||||
for x in _holes:
|
for x in _holes:
|
||||||
print(f" PLACEHOLDERS differ: {x}")
|
print(f" PLACEHOLDERS differ: {x}")
|
||||||
check("every Dutch string fills the same {placeholders} as its English one",
|
check("every Dutch string fills the same {placeholders} as its English one",
|
||||||
@@ -94,10 +106,14 @@ print("\n2. every string follows STYLE.md, in both languages")
|
|||||||
JARGON = {
|
JARGON = {
|
||||||
"en": r"controllers?|control loop|setpoints?|SoC|state of charge|BMS|PV|"
|
"en": r"controllers?|control loop|setpoints?|SoC|state of charge|BMS|PV|"
|
||||||
r"failsafe|trip(?:ped)?|parked|discharg\w*|P1 feed|quarter-hour|"
|
r"failsafe|trip(?:ped)?|parked|discharg\w*|P1 feed|quarter-hour|"
|
||||||
r"payload|endpoint|JSON|API|timeout|boolean|config",
|
r"payload|endpoint|JSON|API|timeout|boolean|config|commands?|"
|
||||||
"nl": r"setpoints?|SoC|BMS|PV|failsafe|regelaar|regellus|"
|
r"charge level|ceiling|billed quarter|quarters?|banked|soft limit|"
|
||||||
r"latch|ontlad\w*|payload|endpoint|JSON|API|time-?out|boolean|"
|
r"open block|actuator|ESP32|master|meter source|circulating|"
|
||||||
r"config|kwartuur\w*",
|
r"sign disagreement|rail",
|
||||||
|
"nl": r"setpoints?|setpunt\w*|SoC|laadtoestand|BMS|PV|failsafe|regelaar|"
|
||||||
|
r"regellus|controller|latch|ontlad\w*|payload|endpoint|JSON|API|"
|
||||||
|
r"time-?out|boolean|config|kwartuur\w*|commando\w*|geparkeerd|"
|
||||||
|
r"ESP32|master",
|
||||||
}
|
}
|
||||||
JARGON = {k: re.compile(r"\b(?:" + v + r")\b", re.I) for k, v in JARGON.items()}
|
JARGON = {k: re.compile(r"\b(?:" + v + r")\b", re.I) for k, v in JARGON.items()}
|
||||||
# Details belong behind the page's DOCS link, never in the string itself.
|
# Details belong behind the page's DOCS link, never in the string itself.
|
||||||
@@ -167,6 +183,22 @@ check("a link or section in a string is caught",
|
|||||||
"See https://example.org for the rest."))
|
"See https://example.org for the rest."))
|
||||||
check("a snake_case option key is caught",
|
check("a snake_case option key is caught",
|
||||||
problems("nl", ("x",), "Zet peak_cap_w hoger"))
|
problems("nl", ("x",), "Zet peak_cap_w hoger"))
|
||||||
|
check("a semicolon is caught",
|
||||||
|
problems("en", ("x",), "Saved; restart to apply"))
|
||||||
|
check("a 26-word description is caught, and 25 words pass",
|
||||||
|
problems("en", ("x", "description"), " ".join(["word"] * 26) + ".")
|
||||||
|
and not problems("en", ("x", "description"),
|
||||||
|
" ".join(["word"] * 25) + "."))
|
||||||
|
check("a 3-sentence line is caught",
|
||||||
|
problems("en", ("x",), "One. Two. Three."))
|
||||||
|
check("a key only one language has is caught, both ways",
|
||||||
|
missing({("a",): "x", ("b",): "y"}, {("a",): "x"}) == (["en.json:b"], [])
|
||||||
|
and missing({("a",): "x"}, {("a",): "x", ("c",): "z"})
|
||||||
|
== ([], ["nl.json:c"]))
|
||||||
|
check("a placeholder dropped or renamed in Dutch is caught",
|
||||||
|
holes({("a",): "Window {row}"}, {("a",): "Tijdvak"})
|
||||||
|
and holes({("a",): "{w} now"}, {("a",): "nu {d}"})
|
||||||
|
and not holes({("a",): "{a} {b}"}, {("a",): "{b} {a}"}))
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================ #
|
# ============================================================================ #
|
||||||
@@ -219,14 +251,23 @@ print("\n5. no Dutch string is left in English")
|
|||||||
# Words that are the same in both, or names. Anything else equal is untranslated.
|
# Words that are the same in both, or names. Anything else equal is untranslated.
|
||||||
SAME = {"Meter", "Timing", "Support", "Details", "Alarm", "Info", "per kWh",
|
SAME = {"Meter", "Timing", "Support", "Details", "Alarm", "Info", "per kWh",
|
||||||
"{hour}:00 UTC", "Flanders, Fluvius Antwerpen", "Modus", "Status"}
|
"{hour}:00 UTC", "Flanders, Fluvius Antwerpen", "Modus", "Status"}
|
||||||
_same = sorted(f"{where('nl', p)} = {t!r}" for p, t in _nl.items()
|
|
||||||
if p in _en and t == _en[p] and t not in SAME
|
|
||||||
and PLACEHOLDER.sub("", t).strip(" .:,"))
|
def english(en, nl):
|
||||||
|
return sorted(f"{where('nl', p)} = {t!r}" for p, t in nl.items()
|
||||||
|
if p in en and t == en[p] and t not in SAME
|
||||||
|
and PLACEHOLDER.sub("", t).strip(" .:,"))
|
||||||
|
|
||||||
|
|
||||||
|
_same = english(_en, _nl)
|
||||||
for x in _same:
|
for x in _same:
|
||||||
print(f" ENGLISH {x}")
|
print(f" ENGLISH {x}")
|
||||||
check(f"every Dutch string is Dutch ({len(_same)} still English)", not _same)
|
check(f"every Dutch string is Dutch ({len(_same)} still English)", not _same)
|
||||||
check("mutation: an English string in nl.json is caught",
|
_spoiled = dict(_nl)
|
||||||
"Save" != NL["settings"]["savebar"]["save"] and "Save" not in SAME)
|
_spoiled[("settings", "savebar", "save")] = _en[("settings", "savebar",
|
||||||
|
"save")]
|
||||||
|
check("mutation: an English string left in nl.json is caught",
|
||||||
|
english(_en, _spoiled) == ["nl.json:settings.savebar.save = 'Save'"])
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================ #
|
# ============================================================================ #
|
||||||
|
|||||||
@@ -283,6 +283,13 @@ check("a gap is named: the day and the times",
|
|||||||
refused(rows(("07:00", "22:00", "mon-sun", 0.3),
|
refused(rows(("07:00", "22:00", "mon-sun", 0.3),
|
||||||
("22:00", "24:00", "mon-sun", 0.2)))
|
("22:00", "24:00", "mon-sun", 0.2)))
|
||||||
== ("period_gap", {"day": "mon", "from": "00:00", "to": "07:00"}))
|
== ("period_gap", {"day": "mon", "from": "00:00", "to": "07:00"}))
|
||||||
|
check("an overlap that changes partners is named piece by piece: windows 1 "
|
||||||
|
"and 2 first, over their own times only",
|
||||||
|
refused(rows(("00:00", "24:00", "mon-sun", 0.3),
|
||||||
|
("06:00", "08:00", "mon-sun", 0.2),
|
||||||
|
("08:00", "10:00", "mon-sun", 0.2)))
|
||||||
|
== ("period_overlap", {"day": "mon", "from": "06:00", "to": "08:00",
|
||||||
|
"a": 1, "b": 2}))
|
||||||
check("a gap on one day only is found on that day",
|
check("a gap on one day only is found on that day",
|
||||||
refused(rows(("00:00", "24:00", "mon-fri", 0.3),
|
refused(rows(("00:00", "24:00", "mon-fri", 0.3),
|
||||||
("00:00", "24:00", "sat", 0.2)))
|
("00:00", "24:00", "sat", 0.2)))
|
||||||
|
|||||||
Reference in New Issue
Block a user