diff --git a/src/apps/translatewp/__tests__/switcherSurfaces.test.tsx b/src/apps/translatewp/__tests__/switcherSurfaces.test.tsx index 618cece..8fae5e3 100644 --- a/src/apps/translatewp/__tests__/switcherSurfaces.test.tsx +++ b/src/apps/translatewp/__tests__/switcherSurfaces.test.tsx @@ -379,6 +379,36 @@ describe('TranslateWP switcher surfaces', () => } }); + it('lets the visitor choose a language when the page holds a hidden copy of the switcher', async () => + { + // Menus and page builder headers print a desktop copy and a hidden mobile copy. + installContext(true, 'dropdown'); + window.TranslateWP.context.home_url = window.location.origin; + document.body.innerHTML = ` + + + `; + window.history.replaceState(null, '', '/about/'); + pendingBatch(); + + await renderApp(); + + const desktop = document.querySelector('.desktop') as HTMLElement; + const mobile = document.querySelector('.mobile') as HTMLElement; + await act(async () => { (desktop.querySelector('.twp-trigger') as HTMLButtonElement).click(); }); + expect(mobile.querySelector('.twp-dropdown-item')).toBeNull(); + + const option = Array.from(desktop.querySelectorAll('.twp-dropdown-item')) + .find((item) => item.textContent?.includes('EspaƱol')) as HTMLButtonElement; + expect(option).toBeDefined(); + // A real click presses the mouse first; every other copy sees that press as outside itself. + await act(async () => { option.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); }); + await act(async () => { option.click(); }); + + expect(window.location.pathname).toBe('/es/about/'); + expect(document.documentElement.lang).toBe('es'); + }); + it('translates an address in a language folder into that language and leaves the address alone', async () => { installContext(true, 'dropdown'); diff --git a/src/apps/translatewp/index.tsx b/src/apps/translatewp/index.tsx index c5041cc..deba7ad 100644 --- a/src/apps/translatewp/index.tsx +++ b/src/apps/translatewp/index.tsx @@ -61,7 +61,10 @@ export const TranslateWP = () => [languagesList] ); const defaultLocale = languages.find((l) => l.default)?.locale ?? languages[0]?.locale ?? ''; - const [isOpen, setIsOpen] = useState(false); + // Which switcher on the page is open. A page can hold several copies, + // often one hidden for mobile, and only the one the visitor opened may + // close its own list. + const [openSwitcher, setOpenSwitcher] = useState(null); const [selectedLocale, setSelectedLocale] = useState(defaultLocale); const selectedLocaleRef = useRef(defaultLocale); selectedLocaleRef.current = selectedLocale; @@ -200,7 +203,7 @@ export const TranslateWP = () => function beginLanguageSelection(locale: string, history: 'push' | 'replace'): void { setActiveLocale(locale); - setIsOpen(false); + setOpenSwitcher(null); setLanguageAddress(locale, history); applyDocumentLanguage(locale); persistLanguageSelection(locale); @@ -295,7 +298,7 @@ export const TranslateWP = () => if (selectedLocale !== defaultLocale) { resetToDefaultLanguage(); - setIsOpen(false); + setOpenSwitcher(null); restorePageTitle(); setIsTranslating(false); @@ -600,11 +603,17 @@ export const TranslateWP = () => const FrontendEditor = managerCan().edit ? resolveFrontendSlot('frontendEditor') : null; const viewProps: SwitcherViewProps = { - languages, switcher: switcher ?? {}, selectedLocale, isOpen, notice, isTranslating, - onOpenChange: setIsOpen, + languages, switcher: switcher ?? {}, selectedLocale, notice, isTranslating, + isOpen: openSwitcher === 'floating', + onOpenChange: open => setOpenSwitcher(current => (open ? 'floating' : (current === 'floating' ? null : current))), onLanguageSelect: language => translate(language), onDismissNotice: hidePendingNotice, }; + const inlineProps = (placeholder: HTMLElement): SwitcherViewProps => ({ + ...viewProps, + isOpen: openSwitcher === placeholder, + onOpenChange: open => setOpenSwitcher(current => (open ? placeholder : (current === placeholder ? null : current))), + }); return ( <> @@ -622,7 +631,7 @@ export const TranslateWP = () => )} {placeholders.map((placeholder) => ( createPortal( - , + , placeholder ) ))}