Update option name to forceGoogleTranslate

This commit is contained in:
DS Boyce
2026-03-03 11:38:22 -08:00
parent b7ab03bf18
commit 116784463a
13 changed files with 98 additions and 112 deletions
+5 -5
View File
@@ -148,12 +148,12 @@ function TranslationLanguageSelect({
sourceLanguage: sourceLangCode, sourceLanguage: sourceLangCode,
targetLanguage: langPrefs.primaryLanguage, targetLanguage: langPrefs.primaryLanguage,
}) })
void translate( void translate({
translationKey, key: translationKey,
postText, text: postText,
langPrefs.primaryLanguage, targetLangCode: langPrefs.primaryLanguage,
sourceLangCode, sourceLangCode,
) })
} }
return ( return (
@@ -106,7 +106,7 @@ let PostMenuItems = ({
threadgateRecord, threadgateRecord,
onShowLess, onShowLess,
logContext, logContext,
googleTranslate, forceGoogleTranslate,
}: { }: {
testID: string testID: string
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<AppBskyFeedDefs.PostView>
@@ -121,7 +121,7 @@ let PostMenuItems = ({
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: AppBskyFeedThreadgate.Record
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
googleTranslate: boolean forceGoogleTranslate: boolean
}): React.ReactNode => { }): React.ReactNode => {
const {hasSession, currentAccount} = useSession() const {hasSession, currentAccount} = useSession()
const {t: l} = useLingui() const {t: l} = useLingui()
@@ -258,15 +258,12 @@ let PostMenuItems = ({
} }
const onPressTranslate = () => { const onPressTranslate = () => {
void translate( void translate({
translationKey, key: translationKey,
record.text, text: record.text,
langPrefs.primaryLanguage, targetLangCode: langPrefs.primaryLanguage,
undefined, forceGoogleTranslate,
{ })
googleTranslate,
},
)
if ( if (
bsky.dangerousIsType<AppBskyFeedPost.Record>( bsky.dangerousIsType<AppBskyFeedPost.Record>(
@@ -29,7 +29,7 @@ let PostMenuButton = ({
onShowLess, onShowLess,
hitSlop, hitSlop,
logContext, logContext,
googleTranslate, forceGoogleTranslate,
}: { }: {
testID: string testID: string
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<AppBskyFeedDefs.PostView>
@@ -43,7 +43,7 @@ let PostMenuButton = ({
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
hitSlop?: Insets hitSlop?: Insets
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
googleTranslate: boolean forceGoogleTranslate: boolean
}): React.ReactNode => { }): React.ReactNode => {
const {t: l} = useLingui() const {t: l} = useLingui()
@@ -91,7 +91,7 @@ let PostMenuButton = ({
threadgateRecord={threadgateRecord} threadgateRecord={threadgateRecord}
onShowLess={onShowLess} onShowLess={onShowLess}
logContext={logContext} logContext={logContext}
googleTranslate={googleTranslate} forceGoogleTranslate={forceGoogleTranslate}
/> />
)} )}
</Menu.Root> </Menu.Root>
+8 -6
View File
@@ -55,7 +55,7 @@ let PostControls = ({
onShowLess, onShowLess,
viaRepost, viaRepost,
variant, variant,
googleTranslate = true, forceGoogleTranslate = false,
}: { }: {
big?: boolean big?: boolean
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<AppBskyFeedDefs.PostView>
@@ -71,7 +71,7 @@ let PostControls = ({
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
viaRepost?: {uri: string; cid: string} viaRepost?: {uri: string; cid: string}
variant?: 'compact' | 'normal' | 'large' variant?: 'compact' | 'normal' | 'large'
googleTranslate?: boolean forceGoogleTranslate?: boolean
}): React.ReactNode => { }): React.ReactNode => {
const ax = useAnalytics() const ax = useAnalytics()
const {t: l} = useLingui() const {t: l} = useLingui()
@@ -125,7 +125,8 @@ let PostControls = ({
} else { } else {
await queueUnlike() await queueUnlike()
} }
} catch (e: any) { } catch (err) {
const e = err as Error
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
throw e throw e
} }
@@ -150,7 +151,8 @@ let PostControls = ({
} else { } else {
await queueUnrepost() await queueUnrepost()
} }
} catch (e: any) { } catch (err) {
const e = err as Error
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
throw e throw e
} }
@@ -252,7 +254,7 @@ let PostControls = ({
<RepostButton <RepostButton
isReposted={!!post.viewer?.repost} isReposted={!!post.viewer?.repost}
repostCount={(post.repostCount ?? 0) + (post.quoteCount ?? 0)} repostCount={(post.repostCount ?? 0) + (post.quoteCount ?? 0)}
onRepost={onRepost} onRepost={() => void onRepost()}
onQuote={onQuote} onQuote={onQuote}
big={big} big={big}
embeddingDisabled={Boolean(post.viewer?.embeddingDisabled)} embeddingDisabled={Boolean(post.viewer?.embeddingDisabled)}
@@ -337,7 +339,7 @@ let PostControls = ({
left: secondaryControlSpacingStyles.gap / 2, left: secondaryControlSpacingStyles.gap / 2,
}} }}
logContext={logContext} logContext={logContext}
googleTranslate={googleTranslate} forceGoogleTranslate={forceGoogleTranslate}
/> />
</View> </View>
</View> </View>
+11 -8
View File
@@ -1,16 +1,19 @@
import {createContext} from 'react' import {createContext} from 'react'
import {type Options, type TranslationState} from './types' import {type TranslationState} from './types'
export const Context = createContext<{ export const Context = createContext<{
translationState: Record<string, TranslationState> translationState: Record<string, TranslationState>
translate: ( translate: (parameters: {
key: string, key: string
text: string, text: string
targetLangCode: string, targetLangCode: string
sourceLangCode?: string, sourceLangCode?: string
options?: Options, /**
) => Promise<void> * Whether to force the use of Google Translate. Default is false.
*/
forceGoogleTranslate?: boolean
}) => Promise<void>
clearTranslation: (key: string) => void clearTranslation: (key: string) => void
acquireTranslation: (key: string) => () => void acquireTranslation: (key: string) => () => void
} | null>(null) } | null>(null)
+16 -12
View File
@@ -6,10 +6,9 @@ import {useFocusEffect} from '@react-navigation/native'
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate' import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useLanguagePrefs} from '#/state/preferences'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {Context} from './context' import {Context} from './context'
import {type Options, type TranslationState} from './types' import {type TranslationState} from './types'
/** /**
* Attempts on-device translation via @bsky.app/expo-translate-text. * Attempts on-device translation via @bsky.app/expo-translate-text.
@@ -111,7 +110,6 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
>({}) >({})
const [refCounts, setRefCounts] = useState<Record<string, number>>({}) const [refCounts, setRefCounts] = useState<Record<string, number>>({})
const ax = useAnalytics() const ax = useAnalytics()
const {primaryLanguage} = useLanguagePrefs()
const googleTranslate = useGoogleTranslate() const googleTranslate = useGoogleTranslate()
useEffect(() => { useEffect(() => {
@@ -163,14 +161,20 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
}, []) }, [])
const translate = useCallback( const translate = useCallback(
async ( async ({
key: string, key,
text: string, text,
targetLangCode: string = primaryLanguage, targetLangCode,
sourceLangCode?: string, sourceLangCode,
options?: Options, ...options
) => { }: {
if (options?.googleTranslate) { key: string
text: string
targetLangCode: string
sourceLangCode?: string
forceGoogleTranslate?: boolean
}) => {
if (options?.forceGoogleTranslate) {
ax.metric('translate:result', { ax.metric('translate:result', {
method: 'google-translate', method: 'google-translate',
os: Platform.OS, os: Platform.OS,
@@ -225,7 +229,7 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
await googleTranslate(text, targetLangCode, sourceLangCode) await googleTranslate(text, targetLangCode, sourceLangCode)
} }
}, },
[ax, googleTranslate, primaryLanguage], [ax, googleTranslate],
) )
const ctx = useMemo( const ctx = useMemo(
+12 -11
View File
@@ -1,10 +1,9 @@
import {useCallback, useContext, useMemo} from 'react' import {useCallback, useContext, useMemo} from 'react'
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate' import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
import {useLanguagePrefs} from '#/state/preferences'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {Context} from './context' import {Context} from './context'
import {type Options, type TranslationState} from './types' import {type TranslationState} from './types'
const translationState: Record<string, TranslationState> = {} const translationState: Record<string, TranslationState> = {}
const acquireTranslation = (_key: string) => { const acquireTranslation = (_key: string) => {
@@ -29,17 +28,19 @@ export function useTranslate() {
export function Provider({children}: React.PropsWithChildren<unknown>) { export function Provider({children}: React.PropsWithChildren<unknown>) {
const ax = useAnalytics() const ax = useAnalytics()
const {primaryLanguage} = useLanguagePrefs()
const googleTranslate = useGoogleTranslate() const googleTranslate = useGoogleTranslate()
const translate = useCallback( const translate = useCallback(
async ( async ({
_key: string, text,
text: string, targetLangCode,
targetLangCode: string = primaryLanguage, sourceLangCode,
sourceLangCode?: string, }: {
_options?: Options, key: string
) => { text: string
targetLangCode: string
sourceLangCode?: string
}) => {
ax.metric('translate:result', { ax.metric('translate:result', {
method: 'google-translate', method: 'google-translate',
os: 'web', os: 'web',
@@ -48,7 +49,7 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
}) })
await googleTranslate(text, targetLangCode, sourceLangCode) await googleTranslate(text, targetLangCode, sourceLangCode)
}, },
[ax, googleTranslate, primaryLanguage], [ax, googleTranslate],
) )
const ctx = useMemo( const ctx = useMemo(
-7
View File
@@ -9,10 +9,3 @@ export type TranslationState =
sourceLanguage: TranslationTaskResult['sourceLanguage'] sourceLanguage: TranslationTaskResult['sourceLanguage']
targetLanguage: TranslationTaskResult['targetLanguage'] targetLanguage: TranslationTaskResult['targetLanguage']
} }
export type Options = {
/**
* Whether to force the use of Google Translate. Default is false.
*/
googleTranslate?: boolean
}
@@ -312,8 +312,6 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
} }
} }
const translationKey = post.uri
return ( return (
<> <>
<ThreadItemAnchorParentReplyLine isRoot={isRoot} /> <ThreadItemAnchorParentReplyLine isRoot={isRoot} />
@@ -409,10 +407,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
shouldProxyLinks={true} shouldProxyLinks={true}
/> />
) : undefined} ) : undefined}
<TranslatedPost <TranslatedPost translationKey={post.uri} postText={record.text} />
translationKey={translationKey}
postText={record.text}
/>
{post.embed && ( {post.embed && (
<View style={[a.py_xs]}> <View style={[a.py_xs]}>
<Embed <Embed
@@ -538,7 +533,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
feedContext={postSource?.post?.feedContext} feedContext={postSource?.post?.feedContext}
reqId={postSource?.post?.reqId} reqId={postSource?.post?.reqId}
viaRepost={viaRepost} viaRepost={viaRepost}
googleTranslate={false} forceGoogleTranslate={true}
/> />
</FeedFeedbackProvider> </FeedFeedbackProvider>
</View> </View>
@@ -336,6 +336,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
onPressReply={onPressReply} onPressReply={onPressReply}
logContext="PostThreadItem" logContext="PostThreadItem"
threadgateRecord={threadgateRecord} threadgateRecord={threadgateRecord}
forceGoogleTranslate={true}
/> />
<DebugFieldDisplay subject={post} /> <DebugFieldDisplay subject={post} />
</View> </View>
@@ -377,6 +377,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
onPressReply={onPressReply} onPressReply={onPressReply}
logContext="PostThreadItem" logContext="PostThreadItem"
threadgateRecord={threadgateRecord} threadgateRecord={threadgateRecord}
forceGoogleTranslate={true}
/> />
<DebugFieldDisplay subject={post} /> <DebugFieldDisplay subject={post} />
</View> </View>
+30 -38
View File
@@ -33,9 +33,7 @@ import {
type ModerationDecision, type ModerationDecision,
RichText as RichTextAPI, RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
import {msg} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import { import {
type RouteProp, type RouteProp,
useFocusEffect, useFocusEffect,
@@ -451,7 +449,7 @@ function Feed() {
} }
onEndReached={() => { onEndReached={() => {
if (hasNextPage && !isFetchingNextPage) { if (hasNextPage && !isFetchingNextPage) {
fetchNextPage() void fetchNextPage()
} }
}} }}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
@@ -515,6 +513,7 @@ let VideoItem = ({
} }
} }
}, [ }, [
ax,
active, active,
post.uri, post.uri,
post.author.did, post.author.did,
@@ -621,7 +620,7 @@ function ModerationOverlay({
embed: AppBskyEmbedVideo.View embed: AppBskyEmbedVideo.View
onPressShow: () => void onPressShow: () => void
}) { }) {
const {_} = useLingui() const {t: l} = useLingui()
const hider = Hider.useHider() const hider = Hider.useHider()
const {bottom} = useSafeAreaInsets() const {bottom} = useSafeAreaInsets()
@@ -648,7 +647,7 @@ function ModerationOverlay({
<Trans>Hidden by your moderation settings.</Trans> <Trans>Hidden by your moderation settings.</Trans>
</Text> </Text>
<Button <Button
label={_(msg`Show anyway`)} label={l`Show anyway`}
size="small" size="small"
variant="solid" variant="solid"
color="secondary_inverted" color="secondary_inverted"
@@ -676,7 +675,7 @@ function ModerationOverlay({
<Divider style={{borderColor: 'white'}} /> <Divider style={{borderColor: 'white'}} />
<View> <View>
<Button <Button
label={_(msg`View details`)} label={l`View details`}
onPress={() => { onPress={() => {
hider.showInfoDialog() hider.showInfoDialog()
}} }}
@@ -724,7 +723,7 @@ function Overlay({
feedContext: string | undefined feedContext: string | undefined
reqId: string | undefined reqId: string | undefined
}) { }) {
const {_} = useLingui() const {t: l} = useLingui()
const t = useTheme() const t = useTheme()
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -811,11 +810,9 @@ function Overlay({
<Animated.View style={[a.px_md, animatedStyle]}> <Animated.View style={[a.px_md, animatedStyle]}>
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_md]}> <View style={[a.w_full, a.flex_row, a.align_center, a.gap_md]}>
<Link <Link
label={_( label={l`View ${sanitizeDisplayName(
msg`View ${sanitizeDisplayName( post.author.displayName || post.author.handle,
post.author.displayName || post.author.handle, )}'s profile`}
)}'s profile`,
)}
to={{ to={{
screen: 'Profile', screen: 'Profile',
params: {name: post.author.did}, params: {name: post.author.did},
@@ -848,13 +845,11 @@ function Overlay({
<Button <Button
label={ label={
profile.viewer?.following profile.viewer?.following
? _(msg`Following ${handle}`) ? l`Following ${handle}`
: _(msg`Follow ${handle}`) : l`Follow ${handle}`
} }
accessibilityHint={ accessibilityHint={
profile.viewer?.following profile.viewer?.following ? l`Unfollows the user` : ''
? _(msg`Unfollows the user`)
: ''
} }
size="small" size="small"
variant="solid" variant="solid"
@@ -862,8 +857,8 @@ function Overlay({
style={[a.mb_xs]} style={[a.mb_xs]}
onPress={() => onPress={() =>
profile.viewer?.following profile.viewer?.following
? queueUnfollow() ? void queueUnfollow()
: queueFollow() : void queueFollow()
}> }>
{!!profile.viewer?.following && ( {!!profile.viewer?.following && (
<ButtonIcon icon={CheckIcon} /> <ButtonIcon icon={CheckIcon} />
@@ -892,6 +887,7 @@ function Overlay({
record={record} record={record}
feedContext={feedContext} feedContext={feedContext}
logContext="FeedItem" logContext="FeedItem"
forceGoogleTranslate={true}
onPressReply={() => onPressReply={() =>
navigation.navigate('PostThread', { navigation.navigate('PostThread', {
name: post.author.did, name: post.author.did,
@@ -947,7 +943,7 @@ function ExpandableRichTextView({
const [hasBeenExpanded, setHasBeenExpanded] = useState(false) const [hasBeenExpanded, setHasBeenExpanded] = useState(false)
const [constrained, setConstrained] = useState(false) const [constrained, setConstrained] = useState(false)
const [contentHeight, setContentHeight] = useState(0) const [contentHeight, setContentHeight] = useState(0)
const {_} = useLingui() const {t: l} = useLingui()
const {screenReaderEnabled} = useA11y() const {screenReaderEnabled} = useA11y()
if (expanded && !hasBeenExpanded) { if (expanded && !hasBeenExpanded) {
@@ -988,8 +984,8 @@ function ExpandableRichTextView({
/> />
{constrained && !screenReaderEnabled && ( {constrained && !screenReaderEnabled && (
<Pressable <Pressable
accessibilityHint={_(msg`Expands or collapses post text`)} accessibilityHint={l`Expands or collapses post text`}
accessibilityLabel={expanded ? _(msg`Read less`) : _(msg`Read more`)} accessibilityLabel={expanded ? l`Read less` : l`Read more`}
hitSlop={HITSLOP_20} hitSlop={HITSLOP_20}
onPress={() => setExpanded(prev => !prev)} onPress={() => setExpanded(prev => !prev)}
style={[a.absolute, a.inset_0]} style={[a.absolute, a.inset_0]}
@@ -1049,7 +1045,7 @@ function PlayPauseTapArea({
feedContext: string | undefined feedContext: string | undefined
reqId: string | undefined reqId: string | undefined
}) { }) {
const {_} = useLingui() const {t: l} = useLingui()
const doubleTapRef = useRef<ReturnType<typeof setTimeout> | null>(null) const doubleTapRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const playHaptic = useHaptics() const playHaptic = useHaptics()
// TODO: implement viaRepost -sfn // TODO: implement viaRepost -sfn
@@ -1092,7 +1088,7 @@ function PlayPauseTapArea({
clearTimeout(doubleTapRef.current) clearTimeout(doubleTapRef.current)
doubleTapRef.current = null doubleTapRef.current = null
playHaptic('Light') playHaptic('Light')
queueLike() void queueLike()
sendInteraction({ sendInteraction({
item: post.uri, item: post.uri,
event: 'app.bsky.feed.defs#interactionLike', event: 'app.bsky.feed.defs#interactionLike',
@@ -1107,16 +1103,12 @@ function PlayPauseTapArea({
return ( return (
<Button <Button
disabled={!player} disabled={!player}
aria-valuetext={ aria-valuetext={isPlaying ? l`Video is playing` : l`Video is paused`}
isPlaying ? _(msg`Video is playing`) : _(msg`Video is paused`) label={l`Video from ${sanitizeHandle(
} post.author.handle,
label={_( '@',
msg`Video from ${sanitizeHandle( )}. Tap to play or pause the video`}
post.author.handle, accessibilityHint={l`Double tap to like`}
'@',
)}. Tap to play or pause the video`,
)}
accessibilityHint={_(msg`Double tap to like`)}
onPress={onPress} onPress={onPress}
style={[a.absolute, a.inset_0, a.z_10]}> style={[a.absolute, a.inset_0, a.z_10]}>
<View /> <View />
@@ -1126,7 +1118,7 @@ function PlayPauseTapArea({
function EndMessage() { function EndMessage() {
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {_} = useLingui() const {t: l} = useLingui()
const t = useTheme() const t = useTheme()
return ( return (
<View <View
@@ -1177,8 +1169,8 @@ function EndMessage() {
variant="solid" variant="solid"
color="secondary_inverted" color="secondary_inverted"
size="small" size="small"
label={_(msg`Go back`)} label={l`Go back`}
accessibilityHint={_(msg`Returns to previous page`)}> accessibilityHint={l`Returns to previous page`}>
<ButtonIcon icon={ArrowLeftIcon} /> <ButtonIcon icon={ArrowLeftIcon} />
<ButtonText> <ButtonText>
<Trans>Go back</Trans> <Trans>Go back</Trans>
+1 -4
View File
@@ -154,8 +154,6 @@ function PostInner({
const [hover, setHover] = useState(false) const [hover, setHover] = useState(false)
const translationKey = post.uri
return ( return (
<Link <Link
href={itemHref} href={itemHref}
@@ -220,7 +218,7 @@ function PostInner({
/> />
)} )}
<TranslatedPost <TranslatedPost
translationKey={translationKey} translationKey={post.uri}
postText={record.text} postText={record.text}
/> />
</View> </View>
@@ -239,7 +237,6 @@ function PostInner({
richText={richText} richText={richText}
onPressReply={onPressReply} onPressReply={onPressReply}
logContext="Post" logContext="Post"
googleTranslate={false}
/> />
</View> </View>
</View> </View>