Rename translation properties for clarity (#10061)
This commit is contained in:
@@ -708,24 +708,83 @@ export type Events = {
|
|||||||
|
|
||||||
translate: {
|
translate: {
|
||||||
os: Platform['OS']
|
os: Platform['OS']
|
||||||
sourceLanguages: string[]
|
/**
|
||||||
targetLanguage: string
|
* The languages the content might be in, such as the user-supplied
|
||||||
|
* language codes on posts. Currently only available on posts.
|
||||||
|
*/
|
||||||
|
possibleSourceLanguages: string[] | undefined
|
||||||
|
/**
|
||||||
|
* This is the user's configured primary language, which is always defined.
|
||||||
|
*/
|
||||||
|
expectedTargetLanguage: string
|
||||||
|
/**
|
||||||
|
* The length of the text being translated. We assume shorter texts are
|
||||||
|
* more likely to have inaccurate translations.
|
||||||
|
*/
|
||||||
textLength: number
|
textLength: number
|
||||||
|
googleTranslate: boolean
|
||||||
}
|
}
|
||||||
'translate:result': {
|
'translate:result': {
|
||||||
method: 'on-device' | 'fallback-alert'
|
success: boolean
|
||||||
os: Platform['OS']
|
os: Platform['OS']
|
||||||
sourceSelection: 'automatic' | 'manual'
|
/**
|
||||||
sourceLanguage: string | null
|
* The languages the content might be in, such as the user-supplied
|
||||||
targetLanguage: string
|
* language codes on posts. Currently only available on posts.
|
||||||
|
*/
|
||||||
/* Only relevant to posts */
|
possibleSourceLanguages: string[] | undefined
|
||||||
postLanguages?: string[]
|
/**
|
||||||
|
* The language we expected the content to be in. This could be based on
|
||||||
|
* user selection or on our confidence in the detected language. This is
|
||||||
|
* nullable because we may not always have an expected source language.
|
||||||
|
*/
|
||||||
|
expectedSourceLanguage: string | null
|
||||||
|
/**
|
||||||
|
* This is the user's configured primary language, which is always defined.
|
||||||
|
*/
|
||||||
|
expectedTargetLanguage: string
|
||||||
|
/**
|
||||||
|
* The language the translation result was actually in. This is nullable
|
||||||
|
* because the translation could have failed, in which case we won't have a
|
||||||
|
* result source language.
|
||||||
|
*/
|
||||||
|
resultSourceLanguage: string | null
|
||||||
|
/**
|
||||||
|
* The language the translation result was translated into. This should be
|
||||||
|
* the same as `expectedTargetLanguage`, but we include it for completeness
|
||||||
|
* and in case there are any edge cases where they differ. This is nullable
|
||||||
|
* because if the translation failed, we won't have a result target
|
||||||
|
* language.
|
||||||
|
*/
|
||||||
|
resultTargetLanguage: string | null
|
||||||
|
/**
|
||||||
|
* The length of the text being translated. We assume shorter texts are
|
||||||
|
* more likely to have inaccurate translations.
|
||||||
|
*/
|
||||||
|
textLength: number
|
||||||
}
|
}
|
||||||
'translate:override': {
|
'translate:override': {
|
||||||
os: Platform['OS']
|
os: Platform['OS']
|
||||||
sourceLanguage: string
|
/**
|
||||||
targetLanguage: string
|
* The languages the content might be in, such as the user-supplied
|
||||||
|
* language codes on posts. Currently only available on posts.
|
||||||
|
*/
|
||||||
|
possibleSourceLanguages: string[] | undefined
|
||||||
|
/**
|
||||||
|
* The language the user has indicated the content is actually in, which
|
||||||
|
* may be different from the expected source language if the user is
|
||||||
|
* overriding the auto-detected language. This is the language the user
|
||||||
|
* wants to translate from after overriding.
|
||||||
|
*/
|
||||||
|
expectedSourceLanguage: string
|
||||||
|
/**
|
||||||
|
* This is the user's configured primary language, which is always defined.
|
||||||
|
*/
|
||||||
|
expectedTargetLanguage: string
|
||||||
|
/**
|
||||||
|
* The language the translation result was actually in, which the user now
|
||||||
|
* wishes to override.
|
||||||
|
*/
|
||||||
|
resultSourceLanguage: string
|
||||||
}
|
}
|
||||||
|
|
||||||
'postMenu:openMuteWordsDialog': {
|
'postMenu:openMuteWordsDialog': {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import {useCallback, useMemo} from 'react'
|
import {useCallback, useMemo} from 'react'
|
||||||
import {Platform, type StyleProp, type TextStyle, View} from 'react-native'
|
import {Platform, type StyleProp, type TextStyle, View} from 'react-native'
|
||||||
import {type AppBskyFeedDefs} from '@atproto/api'
|
import {type AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {HITSLOP_30} from '#/lib/constants'
|
import {HITSLOP_30} from '#/lib/constants'
|
||||||
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
|
|
||||||
import {useTranslate} from '#/lib/translation'
|
import {useTranslate} from '#/lib/translation'
|
||||||
import {type TranslationFunction} from '#/lib/translation'
|
import {
|
||||||
|
type TranslationFunction,
|
||||||
|
type TranslationFunctionParams,
|
||||||
|
} from '#/lib/translation'
|
||||||
import {
|
import {
|
||||||
codeToLanguageName,
|
codeToLanguageName,
|
||||||
getPostLanguageTags,
|
getPostLanguageTags,
|
||||||
@@ -26,26 +28,39 @@ import * as Select from '#/components/Select'
|
|||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
const X_ICON_OFFSET = 16
|
const X_ICON_OFFSET = 16
|
||||||
|
|
||||||
export function TranslatedPost({
|
export function TranslatedPost({
|
||||||
hideTranslateLink = false,
|
hideTranslateLink = false,
|
||||||
post,
|
post,
|
||||||
postText,
|
|
||||||
postTextStyle = a.text_md,
|
postTextStyle = a.text_md,
|
||||||
}: {
|
}: {
|
||||||
hideTranslateLink?: boolean
|
hideTranslateLink?: boolean
|
||||||
post: AppBskyFeedDefs.PostView
|
post: AppBskyFeedDefs.PostView
|
||||||
postText: string
|
|
||||||
postTextStyle?: StyleProp<TextStyle>
|
postTextStyle?: StyleProp<TextStyle>
|
||||||
}) {
|
}) {
|
||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
const {clearTranslation, translate, translationState} = useTranslate({
|
const {clearTranslation, translate, translationState} = useTranslate({
|
||||||
key: post.uri,
|
key: post.uri,
|
||||||
postLangCodes: getPostLanguageTags(post),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const record = useMemo<AppBskyFeedPost.Record | undefined>(() => {
|
||||||
|
return bsky.dangerousIsType<AppBskyFeedPost.Record>(
|
||||||
|
post.record,
|
||||||
|
AppBskyFeedPost.isRecord,
|
||||||
|
)
|
||||||
|
? post.record
|
||||||
|
: undefined
|
||||||
|
}, [post])
|
||||||
|
const initialTranslationParams = useMemo<TranslationFunctionParams>(() => {
|
||||||
|
return {
|
||||||
|
text: record?.text || '',
|
||||||
|
expectedTargetLanguage: langPrefs.primaryLanguage,
|
||||||
|
possibleSourceLanguages: getPostLanguageTags(post),
|
||||||
|
}
|
||||||
|
}, [post, record, langPrefs])
|
||||||
const needsTranslation = useMemo(() => {
|
const needsTranslation = useMemo(() => {
|
||||||
if (hideTranslateLink) return false
|
if (hideTranslateLink) return false
|
||||||
return !isPostInLanguage(post, [langPrefs.primaryLanguage])
|
return !isPostInLanguage(post, [langPrefs.primaryLanguage])
|
||||||
@@ -57,11 +72,11 @@ export function TranslatedPost({
|
|||||||
case 'success':
|
case 'success':
|
||||||
return (
|
return (
|
||||||
<TranslationResult
|
<TranslationResult
|
||||||
clearTranslation={clearTranslation}
|
|
||||||
translate={translate}
|
translate={translate}
|
||||||
postText={postText}
|
clearTranslation={clearTranslation}
|
||||||
|
initialTranslationParams={initialTranslationParams}
|
||||||
postTextStyle={postTextStyle}
|
postTextStyle={postTextStyle}
|
||||||
sourceLanguage={
|
resultSourceLanguage={
|
||||||
translationState.sourceLanguage ?? null // Fallback primarily for iOS
|
translationState.sourceLanguage ?? null // Fallback primarily for iOS
|
||||||
}
|
}
|
||||||
translatedText={translationState.translatedText}
|
translatedText={translationState.translatedText}
|
||||||
@@ -70,19 +85,18 @@ export function TranslatedPost({
|
|||||||
case 'error':
|
case 'error':
|
||||||
return (
|
return (
|
||||||
<TranslationError
|
<TranslationError
|
||||||
|
translate={translate}
|
||||||
clearTranslation={clearTranslation}
|
clearTranslation={clearTranslation}
|
||||||
message={translationState.message}
|
message={translationState.message}
|
||||||
postText={postText}
|
initialTranslationParams={initialTranslationParams}
|
||||||
primaryLanguage={langPrefs.primaryLanguage}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
needsTranslation && (
|
needsTranslation && (
|
||||||
<TranslationLink
|
<TranslationLink
|
||||||
postText={postText}
|
|
||||||
primaryLanguage={langPrefs.primaryLanguage}
|
|
||||||
translate={translate}
|
translate={translate}
|
||||||
|
initialTranslationParams={initialTranslationParams}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -105,31 +119,18 @@ function TranslationLoading() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TranslationLink({
|
function TranslationLink({
|
||||||
postText,
|
|
||||||
primaryLanguage,
|
|
||||||
translate,
|
translate,
|
||||||
|
initialTranslationParams,
|
||||||
}: {
|
}: {
|
||||||
postText: string
|
|
||||||
primaryLanguage: string
|
|
||||||
translate: TranslationFunction
|
translate: TranslationFunction
|
||||||
|
initialTranslationParams: TranslationFunctionParams
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const ax = useAnalytics()
|
|
||||||
|
|
||||||
const handleTranslate = useCallback(() => {
|
const handleTranslate = useCallback(() => {
|
||||||
void translate({
|
void translate(initialTranslationParams)
|
||||||
text: postText,
|
}, [initialTranslationParams, translate])
|
||||||
targetLangCode: primaryLanguage,
|
|
||||||
})
|
|
||||||
|
|
||||||
ax.metric('translate', {
|
|
||||||
os: Platform.OS,
|
|
||||||
sourceLanguages: [], // todo: get from post maybe?
|
|
||||||
targetLanguage: primaryLanguage,
|
|
||||||
textLength: postText.length,
|
|
||||||
})
|
|
||||||
}, [ax, postText, primaryLanguage, translate])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@@ -161,22 +162,24 @@ function TranslationLink({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TranslationError({
|
function TranslationError({
|
||||||
|
translate,
|
||||||
clearTranslation,
|
clearTranslation,
|
||||||
message,
|
message,
|
||||||
postText,
|
initialTranslationParams,
|
||||||
primaryLanguage,
|
|
||||||
}: {
|
}: {
|
||||||
|
translate: TranslationFunction
|
||||||
clearTranslation: () => void
|
clearTranslation: () => void
|
||||||
message: string
|
message: string
|
||||||
postText: string
|
initialTranslationParams: TranslationFunctionParams
|
||||||
primaryLanguage: string
|
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const translate = useGoogleTranslate()
|
|
||||||
|
|
||||||
const handleFallback = () => {
|
const handleFallback = () => {
|
||||||
void translate(postText, primaryLanguage)
|
void translate({
|
||||||
|
...initialTranslationParams,
|
||||||
|
forceGoogleTranslate: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -247,24 +250,24 @@ function TranslationError({
|
|||||||
function TranslationResult({
|
function TranslationResult({
|
||||||
clearTranslation,
|
clearTranslation,
|
||||||
translate,
|
translate,
|
||||||
postText,
|
|
||||||
postTextStyle,
|
postTextStyle,
|
||||||
sourceLanguage,
|
resultSourceLanguage,
|
||||||
translatedText,
|
translatedText,
|
||||||
|
initialTranslationParams,
|
||||||
}: {
|
}: {
|
||||||
clearTranslation: () => void
|
clearTranslation: () => void
|
||||||
translate: TranslationFunction
|
translate: TranslationFunction
|
||||||
postText: string
|
|
||||||
postTextStyle?: StyleProp<TextStyle>
|
postTextStyle?: StyleProp<TextStyle>
|
||||||
sourceLanguage: string | null
|
resultSourceLanguage: string | null
|
||||||
translatedText: string
|
translatedText: string
|
||||||
|
initialTranslationParams: TranslationFunctionParams
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
const {i18n, t: l} = useLingui()
|
const {i18n, t: l} = useLingui()
|
||||||
|
|
||||||
const langName = sourceLanguage
|
const langName = resultSourceLanguage
|
||||||
? codeToLanguageName(sourceLanguage, i18n.locale)
|
? codeToLanguageName(resultSourceLanguage, i18n.locale)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const flattenedStyle = flatten(postTextStyle) ?? {}
|
const flattenedStyle = flatten(postTextStyle) ?? {}
|
||||||
@@ -323,7 +326,7 @@ function TranslationResult({
|
|||||||
<Trans>Translated</Trans>
|
<Trans>Translated</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{sourceLanguage != null && (
|
{resultSourceLanguage != null && (
|
||||||
<>
|
<>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
@@ -336,9 +339,9 @@ function TranslationResult({
|
|||||||
·{' '}
|
·{' '}
|
||||||
</Text>
|
</Text>
|
||||||
<TranslationLanguageSelect
|
<TranslationLanguageSelect
|
||||||
sourceLanguage={sourceLanguage}
|
resultSourceLanguage={resultSourceLanguage}
|
||||||
translate={translate}
|
translate={translate}
|
||||||
postText={postText}
|
initialTranslationParams={initialTranslationParams}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -362,12 +365,12 @@ function TranslationResult({
|
|||||||
|
|
||||||
function TranslationLanguageSelect({
|
function TranslationLanguageSelect({
|
||||||
translate,
|
translate,
|
||||||
postText,
|
resultSourceLanguage,
|
||||||
sourceLanguage,
|
initialTranslationParams,
|
||||||
}: {
|
}: {
|
||||||
translate: TranslationFunction
|
translate: TranslationFunction
|
||||||
postText: string
|
resultSourceLanguage: string
|
||||||
sourceLanguage: string
|
initialTranslationParams: TranslationFunctionParams
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
@@ -383,8 +386,8 @@ function TranslationLanguageSelect({
|
|||||||
)
|
)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
// Prioritize sourceLanguage at the top
|
// Prioritize sourceLanguage at the top
|
||||||
if (a.code2 === sourceLanguage) return -1
|
if (a.code2 === resultSourceLanguage) return -1
|
||||||
if (b.code2 === sourceLanguage) return 1
|
if (b.code2 === resultSourceLanguage) return 1
|
||||||
// Localized sort
|
// Localized sort
|
||||||
return languageName(a, langPrefs.appLanguage).localeCompare(
|
return languageName(a, langPrefs.appLanguage).localeCompare(
|
||||||
languageName(b, langPrefs.appLanguage),
|
languageName(b, langPrefs.appLanguage),
|
||||||
@@ -395,26 +398,28 @@ function TranslationLanguageSelect({
|
|||||||
label: languageName(l, langPrefs.appLanguage), // The viewer may not be familiar with the source language, so localize the name
|
label: languageName(l, langPrefs.appLanguage), // The viewer may not be familiar with the source language, so localize the name
|
||||||
value: l.code2,
|
value: l.code2,
|
||||||
})),
|
})),
|
||||||
[langPrefs, sourceLanguage],
|
[langPrefs, resultSourceLanguage],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleChangeTranslationLanguage = (sourceLangCode: string) => {
|
const handleChangeTranslationLanguage = (sourceLangCode: string) => {
|
||||||
ax.metric('translate:override', {
|
ax.metric('translate:override', {
|
||||||
os: Platform.OS,
|
os: Platform.OS,
|
||||||
sourceLanguage: sourceLangCode,
|
possibleSourceLanguages: initialTranslationParams.possibleSourceLanguages,
|
||||||
targetLanguage: langPrefs.primaryLanguage,
|
expectedSourceLanguage: sourceLangCode,
|
||||||
|
expectedTargetLanguage: initialTranslationParams.expectedTargetLanguage,
|
||||||
|
resultSourceLanguage,
|
||||||
})
|
})
|
||||||
void translate({
|
void translate({
|
||||||
text: postText,
|
text: initialTranslationParams.text,
|
||||||
targetLangCode: langPrefs.primaryLanguage,
|
expectedTargetLanguage: initialTranslationParams.expectedTargetLanguage,
|
||||||
sourceLangCode,
|
expectedSourceLanguage: sourceLangCode,
|
||||||
sourceSelection: 'manual',
|
possibleSourceLanguages: initialTranslationParams.possibleSourceLanguages,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select.Root
|
<Select.Root
|
||||||
value={sourceLanguage}
|
value={resultSourceLanguage}
|
||||||
onValueChange={handleChangeTranslationLanguage}>
|
onValueChange={handleChangeTranslationLanguage}>
|
||||||
<Select.Trigger label={l`Change the source language`}>
|
<Select.Trigger label={l`Change the source language`}>
|
||||||
{({props}) => {
|
{({props}) => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import * as Clipboard from 'expo-clipboard'
|
import * as Clipboard from 'expo-clipboard'
|
||||||
import {
|
import {
|
||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
type AppBskyFeedPost,
|
||||||
type AppBskyFeedThreadgate,
|
type AppBskyFeedThreadgate,
|
||||||
AtUri,
|
AtUri,
|
||||||
type RichText as RichTextAPI,
|
type RichText as RichTextAPI,
|
||||||
@@ -96,7 +96,6 @@ import * as Prompt from '#/components/Prompt'
|
|||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {IS_INTERNAL} from '#/env'
|
import {IS_INTERNAL} from '#/env'
|
||||||
import * as bsky from '#/types/bsky'
|
|
||||||
|
|
||||||
let PostMenuItems = ({
|
let PostMenuItems = ({
|
||||||
post,
|
post,
|
||||||
@@ -138,7 +137,6 @@ let PostMenuItems = ({
|
|||||||
const openLink = useOpenLink()
|
const openLink = useOpenLink()
|
||||||
const {clearTranslation, translate, translationState} = useTranslate({
|
const {clearTranslation, translate, translationState} = useTranslate({
|
||||||
key: post.uri,
|
key: post.uri,
|
||||||
postLangCodes: getPostLanguageTags(post),
|
|
||||||
forceGoogleTranslate,
|
forceGoogleTranslate,
|
||||||
})
|
})
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
@@ -279,22 +277,9 @@ let PostMenuItems = ({
|
|||||||
const onPressTranslate = () => {
|
const onPressTranslate = () => {
|
||||||
void translate({
|
void translate({
|
||||||
text: record.text,
|
text: record.text,
|
||||||
targetLangCode: langPrefs.primaryLanguage,
|
expectedTargetLanguage: langPrefs.primaryLanguage,
|
||||||
|
possibleSourceLanguages: getPostLanguageTags(post),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (
|
|
||||||
bsky.dangerousIsType<AppBskyFeedPost.Record>(
|
|
||||||
post.record,
|
|
||||||
AppBskyFeedPost.isRecord,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ax.metric('translate', {
|
|
||||||
os: Platform.OS,
|
|
||||||
sourceLanguages: post.record.langs ?? [],
|
|
||||||
targetLanguage: langPrefs.primaryLanguage,
|
|
||||||
textLength: post.record.text.length,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onHidePost = () => {
|
const onHidePost = () => {
|
||||||
|
|||||||
@@ -68,9 +68,10 @@ export let MessageContextMenu = ({
|
|||||||
|
|
||||||
ax.metric('translate', {
|
ax.metric('translate', {
|
||||||
os: Platform.OS,
|
os: Platform.OS,
|
||||||
sourceLanguages: [],
|
possibleSourceLanguages: [], // N/A for chats
|
||||||
targetLanguage: langPrefs.primaryLanguage,
|
expectedTargetLanguage: langPrefs.primaryLanguage,
|
||||||
textLength: message.text.length,
|
textLength: message.text.length,
|
||||||
|
googleTranslate: true,
|
||||||
})
|
})
|
||||||
}, [ax, langPrefs.primaryLanguage, message.text, translate])
|
}, [ax, langPrefs.primaryLanguage, message.text, translate])
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ async function attemptTranslation(
|
|||||||
export function useTranslate({
|
export function useTranslate({
|
||||||
key,
|
key,
|
||||||
forceGoogleTranslate = false,
|
forceGoogleTranslate = false,
|
||||||
postLangCodes,
|
|
||||||
}: TranslationOptions) {
|
}: TranslationOptions) {
|
||||||
const context = useContext(Context)
|
const context = useContext(Context)
|
||||||
if (!context) {
|
if (!context) {
|
||||||
@@ -121,14 +120,17 @@ export function useTranslate({
|
|||||||
|
|
||||||
const translate = useCallback(
|
const translate = useCallback(
|
||||||
async (params: TranslationFunctionParams) => {
|
async (params: TranslationFunctionParams) => {
|
||||||
return context.translate({
|
return context.translate(
|
||||||
...params,
|
{
|
||||||
key,
|
...params,
|
||||||
forceGoogleTranslate,
|
},
|
||||||
postLangCodes,
|
{
|
||||||
})
|
key,
|
||||||
|
forceGoogleTranslate,
|
||||||
|
},
|
||||||
|
)
|
||||||
},
|
},
|
||||||
[context, forceGoogleTranslate, key, postLangCodes],
|
[context, forceGoogleTranslate, key],
|
||||||
)
|
)
|
||||||
|
|
||||||
const clearTranslation = useCallback(
|
const clearTranslation = useCallback(
|
||||||
@@ -208,17 +210,34 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const translate = useCallback<ContextType['translate']>(
|
const translate = useCallback<ContextType['translate']>(
|
||||||
async ({
|
async (
|
||||||
key,
|
{
|
||||||
text,
|
text,
|
||||||
targetLangCode,
|
expectedTargetLanguage,
|
||||||
sourceLangCode,
|
expectedSourceLanguage,
|
||||||
sourceSelection = 'automatic',
|
possibleSourceLanguages,
|
||||||
postLangCodes,
|
forceGoogleTranslate: forceGoogleTranslateOverride,
|
||||||
...options
|
},
|
||||||
}) => {
|
{key, forceGoogleTranslate},
|
||||||
if (options?.forceGoogleTranslate || !HAS_ON_DEVICE_TRANSLATION) {
|
) => {
|
||||||
await googleTranslate(text, targetLangCode, sourceLangCode)
|
const shouldForceGoogleTranslate = Boolean(
|
||||||
|
forceGoogleTranslateOverride ?? forceGoogleTranslate,
|
||||||
|
)
|
||||||
|
|
||||||
|
ax.metric('translate', {
|
||||||
|
os: Platform.OS,
|
||||||
|
possibleSourceLanguages,
|
||||||
|
expectedTargetLanguage: expectedTargetLanguage,
|
||||||
|
textLength: text.length,
|
||||||
|
googleTranslate: shouldForceGoogleTranslate,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (shouldForceGoogleTranslate || !HAS_ON_DEVICE_TRANSLATION) {
|
||||||
|
await googleTranslate(
|
||||||
|
text,
|
||||||
|
expectedTargetLanguage,
|
||||||
|
expectedSourceLanguage,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,16 +251,18 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
|
|||||||
try {
|
try {
|
||||||
const result = await attemptTranslation(
|
const result = await attemptTranslation(
|
||||||
text,
|
text,
|
||||||
targetLangCode,
|
expectedTargetLanguage,
|
||||||
sourceLangCode,
|
expectedSourceLanguage,
|
||||||
)
|
)
|
||||||
ax.metric('translate:result', {
|
ax.metric('translate:result', {
|
||||||
method: 'on-device',
|
success: true,
|
||||||
os: Platform.OS,
|
os: Platform.OS,
|
||||||
sourceSelection,
|
possibleSourceLanguages,
|
||||||
sourceLanguage: result.sourceLanguage,
|
expectedSourceLanguage: expectedSourceLanguage ?? null,
|
||||||
targetLanguage: result.targetLanguage,
|
expectedTargetLanguage,
|
||||||
postLanguages: postLangCodes,
|
resultSourceLanguage: result.sourceLanguage,
|
||||||
|
resultTargetLanguage: result.targetLanguage,
|
||||||
|
textLength: text.length,
|
||||||
})
|
})
|
||||||
if (!IS_ANDROID) {
|
if (!IS_ANDROID) {
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||||
@@ -253,7 +274,7 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
|
|||||||
translatedText: result.translatedText,
|
translatedText: result.translatedText,
|
||||||
sourceLanguage: result.sourceLanguage,
|
sourceLanguage: result.sourceLanguage,
|
||||||
targetLanguage: result.targetLanguage,
|
targetLanguage: result.targetLanguage,
|
||||||
postLanguages: postLangCodes,
|
postLanguages: possibleSourceLanguages,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -261,12 +282,14 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
|
|||||||
// On-device translation failed (language pack missing or user
|
// On-device translation failed (language pack missing or user
|
||||||
// dismissed the download prompt).
|
// dismissed the download prompt).
|
||||||
ax.metric('translate:result', {
|
ax.metric('translate:result', {
|
||||||
method: 'fallback-alert',
|
success: false,
|
||||||
os: Platform.OS,
|
os: Platform.OS,
|
||||||
sourceSelection,
|
possibleSourceLanguages,
|
||||||
sourceLanguage: sourceLangCode ?? null,
|
expectedSourceLanguage: expectedSourceLanguage ?? null,
|
||||||
targetLanguage: targetLangCode,
|
expectedTargetLanguage,
|
||||||
postLanguages: postLangCodes,
|
resultSourceLanguage: null,
|
||||||
|
resultTargetLanguage: null,
|
||||||
|
textLength: text.length,
|
||||||
})
|
})
|
||||||
let errorMessage = l`Device failed to translate :(`
|
let errorMessage = l`Device failed to translate :(`
|
||||||
if (!IS_ANDROID) {
|
if (!IS_ANDROID) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const clearTranslation = (_key: string) => {}
|
|||||||
/**
|
/**
|
||||||
* Web always opens Google Translate.
|
* Web always opens Google Translate.
|
||||||
*/
|
*/
|
||||||
export function useTranslate({key, postLangCodes}: TranslationOptions) {
|
export function useTranslate({key}: TranslationOptions) {
|
||||||
const context = useContext(Context)
|
const context = useContext(Context)
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -33,14 +33,17 @@ export function useTranslate({key, postLangCodes}: TranslationOptions) {
|
|||||||
// Always call hooks in consistent order
|
// Always call hooks in consistent order
|
||||||
const translate = useCallback(
|
const translate = useCallback(
|
||||||
async (params: TranslationFunctionParams) => {
|
async (params: TranslationFunctionParams) => {
|
||||||
return context.translate({
|
return context.translate(
|
||||||
...params,
|
{
|
||||||
key,
|
...params,
|
||||||
forceGoogleTranslate: true,
|
},
|
||||||
postLangCodes,
|
{
|
||||||
})
|
key,
|
||||||
|
forceGoogleTranslate: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
},
|
},
|
||||||
[key, context, postLangCodes],
|
[key, context],
|
||||||
)
|
)
|
||||||
|
|
||||||
const clearTranslation = useCallback(() => {
|
const clearTranslation = useCallback(() => {
|
||||||
@@ -61,8 +64,24 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
|
|||||||
const googleTranslate = useGoogleTranslate()
|
const googleTranslate = useGoogleTranslate()
|
||||||
|
|
||||||
const translate = useCallback<ContextType['translate']>(
|
const translate = useCallback<ContextType['translate']>(
|
||||||
async ({text, targetLangCode, sourceLangCode}) => {
|
async ({
|
||||||
await googleTranslate(text, targetLangCode, sourceLangCode)
|
text,
|
||||||
|
expectedTargetLanguage,
|
||||||
|
expectedSourceLanguage,
|
||||||
|
possibleSourceLanguages,
|
||||||
|
}) => {
|
||||||
|
ax.metric('translate', {
|
||||||
|
os: 'web',
|
||||||
|
possibleSourceLanguages,
|
||||||
|
expectedTargetLanguage,
|
||||||
|
textLength: text.length,
|
||||||
|
googleTranslate: true,
|
||||||
|
})
|
||||||
|
await googleTranslate(
|
||||||
|
text,
|
||||||
|
expectedTargetLanguage,
|
||||||
|
expectedSourceLanguage,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
[ax, googleTranslate],
|
[ax, googleTranslate],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,38 +22,45 @@ export type TranslationFunctionParams = {
|
|||||||
/**
|
/**
|
||||||
* The language to translate the text into.
|
* The language to translate the text into.
|
||||||
*/
|
*/
|
||||||
targetLangCode: string
|
expectedTargetLanguage: string
|
||||||
/**
|
/**
|
||||||
* The source language of the text. Will auto-detect if not provided.
|
* We auto-detect the source language by default, but the user has the option
|
||||||
|
* to specify a source language if they want to. If this value is present, it
|
||||||
|
* means the user selected a source language, or we were certain of the
|
||||||
|
* source language and want to specify it explicitly.
|
||||||
*/
|
*/
|
||||||
sourceLangCode?: string
|
expectedSourceLanguage?: string
|
||||||
/**
|
/**
|
||||||
* Whether we auto-detected the language or it was selected manually. Defaults to 'automatic'.
|
* The languages the content might be in, such as the user-supplied
|
||||||
|
* language codes on posts. Currently only available on posts.
|
||||||
*/
|
*/
|
||||||
sourceSelection?: 'automatic' | 'manual'
|
possibleSourceLanguages?: string[]
|
||||||
|
/**
|
||||||
|
* Override the default behavior and always use Google Translate.
|
||||||
|
*/
|
||||||
|
forceGoogleTranslate?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TranslationOptions = {
|
export type TranslationOptions = {
|
||||||
key: string
|
|
||||||
forceGoogleTranslate?: boolean
|
|
||||||
/**
|
/**
|
||||||
* The language(s) of the post being translated. Used for analytics purposes
|
* A unique key to identify this translation instance e.g. the post URI
|
||||||
* to understand translation usage patterns better. Optional because it may
|
|
||||||
* not always be available (e.g. if the post text is empty or if the
|
|
||||||
* translation is triggered from a non-post
|
|
||||||
* context).
|
|
||||||
*/
|
*/
|
||||||
postLangCodes?: string[]
|
key: string
|
||||||
|
/**
|
||||||
|
* Override the default behavior and always use Google Translate.
|
||||||
|
*/
|
||||||
|
forceGoogleTranslate?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TranslationFunction = (
|
export type TranslationFunction = (
|
||||||
parameters: TranslationFunctionParams,
|
params: TranslationFunctionParams,
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
|
|
||||||
export type ContextType = {
|
export type ContextType = {
|
||||||
translationState: Record<string, TranslationState>
|
translationState: Record<string, TranslationState>
|
||||||
translate: (
|
translate: (
|
||||||
parameters: TranslationFunctionParams & TranslationOptions,
|
params: TranslationFunctionParams,
|
||||||
|
options: TranslationOptions,
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
clearTranslation: (key: string) => void
|
clearTranslation: (key: string) => void
|
||||||
acquireTranslation: (key: string) => () => void
|
acquireTranslation: (key: string) => () => void
|
||||||
|
|||||||
@@ -411,11 +411,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
shouldProxyLinks={true}
|
shouldProxyLinks={true}
|
||||||
/>
|
/>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
<TranslatedPost
|
<TranslatedPost post={post} postTextStyle={[a.text_lg]} />
|
||||||
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
|
||||||
|
|||||||
@@ -321,11 +321,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
<TranslatedPost
|
<TranslatedPost hideTranslateLink post={post} />
|
||||||
hideTranslateLink={true}
|
|
||||||
post={post}
|
|
||||||
postText={record.text}
|
|
||||||
/>
|
|
||||||
{post.embed && (
|
{post.embed && (
|
||||||
<View style={[a.pb_xs]}>
|
<View style={[a.pb_xs]}>
|
||||||
<Embed
|
<Embed
|
||||||
|
|||||||
@@ -361,11 +361,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
<TranslatedPost
|
<TranslatedPost hideTranslateLink post={post} />
|
||||||
hideTranslateLink={true}
|
|
||||||
post={post}
|
|
||||||
postText={record.text}
|
|
||||||
/>
|
|
||||||
{post.embed && (
|
{post.embed && (
|
||||||
<View style={[a.pb_xs]}>
|
<View style={[a.pb_xs]}>
|
||||||
<Embed
|
<Embed
|
||||||
|
|||||||
@@ -219,11 +219,7 @@ function PostInner({
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
<TranslatedPost
|
<TranslatedPost hideTranslateLink post={post} />
|
||||||
hideTranslateLink={true}
|
|
||||||
post={post}
|
|
||||||
postText={record.text}
|
|
||||||
/>
|
|
||||||
{post.embed ? (
|
{post.embed ? (
|
||||||
<Embed
|
<Embed
|
||||||
embed={post.embed}
|
embed={post.embed}
|
||||||
|
|||||||
@@ -490,13 +490,7 @@ let PostContent = ({
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
{record && (
|
{record && <TranslatedPost hideTranslateLink post={post} />}
|
||||||
<TranslatedPost
|
|
||||||
hideTranslateLink={true}
|
|
||||||
post={post}
|
|
||||||
postText={record.text}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{postEmbed ? (
|
{postEmbed ? (
|
||||||
<View style={[a.pb_xs]}>
|
<View style={[a.pb_xs]}>
|
||||||
<Embed
|
<Embed
|
||||||
|
|||||||
Reference in New Issue
Block a user