From 7acd1910511860987c633ea185cdf6db372681d4 Mon Sep 17 00:00:00 2001 From: Giorgio Gilestro Date: Wed, 27 May 2026 20:08:54 +0200 Subject: [PATCH] log: serve localized content from the HTMX endpoints The /log page renders its content asynchronously by hitting /api/log/latest?as=html and /api/log/by-date/{day}?as=html via HTMX. Both endpoints returned StrategicLog.content (English) verbatim, ignoring the new StrategicLogTranslation table entirely. The _resolve_log_content helper I added to pages.py earlier was wired into the page handlers themselves but never reached for HTMX swaps, so Italian users only ever saw English content despite their lang='it' preference being persisted and translations being generated correctly. Fix: add a _localized_content helper in api.py that looks up the matching translation row for the requesting principal's lang. _log_partial_payload gains a content_override arg; both HTMX endpoints (log_latest, log_by_date) compute the override and pass it through. JSON paths (?as= other than html) remain English to avoid changing the public API contract. Co-Authored-By: Claude Opus 4.7 --- app/routers/api.py | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/app/routers/api.py b/app/routers/api.py index ee8eda1..7751ed9 100644 --- a/app/routers/api.py +++ b/app/routers/api.py @@ -39,6 +39,7 @@ from app.models import ( JobRun, Quote, StrategicLog, + StrategicLogTranslation, User, ) from app.schemas import ( @@ -297,11 +298,15 @@ async def news_list( # --- Strategic log ----------------------------------------------------------- -def _log_partial_payload(row: StrategicLog | None) -> dict | None: +def _log_partial_payload( + row: StrategicLog | None, + content_override: str | None = None, +) -> dict | None: if row is None: return None + content = content_override if content_override is not None else row.content return { - "content_html": _md_to_html(row.content), + "content_html": _md_to_html(content), "generated_at": row.generated_at, "model": row.model, "tone": row.tone, @@ -313,6 +318,28 @@ def _log_partial_payload(row: StrategicLog | None) -> dict | None: } +async def _localized_content( + session: AsyncSession, + row: StrategicLog | None, + principal: CurrentUser | None, +) -> str | None: + """Return the translated content_md for ``row`` when the principal has + a non-English lang preference and a matching translation row exists. + Returns None to signal 'use row.content as-is' (the default English + path).""" + if row is None or principal is None or principal.user is None: + return None + lang = (principal.user.lang or "en") + if lang == "en": + return None + t = (await session.execute( + select(StrategicLogTranslation) + .where(StrategicLogTranslation.log_id == row.id) + .where(StrategicLogTranslation.lang == lang) + )).scalar_one_or_none() + return t.content_md if t is not None else None + + def _resolve_tone_param(tone: str | None) -> str: """Normalise a query-param tone to one of the two valid values. PRO is silently mapped to INTERMEDIATE (see openrouter.PROMPT_VERSION 6).""" @@ -368,10 +395,11 @@ async def log_latest( row = (await session.execute(fallback)).scalar_one_or_none() if as_ == "html": + content_override = await _localized_content(session, row, principal) return templates.TemplateResponse( request, "partials/log.html", - {"log": _log_partial_payload(row), "tone": wanted_tone, - "paid": not free_only}, + {"log": _log_partial_payload(row, content_override=content_override), + "tone": wanted_tone, "paid": not free_only}, ) if row is None: @@ -422,10 +450,11 @@ async def log_by_date( row = (await session.execute(fallback)).scalar_one_or_none() if as_ == "html": + content_override = await _localized_content(session, row, principal) return templates.TemplateResponse( request, "partials/log.html", - {"log": _log_partial_payload(row), "tone": wanted_tone, - "paid": not free_only}, + {"log": _log_partial_payload(row, content_override=content_override), + "tone": wanted_tone, "paid": not free_only}, ) if row is None: raise HTTPException(status_code=404, detail="No log on this date")