Pablan, as it stands

Self-hosted knowledge management for SMEs: a split-screen Markdown editor
whose sections an LLM refines while you write, and RAG question answering
over the documents that result. FastAPI + Postgres/pgvector on the back,
SvelteKit on the front, everything OpenAI-compatible and self-hostable.

Squashed into a single commit; the development history stays local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
This commit is contained in:
ProfessorNova
2026-09-04 08:36:17 +02:00
co-authored by Claude Opus 5
commit 97dbff309c
346 changed files with 43430 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""The backend never renders UI-language strings (CLAUDE.md).
API errors are `{detail, code}` and the frontend translates by `code`; SSE
`state` events carry counts and markers, and the frontend writes the
sentence. That contract is only worth anything if it is checked: the moment
one German error message ships from the backend, an English interface has a
German sentence in it that no message file can reach.
This looks for German text in the places that reach a user: `ApiError`
messages and `detail` fields. Prompts, template content, the fixture corpus
and comments are deliberately out of scope, because those are CONTENT and
German is correct there.
"""
import re
import sys
from pathlib import Path
APP = Path(__file__).resolve().parents[1] / "app"
# Words that do not appear in English but are common in German UI copy.
# Umlauts alone are too weak (proper nouns), and a full language detector
# would be a dependency for a rule this narrow.
GERMAN_MARKERS = re.compile(
r"\b("
r"nicht|nichts|kein|keine|keinen|wurde|wurden|werden|wird|"
r"bitte|erneut|konnte|könnte|müssen|muss|darfst|kannst|"
r"deine|deinem|deiner|dein|Ihre|Ihrem|"
r"Fehler|Anfrage|Dokument|Abteilung|Benutzer|Gespräch|Vorlage"
r")\b",
re.IGNORECASE,
)
# Only the strings that can reach a user, not every literal in the file.
USER_FACING = re.compile(
r"""ApiError\s*\(\s*\d+\s*,\s*(?P<q>["'])(?P<text>.*?)(?P=q)"""
r"""|detail\s*=\s*(?P<q2>["'])(?P<text2>.*?)(?P=q2)""",
re.DOTALL,
)
def main() -> int:
problems: list[str] = []
for path in sorted(APP.rglob("*.py")):
source = path.read_text(encoding="utf-8")
for match in USER_FACING.finditer(source):
text = match.group("text") or match.group("text2") or ""
if not GERMAN_MARKERS.search(text):
continue
line = source[: match.start()].count("\n") + 1
relative = path.relative_to(APP.parent)
problems.append(
f"{relative}:{line}: user-facing string looks German: "
f"{text[:70]!r}. The backend returns codes, the frontend "
f"writes the sentence (docs/i18n.md)."
)
if problems:
print("check-no-ui-strings: FAILED", file=sys.stderr)
for problem in problems:
print(f" {problem}", file=sys.stderr)
return 1
print("check-no-ui-strings: no UI-language strings in backend responses")
return 0
if __name__ == "__main__":
sys.exit(main())