fix(messages): the welcome reads in the reader's language, not the one it was created in

E31 S1: the welcome is rendered once, at account creation, so an account that
switched language kept it in the old one (the owner's, English under a
Hungarian UI). A stored copy is recognised by its opening line (derived from
WELCOME) and served in the reader's language; other system messages stay as
stored.
This commit is contained in:
2026-10-01 21:17:09 +02:00
parent 29c0715237
commit 2543d44860
2 changed files with 75 additions and 5 deletions
+22 -5
View File
@@ -94,13 +94,30 @@ def _system_partner() -> dict:
return {"id": SYSTEM_PARTNER_ID, "name": SYSTEM_NAME, "avatar_url": None}
def _serialize_msg(m: Message) -> dict:
# The welcome's opening line in every language — how a stored copy is recognised. It was rendered
# once, in the language of the day it was created, so an account that later switched language kept
# reading it in the old one (E31 S1: the owner's, in English). Recognised here, it is served in the
# reader's language instead. Derived from WELCOME itself, so only a change to an OPENING line would
# orphan the copies already stored.
_WELCOME_OPENINGS = {text.partition("\n")[0] for text in WELCOME.values()}
def _system_body(body: str | None, lang: str | None) -> str | None:
"""A system message's text for a reader of `lang`: the welcome in their language, any other as
stored."""
if body is None or lang is None or body.partition("\n")[0] not in _WELCOME_OPENINGS:
return body
return WELCOME.get(lang, WELCOME["en"])
def _serialize_msg(m: Message, lang: str | None = None) -> dict:
"""`lang`: the reader's language, for a system message's text (a user message is ciphertext)."""
return {
"id": m.id,
"kind": m.kind,
"sender_id": m.sender_id,
"recipient_id": m.recipient_id,
"body": m.body, # only set for system messages
"body": _system_body(m.body, lang) if m.kind == "system" else m.body,
"ciphertext": m.ciphertext,
"iv": m.iv,
"read_at": iso(m.read_at),
@@ -124,7 +141,7 @@ def ensure_welcome(db: Session, user: User) -> None:
db.add(m)
db.commit()
db.refresh(m)
manager.push(user.id, {"type": "message", "message": _serialize_msg(m)})
manager.push(user.id, {"type": "message", "message": _serialize_msg(m, lang_for_user(user))})
# --- E2EE key directory / blob store -----------------------------------------
@@ -313,7 +330,7 @@ def list_conversations(user: User = Depends(require_human), db: Session = Depend
items.append(
{
"partner": partner,
"last_message": _serialize_msg(m),
"last_message": _serialize_msg(m, lang_for_user(user)),
"unread": int(unread.get(pid, 0)),
}
)
@@ -366,7 +383,7 @@ def get_thread(
# refreshes live instead of waiting for the next 20s poll. The client re-fetches the
# count on this signal, so no number is carried here.
manager.push(user.id, {"type": "unread"})
return {"partner": partner, "items": [_serialize_msg(m) for m in msgs]}
return {"partner": partner, "items": [_serialize_msg(m, lang_for_user(user)) for m in msgs]}
@router.post("")