/* * Copyright (C) 2022 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ import cockpit from "cockpit"; import React, { useContext, useEffect, useState } from "react"; import { Form, FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js"; import { setLocale } from "../../apis/boss.js"; import { setLanguage, } from "../../apis/localization.js"; import { convertToCockpitLang, getLangCookie, setLangCookie } from "../../helpers/language.js"; import { error as loggerError } from "../../helpers/log.js"; import { LanguageContext, PageContext, SystemTypeContext } from "../../contexts/Common.jsx"; import { MenuSearch } from "../common/MenuSearch.jsx"; import { Keyboard } from "./Keyboard.jsx"; import "./InstallationLanguage.scss"; const _ = cockpit.gettext; const SCREEN_ID = "anaconda-screen-language"; const error = loggerError.bind(null, SCREEN_ID + ":"); const getLanguageEnglishName = lang => lang["english-name"].v; const getLanguageNativeName = lang => lang["native-name"].v; const getLocaleId = locale => locale["locale-id"].v; const getLocaleNativeName = locale => locale["native-name"].v; class LanguageSelector extends React.Component { static contextType = PageContext; constructor (props) { super(props); this.initiallySelectedLanguage = props.language; this.renderOptions = this.renderOptions.bind(this); } componentDidMount () { try { const cockpitLang = convertToCockpitLang({ lang: this.props.language }); if (getLangCookie() !== cockpitLang) { setLangCookie({ cockpitLang }); window.location.reload(true); } setLocale({ locale: this.props.language }); } catch (e) { this.context?.setStepNotification?.({ step: SCREEN_ID, ...e, }); } } renderOptions () { const { commonLocales, languages } = this.props; const options = []; // Returns a locale with a given code. const findLocaleWithId = (localeCode) => { for (const languageId in languages) { const languageItem = languages[languageId]; for (const locale of languageItem.locales) { if (getLocaleId(locale) === localeCode) { return locale; } } } error(`Locale with code ${localeCode} not found.`); }; const onSearch = (locale, search) => { const searchLower = search.toLowerCase(); return ( getLocaleNativeName(locale).toLowerCase() .includes(searchLower) || getLanguageNativeName(locale).toLowerCase() .includes(searchLower) || getLanguageEnglishName(locale).toLowerCase() .includes(searchLower) ); }; // Helper to create a menu item const createMenuItem = (locale, prefix) => { return ({ id: `${SCREEN_ID}-language-${prefix}-${getLocaleId(locale).split(".UTF-8")[0]}`, item: locale, itemId: getLocaleId(locale), itemLang: convertToCockpitLang({ lang: getLocaleId(locale) }), itemText: getLocaleNativeName(locale), itemType: "menu-item", key: `${prefix}-${getLocaleId(locale)}`, onSearch: onSearch.bind(null, locale), }); }; const suggestedItems = commonLocales .map(findLocaleWithId) .filter(locale => locale !== undefined) .sort((a, b) => { // Sort alphabetically by native name but keep the default locale at the top if (getLocaleId(a) === this.initiallySelectedLanguage) { return -1; } else if (getLocaleId(b) === this.initiallySelectedLanguage) { return 1; } return getLocaleNativeName(a).localeCompare(getLocaleNativeName(b)); }) .map(locale => createMenuItem(locale, "option-common")); if (suggestedItems.length > 0) { options.push({ id: `${SCREEN_ID}-common-languages`, itemChildren: suggestedItems, itemLabel: _("Suggested languages"), itemLabelHeadingLevel: "h3", itemType: "menu-group", }); } const otherItems = Object.keys(languages) .sort((a, b) => { return getLanguageNativeName(languages[a].locales[0]).localeCompare(getLanguageNativeName(languages[b].locales[0])); }) .flatMap(languageId => { const languageItem = languages[languageId]; return languageItem.locales; }) .filter(locale => commonLocales.indexOf(getLocaleId(locale)) === -1) .map(locale => createMenuItem(locale, "option-alpha")); if (otherItems.length > 0) { options.push({ id: `${SCREEN_ID}-additional-languages`, itemChildren: otherItems, itemLabel: _("Additional languages"), itemLabelHeadingLevel: "h3", itemType: "menu-group", }); } return options; } render () { const { languages } = this.props; const handleOnSelect = (_event, item) => { for (const languageItem in languages) { for (const localeItem of languages[languageItem].locales) { if (getLocaleId(localeItem) === item) { setLangCookie({ cockpitLang: convertToCockpitLang({ lang: getLocaleId(localeItem) }) }); setLanguage({ lang: getLocaleId(localeItem) }) .then(() => setLocale({ locale: getLocaleId(localeItem) })) .catch(ex => { this.context?.setStepNotification?.({ step: SCREEN_ID, ...ex, }); }); this.setState({ lang: item }); fetch("po.js").then(response => response.text()) .then(body => { // always reset old translations cockpit.locale(null); // en_US is always null if (body.trim() === "") { cockpit.locale({ "": { language: "en_US", "language-direction": "ltr", } }); } else { // eslint-disable-next-line no-eval eval(body); } const langEvent = new CustomEvent("cockpit-lang"); window.dispatchEvent(langEvent); }); return; } } } }; const options = this.renderOptions(); return ( ); } } export const InstallationLanguage = ({ dispatch }) => { const { setIsFormValid } = useContext(PageContext) ?? {}; const { commonLocales, keyboardLayouts, language, languages } = useContext(LanguageContext); const { desktopVariant } = useContext(SystemTypeContext); const isGnome = desktopVariant === "GNOME"; const [isLanguageValid, setIsLanguageValid] = useState(false); const [isKeyboardValid, setIsKeyboardValid] = useState(false); useEffect(() => { dispatch({ type: "SET_LANGUAGE_USER_CONFIGURED" }); }, [dispatch]); useEffect(() => { setIsLanguageValid(language !== ""); }, [language]); useEffect(() => { setIsFormValid(isLanguageValid && isKeyboardValid); }, [isLanguageValid, isKeyboardValid, setIsFormValid]); return ( <>
{keyboardLayouts.length > 0 && ( )}
); };