From b091ef5686f7f51ae05caef30ac2258f73735e58 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Mon, 22 Apr 2024 13:32:55 -0400 Subject: [PATCH] Remove access to global state from i18n/i18n (#26750) --- .../components/user_settings/display/index.ts | 17 +++++++++++--- .../display/manage_languages/index.ts | 12 +++++++++- .../manage_languages.test.tsx | 2 ++ .../manage_languages/manage_languages.tsx | 12 +++++----- .../display/user_settings_display.test.tsx | 4 +++- .../display/user_settings_display.tsx | 15 ++++++------ webapp/channels/src/i18n/i18n.jsx | 23 ++++++++++++++----- webapp/channels/src/selectors/i18n.ts | 4 ++-- 8 files changed, 62 insertions(+), 27 deletions(-) diff --git a/webapp/channels/src/components/user_settings/display/index.ts b/webapp/channels/src/components/user_settings/display/index.ts index faba3e9fc3..9419f10b1d 100644 --- a/webapp/channels/src/components/user_settings/display/index.ts +++ b/webapp/channels/src/components/user_settings/display/index.ts @@ -7,6 +7,7 @@ import type {Dispatch} from 'redux'; import timezones from 'timezones.json'; import {CollapsedThreads} from '@mattermost/types/config'; +import type {UserProfile} from '@mattermost/types/users'; import {savePreferences} from 'mattermost-redux/actions/preferences'; import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone'; @@ -17,14 +18,19 @@ import {getCurrentTimezoneFull, getCurrentTimezoneLabel} from 'mattermost-redux/ import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users'; import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils'; +import {getLanguages, isLanguageAvailable} from 'i18n/i18n'; import {Preferences} from 'utils/constants'; import type {GlobalState} from 'types/store'; import UserSettingsDisplay from './user_settings_display'; +type OwnProps = { + user: UserProfile; +} + export function makeMapStateToProps() { - return (state: GlobalState) => { + return (state: GlobalState, props: OwnProps) => { const config = getConfig(state); const currentUserId = getCurrentUserId(state); const userTimezone = getCurrentTimezoneFull(state); @@ -33,7 +39,6 @@ export function makeMapStateToProps() { const timezoneLabel = getCurrentTimezoneLabel(state); const allowCustomThemes = config.AllowCustomThemes === 'true'; const enableLinkPreviews = config.EnableLinkPreviews === 'true'; - const defaultClientLocale = config.DefaultClientLocale as string; const enableThemeSelection = config.EnableThemeSelection === 'true'; const lockTeammateNameDisplay = getLicense(state).LockTeammateNameDisplay === 'true' && config.LockTeammateNameDisplay === 'true'; const configTeammateNameDisplay = config.TeammateNameDisplay as string; @@ -45,12 +50,18 @@ export function makeMapStateToProps() { lastActiveDisplay = false; } + let userLocale = props.user.locale; + if (!isLanguageAvailable(state, userLocale)) { + userLocale = config.DefaultClientLocale as string; + } + return { lockTeammateNameDisplay, allowCustomThemes, configTeammateNameDisplay, enableLinkPreviews, - defaultClientLocale, + locales: getLanguages(state), + userLocale, enableThemeSelection, timezones, timezoneLabel, diff --git a/webapp/channels/src/components/user_settings/display/manage_languages/index.ts b/webapp/channels/src/components/user_settings/display/manage_languages/index.ts index 1b7ccd1cb8..616a9cb4a3 100644 --- a/webapp/channels/src/components/user_settings/display/manage_languages/index.ts +++ b/webapp/channels/src/components/user_settings/display/manage_languages/index.ts @@ -7,8 +7,18 @@ import type {Dispatch} from 'redux'; import {updateMe} from 'mattermost-redux/actions/users'; +import {getLanguages} from 'i18n/i18n'; + +import type {GlobalState} from 'types/store'; + import ManageLanguages from './manage_languages'; +function mapStateToProps(state: GlobalState) { + return { + locales: getLanguages(state), + }; +} + function mapDispatchToProps(dispatch: Dispatch) { return { actions: bindActionCreators({ @@ -17,4 +27,4 @@ function mapDispatchToProps(dispatch: Dispatch) { }; } -export default connect(null, mapDispatchToProps)(ManageLanguages); +export default connect(mapStateToProps, mapDispatchToProps)(ManageLanguages); diff --git a/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.test.tsx b/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.test.tsx index bfce6eccad..c0795a3818 100644 --- a/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.test.tsx +++ b/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.test.tsx @@ -5,6 +5,7 @@ import React from 'react'; import type {UserProfile} from '@mattermost/types/users'; +import {getAllLanguages} from 'i18n/i18n'; import {shallowWithIntl} from 'tests/helpers/intl-test-helper'; import ManageLanguages from './manage_languages'; @@ -18,6 +19,7 @@ describe('components/user_settings/display/manage_languages/manage_languages', ( const requiredProps = { user: user as UserProfile, locale: 'en', + locales: getAllLanguages(), updateSection: jest.fn(), actions: { updateMe: jest.fn(() => Promise.resolve({})), diff --git a/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.tsx b/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.tsx index c98e05901a..e52afa1839 100644 --- a/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.tsx +++ b/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.tsx @@ -14,7 +14,7 @@ import type {ActionResult} from 'mattermost-redux/types/actions'; import ExternalLink from 'components/external_link'; import SettingItemMax from 'components/setting_item_max'; -import * as I18n from 'i18n/i18n.jsx'; +import type {Language} from 'i18n/i18n'; import Constants from 'utils/constants'; import {isKeyPressed} from 'utils/keyboard'; @@ -26,6 +26,7 @@ type Props = { intl: IntlShape; user: UserProfile; locale: string; + locales: Record; updateSection: (section: string) => void; actions: Actions; }; @@ -47,11 +48,10 @@ export class ManageLanguage extends React.PureComponent { reactSelectContainer: React.RefObject; constructor(props: Props) { super(props); - const locales: any = I18n.getLanguages(); const userLocale = props.locale; const selectedOption = { - value: locales[userLocale].value, - label: locales[userLocale].name, + value: props.locales[userLocale].value, + label: props.locales[userLocale].name, }; this.reactSelectContainer = React.createRef(); @@ -155,7 +155,8 @@ export class ManageLanguage extends React.PureComponent { }; render() { - const {intl} = this.props; + const {intl, locales} = this.props; + let serverError; if (this.state.serverError) { serverError = ( @@ -164,7 +165,6 @@ export class ManageLanguage extends React.PureComponent { } const options: SelectedOption[] = []; - const locales: any = I18n.getLanguages(); const languages = Object.keys(locales). map((l) => { diff --git a/webapp/channels/src/components/user_settings/display/user_settings_display.test.tsx b/webapp/channels/src/components/user_settings/display/user_settings_display.test.tsx index a80d0a6963..ee7302a847 100644 --- a/webapp/channels/src/components/user_settings/display/user_settings_display.test.tsx +++ b/webapp/channels/src/components/user_settings/display/user_settings_display.test.tsx @@ -9,6 +9,7 @@ import type {UserProfile} from '@mattermost/types/users'; import configureStore from 'store'; +import {getAllLanguages} from 'i18n/i18n'; import {mountWithIntl} from 'tests/helpers/intl-test-helper'; import UserSettingsDisplay from './user_settings_display'; @@ -35,7 +36,8 @@ describe('components/user_settings/display/UserSettingsDisplay', () => { setEnforceFocus: jest.fn(), enableLinkPreviews: true, enableThemeSelection: false, - defaultClientLocale: 'en', + locales: getAllLanguages(), + userLocale: 'en', canCreatePublicChannel: true, canCreatePrivateChannel: true, timezoneLabel: '', diff --git a/webapp/channels/src/components/user_settings/display/user_settings_display.tsx b/webapp/channels/src/components/user_settings/display/user_settings_display.tsx index 58a707c162..38bdaca613 100644 --- a/webapp/channels/src/components/user_settings/display/user_settings_display.tsx +++ b/webapp/channels/src/components/user_settings/display/user_settings_display.tsx @@ -20,7 +20,8 @@ import SettingItem from 'components/setting_item'; import SettingItemMax from 'components/setting_item_max'; import ThemeSetting from 'components/user_settings/display/user_settings_theme'; -import * as I18n from 'i18n/i18n.jsx'; +import type {Language} from 'i18n/i18n'; +import {getLanguageInfo} from 'i18n/i18n'; import Constants from 'utils/constants'; import {t} from 'utils/i18n'; import {getBrowserTimezone} from 'utils/timezone'; @@ -104,7 +105,8 @@ type Props = { userTimezone: UserTimezone; allowCustomThemes: boolean; enableLinkPreviews: boolean; - defaultClientLocale: string; + locales: Record; + userLocale: string; enableThemeSelection: boolean; configTeammateNameDisplay: string; currentUserTimezone: string; @@ -1015,11 +1017,8 @@ export default class UserSettingsDisplay extends React.PureComponent @@ -1047,7 +1046,7 @@ export default class UserSettingsDisplay extends React.PureComponent ); - if (Object.keys(I18n.getLanguages()).length === 1) { + if (Object.keys(this.props.locales).length === 1) { languagesSection = null; } diff --git a/webapp/channels/src/i18n/i18n.jsx b/webapp/channels/src/i18n/i18n.jsx index a0e468740c..fd9bd301e3 100644 --- a/webapp/channels/src/i18n/i18n.jsx +++ b/webapp/channels/src/i18n/i18n.jsx @@ -1,6 +1,10 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +/** + * @typedef {} Language + */ + /* eslint-disable import/order */ import bg from './bg.json'; import de from './de.json'; @@ -26,8 +30,6 @@ import zhCN from './zh-CN.json'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; -import store from 'stores/redux_store'; - // should match the values in server/public/shared/i18n/i18n.go const languages = { de: { @@ -168,8 +170,12 @@ export function getAllLanguages() { return languages; } -export function getLanguages() { - const config = getConfig(store.getState()); +/** + * @param {import('types/store').GlobalState} state + * @returns {Record} + */ +export function getLanguages(state) { + const config = getConfig(state); if (!config.AvailableLocales) { return getAllLanguages(); } @@ -185,6 +191,11 @@ export function getLanguageInfo(locale) { return getAllLanguages()[locale]; } -export function isLanguageAvailable(locale) { - return Boolean(getLanguages()[locale]); +/** + * @param {import('types/store').GlobalState} state + * @param {string} locale + * @returns {boolean} + */ +export function isLanguageAvailable(state, locale) { + return Boolean(getLanguages(state)[locale]); } diff --git a/webapp/channels/src/selectors/i18n.ts b/webapp/channels/src/selectors/i18n.ts index d52395da90..ff64bf138e 100644 --- a/webapp/channels/src/selectors/i18n.ts +++ b/webapp/channels/src/selectors/i18n.ts @@ -15,10 +15,10 @@ export function getCurrentLocale(state: GlobalState): string { // If locale is provided in query parameter and the user is not logged in, we try get locale from param const localeFromParam: string | null = (new URLSearchParams(window.location?.search)).get('locale'); const defaultLocale: string | undefined = - localeFromParam && I18n.isLanguageAvailable(localeFromParam) ? localeFromParam : getConfig(state).DefaultClientLocale; + localeFromParam && I18n.isLanguageAvailable(state, localeFromParam) ? localeFromParam : getConfig(state).DefaultClientLocale; const currentLocale: string = getCurrentUserLocale(state, defaultLocale); - if (I18n.isLanguageAvailable(currentLocale)) { + if (I18n.isLanguageAvailable(state, currentLocale)) { return currentLocale; } return General.DEFAULT_LOCALE;