From 00c66eca472748c2ad1e8bfa745d4cad1f9a0f12 Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Tue, 3 Mar 2026 12:36:22 -0800 Subject: [PATCH] Pass key as argument to useTranslate hook --- src/components/Post/Translated/index.tsx | 17 +++---- .../PostControls/PostMenu/PostMenuItems.tsx | 13 +++--- src/lib/translation/context.ts | 9 ++++ src/lib/translation/index.tsx | 44 ++++++++++++------- src/lib/translation/index.web.tsx | 42 ++++++++++++++++-- 5 files changed, 90 insertions(+), 35 deletions(-) diff --git a/src/components/Post/Translated/index.tsx b/src/components/Post/Translated/index.tsx index 66489f47db..2b6c199a41 100644 --- a/src/components/Post/Translated/index.tsx +++ b/src/components/Post/Translated/index.tsx @@ -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 } - if (translationState[translationKey]?.status === 'success') { + if (translationState.status === 'success') { return ( ) } @@ -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, diff --git a/src/components/PostControls/PostMenu/PostMenuItems.tsx b/src/components/PostControls/PostMenu/PostMenuItems.tsx index 58b51bfa4c..ece506feba 100644 --- a/src/components/PostControls/PostMenu/PostMenuItems.tsx +++ b/src/components/PostControls/PostMenu/PostMenuItems.tsx @@ -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() 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 = ({ {!hideInPWI || hasSession ? ( <> - {translationState[translationKey]?.status === 'loading' ? ( + {translationState.status === 'loading' ? ( {l`Translating…`} - ) : translationState[translationKey]?.status === 'success' ? ( + ) : translationState.status === 'success' ? ( 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. diff --git a/src/lib/translation/index.tsx b/src/lib/translation/index.tsx index 9ebee4d49e..7a16aea04e 100644 --- a/src/lib/translation/index.tsx +++ b/src/lib/translation/index.tsx @@ -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) { @@ -184,7 +199,6 @@ export function Provider({children}: React.PropsWithChildren) { await googleTranslate(text, targetLangCode, sourceLangCode) return } - LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) setTranslationState(prev => ({ ...prev, [key]: {status: 'loading'}, diff --git a/src/lib/translation/index.web.tsx b/src/lib/translation/index.web.tsx index 3ed4811392..fbcb4e9f55 100644 --- a/src/lib/translation/index.web.tsx +++ b/src/lib/translation/index.web.tsx @@ -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 }