diff --git a/package.json b/package.json
index f01c01b7af..f4bbe343b6 100644
--- a/package.json
+++ b/package.json
@@ -85,7 +85,7 @@
"@braintree/sanitize-url": "^6.0.2",
"@bsky.app/alf": "^0.1.7",
"@bsky.app/expo-image-crop-tool": "^0.5.0",
- "@bsky.app/expo-translate-text": "^0.2.4",
+ "@bsky.app/expo-translate-text": "^0.2.5",
"@bsky.app/react-native-mmkv": "2.12.5",
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
"@emoji-mart/react": "^1.1.1",
diff --git a/src/components/Post/Translated/index.tsx b/src/components/Post/Translated/index.tsx
index 049f21178b..ed5f05aa40 100644
--- a/src/components/Post/Translated/index.tsx
+++ b/src/components/Post/Translated/index.tsx
@@ -18,6 +18,7 @@ import {atoms as a, native, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow'
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
+import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning'
import {InlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader'
import * as Select from '#/components/Select'
@@ -45,6 +46,17 @@ export function TranslatedPost({
[post, langPrefs.primaryLanguage],
)
+ if (translationState.status === 'error') {
+ return (
+
+ )
+ }
+
if (translationState.status === 'loading') {
return
}
@@ -137,6 +149,63 @@ function TranslationLink({
)
}
+function TranslationError({
+ clearTranslation,
+ message,
+ postText,
+ primaryLanguage,
+}: {
+ clearTranslation: () => void
+ message: string
+ postText: string
+ primaryLanguage: string
+}) {
+ const t = useTheme()
+ const {t: l} = useLingui()
+
+ return (
+
+
+
+
+
+ {' '}
+ {message}
+
+
+
+
+
+
+
+
+
+ Try Google Translate
+
+
+
+
+ )
+}
+
function TranslationResult({
clearTranslation,
translate,
@@ -213,7 +282,12 @@ function TranslationResult({
)}
{sourceLanguage != null && (
<>
-
+
{' '}
·{' '}
@@ -250,6 +324,7 @@ function TranslationLanguageSelect({
postText: string
sourceLanguage: string
}) {
+ const t = useTheme()
const ax = useAnalytics()
const {t: l} = useLingui()
const langPrefs = useLanguagePrefs()
@@ -300,7 +375,8 @@ function TranslationLanguageSelect({
{...props}
hitSlop={HITSLOP_30}
hoverStyle={native({opacity: 0.5})}>
-
+
Change
diff --git a/src/lib/translation/index.tsx b/src/lib/translation/index.tsx
index 964c75db15..3f7f2c69b9 100644
--- a/src/lib/translation/index.tsx
+++ b/src/lib/translation/index.tsx
@@ -2,6 +2,7 @@ import {useCallback, useContext, useEffect, useMemo, useState} from 'react'
import {LayoutAnimation, Platform} from 'react-native'
import {getLocales} from 'expo-localization'
import {type TranslationTaskResult} from '@bsky.app/expo-translate-text/build/ExpoTranslateText.types'
+import {useLingui} from '@lingui/react/macro'
import {useFocusEffect} from '@react-navigation/native'
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
@@ -61,9 +62,15 @@ async function attemptTranslation(
})
// Since `input` is always a string, the result should always be a string.
+ const translatedText =
+ typeof result.translatedTexts === 'string' ? result.translatedTexts : ''
+
+ if (translatedText === input) {
+ throw new Error('Translation result is the same as the source text.')
+ }
+
return {
- translatedText:
- typeof result.translatedTexts === 'string' ? result.translatedTexts : '',
+ translatedText,
targetLanguage: result.targetLanguage,
sourceLanguage: result.sourceLanguage ?? sourceLangCode ?? null, // iOS doesn't return the source language
}
@@ -125,6 +132,7 @@ export function Provider({children}: React.PropsWithChildren) {
>({})
const [refCounts, setRefCounts] = useState>({})
const ax = useAnalytics()
+ const {t: l} = useLingui()
const googleTranslate = useGoogleTranslate()
useEffect(() => {
@@ -235,12 +243,12 @@ export function Provider({children}: React.PropsWithChildren) {
sourceLanguage: sourceLangCode ?? null,
targetLanguage: targetLangCode,
})
+ let errorMessage = l`Device failed to translate. :(`
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setTranslationState(prev => ({
...prev,
- [key]: {status: 'idle'},
+ [key]: {status: 'error', message: errorMessage},
}))
- await googleTranslate(text, targetLangCode, sourceLangCode)
}
}
@@ -254,7 +262,7 @@ export function Provider({children}: React.PropsWithChildren) {
[key]: {status: 'loading'},
}))
},
- [ax, googleTranslate],
+ [ax, googleTranslate, l],
)
const ctx = useMemo(
diff --git a/src/lib/translation/types.ts b/src/lib/translation/types.ts
index 23eb73ba1e..4bf2d021e7 100644
--- a/src/lib/translation/types.ts
+++ b/src/lib/translation/types.ts
@@ -9,6 +9,10 @@ export type TranslationState =
sourceLanguage: TranslationTaskResult['sourceLanguage']
targetLanguage: TranslationTaskResult['targetLanguage']
}
+ | {
+ status: 'error'
+ message: string
+ }
export type TranslationFunctionParams = {
/**
diff --git a/yarn.lock b/yarn.lock
index 26658a61f9..7cfca5e096 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3731,10 +3731,10 @@
resolved "https://registry.yarnpkg.com/@bsky.app/expo-image-crop-tool/-/expo-image-crop-tool-0.5.0.tgz#4308fbde5c15e6be9122601797bc3d9549c95e31"
integrity sha512-gmhQr2HWTRFyPO00fn5OmtiEVtikXusHMrN5Zoq26pu1VZX3zVE+aoc668etTqrvsQcm2Qu8fo96k5F3Wu+6wg==
-"@bsky.app/expo-translate-text@^0.2.4":
- version "0.2.4"
- resolved "https://registry.yarnpkg.com/@bsky.app/expo-translate-text/-/expo-translate-text-0.2.4.tgz#6e7f20f286111ee4d550c0c84f57393fc215a675"
- integrity sha512-7mvFggNfkJEufI5A3WnjfjdN3H9P6Dpx7CpDkA9npWqA8Cb2icXq3k3nz3MaXGrVKTYiJnytAffYtos7mPoeOg==
+"@bsky.app/expo-translate-text@^0.2.5":
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/@bsky.app/expo-translate-text/-/expo-translate-text-0.2.5.tgz#eb0bf41522b710b11e64d14a06d4ec770a72ad20"
+ integrity sha512-nWGxK8bwKN6G7AtN7bvCIyO3Ynbzeg+NDzfGcWh0nEfuP4gKTrFO6QnBMMcZ9r+SzLP3EryqEs0AzeZ56n6Y+A==
"@bsky.app/react-native-mmkv@2.12.5":
version "2.12.5"