/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ import React, { useState, useEffect } from "react"; import { Localized } from "./MSLocalized"; import { AboutWelcomeUtils } from "../../lib/aboutwelcome-utils"; /** * The language switcher implements a hook that should be placed at a higher level * than the actual language switcher component, as it needs to preemptively fetch * and install langpacks for the user if there is a language mismatch screen. */ export function useLanguageSwitcher( appAndSystemLocaleInfo, screens, screenIndex, setScreenIndex ) { const languageMismatchScreenIndex = screens.findIndex( ({ id }) => id === "AW_LANGUAGE_MISMATCH" ); const screen = screens[languageMismatchScreenIndex]; // Ensure fluent messages have the negotiatedLanguage args set, as they are rendered // before the negotiatedLanguage is known. If the arg isn't present then Firefox will // crash in development mode. useEffect(() => { if (screen?.content?.languageSwitcher) { for (const text of Object.values(screen.content.languageSwitcher)) { if (text?.args && text.args.negotiatedLanguage === undefined) { text.args.negotiatedLanguage = ""; } } } }, [screen]); // If there is a mismatch, then Firefox can negotiate a better langpack to offer // the user. const [negotiatedLanguage, setNegotiatedLanguage] = useState(null); useEffect( function getNegotiatedLanguage() { if (!appAndSystemLocaleInfo) { return; } if (appAndSystemLocaleInfo.matchType !== "language-mismatch") { // There is no language mismatch, so there is no need to negotiate a langpack. return; } (async () => { const { langPack, langPackDisplayName } = await window.AWNegotiateLangPackForLanguageMismatch( appAndSystemLocaleInfo ); if (langPack) { setNegotiatedLanguage({ langPackDisplayName, appDisplayName: appAndSystemLocaleInfo.displayNames.appLanguage, langPack, requestSystemLocales: [ langPack.target_locale, appAndSystemLocaleInfo.appLocaleRaw, ], originalAppLocales: [appAndSystemLocaleInfo.appLocaleRaw], }); } else { setNegotiatedLanguage({ langPackDisplayName: null, appDisplayName: null, langPack: null, requestSystemLocales: null, }); } })(); }, [appAndSystemLocaleInfo] ); /** * @type { * "before-installation" * | "installing" * | "installed" * | "installation-error" * | "none-available" * } */ const [langPackInstallPhase, setLangPackInstallPhase] = useState( "before-installation" ); useEffect( function ensureLangPackInstalled() { if (!negotiatedLanguage) { // There are no negotiated languages to download yet. return; } setLangPackInstallPhase("installing"); window .AWEnsureLangPackInstalled(negotiatedLanguage, screen?.content) .then( content => { // Update screen content with strings that might have changed. screen.content = content; setLangPackInstallPhase("installed"); }, error => { console.error(error); setLangPackInstallPhase("installation-error"); } ); }, [negotiatedLanguage] ); const [languageFilteredScreens, setLanguageFilteredScreens] = useState(screens); useEffect( function filterScreen() { // Remove the language screen if it exists (already removed for no live // reload) and we either don't-need-to or can't switch. if ( screen && (appAndSystemLocaleInfo?.matchType !== "language-mismatch" || negotiatedLanguage?.langPack === null) ) { if (screenIndex > languageMismatchScreenIndex) { setScreenIndex(screenIndex - 1); } setLanguageFilteredScreens( screens.filter(s => s.id !== "AW_LANGUAGE_MISMATCH") ); } else { setLanguageFilteredScreens(screens); } }, [screens, negotiatedLanguage] ); return { negotiatedLanguage, langPackInstallPhase, languageFilteredScreens, }; } /** * The language switcher is a separate component as it needs to perform some asynchronous * network actions such as retrieving the list of langpacks available, and downloading * a new langpack. On a fast connection, this won't be noticeable, but on slow or unreliable * internet this may fail for a user. */ export function LanguageSwitcher(props) { const { content, handleAction, negotiatedLanguage, langPackInstallPhase, messageId, } = props; const [isAwaitingLangpack, setIsAwaitingLangpack] = useState(false); // Determine the status of the langpack installation. useEffect(() => { if (isAwaitingLangpack && langPackInstallPhase !== "installing") { window.AWSetRequestedLocales(negotiatedLanguage.requestSystemLocales); requestAnimationFrame(() => { handleAction( // Simulate the click event. { currentTarget: { value: "download_complete" } } ); }); } }, [isAwaitingLangpack, langPackInstallPhase]); let showWaitingScreen = false; let showPreloadingScreen = false; let showReadyScreen = false; if (isAwaitingLangpack && langPackInstallPhase !== "installed") { showWaitingScreen = true; } else if (langPackInstallPhase === "before-installation") { showPreloadingScreen = true; } else { showReadyScreen = true; } // Use {display: "none"} rather than if statements to prevent layout thrashing with // the localized text elements rendering as blank, then filling in the text. return (
{/* Pre-loading screen */}
{/* Waiting to download the language screen. */}
{/* The typical ready screen. */}
); }