diff --git a/backend/app/services/post_feed_service.py b/backend/app/services/post_feed_service.py
index d21f19d..0921ccd 100644
--- a/backend/app/services/post_feed_service.py
+++ b/backend/app/services/post_feed_service.py
@@ -200,6 +200,8 @@ class PostFeedService:
atts_map = await self._attachments_for([post.id])
item = self._to_dict(post, artist, source, thumbs_map, atts_map)
item["description_full"] = html_to_plain(post.description)
+ # Full (uncapped) translated description for the detail view (#143).
+ item["description_translated_full"] = post.description_translated
# Sanitized HTML body for faithful (semantic) rendering in the post view;
# detail-only (the feed list stays lightweight plain text). None when the
# post has no body. Inline `` sources are remapped to locally-served
@@ -371,6 +373,12 @@ class PostFeedService:
description_plain, truncated = None, False
else:
description_plain, truncated = truncate_at_word(plain_full, DESCRIPTION_LIMIT)
+ # Translation (#143): the stored translated description is already plain
+ # text; truncate it the same way for the card.
+ desc_trans = post.description_translated
+ desc_trans_short = (
+ truncate_at_word(desc_trans, DESCRIPTION_LIMIT)[0] if desc_trans else None
+ )
thumbs_entry = thumbs_map.get(post.id, {"thumbs": [], "more": 0})
# `source` is null for filesystem-imported posts with no live
# subscription (alembic 0030). Frontend renders that as a
@@ -384,6 +392,11 @@ class PostFeedService:
"downloaded_at": post.downloaded_at.isoformat(),
"description_plain": description_plain,
"description_truncated": truncated,
+ # Translation-forward fields (#143): shown by default when present;
+ # UI toggles back to the originals above. Source lang labels the toggle.
+ "post_title_translated": post.post_title_translated,
+ "description_translated": desc_trans_short,
+ "translated_source_lang": post.translated_source_lang,
"artist": {"id": artist.id, "name": artist.name, "slug": artist.slug},
"source": (
{"id": source.id, "platform": source.platform}
diff --git a/backend/app/services/provenance_service.py b/backend/app/services/provenance_service.py
index 4a82b43..f2c7236 100644
--- a/backend/app/services/provenance_service.py
+++ b/backend/app/services/provenance_service.py
@@ -29,6 +29,12 @@ def _post_dict(p: Post) -> dict:
"date": p.post_date.isoformat() if p.post_date else None,
"description_html": sanitize_post_html(p.description),
"attachment_count": p.attachment_count,
+ # Translation (#143): the English title/description shown by default when
+ # a translation exists; the UI toggles to the original. Source lang labels
+ # the original.
+ "title_translated": p.post_title_translated,
+ "description_translated": p.description_translated,
+ "translated_source_lang": p.translated_source_lang,
}
diff --git a/tests/test_post_feed_service.py b/tests/test_post_feed_service.py
index 78a01da..87d0de5 100644
--- a/tests/test_post_feed_service.py
+++ b/tests/test_post_feed_service.py
@@ -283,6 +283,30 @@ async def test_scroll_item_shape_minimal(db):
assert "description_full" not in item
+@pytest.mark.asyncio
+async def test_scroll_surfaces_translation_fields(db):
+ # #143: a translated post exposes the translated title/description + source
+ # lang, with the originals still present for the toggle.
+ artist = await _seed_artist(db, "alice-tr")
+ src = await _seed_source(db, artist.id, "pixiv", "https://p/alice-tr")
+ p = await _seed_post(
+ db, src.id, external_id="TR", post_date=datetime.now(UTC),
+ title="ねこ", description="
かわいい
", + ) + p.post_title_translated = "cat" + p.description_translated = "cute" + p.translated_source_lang = "ja" + await db.commit() + + page = await PostFeedService(db).scroll(cursor=None, limit=10) + item = page["items"][0] + assert item["post_title_translated"] == "cat" + assert item["description_translated"] == "cute" + assert item["translated_source_lang"] == "ja" + assert item["post_title"] == "ねこ" # original kept for the toggle + assert item["description_plain"] == "かわいい" + + @pytest.mark.asyncio async def test_scroll_description_truncates_long_text(db): artist = await _seed_artist(db, "alice-long")