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 <noreply@anthropic.com>
This commit is contained in:
Giorgio Gilestro 2026-05-27 20:08:54 +02:00
parent eb31d09782
commit 7acd191051

View file

@ -39,6 +39,7 @@ from app.models import (
JobRun, JobRun,
Quote, Quote,
StrategicLog, StrategicLog,
StrategicLogTranslation,
User, User,
) )
from app.schemas import ( from app.schemas import (
@ -297,11 +298,15 @@ async def news_list(
# --- Strategic log ----------------------------------------------------------- # --- 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: if row is None:
return None return None
content = content_override if content_override is not None else row.content
return { return {
"content_html": _md_to_html(row.content), "content_html": _md_to_html(content),
"generated_at": row.generated_at, "generated_at": row.generated_at,
"model": row.model, "model": row.model,
"tone": row.tone, "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: def _resolve_tone_param(tone: str | None) -> str:
"""Normalise a query-param tone to one of the two valid values. """Normalise a query-param tone to one of the two valid values.
PRO is silently mapped to INTERMEDIATE (see openrouter.PROMPT_VERSION 6).""" 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() row = (await session.execute(fallback)).scalar_one_or_none()
if as_ == "html": if as_ == "html":
content_override = await _localized_content(session, row, principal)
return templates.TemplateResponse( return templates.TemplateResponse(
request, "partials/log.html", request, "partials/log.html",
{"log": _log_partial_payload(row), "tone": wanted_tone, {"log": _log_partial_payload(row, content_override=content_override),
"paid": not free_only}, "tone": wanted_tone, "paid": not free_only},
) )
if row is None: if row is None:
@ -422,10 +450,11 @@ async def log_by_date(
row = (await session.execute(fallback)).scalar_one_or_none() row = (await session.execute(fallback)).scalar_one_or_none()
if as_ == "html": if as_ == "html":
content_override = await _localized_content(session, row, principal)
return templates.TemplateResponse( return templates.TemplateResponse(
request, "partials/log.html", request, "partials/log.html",
{"log": _log_partial_payload(row), "tone": wanted_tone, {"log": _log_partial_payload(row, content_override=content_override),
"paid": not free_only}, "tone": wanted_tone, "paid": not free_only},
) )
if row is None: if row is None:
raise HTTPException(status_code=404, detail="No log on this date") raise HTTPException(status_code=404, detail="No log on this date")