feat(ai): let users pick the translation target language (LC-688) #654

Merged
longjacksonle merged 1 commit from feat/LC-688-translate-picker into main 2026-08-09 05:08:56 +02:00

Problem

Per-message Translate always targeted the viewer's UI locale (current_lang_code), and i18n::language_name only maps es -> "Spanish" and everything else -> "English". So a viewer whose locale resolved to es always got Spanish with no way to choose, and translating a message already in your own language returned it unchanged (looked broken). The target was coupled to the two shipped UI locales even though the LLM can translate into any language.

Change

An in-block language picker. The Translate menu item still opens the translation block; that block now carries a language <select> (English, Spanish, Chinese, Hindi, Arabic, Portuguese, French, German, Russian, Japanese, Korean, Italian). Changing it re-posts to the translate route with the chosen target and swaps the block in place.

  • The initial menu click posts no body, so the target defaults to the viewer's locale (unchanged first-open behavior); the picker lets them switch from there.
  • New views::translate::TranslateTarget table + resolve_target(requested, viewer_locale) - a pure, unit-tested helper: prefers the picker's choice, else the viewer's locale (primary subtag, so es-MX -> es), else English. The allowlist bounds what reaches the LLM prompt.
  • The route reads lang from an all-optional Form (empty body -> None -> default), feeds target.english to the prompt, and caches per (message, target.code) so each language caches independently. Editing a message still invalidates all cached translations.
  • TranslationFragment carries the target list + selected code; translation_block.html renders the <select>. New i18n key room-msg-translate-language (en + es) for its aria-label.

Tests

  • New unit test resolve_target_prefers_request_then_viewer_then_english.
  • Full lib suite green (317), including i18n en/es catalog parity. cargo fmt + clippy clean; Tailwind rebuild confirms the select classes resolve.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PBAyCesyqJkZ5JgixXf9Lf

## Problem Per-message Translate always targeted the viewer's UI locale (`current_lang_code`), and `i18n::language_name` only maps `es` -> "Spanish" and everything else -> "English". So a viewer whose locale resolved to `es` always got Spanish with **no way to choose**, and translating a message already in your own language returned it unchanged (looked broken). The target was coupled to the two shipped UI locales even though the LLM can translate into any language. ## Change An **in-block language picker**. The Translate menu item still opens the translation block; that block now carries a language `<select>` (English, Spanish, Chinese, Hindi, Arabic, Portuguese, French, German, Russian, Japanese, Korean, Italian). Changing it re-posts to the translate route with the chosen target and swaps the block in place. - The initial menu click posts no body, so the target defaults to the viewer's locale (unchanged first-open behavior); the picker lets them switch from there. - New `views::translate::TranslateTarget` table + `resolve_target(requested, viewer_locale)` - a pure, unit-tested helper: prefers the picker's choice, else the viewer's locale (primary subtag, so `es-MX` -> `es`), else English. The allowlist bounds what reaches the LLM prompt. - The route reads `lang` from an all-optional `Form` (empty body -> `None` -> default), feeds `target.english` to the prompt, and caches per `(message, target.code)` so each language caches independently. Editing a message still invalidates all cached translations. - `TranslationFragment` carries the target list + selected code; `translation_block.html` renders the `<select>`. New i18n key `room-msg-translate-language` (en + es) for its aria-label. ## Tests - New unit test `resolve_target_prefers_request_then_viewer_then_english`. - Full lib suite green (317), including i18n en/es catalog parity. `cargo fmt` + `clippy` clean; Tailwind rebuild confirms the select classes resolve. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01PBAyCesyqJkZ5JgixXf9Lf
feat(ai): let users pick the translation target language (LC-688)
All checks were successful
check-secrets / Nosey parker (pull_request) Successful in 3s
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Kingfisher (push) Successful in 9s
Check / clippy + fmt + tests (pull_request) Successful in 4m33s
Create release / Create release from merged PR (pull_request) Has been skipped
check-secrets / Nosey parker (push) Successful in 6s
check-secrets / Kingfisher (pull_request) Successful in 6s
9f482ad33c
Per-message Translate always targeted the viewer's UI locale (crate::i18n::current_lang_code), and i18n::language_name only maps es -> "Spanish" and everything else -> "English". So a viewer whose locale resolved to es always got Spanish with no way to choose, and translating a message already in your own language returned it unchanged (looked like it did nothing). The target was also coupled to the two shipped UI locales even though the LLM can translate into any language.

This adds an in-block language picker. The Translate menu item still opens the translation block, but that block now carries a language <select> (English, Spanish, Chinese, Hindi, Arabic, Portuguese, French, German, Russian, Japanese, Korean, Italian); changing it re-posts to the translate route with the chosen target and swaps the block in place. The initial menu click posts no body, so the target defaults to the viewer's locale (unchanged first-open behavior), and the picker lets them switch from there.

- New views::translate::TranslateTarget table + resolve_target(requested, viewer_locale) - a pure, unit-tested helper that prefers the picker's choice, else the viewer's locale (primary subtag, so es-MX -> es), else English. The allowlist bounds what language name reaches the LLM prompt.
- The route reads the chosen lang from an all-optional Form (empty body -> None -> default), feeds target.english to the prompt, and caches per (message, target.code) so each language caches independently. Editing a message still invalidates all of its cached translations (delete_for_message).
- TranslationFragment now carries the target list + selected code; translation_block.html renders the <select>. New i18n key room-msg-translate-language (en + es) for its aria-label.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAyCesyqJkZ5JgixXf9Lf
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-08-09 05:06:50 +02:00
longjacksonle deleted branch feat/LC-688-translate-picker 2026-08-09 05:08:56 +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!654
No description provided.