SMTP adapter with TLS and a fake, outbox with retries, backoff, rate limit and restart safety, scheduler tick with job-run records started by gunicorn, and a send_test_mail command. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
75 lines
2.9 KiB
Python
75 lines
2.9 KiB
Python
"""Every user-visible string has German text (AD-15); the informal catalogue only differs."""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import polib
|
|
import pytest
|
|
|
|
PORTAL = Path(__file__).resolve().parent.parent
|
|
LOCALE = PORTAL / "locale"
|
|
|
|
TRANS_TAG = re.compile(r"""\{%\s*trans\s+(?:"([^"]+)"|'([^']+)')""")
|
|
BLOCKTRANS = re.compile(r"\{%\s*blocktrans[^%]*%\}(.*?)\{%\s*endblocktrans\s*%\}", re.S)
|
|
VARIABLE = re.compile(r"\{\{\s*(\w+)\s*\}\}")
|
|
PY_CALL = re.compile(r"""(?<![\w.])(?:_|gettext|gettext_lazy)\(\s*(?:"([^"]+)"|'([^']+)')""")
|
|
|
|
|
|
def msgids_in_code() -> set[str]:
|
|
found = set()
|
|
templates = [*(PORTAL / "templates").rglob("*.html"), *(PORTAL / "templates").rglob("*.txt")]
|
|
for template in templates:
|
|
text = template.read_text(encoding="utf-8")
|
|
found.update(a or b for a, b in TRANS_TAG.findall(text))
|
|
found.update(VARIABLE.sub(r"%(\1)s", body).strip() for body in BLOCKTRANS.findall(text))
|
|
for source in PORTAL.rglob("*.py"):
|
|
if "tests" in source.parts or "migrations" in source.parts:
|
|
continue
|
|
found.update(a or b for a, b in PY_CALL.findall(source.read_text(encoding="utf-8")))
|
|
return found
|
|
|
|
|
|
def catalogue(name: str) -> polib.POFile:
|
|
return polib.pofile(str(LOCALE / name / "LC_MESSAGES" / "django.po"))
|
|
|
|
|
|
def test_the_scan_finds_the_strings():
|
|
assert {"Sign out", "Home", "Hello, %(name)s", "Audit log"} <= msgids_in_code()
|
|
|
|
|
|
def test_every_string_in_the_code_has_german_text():
|
|
german = {e.msgid: e for e in catalogue("de")}
|
|
missing = sorted(msgids_in_code() - german.keys())
|
|
assert not missing, f"no German entry for: {missing}"
|
|
for entry in german.values():
|
|
assert entry.msgstr.strip(), f"empty German text for {entry.msgid!r}"
|
|
assert "fuzzy" not in entry.flags, f"fuzzy entry {entry.msgid!r}"
|
|
|
|
|
|
def test_the_german_catalogue_has_no_stale_entries():
|
|
stale = sorted({e.msgid for e in catalogue("de")} - msgids_in_code())
|
|
assert not stale, f"entries no longer used: {stale}"
|
|
|
|
|
|
def test_the_informal_catalogue_only_holds_differences_to_known_strings():
|
|
german = {e.msgid: e.msgstr for e in catalogue("de")}
|
|
for entry in catalogue("de_DU"):
|
|
assert entry.msgid in german, f"unknown string {entry.msgid!r}"
|
|
assert entry.msgstr.strip() and "fuzzy" not in entry.flags
|
|
assert entry.msgstr != german[entry.msgid], f"{entry.msgid!r} does not differ"
|
|
|
|
|
|
@pytest.mark.parametrize("name", ["de", "de_DU"])
|
|
def test_placeholders_survive_translation(name):
|
|
for entry in catalogue(name):
|
|
assert set(re.findall(r"%\(\w+\)s", entry.msgid)) == set(
|
|
re.findall(r"%\(\w+\)s", entry.msgstr)
|
|
), entry.msgid
|
|
|
|
|
|
def test_the_formal_text_does_not_use_du_and_the_informal_does_not_use_sie():
|
|
formal = " ".join(e.msgstr for e in catalogue("de"))
|
|
assert not re.search(r"\b(du|dir|dich|dein|deine)\b", formal, re.I)
|
|
informal = " ".join(e.msgstr for e in catalogue("de_DU"))
|
|
assert not re.search(r"\b(Sie|Ihnen|Ihr|Ihre)\b", informal)
|