diff --git a/src/components/translator/LanguageSelector.tsx b/src/components/translator/LanguageSelector.tsx index 23cec8d8f..1f51e5e04 100644 --- a/src/components/translator/LanguageSelector.tsx +++ b/src/components/translator/LanguageSelector.tsx @@ -28,6 +28,7 @@ export type Props = { tgtLang: string; setTgtLang: (code: string) => void; recentTgtLangs: Array; + swapLangs?: () => void; detectLangEnabled: boolean; detectedLang: string | null; @@ -37,7 +38,6 @@ export type Props = { type SharedProps = Props & { srcLangs: NamedLangs; tgtLangs: NamedLangs; - swapLangs?: () => void; detectingLang: boolean; setDetectingLang: (detecting: boolean) => void; onDetectLang: () => void; @@ -423,18 +423,7 @@ const DesktopLanguageSelector = ({ }; const LanguageSelector = (props: Props): React.ReactElement => { - const { pairs, srcLang, setSrcLang, recentSrcLangs, setRecentSrcLangs, tgtLang, setTgtLang, setDetectedLang } = props; - - const swapLangs = React.useMemo( - () => - isPair(pairs, tgtLang, srcLang) - ? () => { - setSrcLang(tgtLang); - setTgtLang(srcLang); - } - : undefined, - [pairs, setSrcLang, setTgtLang, srcLang, tgtLang], - ); + const { pairs, srcLang, setSrcLang, recentSrcLangs, setRecentSrcLangs, setDetectedLang, swapLangs } = props; const [detectingLang, setDetectingLang] = React.useState(false); diff --git a/src/components/translator/TextTranslationForm.tsx b/src/components/translator/TextTranslationForm.tsx index 061b76aa8..3e3f7306b 100644 --- a/src/components/translator/TextTranslationForm.tsx +++ b/src/components/translator/TextTranslationForm.tsx @@ -11,15 +11,13 @@ import { useHistory } from 'react-router-dom'; import { useMatomo } from '@datapunt/matomo-tracker-react'; import { DetectCompleteEvent, DetectEvent, PairPrefValues, TranslateEvent, baseUrlParams } from '.'; -import { MaxURLLength, buildNewSearch, getUrlParam } from '../../util/url'; +import { MaxURLLength, buildNewSearch } from '../../util/url'; import { APyContext } from '../../context'; import { buildUrl as buildWebpageTranslationUrl } from './WebpageTranslationForm'; import { langDirection } from '../../util/languages'; -import useLocalStorage from '../../util/useLocalStorage'; +import { textUrlParam } from './Translator'; import { useLocalization } from '../../util/localization'; -const textUrlParam = 'q'; - const instantTranslationPunctuationDelay = 1000, instantTranslationDelay = 3000; @@ -36,6 +34,10 @@ export type Props = { markUnknown: boolean; pairPrefs: PairPrefValues; setLoading: (loading: boolean) => void; + srcText: string; + tgtText: string; + setSrcText: (text: string) => void; + setTgtText: (text: string) => void; }; const TextTranslationForm = ({ @@ -45,6 +47,10 @@ const TextTranslationForm = ({ instantTranslation, pairPrefs, setLoading, + srcText, + tgtText, + setSrcText, + setTgtText, }: Props): React.ReactElement => { const { t } = useLocalization(); const history = useHistory(); @@ -54,11 +60,6 @@ const TextTranslationForm = ({ const srcTextareaRef = React.useRef(null); const tgtTextareaRef = React.useRef(null); - const [srcText, setSrcText] = useLocalStorage('srcText', '', { - overrideValue: getUrlParam(history.location.search, textUrlParam), - }); - const [tgtText, setTgtText] = React.useState(''); - React.useEffect(() => { const baseParams = baseUrlParams({ srcLang, tgtLang }); let search = buildNewSearch({ ...baseParams, [textUrlParam]: srcText }); @@ -130,7 +131,7 @@ const TextTranslationForm = ({ } })(); }, - [apyFetch, markUnknown, prefs, setLoading, srcLang, srcText, tgtLang, trackEvent], + [apyFetch, markUnknown, prefs, setLoading, setTgtText, srcLang, srcText, tgtLang, trackEvent], ); const translationTimer = React.useRef(null); diff --git a/src/components/translator/Translator.tsx b/src/components/translator/Translator.tsx index 2dadf31bf..c26616869 100644 --- a/src/components/translator/Translator.tsx +++ b/src/components/translator/Translator.tsx @@ -34,6 +34,7 @@ import useLocalStorage from '../../util/useLocalStorage'; import { useLocalization } from '../../util/localization'; const recentLangsCount = 3; +const textUrlParam = 'q'; const defaultSrcLang = (pairs: Pairs): string => { const browserLangs = window.navigator.languages; @@ -150,11 +151,15 @@ type WithTgtLangsProps = { setRecentTgtLangs: (langs: Array) => void; pairPrefs: PairPrefValues; setPairPrefs: (prefs: PairPrefValues) => void; + swapLangs?: () => void; }; const WithTgtLang = ({ pairs, srcLang, + tgtText, + setSrcText, + setSrcLang, urlTgtLang, selectedPrefs, setSelectedPrefs, @@ -162,6 +167,9 @@ const WithTgtLang = ({ }: { pairs: Pairs; srcLang: string; + tgtText: string; + setSrcText: (text: string) => void; + setSrcLang: (lang: string) => void; urlTgtLang: string | null; selectedPrefs: Record; setSelectedPrefs: (prefs: Record) => void; @@ -236,8 +244,18 @@ const WithTgtLang = ({ }, [pair, selectedPrefs, setSelectedPrefs], ); - - return children({ tgtLang, setTgtLang, recentTgtLangs, setRecentTgtLangs, pairPrefs, setPairPrefs }); + const swapLangs = React.useMemo( + () => + isPair(pairs, tgtLang, srcLang) + ? () => { + setSrcLang(tgtLang); + setTgtLang(srcLang); + setSrcText(tgtText); + } + : undefined, + [pairs, setSrcLang, setTgtLang, setSrcText, srcLang, tgtLang, tgtText], + ); + return children({ tgtLang, setTgtLang, recentTgtLangs, setRecentTgtLangs, pairPrefs, setPairPrefs, swapLangs }); }; const Translator = ({ mode: initialMode }: { mode?: Mode }): React.ReactElement => { @@ -248,6 +266,10 @@ const Translator = ({ mode: initialMode }: { mode?: Mode }): React.ReactElement const config = React.useContext(ConfigContext); const [loading, setLoading] = React.useState(false); + const [srcText, setSrcText] = useLocalStorage('srcText', '', { + overrideValue: getUrlParam(history.location.search, textUrlParam), + }); + const [tgtText, setTgtText] = React.useState(''); const [markUnknown, setMarkUnknown] = useLocalStorage('markUnknown', false); const [instantTranslation, setInstantTranslation] = useLocalStorage('instantTranslation', true); @@ -289,8 +311,10 @@ const Translator = ({ mode: initialMode }: { mode?: Mode }): React.ReactElement detectedLang, setDetectedLang, }: WithSrcLangsProps) => ( - - {({ tgtLang, setTgtLang, recentTgtLangs, pairPrefs, setPairPrefs }: WithTgtLangsProps) => ( + + {({ tgtLang, setTgtLang, recentTgtLangs, pairPrefs, setPairPrefs, swapLangs }: WithTgtLangsProps) => ( <> {(mode === Mode.Text || !mode) && ( <> @@ -377,4 +413,5 @@ const Translator = ({ mode: initialMode }: { mode?: Mode }): React.ReactElement ); }; +export { textUrlParam }; export default Translator; diff --git a/src/components/translator/__tests__/LanguageSelector.test.tsx b/src/components/translator/__tests__/LanguageSelector.test.tsx index 7b6b9e20c..cd1829218 100644 --- a/src/components/translator/__tests__/LanguageSelector.test.tsx +++ b/src/components/translator/__tests__/LanguageSelector.test.tsx @@ -23,6 +23,7 @@ const renderLanguageSelector = (props_: Partial = {}): Props => { detectLangEnabled: true, detectedLang: null, setDetectedLang: jest.fn(), + swapLangs: jest.fn(), ...props_, }; @@ -84,20 +85,19 @@ it('switches between mobile and desktop', () => { describe('swapping', () => { it('does not allow swapping when swapped pair invalid', () => { - renderLanguageSelector(); + renderLanguageSelector({ swapLangs: undefined }); expect((screen.getByTestId('swap-langs-button') as HTMLButtonElement).disabled).toBeTruthy(); }); it('allow swapping when swapped pair valid', () => { - const { srcLang, tgtLang, setSrcLang, setTgtLang } = renderLanguageSelector({ + const { swapLangs } = renderLanguageSelector({ tgtLang: 'spa', }); userEvent.click(screen.getByTestId('swap-langs-button')); - expect(setSrcLang).toHaveBeenCalledWith(tgtLang); - expect(setTgtLang).toHaveBeenCalledWith(srcLang); + expect(swapLangs).toHaveBeenCalled(); }); }); diff --git a/src/components/translator/__tests__/TextTranslationForm.test.tsx b/src/components/translator/__tests__/TextTranslationForm.test.tsx index 82ccc1557..3834daadf 100644 --- a/src/components/translator/__tests__/TextTranslationForm.test.tsx +++ b/src/components/translator/__tests__/TextTranslationForm.test.tsx @@ -7,6 +7,9 @@ import userEvent from '@testing-library/user-event'; import { DetectCompleteEvent, DetectEvent, TranslateEvent } from '..'; import TextTranslationForm, { Props } from '../TextTranslationForm'; +import { getUrlParam } from '../../../util/url'; +import { textUrlParam } from '../Translator'; +import useLocalStorage from '../../../util/useLocalStorage'; const renderTextTranslationForm = ( props_: Partial = {}, @@ -20,13 +23,34 @@ const renderTextTranslationForm = ( srcLang: 'eng', tgtLang: 'spa', pairPrefs: {}, + srcText: '', + tgtText: '', + setSrcText: jest.fn(), + setTgtText: jest.fn(), setLoading: jest.fn(), ...props_, }; + const Wrapper = () => { + const [srcText, setSrcText] = useLocalStorage('srcText', '', { + overrideValue: getUrlParam(history.location.search, textUrlParam), + }); + const [tgtText, setTgtText] = React.useState(''); + + return ( + + ); + }; + render( - + , ); @@ -149,16 +173,31 @@ describe('translation', () => { srcLang: 'eng', tgtLang: 'spa', pairPrefs: {}, + srcText: '', + tgtText: '', + setSrcText: jest.fn(), + setTgtText: jest.fn(), setLoading: jest.fn(), }; const Container = () => { const [srcLang, setSrcLang] = React.useState('eng'); + const [srcText, setSrcText] = useLocalStorage('srcText', '', { + overrideValue: getUrlParam(history.location.search, textUrlParam), + }); + const [tgtText, setTgtText] = React.useState(''); return ( <> - + );