Refine presentation of translation results (#10015)

Co-authored-by: Eric Bailey <git@esb.lol>
(cherry picked from commit f8886fbfe6)
This commit is contained in:
DS Boyce
2026-03-06 13:36:58 -08:00
committed by Eric Bailey
parent 3f7113cfd6
commit 5049aef608
2 changed files with 94 additions and 57 deletions
+89 -56
View File
@@ -1,5 +1,5 @@
import {useCallback, useMemo} from 'react' import {useCallback, useMemo} from 'react'
import {Platform, View} from 'react-native' import {Platform, type StyleProp, type TextStyle, View} from 'react-native'
import {type AppBskyFeedDefs} from '@atproto/api' import {type AppBskyFeedDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
@@ -14,7 +14,7 @@ import {
} from '#/locale/helpers' } from '#/locale/helpers'
import {LANGUAGES} from '#/locale/languages' import {LANGUAGES} from '#/locale/languages'
import {useLanguagePrefs} from '#/state/preferences' import {useLanguagePrefs} from '#/state/preferences'
import {atoms as a, native, useTheme, web} from '#/alf' import {atoms as a, flatten, native, useTheme, web} from '#/alf'
import {Button} from '#/components/Button' import {Button} from '#/components/Button'
import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRightIcon} from '#/components/icons/Arrow' import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRightIcon} from '#/components/icons/Arrow'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
@@ -26,14 +26,18 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
const X_ICON_OFFSET = 16
export function TranslatedPost({ export function TranslatedPost({
hideTranslateLink = false, hideTranslateLink = false,
post, post,
postText, postText,
postTextStyle = a.text_md,
}: { }: {
hideTranslateLink?: boolean hideTranslateLink?: boolean
post: AppBskyFeedDefs.PostView post: AppBskyFeedDefs.PostView
postText: string postText: string
postTextStyle?: StyleProp<TextStyle>
}) { }) {
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const {clearTranslation, translate, translationState} = useTranslate({ const {clearTranslation, translate, translationState} = useTranslate({
@@ -54,6 +58,7 @@ export function TranslatedPost({
clearTranslation={clearTranslation} clearTranslation={clearTranslation}
translate={translate} translate={translate}
postText={postText} postText={postText}
postTextStyle={postTextStyle}
sourceLanguage={ sourceLanguage={
translationState.sourceLanguage ?? null // Fallback primarily for iOS translationState.sourceLanguage ?? null // Fallback primarily for iOS
} }
@@ -86,12 +91,12 @@ function TranslationLoading() {
const t = useTheme() const t = useTheme()
return ( return (
<View style={[a.gap_md, a.pt_md, a.align_start]}> <View style={[a.gap_md, a.mt_sm, a.align_start]}>
<View style={[a.flex_row, a.align_center, a.gap_xs]}> <View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Loader size="xs" />
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}> <Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
<Trans>Translating</Trans> <Trans>Translating</Trans>
</Text> </Text>
<Loader size="xs" fill={t.atoms.text_contrast_medium.color} />
</View> </View>
</View> </View>
) )
@@ -127,7 +132,7 @@ function TranslationLink({
<View <View
style={[ style={[
a.gap_md, a.gap_md,
a.pt_md, a.mt_sm,
a.align_start, a.align_start,
a.flex_row, a.flex_row,
a.align_center, a.align_center,
@@ -174,30 +179,41 @@ function TranslationError({
return ( return (
<View <View
style={[ style={[
a.px_lg, a.p_md,
a.pt_sm,
a.pb_md,
a.mt_sm, a.mt_sm,
a.border, a.border,
a.rounded_lg, a.rounded_lg,
a.gap_xs,
t.atoms.border_contrast_high, t.atoms.border_contrast_high,
]}> ]}>
<View style={[a.flex_row, a.align_center, a.justify_between]}> <View
<View style={[a.flex_row, a.align_center, a.mb_sm, a.gap_xs]}> style={[
<WarningIcon size="sm" fill={t.atoms.text_contrast_medium.color} /> a.flex_row,
<Text style={[a.text_xs, a.font_medium, t.atoms.text_contrast_high]}> a.align_start,
{message} a.gap_xs,
</Text> {
</View> paddingRight: X_ICON_OFFSET,
<View style={[a.flex_row, a.align_center, a.mb_xs]}> },
<Button ]}>
label={l`Hide translation`} <WarningIcon size="sm" fill={t.atoms.text_contrast_medium.color} />
hitSlop={HITSLOP_30} <Text
hoverStyle={{opacity: 0.5}} style={[
onPress={clearTranslation}> a.flex_1,
<XIcon size="sm" fill={t.atoms.text_contrast_medium.color} /> a.text_xs,
</Button> a.leading_snug,
</View> t.atoms.text_contrast_high,
]}>
{message}
</Text>
<Button
label={l`Hide translation`}
hitSlop={HITSLOP_30}
hoverStyle={native({opacity: 0.5})}
style={[a.absolute, a.z_10, {top: 0, right: 0}]}
onPress={clearTranslation}>
<XIcon size="sm" fill={t.atoms.text_contrast_medium.color} />
</Button>
</View> </View>
<View style={[a.flex_row, a.align_center]}> <View style={[a.flex_row, a.align_center]}>
<Link <Link
@@ -211,7 +227,12 @@ function TranslationError({
]} ]}
hitSlop={HITSLOP_30}> hitSlop={HITSLOP_30}>
<Text <Text
style={[a.text_xs, a.font_medium, {color: t.palette.primary_500}]}> style={[
a.text_xs,
a.font_medium,
a.leading_snug,
{color: t.palette.primary_500},
]}>
<Trans>Try Google Translate</Trans> <Trans>Try Google Translate</Trans>
</Text> </Text>
</Link> </Link>
@@ -224,12 +245,14 @@ function TranslationResult({
clearTranslation, clearTranslation,
translate, translate,
postText, postText,
postTextStyle,
sourceLanguage, sourceLanguage,
translatedText, translatedText,
}: { }: {
clearTranslation: () => void clearTranslation: () => void
translate: TranslationFunction translate: TranslationFunction
postText: string postText: string
postTextStyle?: StyleProp<TextStyle>
sourceLanguage: string | null sourceLanguage: string | null
translatedText: string translatedText: string
}) { }) {
@@ -241,39 +264,47 @@ function TranslationResult({
? codeToLanguageName(sourceLanguage, i18n.locale) ? codeToLanguageName(sourceLanguage, i18n.locale)
: undefined : undefined
const flattenedStyle = flatten(postTextStyle) ?? {}
const fontSize = flattenedStyle.fontSize
return ( return (
<View> <View>
<View <View
style={[ style={[
a.px_lg, a.p_md,
a.pt_sm,
a.pb_md,
a.mt_sm, a.mt_sm,
a.border, a.border,
a.rounded_lg, a.rounded_lg,
a.gap_xs,
t.atoms.border_contrast_high, t.atoms.border_contrast_high,
]}> ]}>
<View style={[a.flex_row, a.align_center, a.mb_xs]}> <View
style={[
a.flex_row,
a.align_center,
a.flex_wrap,
{
paddingRight: X_ICON_OFFSET,
},
]}>
{langName ? ( {langName ? (
<View style={[a.flex_row, a.align_center]}> <>
<Text <Text
style={[ style={[
a.text_xs, a.text_xs,
a.font_medium, a.leading_snug,
t.atoms.text_contrast_medium, t.atoms.text_contrast_medium,
]}> ]}>
{langName}{' '} {langName}{' '}
</Text> </Text>
<View style={[a.mt_2xs]}> <ArrowRightIcon
<ArrowRightIcon size="xs"
size="xs" fill={t.atoms.text_contrast_medium.color}
fill={t.atoms.text_contrast_medium.color} />
/>
</View>
<Text <Text
style={[ style={[
a.text_xs, a.text_xs,
a.font_medium, a.leading_snug,
t.atoms.text_contrast_medium, t.atoms.text_contrast_medium,
]}> ]}>
{' '} {' '}
@@ -282,15 +313,10 @@ function TranslationResult({
langPrefs.appLanguage, langPrefs.appLanguage,
)} )}
</Text> </Text>
</View> </>
) : ( ) : (
<Text <Text
style={[ style={[a.text_xs, a.leading_snug, t.atoms.text_contrast_medium]}>
a.text_xs,
a.font_medium,
t.atoms.text_contrast_medium,
a.mb_xs,
]}>
<Trans>Translated</Trans> <Trans>Translated</Trans>
</Text> </Text>
)} )}
@@ -300,6 +326,7 @@ function TranslationResult({
style={[ style={[
a.text_xs, a.text_xs,
a.font_medium, a.font_medium,
a.leading_snug,
t.atoms.text_contrast_medium, t.atoms.text_contrast_medium,
]}> ]}>
{' '} {' '}
@@ -312,18 +339,19 @@ function TranslationResult({
/> />
</> </>
)} )}
<Button
label={l`Hide translation`}
hitSlop={HITSLOP_30}
hoverStyle={native({opacity: 0.5})}
style={[a.absolute, a.z_10, {top: 0, right: 0}]}
onPress={clearTranslation}>
<XIcon size="sm" fill={t.atoms.text_contrast_medium.color} />
</Button>
</View> </View>
<Text emoji selectable style={[a.text_md, a.leading_snug]}> <Text emoji selectable style={[a.leading_snug, {fontSize}]}>
{translatedText} {translatedText}
</Text> </Text>
<Button
label={l`Hide translation`}
hitSlop={HITSLOP_30}
hoverStyle={native({opacity: 0.5})}
style={[a.absolute, a.z_10, {top: 12, right: 14}]}
onPress={clearTranslation}>
<XIcon size="sm" fill={t.atoms.text_contrast_medium.color} />
</Button>
</View> </View>
</View> </View>
) )
@@ -393,7 +421,12 @@ function TranslationLanguageSelect({
hitSlop={HITSLOP_30} hitSlop={HITSLOP_30}
hoverStyle={native({opacity: 0.5})}> hoverStyle={native({opacity: 0.5})}>
<Text <Text
style={[a.text_xs, a.font_medium, t.atoms.text_contrast_high]}> style={[
a.text_xs,
a.font_medium,
a.leading_snug,
t.atoms.text_contrast_high,
]}>
<Trans>Change</Trans> <Trans>Change</Trans>
</Text> </Text>
</Button> </Button>
@@ -407,7 +407,11 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
shouldProxyLinks={true} shouldProxyLinks={true}
/> />
) : undefined} ) : undefined}
<TranslatedPost post={post} postText={record.text} /> <TranslatedPost
post={post}
postText={record.text}
postTextStyle={[a.text_lg]}
/>
{post.embed && ( {post.embed && (
<View style={[a.py_xs]}> <View style={[a.py_xs]}>
<Embed <Embed