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 {Trans, useLingui} from '@lingui/react/macro'
import {HITSLOP_30} from '#/lib/constants' import {HITSLOP_30} from '#/lib/constants'
import {useTranslate, useTranslationKey} from '#/lib/translation' import {useTranslate} from '#/lib/translation'
import {codeToLanguageName, languageName} from '#/locale/helpers' import {codeToLanguageName, languageName} from '#/locale/helpers'
import {LANGUAGES} from '#/locale/languages' import {LANGUAGES} from '#/locale/languages'
import {useLanguagePrefs} from '#/state/preferences' import {useLanguagePrefs} from '#/state/preferences'
@@ -21,21 +21,19 @@ export function TranslatedPost({
translationKey: string translationKey: string
postText: string postText: string
}) { }) {
const {translationState} = useTranslate() const {translationState} = useTranslate({key: translationKey})
// Register this component as using this translation key with focus-based cleanup
useTranslationKey(translationKey)
if (translationState[translationKey]?.status === 'loading') { if (translationState.status === 'loading') {
return <TranslationLoading /> return <TranslationLoading />
} }
if (translationState[translationKey]?.status === 'success') { if (translationState.status === 'success') {
return ( return (
<TranslationResult <TranslationResult
translationKey={translationKey} translationKey={translationKey}
postText={postText} postText={postText}
sourceLanguage={translationState[translationKey]?.sourceLanguage} sourceLanguage={translationState.sourceLanguage}
translatedText={translationState[translationKey]?.translatedText} translatedText={translationState.translatedText}
/> />
) )
} }
@@ -119,7 +117,7 @@ function TranslationLanguageSelect({
const ax = useAnalytics() const ax = useAnalytics()
const {t: l} = useLingui() const {t: l} = useLingui()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const {translate} = useTranslate() const {translate} = useTranslate({key: translationKey})
const items = useMemo( const items = useMemo(
() => () =>
@@ -149,7 +147,6 @@ function TranslationLanguageSelect({
targetLanguage: langPrefs.primaryLanguage, targetLanguage: langPrefs.primaryLanguage,
}) })
void translate({ void translate({
key: translationKey,
text: postText, text: postText,
targetLangCode: langPrefs.primaryLanguage, targetLangCode: langPrefs.primaryLanguage,
sourceLangCode, sourceLangCode,
@@ -135,7 +135,9 @@ let PostMenuItems = ({
const {hidePost} = useHiddenPostsApi() const {hidePost} = useHiddenPostsApi()
const feedFeedback = useFeedFeedbackContext() const feedFeedback = useFeedFeedbackContext()
const openLink = useOpenLink() const openLink = useOpenLink()
const {clearTranslation, translate, translationState} = useTranslate() const {clearTranslation, translate, translationState} = useTranslate({
key: post.uri,
})
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {mutedWordsDialogControl} = useGlobalDialogsControlContext() const {mutedWordsDialogControl} = useGlobalDialogsControlContext()
const blockPromptControl = useDialogControl() const blockPromptControl = useDialogControl()
@@ -190,8 +192,6 @@ let PostMenuItems = ({
return makeProfileLink(postAuthor, 'post', urip.rkey) return makeProfileLink(postAuthor, 'post', urip.rkey)
}, [postUri, postAuthor]) }, [postUri, postAuthor])
const translationKey = post.uri
const onDeletePost = () => { const onDeletePost = () => {
deletePostMutate({uri: postUri}).then( deletePostMutate({uri: postUri}).then(
() => { () => {
@@ -259,7 +259,6 @@ let PostMenuItems = ({
const onPressTranslate = () => { const onPressTranslate = () => {
void translate({ void translate({
key: translationKey,
text: record.text, text: record.text,
targetLangCode: langPrefs.primaryLanguage, targetLangCode: langPrefs.primaryLanguage,
forceGoogleTranslate, forceGoogleTranslate,
@@ -465,7 +464,7 @@ let PostMenuItems = ({
const onSignIn = () => requireSignIn(() => {}) const onSignIn = () => requireSignIn(() => {})
const onPressHideTranslation = () => clearTranslation(translationKey) const onPressHideTranslation = () => clearTranslation()
const isDiscoverDebugUser = const isDiscoverDebugUser =
IS_INTERNAL || IS_INTERNAL ||
@@ -501,7 +500,7 @@ let PostMenuItems = ({
<Menu.Group> <Menu.Group>
{!hideInPWI || hasSession ? ( {!hideInPWI || hasSession ? (
<> <>
{translationState[translationKey]?.status === 'loading' ? ( {translationState.status === 'loading' ? (
<Menu.Item <Menu.Item
testID="postDropdownTranslateBtn" testID="postDropdownTranslateBtn"
label={l`Translating…`} label={l`Translating…`}
@@ -509,7 +508,7 @@ let PostMenuItems = ({
<Menu.ItemText>{l`Translating…`}</Menu.ItemText> <Menu.ItemText>{l`Translating…`}</Menu.ItemText>
<Menu.ItemIcon icon={Translate} position="right" /> <Menu.ItemIcon icon={Translate} position="right" />
</Menu.Item> </Menu.Item>
) : translationState[translationKey]?.status === 'success' ? ( ) : translationState.status === 'success' ? (
<Menu.Item <Menu.Item
testID="postDropdownTranslateBtn" testID="postDropdownTranslateBtn"
label={l`Hide translation`} label={l`Hide translation`}
+9
View File
@@ -6,8 +6,17 @@ export const Context = createContext<{
translationState: Record<string, TranslationState> translationState: Record<string, TranslationState>
translate: (parameters: { translate: (parameters: {
key: string key: string
/**
* The text to be translated.
*/
text: string text: string
/**
* The language to translate the text into.
*/
targetLangCode: string targetLangCode: string
/**
* The source language of the text. Will auto-detect if not provided.
*/
sourceLangCode?: string sourceLangCode?: string
/** /**
* Whether to force the use of Google Translate. Default is false. * 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. * Web uses index.web.ts which always opens Google Translate.
*/ */
export function useTranslate() { export function useTranslate({key}: {key: string}) {
const context = useContext(Context) const context = useContext(Context)
if (!context) { if (!context) {
throw new Error( throw new Error(
'useTranslate must be used within a TranslateOnDeviceProvider', '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( useFocusEffect(
useCallback(() => { useCallback(() => {
const cleanup = acquireTranslation(key) if (!key) return
const cleanup = context.acquireTranslation(key)
return cleanup 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>) { export function Provider({children}: React.PropsWithChildren<unknown>) {
@@ -184,7 +199,6 @@ export function Provider({children}: React.PropsWithChildren<unknown>) {
await googleTranslate(text, targetLangCode, sourceLangCode) await googleTranslate(text, targetLangCode, sourceLangCode)
return return
} }
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setTranslationState(prev => ({ setTranslationState(prev => ({
...prev, ...prev,
[key]: {status: 'loading'}, [key]: {status: 'loading'},
+39 -3
View File
@@ -11,18 +11,54 @@ const acquireTranslation = (_key: string) => {
} }
const clearTranslation = (_key: string) => {} const clearTranslation = (_key: string) => {}
export function useTranslationKey(_key: string) {}
/** /**
* Web always opens Google Translate. * Web always opens Google Translate.
*/ */
export function useTranslate() { export function useTranslate(key: string) {
const context = useContext(Context) const context = useContext(Context)
if (!context) { if (!context) {
throw new Error( throw new Error(
'useTranslate must be used within a TranslateOnDeviceProvider', '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 return context
} }