Pass key as argument to useTranslate hook

This commit is contained in:
DS Boyce
2026-03-03 12:36:22 -08:00
parent 06daefabac
commit 00c66eca47
5 changed files with 90 additions and 35 deletions
+7 -10
View File
@@ -3,7 +3,7 @@ import {Platform, View} from 'react-native'
import {Trans, useLingui} from '@lingui/react/macro'
import {HITSLOP_30} from '#/lib/constants'
import {useTranslate, useTranslationKey} from '#/lib/translation'
import {useTranslate} from '#/lib/translation'
import {codeToLanguageName, languageName} from '#/locale/helpers'
import {LANGUAGES} from '#/locale/languages'
import {useLanguagePrefs} from '#/state/preferences'
@@ -21,21 +21,19 @@ export function TranslatedPost({
translationKey: string
postText: string
}) {
const {translationState} = useTranslate()
// Register this component as using this translation key with focus-based cleanup
useTranslationKey(translationKey)
const {translationState} = useTranslate({key: translationKey})
if (translationState[translationKey]?.status === 'loading') {
if (translationState.status === 'loading') {
return <TranslationLoading />
}
if (translationState[translationKey]?.status === 'success') {
if (translationState.status === 'success') {
return (
<TranslationResult
translationKey={translationKey}
postText={postText}
sourceLanguage={translationState[translationKey]?.sourceLanguage}
translatedText={translationState[translationKey]?.translatedText}
sourceLanguage={translationState.sourceLanguage}
translatedText={translationState.translatedText}
/>
)
}
@@ -119,7 +117,7 @@ function TranslationLanguageSelect({
const ax = useAnalytics()
const {t: l} = useLingui()
const langPrefs = useLanguagePrefs()
const {translate} = useTranslate()
const {translate} = useTranslate({key: translationKey})
const items = useMemo(
() =>
@@ -149,7 +147,6 @@ function TranslationLanguageSelect({
targetLanguage: langPrefs.primaryLanguage,
})
void translate({
key: translationKey,
text: postText,
targetLangCode: langPrefs.primaryLanguage,
sourceLangCode,
@@ -135,7 +135,9 @@ let PostMenuItems = ({
const {hidePost} = useHiddenPostsApi()
const feedFeedback = useFeedFeedbackContext()
const openLink = useOpenLink()
const {clearTranslation, translate, translationState} = useTranslate()
const {clearTranslation, translate, translationState} = useTranslate({
key: post.uri,
})
const navigation = useNavigation<NavigationProp>()
const {mutedWordsDialogControl} = useGlobalDialogsControlContext()
const blockPromptControl = useDialogControl()
@@ -190,8 +192,6 @@ let PostMenuItems = ({
return makeProfileLink(postAuthor, 'post', urip.rkey)
}, [postUri, postAuthor])
const translationKey = post.uri
const onDeletePost = () => {
deletePostMutate({uri: postUri}).then(
() => {
@@ -259,7 +259,6 @@ let PostMenuItems = ({
const onPressTranslate = () => {
void translate({
key: translationKey,
text: record.text,
targetLangCode: langPrefs.primaryLanguage,
forceGoogleTranslate,
@@ -465,7 +464,7 @@ let PostMenuItems = ({
const onSignIn = () => requireSignIn(() => {})
const onPressHideTranslation = () => clearTranslation(translationKey)
const onPressHideTranslation = () => clearTranslation()
const isDiscoverDebugUser =
IS_INTERNAL ||
@@ -501,7 +500,7 @@ let PostMenuItems = ({
<Menu.Group>
{!hideInPWI || hasSession ? (
<>
{translationState[translationKey]?.status === 'loading' ? (
{translationState.status === 'loading' ? (
<Menu.Item
testID="postDropdownTranslateBtn"
label={l`Translating…`}
@@ -509,7 +508,7 @@ let PostMenuItems = ({
<Menu.ItemText>{l`Translating…`}</Menu.ItemText>
<Menu.ItemIcon icon={Translate} position="right" />
</Menu.Item>
) : translationState[translationKey]?.status === 'success' ? (
) : translationState.status === 'success' ? (
<Menu.Item
testID="postDropdownTranslateBtn"
label={l`Hide translation`}
+9
View File
@@ -6,8 +6,17 @@ export const Context = createContext<{
translationState: Record<string, TranslationState>
translate: (parameters: {
key: string
/**
* The text to be translated.
*/
text: string
/**
* The language to translate the text into.
*/
targetLangCode: string
/**
* The source language of the text. Will auto-detect if not provided.
*/
sourceLangCode?: string
/**
* Whether to force the use of Google Translate. Default is false.
+29 -15
View File
@@ -77,31 +77,46 @@ async function attemptTranslation(
*
* Web uses index.web.ts which always opens Google Translate.
*/
export function useTranslate() {
export function useTranslate({key}: {key: string}) {
const context = useContext(Context)
if (!context) {
throw new Error(
'useTranslate must be used within a TranslateOnDeviceProvider',
)
}
return context
}
/**
* Hook to register a component as using a translation key.
* Automatically handles ref counting with screen focus management
* via useFocusEffect and cleans up the translation when the component
* loses focus.
*/
export function useTranslationKey(key: string) {
const {acquireTranslation} = useTranslate()
useFocusEffect(
useCallback(() => {
const cleanup = acquireTranslation(key)
if (!key) return
const cleanup = context.acquireTranslation(key)
return cleanup
}, [key, acquireTranslation]),
}, [key, context]),
)
const translate = useCallback(
async (params: {
text: string
targetLangCode: string
sourceLangCode?: string
forceGoogleTranslate?: boolean
}) => {
return context.translate({...params, key})
},
[key, context],
)
const clearTranslation = useCallback(
() => context.clearTranslation(key),
[key, context],
)
return {
translationState: context.translationState[key] ?? {
status: 'idle',
},
translate,
clearTranslation,
}
}
export function Provider({children}: React.PropsWithChildren<unknown>) {
@@ -184,7 +199,6 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
await googleTranslate(text, targetLangCode, sourceLangCode)
return
}
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setTranslationState(prev => ({
...prev,
[key]: {status: 'loading'},
+39 -3
View File
@@ -11,18 +11,54 @@ const acquireTranslation = (_key: string) => {
}
const clearTranslation = (_key: string) => {}
export function useTranslationKey(_key: string) {}
/**
* Web always opens Google Translate.
*/
export function useTranslate() {
export function useTranslate(key: string) {
const context = useContext(Context)
if (!context) {
throw new Error(
'useTranslate must be used within a TranslateOnDeviceProvider',
)
}
// Always call hooks in consistent order
const translate = useCallback(
async (params: {
text: string
targetLangCode: string
sourceLangCode?: string
}) => {
if (!key) {
throw new Error(
'translate requires a key. Either pass key to useTranslate() or use context.translate() with key parameter',
)
}
return context.translate({...params, key})
},
[key, context],
)
const clearTranslation = useCallback(() => {
if (!key) {
throw new Error(
'clearTranslation requires a key. Either pass key to useTranslate() or use context.clearTranslation() with key parameter',
)
}
return context.clearTranslation(key)
}, [key, context])
// If a key is provided, return wrapped versions that automatically use the key
if (key) {
return {
translationState: context.translationState[key] ?? {
status: 'idle' as const,
},
translate,
clearTranslation,
}
}
return context
}