diff --git a/goodwe_controller/app/ui_text/nl.json b/goodwe_controller/app/ui_text/nl.json index fc616b0..be52870 100644 --- a/goodwe_controller/app/ui_text/nl.json +++ b/goodwe_controller/app/ui_text/nl.json @@ -119,12 +119,12 @@ "periods": {"window": "Tijdvak", "from": "Van", "to": "Tot", "days": "Dagen", "price": "Prijs", "add": "Tijdvak toevoegen"}, "reversed": "Meet negatief", "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}", "hour": "{hour}:00 UTC", "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."}, - "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."}, "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."}, @@ -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."}}, "tier": {"bad": "Oplossen", "warn": "Nakijken", "info": "Opmerking", "ok": "In orde"}, "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."}, "flat_named": {"description": "Voeg een tweede omvormer toe voor u deze een naam geeft."}, "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_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."}, @@ -151,7 +151,7 @@ }, "setup": { "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."}, "step": "Stap {n} van {total}", "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"}, "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."}, - "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": { "no_reading": "Geen meting van deze batterij.", "would_rest": "{zero}. Zou nu rusten.", diff --git a/goodwe_controller/test_ui_text.py b/goodwe_controller/test_ui_text.py index beba82d..d23a8cd 100644 --- a/goodwe_controller/test_ui_text.py +++ b/goodwe_controller/test_ui_text.py @@ -68,18 +68,30 @@ def where(lang, path): # ============================================================================ # print("1. English and Dutch have the same keys and placeholders") _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: print(f" MISSING in the other language: {x}") 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) -_holes = 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])))) +_holes = holes(_en, _nl) for x in _holes: print(f" PLACEHOLDERS differ: {x}") 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 = { "en": r"controllers?|control loop|setpoints?|SoC|state of charge|BMS|PV|" r"failsafe|trip(?:ped)?|parked|discharg\w*|P1 feed|quarter-hour|" - r"payload|endpoint|JSON|API|timeout|boolean|config", - "nl": r"setpoints?|SoC|BMS|PV|failsafe|regelaar|regellus|" - r"latch|ontlad\w*|payload|endpoint|JSON|API|time-?out|boolean|" - r"config|kwartuur\w*", + r"payload|endpoint|JSON|API|timeout|boolean|config|commands?|" + r"charge level|ceiling|billed quarter|quarters?|banked|soft limit|" + r"open block|actuator|ESP32|master|meter source|circulating|" + 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()} # 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.")) check("a snake_case option key is caught", 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. SAME = {"Meter", "Timing", "Support", "Details", "Alarm", "Info", "per kWh", "{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: print(f" ENGLISH {x}") check(f"every Dutch string is Dutch ({len(_same)} still English)", not _same) -check("mutation: an English string in nl.json is caught", - "Save" != NL["settings"]["savebar"]["save"] and "Save" not in SAME) +_spoiled = dict(_nl) +_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'"]) # ============================================================================ # diff --git a/goodwe_controller/test_uimodel.py b/goodwe_controller/test_uimodel.py index 7bb32fa..092e1da 100644 --- a/goodwe_controller/test_uimodel.py +++ b/goodwe_controller/test_uimodel.py @@ -283,6 +283,13 @@ check("a gap is named: the day and the times", refused(rows(("07:00", "22:00", "mon-sun", 0.3), ("22:00", "24:00", "mon-sun", 0.2))) == ("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", refused(rows(("00:00", "24:00", "mon-fri", 0.3), ("00:00", "24:00", "sat", 0.2)))