feat(ui): translation-forward post text + Settings card (#143 step 5)
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
This commit is contained in:
@@ -58,16 +58,23 @@
|
||||
</div>
|
||||
|
||||
<div class="fc-post-card__text">
|
||||
<h3 v-if="plainTitle" class="fc-post-card__title">{{ plainTitle }}</h3>
|
||||
<h3 v-if="displayTitle" class="fc-post-card__title">{{ displayTitle }}</h3>
|
||||
<h3 v-else class="fc-post-card__title fc-post-card__title--missing">
|
||||
Post {{ post.external_post_id }}
|
||||
</h3>
|
||||
<!-- Translation toggle (#143): English shown by default; reveal the
|
||||
original, labelled with its detected language. -->
|
||||
<button
|
||||
v-if="hasTranslation" type="button" class="fc-post-card__lang"
|
||||
:title="showOriginal ? 'Show the English translation' : `Show the original${sourceLangLabel} text`"
|
||||
@click.stop="showOriginal = !showOriginal"
|
||||
>{{ showOriginal ? 'Show translation' : `Show original${sourceLangLabel}` }}</button>
|
||||
|
||||
<!-- Faithful (semantic) body render once expanded: backend-sanitized
|
||||
HTML (headings, lists, links, inline images). Collapsed and the
|
||||
no-detail fallback stay plain text. -->
|
||||
<div
|
||||
v-if="hasDescription && descExpanded && descHtml"
|
||||
v-if="hasDescription && showHtmlBody"
|
||||
class="fc-post-card__desc fc-post-card__desc--html"
|
||||
v-html="descHtml"
|
||||
/>
|
||||
@@ -75,7 +82,7 @@
|
||||
v-else-if="hasDescription" ref="descEl"
|
||||
class="fc-post-card__desc"
|
||||
:class="{ 'fc-post-card__desc--clamped': !descExpanded }"
|
||||
>{{ descText }}</p>
|
||||
>{{ descTextDisplay }}</p>
|
||||
<p v-else class="fc-post-card__desc fc-post-card__desc--missing">
|
||||
(no description)
|
||||
</p>
|
||||
@@ -231,6 +238,32 @@ const canExpand = computed(
|
||||
() => props.post.description_truncated === true || cssOverflow.value,
|
||||
)
|
||||
|
||||
// --- translation-forward (#143): the English is shown by default when a
|
||||
// translation exists; a per-card toggle reveals the original. Translations are
|
||||
// plain text, so showTranslated also decides HTML-vs-plain body rendering.
|
||||
const showOriginal = ref(false)
|
||||
const hasTranslation = computed(
|
||||
() => !!(props.post.post_title_translated || props.post.description_translated),
|
||||
)
|
||||
const showTranslated = computed(() => hasTranslation.value && !showOriginal.value)
|
||||
const sourceLangLabel = computed(() =>
|
||||
props.post.translated_source_lang ? ` (${props.post.translated_source_lang})` : '',
|
||||
)
|
||||
const displayTitle = computed(() =>
|
||||
showTranslated.value && props.post.post_title_translated
|
||||
? toPlainText(props.post.post_title_translated)
|
||||
: plainTitle.value,
|
||||
)
|
||||
const descTextDisplay = computed(() => {
|
||||
if (!showTranslated.value) return descText.value
|
||||
return descExpanded.value
|
||||
? (detail.value?.description_translated_full || props.post.description_translated)
|
||||
: props.post.description_translated
|
||||
})
|
||||
const showHtmlBody = computed(
|
||||
() => !showTranslated.value && descExpanded.value && !!descHtml.value,
|
||||
)
|
||||
|
||||
function measureOverflow () {
|
||||
const el = descEl.value
|
||||
cssOverflow.value = !!el && el.scrollHeight > el.clientHeight + 1
|
||||
@@ -467,6 +500,19 @@ function formatBytes (n) {
|
||||
font-size: 0.85rem; font-weight: 600;
|
||||
}
|
||||
.fc-post-card__more:hover { text-decoration: underline; }
|
||||
/* Translation toggle (#143) — quiet secondary affordance under the title. */
|
||||
.fc-post-card__lang {
|
||||
display: block; margin: 2px 0 6px; padding: 0;
|
||||
background: none; border: 0; cursor: pointer;
|
||||
color: rgb(var(--v-theme-on-surface-variant));
|
||||
font-size: 0.75rem; font-weight: 600;
|
||||
}
|
||||
.fc-post-card__lang:hover {
|
||||
text-decoration: underline; color: rgb(var(--v-theme-accent));
|
||||
}
|
||||
.fc-post-card__lang:focus-visible {
|
||||
outline: 2px solid rgb(var(--v-theme-accent)); outline-offset: 1px;
|
||||
}
|
||||
|
||||
.fc-post-card__atts {
|
||||
display: flex; flex-wrap: wrap; gap: 8px;
|
||||
|
||||
Reference in New Issue
Block a user