Add error handling with Google Translate link
This commit is contained in:
+1
-1
@@ -85,7 +85,7 @@
|
|||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@bsky.app/alf": "^0.1.7",
|
"@bsky.app/alf": "^0.1.7",
|
||||||
"@bsky.app/expo-image-crop-tool": "^0.5.0",
|
"@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",
|
"@bsky.app/react-native-mmkv": "2.12.5",
|
||||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {atoms as a, native, useTheme} from '#/alf'
|
|||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow'
|
import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow'
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
|
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 {InlineLinkText} from '#/components/Link'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import * as Select from '#/components/Select'
|
import * as Select from '#/components/Select'
|
||||||
@@ -45,6 +46,17 @@ export function TranslatedPost({
|
|||||||
[post, langPrefs.primaryLanguage],
|
[post, langPrefs.primaryLanguage],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (translationState.status === 'error') {
|
||||||
|
return (
|
||||||
|
<TranslationError
|
||||||
|
clearTranslation={clearTranslation}
|
||||||
|
message={translationState.message}
|
||||||
|
postText={postText}
|
||||||
|
primaryLanguage={langPrefs.primaryLanguage}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (translationState.status === 'loading') {
|
if (translationState.status === 'loading') {
|
||||||
return <TranslationLoading />
|
return <TranslationLoading />
|
||||||
}
|
}
|
||||||
@@ -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 (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.px_lg,
|
||||||
|
a.py_md,
|
||||||
|
a.mt_sm,
|
||||||
|
a.border,
|
||||||
|
a.rounded_lg,
|
||||||
|
t.atoms.border_contrast_high,
|
||||||
|
]}>
|
||||||
|
<View
|
||||||
|
style={[a.flex_row, a.flex_wrap, a.align_center, a.justify_between]}>
|
||||||
|
<View style={[a.flex_row, a.align_center, a.mb_sm]}>
|
||||||
|
<Warning size="sm" fill={t.atoms.text_contrast_medium.color} />
|
||||||
|
<Text style={[a.text_xs, a.font_medium, t.atoms.text_contrast_high]}>
|
||||||
|
{' '}
|
||||||
|
{message}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[a.flex_row, a.align_center, a.mb_xs]}>
|
||||||
|
<Button
|
||||||
|
label={l`Hide translation`}
|
||||||
|
hitSlop={HITSLOP_30}
|
||||||
|
hoverStyle={native({opacity: 0.5})}
|
||||||
|
onPress={clearTranslation}>
|
||||||
|
<Times size="sm" fill={t.atoms.text_contrast_medium.color} />
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={[a.flex_row, a.align_center]}>
|
||||||
|
<Text>
|
||||||
|
<InlineLinkText
|
||||||
|
to={getTranslatorLink(postText, primaryLanguage)}
|
||||||
|
label={l`Try Google Translate`}
|
||||||
|
style={[a.text_xs, a.font_medium]}>
|
||||||
|
<Trans>Try Google Translate</Trans>
|
||||||
|
</InlineLinkText>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function TranslationResult({
|
function TranslationResult({
|
||||||
clearTranslation,
|
clearTranslation,
|
||||||
translate,
|
translate,
|
||||||
@@ -213,7 +282,12 @@ function TranslationResult({
|
|||||||
)}
|
)}
|
||||||
{sourceLanguage != null && (
|
{sourceLanguage != null && (
|
||||||
<>
|
<>
|
||||||
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_xs,
|
||||||
|
a.font_medium,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
{' '}
|
{' '}
|
||||||
·{' '}
|
·{' '}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -250,6 +324,7 @@ function TranslationLanguageSelect({
|
|||||||
postText: string
|
postText: string
|
||||||
sourceLanguage: string
|
sourceLanguage: string
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
@@ -300,7 +375,8 @@ function TranslationLanguageSelect({
|
|||||||
{...props}
|
{...props}
|
||||||
hitSlop={HITSLOP_30}
|
hitSlop={HITSLOP_30}
|
||||||
hoverStyle={native({opacity: 0.5})}>
|
hoverStyle={native({opacity: 0.5})}>
|
||||||
<Text style={[a.text_xs, a.font_medium]}>
|
<Text
|
||||||
|
style={[a.text_xs, a.font_medium, t.atoms.text_contrast_high]}>
|
||||||
<Trans>Change</Trans>
|
<Trans>Change</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {useCallback, useContext, useEffect, useMemo, useState} from 'react'
|
|||||||
import {LayoutAnimation, Platform} from 'react-native'
|
import {LayoutAnimation, Platform} from 'react-native'
|
||||||
import {getLocales} from 'expo-localization'
|
import {getLocales} from 'expo-localization'
|
||||||
import {type TranslationTaskResult} from '@bsky.app/expo-translate-text/build/ExpoTranslateText.types'
|
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 {useFocusEffect} from '@react-navigation/native'
|
||||||
|
|
||||||
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
|
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.
|
// 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 {
|
return {
|
||||||
translatedText:
|
translatedText,
|
||||||
typeof result.translatedTexts === 'string' ? result.translatedTexts : '',
|
|
||||||
targetLanguage: result.targetLanguage,
|
targetLanguage: result.targetLanguage,
|
||||||
sourceLanguage: result.sourceLanguage ?? sourceLangCode ?? null, // iOS doesn't return the source language
|
sourceLanguage: result.sourceLanguage ?? sourceLangCode ?? null, // iOS doesn't return the source language
|
||||||
}
|
}
|
||||||
@@ -125,6 +132,7 @@ 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 {t: l} = useLingui()
|
||||||
const googleTranslate = useGoogleTranslate()
|
const googleTranslate = useGoogleTranslate()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -235,12 +243,12 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
|
|||||||
sourceLanguage: sourceLangCode ?? null,
|
sourceLanguage: sourceLangCode ?? null,
|
||||||
targetLanguage: targetLangCode,
|
targetLanguage: targetLangCode,
|
||||||
})
|
})
|
||||||
|
let errorMessage = l`Device failed to translate. :(`
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||||
setTranslationState(prev => ({
|
setTranslationState(prev => ({
|
||||||
...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<unknown>) {
|
|||||||
[key]: {status: 'loading'},
|
[key]: {status: 'loading'},
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
[ax, googleTranslate],
|
[ax, googleTranslate, l],
|
||||||
)
|
)
|
||||||
|
|
||||||
const ctx = useMemo(
|
const ctx = useMemo(
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ export type TranslationState =
|
|||||||
sourceLanguage: TranslationTaskResult['sourceLanguage']
|
sourceLanguage: TranslationTaskResult['sourceLanguage']
|
||||||
targetLanguage: TranslationTaskResult['targetLanguage']
|
targetLanguage: TranslationTaskResult['targetLanguage']
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
status: 'error'
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
export type TranslationFunctionParams = {
|
export type TranslationFunctionParams = {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3731,10 +3731,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@bsky.app/expo-image-crop-tool/-/expo-image-crop-tool-0.5.0.tgz#4308fbde5c15e6be9122601797bc3d9549c95e31"
|
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==
|
integrity sha512-gmhQr2HWTRFyPO00fn5OmtiEVtikXusHMrN5Zoq26pu1VZX3zVE+aoc668etTqrvsQcm2Qu8fo96k5F3Wu+6wg==
|
||||||
|
|
||||||
"@bsky.app/expo-translate-text@^0.2.4":
|
"@bsky.app/expo-translate-text@^0.2.5":
|
||||||
version "0.2.4"
|
version "0.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/@bsky.app/expo-translate-text/-/expo-translate-text-0.2.4.tgz#6e7f20f286111ee4d550c0c84f57393fc215a675"
|
resolved "https://registry.yarnpkg.com/@bsky.app/expo-translate-text/-/expo-translate-text-0.2.5.tgz#eb0bf41522b710b11e64d14a06d4ec770a72ad20"
|
||||||
integrity sha512-7mvFggNfkJEufI5A3WnjfjdN3H9P6Dpx7CpDkA9npWqA8Cb2icXq3k3nz3MaXGrVKTYiJnytAffYtos7mPoeOg==
|
integrity sha512-nWGxK8bwKN6G7AtN7bvCIyO3Ynbzeg+NDzfGcWh0nEfuP4gKTrFO6QnBMMcZ9r+SzLP3EryqEs0AzeZ56n6Y+A==
|
||||||
|
|
||||||
"@bsky.app/react-native-mmkv@2.12.5":
|
"@bsky.app/react-native-mmkv@2.12.5":
|
||||||
version "2.12.5"
|
version "2.12.5"
|
||||||
|
|||||||
Reference in New Issue
Block a user