Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified locale/de/LC_MESSAGES/messages.mo
Binary file not shown.
4 changes: 3 additions & 1 deletion locale/de/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: FileMorph VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-05-08 14:43+0200\n"
"POT-Creation-Date: 2026-05-08 20:50+0200\n"
"PO-Revision-Date: 2026-05-08 11:00+0200\n"
"Last-Translator: FileMorph <hallo@filemorph.io>\n"
"Language: de\n"
Expand Down Expand Up @@ -706,6 +706,8 @@ msgid ""
"I waive my 14-day right of withdrawal and consent to immediate contract "
"execution pursuant to §356 (5) BGB."
msgstr ""
"Ich verzichte auf mein 14-tägiges Widerrufsrecht und stimme der "
"sofortigen Vertragsausführung gemäß § 356 Abs. 5 BGB zu."

msgid "If this email exists, you'll receive a reset link shortly."
msgstr ""
Expand Down
Binary file modified locale/en/LC_MESSAGES/messages.mo
Binary file not shown.
2 changes: 1 addition & 1 deletion locale/en/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: FileMorph VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-05-08 14:43+0200\n"
"POT-Creation-Date: 2026-05-08 20:50+0200\n"
"PO-Revision-Date: 2026-05-07 13:43+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n"
Expand Down
4 changes: 2 additions & 2 deletions locale/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: FileMorph VERSION\n"
"Project-Id-Version: FileMorph 1.0.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-05-08 15:18+0200\n"
"POT-Creation-Date: 2026-05-08 20:50+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down
24 changes: 24 additions & 0 deletions scripts/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,30 @@ def _pybabel(args: list[str]) -> int:
return _run([sys.executable, "-m", "babel.messages.frontend"] + args)


def _project_version() -> str:
"""Read project version from pyproject.toml (single source of truth).

Falling back to "0.0.0" if pyproject is unparseable lets the extract
succeed in degenerate environments rather than blocking the whole
i18n pipeline on a header field.
"""
pyproject = ROOT / "pyproject.toml"
if not pyproject.exists():
return "0.0.0"
try:
for line in pyproject.read_text(encoding="utf-8").splitlines():
stripped = line.strip()
if stripped.startswith("version") and "=" in stripped:
return stripped.split("=", 1)[1].strip().strip('"').strip("'")
except OSError:
pass
return "0.0.0"


def cmd_extract() -> int:
"""Scan all sources → locale/messages.pot."""
LOCALE_DIR.mkdir(parents=True, exist_ok=True)
version = _project_version()
return _pybabel(
[
"extract",
Expand All @@ -51,6 +72,7 @@ def cmd_extract() -> int:
"-o",
str(POT_FILE),
"--project=FileMorph",
f"--version={version}",
"--copyright-holder=FileMorph",
"--no-location",
"--sort-output",
Expand Down Expand Up @@ -113,6 +135,7 @@ def cmd_drift_check() -> int:
fd, tmp_name = tempfile.mkstemp(prefix="messages_drift_", suffix=".pot")
os.close(fd)
tmp_path = Path(tmp_name)
version = _project_version()
try:
rc = _pybabel(
[
Expand All @@ -122,6 +145,7 @@ def cmd_drift_check() -> int:
"-o",
str(tmp_path),
"--project=FileMorph",
f"--version={version}",
"--copyright-holder=FileMorph",
"--no-location",
"--sort-output",
Expand Down
52 changes: 34 additions & 18 deletions tests/test_seo_foundation.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,30 +288,46 @@ def test_pricing_renders_live_buttons_with_waiver_gate_when_stripe_enabled(clien
waiver checkbox. The button starts as ``disabled`` and ``pricing.js``
unlocks it once the user actively ticks the matching waiver checkbox.
Without this two-step consent the §356 (5) BGB waiver in ``terms.html``
§9 cannot be enforced. We assert the structural anchors here so a future
refactor that removes the gate fails loudly.
§9 cannot be enforced. We assert the structural anchors AND the
legally load-bearing label text here so a future refactor that removes
either the gate or the §356/14-day disclosure fails loudly.
"""
from app.core.config import settings
from app.main import templates

monkeypatch.setattr(settings, "pricing_page_enabled", True)
monkeypatch.setattr(settings, "stripe_secret_key", "sk_test_dummy") # gitleaks:allow
from app.main import templates

original_stripe_enabled = templates.env.globals.get("stripe_enabled")
templates.env.globals["stripe_enabled"] = True

r = client.get("/pricing")
assert r.status_code == 200
body = r.text
# Each upgrade tier exposes a waiver checkbox that is wired to its button
# via the `data-target` attribute.
assert 'id="pro-waiver"' in body
assert 'data-target="pro-btn"' in body
assert 'id="business-waiver"' in body
assert 'data-target="business-btn"' in body
# The buttons themselves carry the `disabled` attribute on render — JS
# toggles it off once the checkbox is ticked.
assert 'id="pro-btn" disabled' in body
assert 'id="business-btn" disabled' in body
try:
r = client.get("/pricing")
assert r.status_code == 200
body = r.text
# Each upgrade tier exposes a waiver checkbox that is wired to its button
# via the `data-target` attribute.
assert 'id="pro-waiver"' in body
assert 'data-target="pro-btn"' in body
assert 'id="business-waiver"' in body
assert 'data-target="business-btn"' in body
# The buttons themselves carry the `disabled` attribute on render — JS
# toggles it off once the checkbox is ticked.
assert 'id="pro-btn" disabled' in body
assert 'id="business-btn" disabled' in body
# Legal load-bearing disclosure: the visible label MUST cite §356 BGB
# and the 14-day window. A copy-edit that drops either weakens the
# consent record and breaks dispute reproducibility — pin both.
body_lower = body.lower()
assert "§356" in body or "§ 356" in body, "BGB §356 (5) reference missing from waiver label"
assert "14-day" in body_lower or "14-tägig" in body_lower or "14 day" in body_lower, (
"14-day withdrawal-window disclosure missing from waiver label"
)
finally:
# Jinja globals are session-shared — revert so later tests see the
# original (unset / False) state.
if original_stripe_enabled is None:
templates.env.globals.pop("stripe_enabled", None)
else:
templates.env.globals["stripe_enabled"] = original_stripe_enabled


def test_navbar_omits_pricing_link_when_pricing_disabled(client, monkeypatch):
Expand Down
Loading