Post translation via Interpreter (milestone 143) #201

Merged
bvandeusen merged 7 commits from dev into main 2026-07-07 13:05:18 -04:00
Owner

Translate non-English post title/description to English via the operator's self-hosted Interpreter LAN service (LibreTranslate-compatible), surfaced translation-forward with a per-card "show original" toggle. Platform-agnostic (auto-detects language). Off + unconfigured by default.

  • Data (migration 0083): Post gains post_title_translated / description_translated / translated_source_lang / translation_engine_version / translated_at. ImportSettings gains translation_enabled (off), interpreter_base_url (empty — no default host; operator sets their own, behind a reverse proxy), translation_target_lang (en).
  • Client (interpreter_client.py, sync/requests — no new dep): batch translate mapping translatedText[]↔texts, passthrough unchanged, InterpreterUnavailable (503/conn) vs InterpreterBadRequest (400), health() via /v1/health.
  • Sweep (tasks/translation.py, maintenance_long lane + daily beat): translates untranslated non-English posts per-post, stores engine_version (cache-fast re-runs); idempotent (= rule-89 recovery), wall-clock bounded, no-op unless enabled + URL set + healthy.
  • API: /settings/import exposes the 3 knobs; /settings/translation/status, /run, and /test (on-demand health check of a given URL).
  • UI: PostCard + modal ProvenancePanel translation-forward with "Show original (⟨lang⟩)"; TranslationCard in Settings (enable, base URL w/ generic placeholder, target lang, reachability dot + Test connection + untranslated count + Translate now).
  • Tests across client (mocked HTTP), sweep (stubbed client), settings, endpoints, serialization.

One migration (0083). CI green on dev head 6a25548 across every step.

🤖 Generated with Claude Code

Translate non-English post title/description to English via the operator's self-hosted **Interpreter** LAN service (LibreTranslate-compatible), surfaced translation-forward with a per-card "show original" toggle. Platform-agnostic (auto-detects language). Off + unconfigured by default. - **Data** (migration **0083**): Post gains post_title_translated / description_translated / translated_source_lang / translation_engine_version / translated_at. ImportSettings gains translation_enabled (off), interpreter_base_url (**empty — no default host; operator sets their own, behind a reverse proxy**), translation_target_lang (en). - **Client** (`interpreter_client.py`, sync/`requests` — no new dep): batch translate mapping translatedText[]↔texts, passthrough unchanged, InterpreterUnavailable (503/conn) vs InterpreterBadRequest (400), `health()` via `/v1/health`. - **Sweep** (`tasks/translation.py`, maintenance_long lane + daily beat): translates untranslated non-English posts per-post, stores engine_version (cache-fast re-runs); idempotent (= rule-89 recovery), wall-clock bounded, no-op unless enabled + URL set + healthy. - **API**: `/settings/import` exposes the 3 knobs; `/settings/translation/status`, `/run`, and `/test` (on-demand health check of a given URL). - **UI**: PostCard + modal ProvenancePanel translation-forward with "Show original (⟨lang⟩)"; TranslationCard in Settings (enable, base URL w/ generic placeholder, target lang, reachability dot + **Test connection** + untranslated count + Translate now). - Tests across client (mocked HTTP), sweep (stubbed client), settings, endpoints, serialization. One migration (0083). CI green on dev head `6a25548` across every step. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 7 commits 2026-07-07 13:05:12 -04:00
feat(translation): Post translation columns + settings + migration (#143 step 1)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 18s
CI / backend-lint-and-test (push) Successful in 38s
CI / integration (push) Successful in 3m46s
a3bc98a53c
Post gains post_title_translated / description_translated / translated_source_lang
/ translation_engine_version / translated_at — filled by the translate sweep so
viewing is instant. ImportSettings gains translation_enabled (OFF by default),
interpreter_base_url (EMPTY — no default host; the operator points it at their own
Interpreter proxy behind a reverse proxy) and translation_target_lang (en),
exposed + validated via /settings/import. Migration 0083. Settings defaults +
patch + validation test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(translation): Interpreter client — batch translate + health (#143 step 2)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 21s
CI / backend-lint-and-test (push) Successful in 33s
CI / integration (push) Successful in 3m43s
7f8073c4c8
services/interpreter_client.py: sync (requests) client for the LibreTranslate-
compatible /v1/translate — no new dep, mirrors the platform clients. translate()
maps translatedText[]↔texts (order + length), returns detected_lang +
engine_version (aggregate = first item, fine for a per-post [title, description]
batch); passthrough items come back unchanged in their slot. InterpreterUnavailable
on 503 / connection error (retry later), InterpreterBadRequest on 400. health()
checks /v1/health engines.llm. 10 unit tests with mocked HTTP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(translation): backfill sweep + beat + manual trigger (#143 step 3)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 21s
CI / backend-lint-and-test (push) Successful in 28s
CI / integration (push) Successful in 3m49s
7a4de7278d
tasks/translation.py — translate_posts: picks untranslated posts (title OR
description non-empty), per-post [title, description] batch via the Interpreter
client, stores translations + detected lang + engine_version; passthrough /
already-target posts are marked handled with no stored translation. 503 or a
connection error interrupts (retry next cycle), 400 stops (fix config), per-post
commit keeps progress; wall-clock bounded. Wired into celery (maintenance_long
lane) + a daily beat. No-op unless enabled + base URL set + healthy. GET
/settings/translation/status + POST .../run for the Settings card. Task tests
(stubbed client, monkeypatched session).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(translation): expose translated fields in post feed + provenance payloads (#143 step 4)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 17s
CI / backend-lint-and-test (push) Successful in 31s
CI / integration (push) Successful in 3m44s
ead60978e3
post_feed_service (card + detail) and provenance_service._post_dict now include
post_title_translated, description_translated (card-truncated / detail-uncapped)
and translated_source_lang, keeping the originals for the toggle. Feed
serialization test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(ui): translation-forward post text + Settings card (#143 step 5)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 33s
CI / integration (push) Successful in 3m45s
83c1745fd0
PostCard + modal ProvenancePanel show the English title/description by default when
a translation exists, with a per-card "show original (<lang>)" toggle — translated
bodies render as plain text, originals keep their sanitized HTML. New
TranslationCard in Settings → Ingestion & filters: enable switch, Interpreter base
URL (generic placeholder, no default host), target language, a reachability
indicator + untranslated-posts count + "Translate now".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
test(translation): API endpoint tests for status + run (#143 step 6)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 18s
CI / backend-lint-and-test (push) Successful in 31s
CI / integration (push) Successful in 3m47s
af5aa21e45
/settings/translation/status defaults (off → no health call) + /run 400-when-
unconfigured + 202-when-configured (monkeypatched .delay). requests is already a
backend dep, so no requirements/ci-requirements change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
feat(translation): "Test connection" button — on-demand Interpreter health check (#143)
CI / lint (push) Successful in 4s
CI / frontend-build (push) Successful in 21s
CI / backend-lint-and-test (push) Successful in 32s
CI / integration (push) Successful in 3m43s
6a255482ea
New POST /api/settings/translation/test pings /v1/health for a GIVEN base URL (not
the saved one), so the operator can verify a URL before enabling it. TranslationCard
gains a Test-connection button that reports reachable/unreachable inline and
updates the status dot. Endpoint test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
bvandeusen merged commit ba3fd2a118 into main 2026-07-07 13:05:18 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledCurator#201