feat(translation): expose translated fields in post feed + provenance payloads (#143 step 4)
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
This commit is contained in:
@@ -200,6 +200,8 @@ class PostFeedService:
|
|||||||
atts_map = await self._attachments_for([post.id])
|
atts_map = await self._attachments_for([post.id])
|
||||||
item = self._to_dict(post, artist, source, thumbs_map, atts_map)
|
item = self._to_dict(post, artist, source, thumbs_map, atts_map)
|
||||||
item["description_full"] = html_to_plain(post.description)
|
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;
|
# Sanitized HTML body for faithful (semantic) rendering in the post view;
|
||||||
# detail-only (the feed list stays lightweight plain text). None when the
|
# detail-only (the feed list stays lightweight plain text). None when the
|
||||||
# post has no body. Inline `<img>` sources are remapped to locally-served
|
# post has no body. Inline `<img>` sources are remapped to locally-served
|
||||||
@@ -371,6 +373,12 @@ class PostFeedService:
|
|||||||
description_plain, truncated = None, False
|
description_plain, truncated = None, False
|
||||||
else:
|
else:
|
||||||
description_plain, truncated = truncate_at_word(plain_full, DESCRIPTION_LIMIT)
|
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})
|
thumbs_entry = thumbs_map.get(post.id, {"thumbs": [], "more": 0})
|
||||||
# `source` is null for filesystem-imported posts with no live
|
# `source` is null for filesystem-imported posts with no live
|
||||||
# subscription (alembic 0030). Frontend renders that as a
|
# subscription (alembic 0030). Frontend renders that as a
|
||||||
@@ -384,6 +392,11 @@ class PostFeedService:
|
|||||||
"downloaded_at": post.downloaded_at.isoformat(),
|
"downloaded_at": post.downloaded_at.isoformat(),
|
||||||
"description_plain": description_plain,
|
"description_plain": description_plain,
|
||||||
"description_truncated": truncated,
|
"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},
|
"artist": {"id": artist.id, "name": artist.name, "slug": artist.slug},
|
||||||
"source": (
|
"source": (
|
||||||
{"id": source.id, "platform": source.platform}
|
{"id": source.id, "platform": source.platform}
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ def _post_dict(p: Post) -> dict:
|
|||||||
"date": p.post_date.isoformat() if p.post_date else None,
|
"date": p.post_date.isoformat() if p.post_date else None,
|
||||||
"description_html": sanitize_post_html(p.description),
|
"description_html": sanitize_post_html(p.description),
|
||||||
"attachment_count": p.attachment_count,
|
"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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -283,6 +283,30 @@ async def test_scroll_item_shape_minimal(db):
|
|||||||
assert "description_full" not in item
|
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>かわいい</p>",
|
||||||
|
)
|
||||||
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_scroll_description_truncates_long_text(db):
|
async def test_scroll_description_truncates_long_text(db):
|
||||||
artist = await _seed_artist(db, "alice-long")
|
artist = await _seed_artist(db, "alice-long")
|
||||||
|
|||||||
Reference in New Issue
Block a user