feat: add page shell, preferences and translations
Base layout with top bar, role-based menu, profile menu and phone layout; theme, language and German form of address kept in cookies; German formal, German informal and English text; Fluent avatar bundled in the image by a Node build stage; placeholder pages for the Publisher and Admin groups. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""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()
|
||||
for template in (PORTAL / "templates").rglob("*.html"):
|
||||
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)
|
||||
Reference in New Issue
Block a user