Refine presentation of translation results (#10015)
Co-authored-by: Eric Bailey <git@esb.lol>
(cherry picked from commit f8886fbfe6)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user