diff --git a/CHANGELOG.md b/CHANGELOG.md index 72eb039..552f69f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# v0.8.82-beta — "Poort {port} is vrij" stond er letterlijk + +De poortcontrole werd aangesloten op vertaalsleutels die er al waren, en twee +daarvan verwachten een plaatshouder die niet werd meegegeven: `port_free` wil +`{port}`, `port_in_use` wil `{port}` én `{by}`. In beeld stond dus letterlijk +"Poort {port} is vrij". + +Er draait nu een test die elke `t('sleutel', { … })` in de interface naloopt en +vergelijkt met de plaatshouders in de Nederlandse tekst — in beide richtingen, +en ook voor aanroepen zónder variabelen. Dat laatste was nodig: de eerste versie +van die test keek alleen naar aanroepen die wél iets meegaven, en liet de fout +waar hij voor gemaakt was gewoon passeren. + # v0.8.81-beta — Poorten kiezen zonder gokken, en installeren op een telefoon **De poortcontrole zat er wel, maar werd nooit gevraagd.** `/api/ports/check` diff --git a/VERSION b/VERSION index 4d4311c..1c866df 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.81-beta +0.8.82-beta diff --git a/server-up/templates/index.html b/server-up/templates/index.html index 7b26983..86c8fcb 100644 --- a/server-up/templates/index.html +++ b/server-up/templates/index.html @@ -3282,11 +3282,15 @@ function app() { if (!c.free) { return c.used_by ? this.t('port_used_by', { port: c.port, container: c.used_by }) - : this.t('port_in_use', { port: c.port }); + // `by` staat in de tekst voor het geval we wél weten waardoor; hier + // weten we dat niet, dus leeg. Weglaten zou '{by}' in beeld zetten. + : this.t('port_in_use', { port: c.port, by: '' }); } // Zonder de hulpcontainer zien we alleen Docker; dan is "vrij" een // aanname en dat hoort de melding te zeggen. - return c.host_checked ? this.t('port_free') : this.t('port_free_docker_only'); + return c.host_checked + ? this.t('port_free', { port: c.port }) + : this.t('port_free_docker_only'); }, // Bij het openen van een stap meteen controleren wat er in de poortvelden // staat, in plaats van te wachten tot iemand iets typt. diff --git a/tests/test_i18n.py b/tests/test_i18n.py index d096e94..c235019 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -128,3 +128,86 @@ def test_auditnamen_bestaan_in_beide_talen(): nl, en = _laad("nl"), _laad("en") alleen_nl = sorted(k for k in nl if k.startswith("audit_") and k not in en) assert not alleen_nl, f"ontbreken in en.json: {alleen_nl}" + + +def _meegegeven(blok: str) -> set[str]: + """De sleutels uit een `{…}` in een t()-aanroep. + + JavaScript kent twee vormen: `{port: c.port}` en de verkorte `{name}`, die + hetzelfde betekent als `{name: name}`. Alleen op een dubbele punt zoeken + mist die tweede, en dan meldt de test fouten die er niet zijn. + """ + uit, huidig, diepte = set(), "", 0 + for teken in blok + ",": + if teken in "([{": + diepte += 1 + elif teken in ")]}": + diepte -= 1 + if teken == "," and diepte == 0: + deel = huidig.strip() + if deel: + naam = deel.split(":", 1)[0].strip() + if re.fullmatch(r"\w+", naam): + uit.add(naam) + huidig = "" + else: + huidig += teken + return uit + + +def _t_aanroepen(html: str): + """Elke `t('sleutel', { … })` met de meegegeven sleutels.""" + for m in re.finditer(r"\bt\(\s*'([a-z0-9_]+)'\s*,\s*\{([^{}]*)\}", html): + yield m.group(1), _meegegeven(m.group(2)) + + +def test_elke_plaatshouder_wordt_ook_meegegeven(): + """`t('port_free')` op een tekst met `{port}` erin zet letterlijk "{port}" + in beeld. + + Dat is precies wat er gebeurde toen de poortcontrole werd aangesloten op + sleutels die er al waren: `port_free` verwacht `{port}`, `port_in_use` + verwacht `{port}` én `{by}`, en die werden niet meegegeven. + """ + nl = _laad("nl") + html = (WORTEL / "templates" / "index.html").read_text(encoding="utf-8") + aanroepen = list(_t_aanroepen(html)) + assert aanroepen, "geen t()-aanroepen met variabelen gevonden — vorm veranderd?" + + fouten = [] + for sleutel, gegeven in aanroepen: + tekst = nl.get(sleutel) + if tekst is None: + continue # andere test dekt onbekende sleutels + ontbreekt = set(re.findall(r"\{(\w+)\}", tekst)) - gegeven + if ontbreekt: + fouten.append(f"{sleutel}: mist {sorted(ontbreekt)}") + + # En het geval dat de eerste versie van deze test miste: een aanroep zónder + # variabelen op een tekst die er wél verwacht. Dat was precies de fout — + # `t('port_free')` op "Poort {port} is vrij". + for m in re.finditer(r"\bt\(\s*'([a-z0-9_]+)'\s*\)", html): + sleutel = m.group(1) + tekst = nl.get(sleutel) + if tekst is None: + continue + nodig = sorted(set(re.findall(r"\{(\w+)\}", tekst))) + if nodig: + fouten.append(f"{sleutel}: aangeroepen zonder {nodig}") + assert not fouten, fouten + + +def test_geen_plaatshouder_zonder_tekst(): + """Andersom: een sleutel meegeven die niet in de tekst staat is meestal een + hernoeming die half is doorgevoerd.""" + nl = _laad("nl") + html = (WORTEL / "templates" / "index.html").read_text(encoding="utf-8") + fouten = [] + for sleutel, gegeven in _t_aanroepen(html): + tekst = nl.get(sleutel) + if tekst is None: + continue + over = gegeven - set(re.findall(r"\{(\w+)\}", tekst)) + if over: + fouten.append(f"{sleutel}: geeft {sorted(over)} mee, staat niet in de tekst") + assert not fouten, fouten