about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/PostControls/PostMenu/PostMenuItems.tsx12
-rw-r--r--src/components/dms/MessageContextMenu.tsx13
-rw-r--r--src/lib/hooks/useTranslate.ts54
-rw-r--r--src/screens/PostThread/components/ThreadItemAnchor.tsx19
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx22
5 files changed, 82 insertions, 38 deletions
diff --git a/src/components/PostControls/PostMenu/PostMenuItems.tsx b/src/components/PostControls/PostMenu/PostMenuItems.tsx
index ecc3d0174..3fd919cd3 100644
--- a/src/components/PostControls/PostMenu/PostMenuItems.tsx
+++ b/src/components/PostControls/PostMenu/PostMenuItems.tsx
@@ -19,6 +19,7 @@ import {useNavigation} from '@react-navigation/native'
 
 import {DISCOVER_DEBUG_DIDS} from '#/lib/constants'
 import {useOpenLink} from '#/lib/hooks/useOpenLink'
+import {useTranslate} from '#/lib/hooks/useTranslate'
 import {getCurrentRoute} from '#/lib/routes/helpers'
 import {makeProfileLink} from '#/lib/routes/links'
 import {
@@ -28,7 +29,6 @@ import {
 import {logEvent, useGate} from '#/lib/statsig/statsig'
 import {richTextToString} from '#/lib/strings/rich-text-helpers'
 import {toShareUrl} from '#/lib/strings/url-helpers'
-import {getTranslatorLink} from '#/locale/helpers'
 import {logger} from '#/logger'
 import {type Shadow} from '#/state/cache/post-shadow'
 import {useProfileShadow} from '#/state/cache/profile-shadow'
@@ -118,6 +118,7 @@ let PostMenuItems = ({
   const {hidePost} = useHiddenPostsApi()
   const feedFeedback = useFeedFeedbackContext()
   const openLink = useOpenLink()
+  const translate = useTranslate()
   const navigation = useNavigation<NavigationProp>()
   const {mutedWordsDialogControl} = useGlobalDialogsControlContext()
   const blockPromptControl = useDialogControl()
@@ -172,11 +173,6 @@ let PostMenuItems = ({
     return makeProfileLink(postAuthor, 'post', urip.rkey)
   }, [postUri, postAuthor])
 
-  const translatorUrl = getTranslatorLink(
-    record.text,
-    langPrefs.primaryLanguage,
-  )
-
   const onDeletePost = () => {
     deletePostMutate({uri: postUri}).then(
       () => {
@@ -234,8 +230,8 @@ let PostMenuItems = ({
     Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
   }
 
-  const onPressTranslate = async () => {
-    await openLink(translatorUrl, true)
+  const onPressTranslate = () => {
+    translate(record.text, langPrefs.primaryLanguage)
 
     if (
       bsky.dangerousIsType<AppBskyFeedPost.Record>(
diff --git a/src/components/dms/MessageContextMenu.tsx b/src/components/dms/MessageContextMenu.tsx
index d1771659d..670e677db 100644
--- a/src/components/dms/MessageContextMenu.tsx
+++ b/src/components/dms/MessageContextMenu.tsx
@@ -5,9 +5,8 @@ import {type ChatBskyConvoDefs, RichText} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
-import {useOpenLink} from '#/lib/hooks/useOpenLink'
+import {useTranslate} from '#/lib/hooks/useTranslate'
 import {richTextToString} from '#/lib/strings/rich-text-helpers'
-import {getTranslatorLink} from '#/locale/helpers'
 import {logger} from '#/logger'
 import {isNative} from '#/platform/detection'
 import {useConvoActive} from '#/state/messages/convo'
@@ -39,7 +38,7 @@ export let MessageContextMenu = ({
   const deleteControl = usePromptControl()
   const reportControl = usePromptControl()
   const langPrefs = useLanguagePrefs()
-  const openLink = useOpenLink()
+  const translate = useTranslate()
 
   const isFromSelf = message.sender?.did === currentAccount?.did
 
@@ -57,11 +56,7 @@ export let MessageContextMenu = ({
   }, [_, message.text, message.facets])
 
   const onPressTranslateMessage = useCallback(() => {
-    const translatorUrl = getTranslatorLink(
-      message.text,
-      langPrefs.primaryLanguage,
-    )
-    openLink(translatorUrl, true)
+    translate(message.text, langPrefs.primaryLanguage)
 
     logger.metric(
       'translate',
@@ -72,7 +67,7 @@ export let MessageContextMenu = ({
       },
       {statsig: false},
     )
-  }, [langPrefs.primaryLanguage, message.text, openLink])
+  }, [langPrefs.primaryLanguage, message.text, translate])
 
   const onDelete = useCallback(() => {
     LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
diff --git a/src/lib/hooks/useTranslate.ts b/src/lib/hooks/useTranslate.ts
new file mode 100644
index 000000000..7824a682c
--- /dev/null
+++ b/src/lib/hooks/useTranslate.ts
@@ -0,0 +1,54 @@
+import {useCallback} from 'react'
+import * as IntentLauncher from 'expo-intent-launcher'
+
+import {getTranslatorLink} from '#/locale/helpers'
+import {isAndroid} from '#/platform/detection'
+import {useOpenLink} from './useOpenLink'
+
+export function useTranslate() {
+  const openLink = useOpenLink()
+
+  return useCallback(
+    async (text: string, language: string) => {
+      const translateUrl = getTranslatorLink(text, language)
+      if (isAndroid) {
+        try {
+          // use getApplicationIconAsync to determine if the translate app is installed
+          if (
+            !(await IntentLauncher.getApplicationIconAsync(
+              'com.google.android.apps.translate',
+            ))
+          ) {
+            throw new Error('Translate app not installed')
+          }
+
+          // TODO: this should only be called one at a time, use something like
+          // RQ's `scope` - otherwise can trigger the browser to open unexpectedly when the call throws -sfn
+          await IntentLauncher.startActivityAsync(
+            'android.intent.action.PROCESS_TEXT',
+            {
+              type: 'text/plain',
+              extra: {
+                'android.intent.extra.PROCESS_TEXT': text,
+                'android.intent.extra.PROCESS_TEXT_READONLY': true,
+              },
+              // note: to skip the intermediate app select, we need to specify a
+              // `className`. however, this isn't safe to hardcode, we'd need to query the
+              // package manager for the correct activity. this requires native code, so
+              // skip for now -sfn
+              // packageName: 'com.google.android.apps.translate',
+              // className: 'com.google.android.apps.translate.TranslateActivity',
+            },
+          )
+        } catch (err) {
+          if (__DEV__) console.error(err)
+          // most likely means they don't have the translate app
+          await openLink(translateUrl)
+        }
+      } else {
+        await openLink(translateUrl)
+      }
+    },
+    [openLink],
+  )
+}
diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx
index 481c335db..39e84d383 100644
--- a/src/screens/PostThread/components/ThreadItemAnchor.tsx
+++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx
@@ -12,7 +12,7 @@ import {useLingui} from '@lingui/react'
 
 import {useActorStatus} from '#/lib/actor-status'
 import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
-import {useOpenLink} from '#/lib/hooks/useOpenLink'
+import {useTranslate} from '#/lib/hooks/useTranslate'
 import {makeProfileLink} from '#/lib/routes/links'
 import {sanitizeDisplayName} from '#/lib/strings/display-names'
 import {sanitizeHandle} from '#/lib/strings/handles'
@@ -509,14 +509,10 @@ function ExpandedPostDetails({
 }) {
   const t = useTheme()
   const {_, i18n} = useLingui()
-  const openLink = useOpenLink()
+  const translate = useTranslate()
   const isRootPost = !('reply' in post.record)
   const langPrefs = useLanguagePrefs()
 
-  const translatorUrl = getTranslatorLink(
-    post.record?.text || '',
-    langPrefs.primaryLanguage,
-  )
   const needsTranslation = useMemo(
     () =>
       Boolean(
@@ -529,7 +525,7 @@ function ExpandedPostDetails({
   const onTranslatePress = useCallback(
     (e: GestureResponderEvent) => {
       e.preventDefault()
-      openLink(translatorUrl, true)
+      translate(post.record.text || '', langPrefs.primaryLanguage)
 
       if (
         bsky.dangerousIsType<AppBskyFeedPost.Record>(
@@ -546,7 +542,7 @@ function ExpandedPostDetails({
 
       return false
     },
-    [openLink, translatorUrl, langPrefs, post],
+    [translate, langPrefs, post],
   )
 
   return (
@@ -566,7 +562,12 @@ function ExpandedPostDetails({
             </Text>
 
             <InlineLinkText
-              to={translatorUrl}
+              // overridden to open an intent on android, but keep
+              // as anchor tag for accessibility
+              to={getTranslatorLink(
+                post.record.text,
+                langPrefs.primaryLanguage,
+              )}
               label={_(msg`Translate`)}
               style={[a.text_sm]}
               onPress={onTranslatePress}>
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 54eea0493..97a1aa8ed 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -19,8 +19,8 @@ import {useLingui} from '@lingui/react'
 import {useActorStatus} from '#/lib/actor-status'
 import {MAX_POST_LINES} from '#/lib/constants'
 import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
-import {useOpenLink} from '#/lib/hooks/useOpenLink'
 import {usePalette} from '#/lib/hooks/usePalette'
+import {useTranslate} from '#/lib/hooks/useTranslate'
 import {makeProfileLink} from '#/lib/routes/links'
 import {sanitizeDisplayName} from '#/lib/strings/display-names'
 import {sanitizeHandle} from '#/lib/strings/handles'
@@ -273,10 +273,6 @@ let PostThreadItemLoaded = ({
   const showFollowButton =
     currentAccount?.did !== post.author.did && !onlyFollowersCanReply
 
-  const translatorUrl = getTranslatorLink(
-    record?.text || '',
-    langPrefs.primaryLanguage,
-  )
   const needsTranslation = useMemo(
     () =>
       Boolean(
@@ -477,8 +473,8 @@ let PostThreadItemLoaded = ({
             </ContentHider>
             <ExpandedPostDetails
               post={post}
+              record={record}
               isThreadAuthor={isThreadAuthor}
-              translatorUrl={translatorUrl}
               needsTranslation={needsTranslation}
             />
             {post.repostCount !== 0 ||
@@ -824,26 +820,26 @@ function PostOuterWrapper({
 
 function ExpandedPostDetails({
   post,
+  record,
   isThreadAuthor,
   needsTranslation,
-  translatorUrl,
 }: {
   post: AppBskyFeedDefs.PostView
+  record: AppBskyFeedPost.Record
   isThreadAuthor: boolean
   needsTranslation: boolean
-  translatorUrl: string
 }) {
   const t = useTheme()
   const pal = usePalette('default')
   const {_, i18n} = useLingui()
-  const openLink = useOpenLink()
+  const translate = useTranslate()
   const isRootPost = !('reply' in post.record)
   const langPrefs = useLanguagePrefs()
 
   const onTranslatePress = useCallback(
     (e: GestureResponderEvent) => {
       e.preventDefault()
-      openLink(translatorUrl, true)
+      translate(record.text || '', langPrefs.primaryLanguage)
 
       if (
         bsky.dangerousIsType<AppBskyFeedPost.Record>(
@@ -864,7 +860,7 @@ function ExpandedPostDetails({
 
       return false
     },
-    [openLink, translatorUrl, langPrefs, post],
+    [translate, record.text, langPrefs, post],
   )
 
   return (
@@ -884,7 +880,9 @@ function ExpandedPostDetails({
             </Text>
 
             <InlineLinkText
-              to={translatorUrl}
+              // overridden to open an intent on android, but keep
+              // as anchor tag for accessibility
+              to={getTranslatorLink(record.text, langPrefs.primaryLanguage)}
               label={_(msg`Translate`)}
               style={[a.text_sm, pal.link]}
               onPress={onTranslatePress}>