feat(translate): inline per-message translation [operator-action] (LC-486) #466

Merged
longjacksonle merged 5 commits from feat/LC-486-inline-translation into main 2026-06-28 00:52:45 +02:00

What

Per-message "Translate" into the viewer's language (LC-486), using the operator LLM, cached. Inspired by Telegram/Teams.

How

  • A Translate item in the message overflow menu translates the message into the viewer's active locale and renders it inline under the body, with a "Show original" toggle.
  • Cache (message_translations, migration 0071) keyed by (message_id, locale) so repeat opens and viewers sharing a locale are free. Editing a message invalidates its cached translations (patch_message -> delete_for_message) so a re-translate reflects the new body.
  • LLM: reuses LlmClient::complete with a translation prompt; target language from the viewer's resolved locale (i18n::current_lang_code + language_name). Markdown/code/URLs/@mentions preserved.
  • Gating: the Translate item is CSS-gated on an LLM being configured via data-lc-llm on the room/DM page root (DmPage gains llm_available; RoomPage already had it). Because the rule is ancestor-based, it also covers live-swapped message fragments - avoiding threading a flag through all 17 MessageView constructors.

Tests

Route: translate returns the model output and caches it; 400 when no LLM; cache upsert + invalidate round-trip. Regenerated the LC-77 webhook/email render fixtures for the new menu item + translation container. just check, just test, just test-saas all green.

Operator note

[operator-action] marker: no action required, but with LETS_CHAT_LLM_URL set, the Translate action now sends a message's text to the endpoint (cached) - in addition to the existing summary/transcript paths. Documented in the CLAUDE.md LLM row.

Verify

just verify builds the release binary clean; boot fails only on the missing SSO env (environmental, as in prior PRs).

## What Per-message "Translate" into the viewer's language (LC-486), using the operator LLM, cached. Inspired by Telegram/Teams. ## How - A **Translate** item in the message overflow menu translates the message into the viewer's active locale and renders it inline under the body, with a "Show original" toggle. - **Cache** (`message_translations`, migration 0071) keyed by (message_id, locale) so repeat opens and viewers sharing a locale are free. Editing a message invalidates its cached translations (`patch_message` -> `delete_for_message`) so a re-translate reflects the new body. - **LLM**: reuses `LlmClient::complete` with a translation prompt; target language from the viewer's resolved locale (`i18n::current_lang_code` + `language_name`). Markdown/code/URLs/@mentions preserved. - **Gating**: the Translate item is CSS-gated on an LLM being configured via `data-lc-llm` on the room/DM page root (DmPage gains `llm_available`; RoomPage already had it). Because the rule is ancestor-based, it also covers live-swapped message fragments - avoiding threading a flag through all 17 `MessageView` constructors. ## Tests Route: translate returns the model output and caches it; 400 when no LLM; cache upsert + invalidate round-trip. Regenerated the LC-77 webhook/email render fixtures for the new menu item + translation container. `just check`, `just test`, `just test-saas` all green. ## Operator note `[operator-action]` marker: no action required, but with `LETS_CHAT_LLM_URL` set, the Translate action now sends a message's text to the endpoint (cached) - in addition to the existing summary/transcript paths. Documented in the CLAUDE.md LLM row. ## Verify `just verify` builds the release binary clean; boot fails only on the missing SSO env (environmental, as in prior PRs).
Migration 0071 adds a (message_id, locale) -> translated cache (CASCADE on delete). db::translations provides get/upsert/delete_for_message. i18n::language_name maps a locale code to an English language name for the translation prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Translate a message into the viewer's active locale using the operator LLM, cache-first (keyed by message + locale). Renders the result through the message markdown pipeline into an inline block with a "show original" toggle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Translate item to the message overflow menu and a per-message #translation-{id} block under the body. The item is CSS-gated on an LLM being configured via data-lc-llm on the room/DM page root (DmPage gains llm_available; RoomPage already had it) - this also covers live-swapped message fragments since the rule is ancestor-based, avoiding threading a flag through all 17 MessageView constructors. patch_message clears cached translations so an edit re-translates. i18n en + es.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Route: translate returns the model output and caches it; 400 when no LLM; cache upsert + delete_for_message round-trip. Regenerate the LC-77 webhook/email render fixtures for the new translation container + menu item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(translate): note LLM now also translates messages [operator-action] (LC-486)
Some checks failed
check-secrets / Kingfisher (pull_request) Failing after 5s
check-secrets / TruffleHog (pull_request) Successful in 5s
check-secrets / Nosey parker (pull_request) Successful in 6s
check-secrets / Kingfisher (push) Failing after 4s
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / TruffleHog (push) Successful in 4s
Check / clippy + fmt + tests (pull_request) Successful in 3m1s
Create release / Create release from merged PR (pull_request) Has been skipped
47b24b5765
Per-message Translate reuses LETS_CHAT_LLM_*, so enabling the LLM now also sends a message's text to the endpoint on demand (cached in message_translations). Documented in the CLAUDE.md LLM env row.

Operator-Action: No action required. With LETS_CHAT_LLM_URL set, a per-message Translate action now sends that message's text to the endpoint (cached); consideration for metered or third-party engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/LC-486-inline-translation 2026-06-28 00:52:45 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!466
No description provided.