summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx')
-rw-r--r--browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx294
1 files changed, 294 insertions, 0 deletions
diff --git a/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx b/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx
new file mode 100644
index 0000000000..9abd749a6d
--- /dev/null
+++ b/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx
@@ -0,0 +1,294 @@
+/* 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 (
+ <div className="action-buttons language-switcher-container">
+ {/* Pre-loading screen */}
+ <div style={{ display: showPreloadingScreen ? "block" : "none" }}>
+ <button
+ className="primary"
+ value="primary_button"
+ disabled={true}
+ type="button"
+ >
+ <img
+ className="language-loader"
+ src="chrome://browser/skin/tabbrowser/tab-connecting.png"
+ alt=""
+ />
+ <Localized text={content.languageSwitcher.waiting} />
+ </button>
+ <div className="secondary-cta">
+ <Localized text={content.languageSwitcher.skip}>
+ <button
+ value="decline_waiting"
+ type="button"
+ className="secondary text-link arrow-icon"
+ onClick={handleAction}
+ />
+ </Localized>
+ </div>
+ </div>
+ {/* Waiting to download the language screen. */}
+ <div style={{ display: showWaitingScreen ? "block" : "none" }}>
+ <button
+ className="primary"
+ value="primary_button"
+ disabled={true}
+ type="button"
+ >
+ <img
+ className="language-loader"
+ src="chrome://browser/skin/tabbrowser/tab-connecting.png"
+ alt=""
+ />
+ <Localized text={content.languageSwitcher.downloading} />
+ </button>
+ <div className="secondary-cta">
+ <Localized text={content.languageSwitcher.cancel}>
+ <button
+ type="button"
+ className="secondary text-link"
+ onClick={() => {
+ setIsAwaitingLangpack(false);
+ handleAction({
+ currentTarget: { value: "cancel_waiting" },
+ });
+ }}
+ />
+ </Localized>
+ </div>
+ </div>
+ {/* The typical ready screen. */}
+ <div style={{ display: showReadyScreen ? "block" : "none" }}>
+ <div>
+ <button
+ className="primary"
+ value="primary_button"
+ onClick={() => {
+ AboutWelcomeUtils.sendActionTelemetry(
+ messageId,
+ "download_langpack"
+ );
+ setIsAwaitingLangpack(true);
+ }}
+ >
+ {content.languageSwitcher.switch ? (
+ <Localized text={content.languageSwitcher.switch} />
+ ) : (
+ // This is the localized name from the Intl.DisplayNames API.
+ negotiatedLanguage?.langPackDisplayName
+ )}
+ </button>
+ </div>
+ <div>
+ <button
+ type="button"
+ className="primary"
+ value="decline"
+ onClick={event => {
+ window.AWSetRequestedLocales(
+ negotiatedLanguage.originalAppLocales
+ );
+ handleAction(event);
+ }}
+ >
+ {content.languageSwitcher.continue ? (
+ <Localized text={content.languageSwitcher.continue} />
+ ) : (
+ // This is the localized name from the Intl.DisplayNames API.
+ negotiatedLanguage?.appDisplayName
+ )}
+ </button>
+ </div>
+ </div>
+ </div>
+ );
+}